@zerotal/core 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,7 +6,105 @@ follows the Zerotal monorepo's unified versioning.
6
6
 
7
7
  **Maturity: `stable`**
8
8
 
9
- ## [Unreleased]
9
+ ## [1.7.0] — 2026-08-16
10
+
11
+ ### Fixed
12
+
13
+ - **Security headers now cover static files.** Files under `public/` are handed to Bun as
14
+ pre-registered responses and served without entering JavaScript, so no middleware ran for
15
+ them — including `SecureHeadersMiddleware`, which the framework advertises as automatic.
16
+ Every asset went out with no `X-Content-Type-Options: nosniff`, which is precisely the
17
+ response class sniffing protection exists for. The header set is baked into the compiled
18
+ response at registration time, so Bun still serves the file natively; a header a mount
19
+ declares itself still wins.
20
+
21
+ - **`BaseMiddleware.with()` type-checks its options.** `Opts` has a default computed from the
22
+ middleware class, but a type parameter in an argument position is inferred from the
23
+ _argument_ and only falls back to its default when inference finds nothing — so
24
+ `with({ … })` inferred `Opts` from the object literal and type-checked the literal against
25
+ itself. Every callback parameter arrived implicitly `any`, and a misspelled option was
26
+ accepted in silence. `NoInfer` on the parameter makes the middleware's own option type the
27
+ one that governs. It caught a real defect on the first run: `StorageProvider` was passing
28
+ an `unknown` where a `StorageManager` was expected.
29
+
30
+ ### Added
31
+
32
+ - **`DeepPartial<T>`, and `deepMerge` accepts it.** `deepMerge` does a deep merge and
33
+ declared `override: Partial<T>`, which only makes the top level optional — so
34
+ `{ drivers: { anthropic: { apiKey } } }`, the commonest thing anyone writes in a config
35
+ file, was a type error against any shape whose nested block has other keys.
36
+ `@zerotal/ai` had already hit this and defined a private copy; that copy is now deleted and
37
+ the type is exported from the kernel. `BaseMiddleware.with()` takes it too, since it
38
+ deep-merges as well.
39
+
40
+ - **`zt doctor --url` reports duplicated security headers.** A header the app sets and the
41
+ proxy also sets is invisible from inside the process. Conflicting values fail — browsers do
42
+ not agree which copy applies, so the control is enforced inconsistently — and identical
43
+ duplicates warn. `Permissions-Policy` and `Referrer-Policy` are deliberately not checked:
44
+ a comma is legitimate syntax there, and a probe that cried wolf on a correct header would
45
+ be switched off before it caught a real one.
46
+
47
+ - **`RequestFailed` carries the error's class name and stack.** It had the message and the
48
+ status, which is enough to say a request failed and not enough to say anything about how.
49
+ A subscriber rendering a failure — the devtools Exception tab is the first — has nothing to
50
+ show without them, and by the time the event is emitted the error object is the only place
51
+ they exist. Both are optional trailing parameters, so nothing that constructs or reads the
52
+ event needs to change.
53
+
54
+ - **`Application.providerReport`** — what each provider cost to boot and what it put in the
55
+ container, in boot order. `bootDurationMs` said the app took 240ms and nothing said which
56
+ provider spent it; the container listed a hundred bindings and nothing said who bound
57
+ them. Boot order is itself the third answer, since it decides who wins a contested
58
+ binding.
59
+
60
+ Provenance comes from diffing the container registry around each provider's hooks rather
61
+ than from the container recording a registrar — it keeps the cost at boot instead of on
62
+ every binding, and adds no mutable state to the container for a question only a debugging
63
+ tool asks. Async hooks are timed across their `await`, not up to it.
64
+
65
+ - **`FrameworkEvents.subscriptions()` and `Emitter.registrations()`** — which events have
66
+ subscribers, and what reacts to them. The bus is the framework's nervous system and had
67
+ been entirely invisible: `handlerCount()` returned one number for the whole thing.
68
+
69
+ - **`redactGraph` on the `@zerotal/core/security` subpath** — the object-graph redaction walk
70
+ that every recorder needs and that three packages had each written for themselves. Copy a
71
+ value, replace what a key name says is a secret, come back with something
72
+ `JSON.stringify` survives.
73
+
74
+ Shared because the hard parts are the same everywhere and are easy to get subtly wrong:
75
+ cycles (a model with a back-reference to its parent is ordinary, and `JSON.stringify`
76
+ throws on it), a depth bound (recording is on the request path), and values that read
77
+ better flat than walked (`Object.entries` on a `Date` or a `File` produces something worse
78
+ than useless).
79
+
80
+ It is not a policy. Callers bring their own markers and their own sensitivity predicate,
81
+ because those are not interchangeable — a debug panel's `‹redacted›` is a display choice,
82
+ while an adapter implementing a published wire protocol has its markers specified for it.
83
+
84
+ ## [1.6.3] — 2026-08-15
85
+
86
+ ### Added
87
+
88
+ - **`serve --dev` says when the framework on disk is no longer the framework running.** A
89
+ running dev server holds the code it imported at boot: `bun add zerotal@latest` in another
90
+ terminal rewrites `node_modules` and nothing else, and a save restarts only the _worker_,
91
+ which re-executes your app against that same in-memory framework. So an upgrade taken
92
+ mid-session appears to do nothing — the fix is installed, the symptom persists, and the
93
+ reasonable conclusion is that the fix does not work.
94
+
95
+ The supervisor now compares the version it booted with against the one installed, on each
96
+ restart, and says which is which:
97
+
98
+ ```text
99
+ [zerotal:dev] ⚠ framework upgraded on disk: running v1.6.2, installed v1.6.3
100
+ [zerotal:dev] restart the dev server to pick it up (a save will not).
101
+ ```
102
+
103
+ Once per version, so a long session is not nagged, and silent where there is nothing to read
104
+ — a workspace checkout or a hoisted layout is not a finding.
105
+
106
+ - **The dev banner carries the version** — `Zerotal v1.6.3 › dev`. There was previously
107
+ nothing on screen naming the framework a running server was actually executing.
10
108
 
11
109
  ## [1.6.2] — 2026-08-15
12
110