@objectstack/metadata 17.2.0 → 17.4.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 +1967 -0
- package/README.md +6 -4
- package/dist/errors.cjs +1 -85
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +1 -22
- package/dist/errors.d.ts +1 -22
- package/dist/errors.js +2 -84
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +1014 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +496 -29
- package/dist/index.d.ts +496 -29
- package/dist/index.js +1013 -299
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +175 -67
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +97 -15
- package/dist/migrations/index.d.ts +97 -15
- package/dist/migrations/index.js +178 -67
- package/dist/migrations/index.js.map +1 -1
- package/dist/node.cjs +1014 -302
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +156 -3
- package/dist/node.d.ts +156 -3
- package/dist/node.js +1013 -299
- package/dist/node.js.map +1 -1
- package/dist/view-container.cjs +37 -0
- package/dist/view-container.cjs.map +1 -0
- package/dist/view-container.d.cts +76 -0
- package/dist/view-container.d.ts +76 -0
- package/dist/view-container.js +12 -0
- package/dist/view-container.js.map +1 -0
- package/package.json +52 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1972 @@
|
|
|
1
1
|
# @objectstack/metadata
|
|
2
2
|
|
|
3
|
+
## 17.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a56baa2: feat(metadata,objectql): a keyed plural read on `MetadataManager`, `listNames` fault parity, and an action audit that answers from the same identity and sources as the router
|
|
8
|
+
|
|
9
|
+
Two plural reads of one metadata plane could disagree with a by-name read of
|
|
10
|
+
that same plane, and the ADR-0110 D5 action-governance audit stood on the
|
|
11
|
+
disagreement — reporting `registered handler with NO declaration … REFUSED at
|
|
12
|
+
dispatch` about a route the router was resolving and dispatching in the same
|
|
13
|
+
boot.
|
|
14
|
+
|
|
15
|
+
**`MetadataManager.listNames` gains the per-loader `try`/`catch` that
|
|
16
|
+
`loadMany` and `list()` have carried since #5108.** One loader fault used to
|
|
17
|
+
produce two different facts depending only on which plural read a caller
|
|
18
|
+
reached for: `loadMany` swallowed it and answered short, `listNames` threw. It
|
|
19
|
+
now degrades the same way, through the same `reportLoaderReadFailure` /
|
|
20
|
+
`reportLoaderReadRecovered` helpers — one outage, one line, one vocabulary.
|
|
21
|
+
Callers that relied on `listNames` throwing to detect an outage should read
|
|
22
|
+
`listDiagnosed()`, which reports `degraded` explicitly.
|
|
23
|
+
|
|
24
|
+
**New: `MetadataManager.loadManyKeyed(type, options?)`** — `loadMany` read under
|
|
25
|
+
the identity the STORE holds each item by, returning `{ name, data }` pairs. It
|
|
26
|
+
delegates to a loader's own `loadManyKeyed` where one is offered (on
|
|
27
|
+
`DatabaseLoader` that shares `loadMany`'s single query, so it costs nothing
|
|
28
|
+
extra) and otherwise falls back to that loader's `list()` + per-name `load()`.
|
|
29
|
+
⛔ **`loadMany`'s published return shape does not change**, and no existing
|
|
30
|
+
consumer is touched: the key travels *beside* the body, never inside it, so a
|
|
31
|
+
body that deliberately carries no `name` stays byte-identical to what was
|
|
32
|
+
stored (#14205).
|
|
33
|
+
|
|
34
|
+
**The action-governance audit now mirrors the router on both halves of the D5
|
|
35
|
+
bijection.** The declaration half enumerates the plane keyed
|
|
36
|
+
(`loadStandaloneActionsKeyed`), so a row whose body does not name itself — a
|
|
37
|
+
`sys_metadata` row keyed by its `name` column, or a `FilesystemLoader` file
|
|
38
|
+
whose identity is its path — is a declaration to the audit exactly as it is to
|
|
39
|
+
the router; the handler half also probes the plane BY NAME
|
|
40
|
+
(`lookupMetadataAction`, `loadDiagnosed`/`load`, injected like the existing
|
|
41
|
+
registry rung), so a loader fault a plural read swallows can no longer turn a
|
|
42
|
+
dispatchable handler into an accusation. Both probes stay conservative in one
|
|
43
|
+
direction only: a source that throws leaves the handler on the list.
|
|
44
|
+
|
|
45
|
+
Additive on every published signature. `runActionGovernanceInventory` and
|
|
46
|
+
`collectEngineActionDeclarations` gain optional parameters and keep their old
|
|
47
|
+
ones working unchanged; declaration rows gain an optional `storeKey` (the new
|
|
48
|
+
exported `ActionDeclarationRow`).
|
|
49
|
+
|
|
50
|
+
**Population change, reported:** `unboundDeclarations` now sees declarations
|
|
51
|
+
whose identity is the store key. Its BEFORE was **0, structurally rather than
|
|
52
|
+
by sampling** — a nameless row was dropped before reconciliation ran, so it
|
|
53
|
+
could never be reported however many a plane held. Its one deliberate
|
|
54
|
+
subtraction: a row with neither an own `name` nor a store key is no longer
|
|
55
|
+
reported as `actionName: undefined`, which read as a parse failure in the
|
|
56
|
+
warning rather than as a finding.
|
|
57
|
+
|
|
58
|
+
Known boundary, stated in the audit's docblock rather than left to be
|
|
59
|
+
rediscovered: a boot-time audit runs outside any request scope, so if a
|
|
60
|
+
composition ever registered `metadata` as `SCOPED` the audit could not reach
|
|
61
|
+
that instance at all — before any read method runs. No shipped composition does
|
|
62
|
+
(`packages/metadata/src/plugin.ts` registers a static instance), and reaching a
|
|
63
|
+
request-scoped service from a boot-time audit is a separate change.
|
|
64
|
+
- c1d274d: fix(metadata): two files sharing one stem are refused with both paths named, instead of one being listed twice and served by extension precedence (#14921)
|
|
65
|
+
|
|
66
|
+
**BREAKING** accept-set narrowing on `FilesystemLoader`, shipped as `minor`
|
|
67
|
+
under the repo's launch-window convention for breaking changes. Ruled on
|
|
68
|
+
#14921 (2026-09-05, option 1 of three).
|
|
69
|
+
|
|
70
|
+
**Remedy: delete or rename the duplicate file.** The refusal names every
|
|
71
|
+
colliding path and the metadata type, so the fix is visible at the point of
|
|
72
|
+
failure.
|
|
73
|
+
|
|
74
|
+
`FilesystemLoader` derives a metadata name by stripping a flat file's
|
|
75
|
+
extension, and resolves a name back to a file under a FIXED extension
|
|
76
|
+
precedence (`.json` → `.yaml` → `.yml` → `.ts` → `.js`). Two files sharing a
|
|
77
|
+
stem therefore produced one name **twice** in `list()` while only the
|
|
78
|
+
first-precedence file was reachable through any name at all. With
|
|
79
|
+
`object/twin.json` and `object/twin.yaml` both present, `list()` answered
|
|
80
|
+
`['twin', 'twin']`, `twin.yaml` was addressable through nothing, and
|
|
81
|
+
`loadMany()` returned both bodies. `MetadataManager.listNames()` unions loader
|
|
82
|
+
output into a `Set`, which collapsed the duplicate and took the count
|
|
83
|
+
discrepancy with it — the file stayed unreachable either way, so a clean
|
|
84
|
+
`listNames()` was never evidence the collision had been absorbed.
|
|
85
|
+
|
|
86
|
+
The invariant that broke: **what is listed is what is loadable.** The listed
|
|
87
|
+
set and the addressable set stopped being the same set. The failure was silent
|
|
88
|
+
in the direction that matters for authoring — convert `twin.json` to
|
|
89
|
+
`twin.yaml` and leave the old file behind, or land one from each of two
|
|
90
|
+
packages, and the JSON one is served forever with no diagnostic anywhere,
|
|
91
|
+
while `admitLoaderItems()`'s documented "keep the first and say nothing"
|
|
92
|
+
absorbs the collision a second time.
|
|
93
|
+
|
|
94
|
+
`FilesystemLoader.list()` now throws `AmbiguousMetadataStemError`
|
|
95
|
+
(`AMBIGUOUS_METADATA_STEM`, HTTP 500) naming both paths and the type, and the
|
|
96
|
+
same refusal fronts the shared `loadMany()` / `loadManyKeyed()` walk, so the
|
|
97
|
+
two-body answer is gone rather than de-duplicated. `MetadataManager.listNames()`
|
|
98
|
+
and `list()` **propagate** it rather than absorbing it into their per-loader
|
|
99
|
+
degradation: an ambiguous stem is an authoring error no retry fixes, and
|
|
100
|
+
degrading it would drop every item the loader holds into a short-but-served
|
|
101
|
+
list while the server keeps reporting healthy. A real storage outage still
|
|
102
|
+
degrades exactly as before — the seams discriminate on a branded predicate,
|
|
103
|
+
`isAmbiguousMetadataStemError`, not on a blanket rethrow.
|
|
104
|
+
|
|
105
|
+
**Refused shape**, precisely: two or more files **directly under
|
|
106
|
+
`ROOT/TYPE/`** whose basenames differ only by an extension belonging to one of
|
|
107
|
+
**this instance's registered serializers**. Register `javascript` and
|
|
108
|
+
`dual.json` + `dual.js` becomes ambiguous; under the manager's default format
|
|
109
|
+
set (`typescript` / `json` / `yaml`) it is not, because `.js` derives no name.
|
|
110
|
+
Nested files are untouched — they are neither listed nor resolvable (#14486),
|
|
111
|
+
so `crm/solo.json` beside a flat `solo.json` is not a collision. The refusal is
|
|
112
|
+
scoped to the type directory that holds it: a clean `view/` still lists while
|
|
113
|
+
`object/` refuses.
|
|
114
|
+
|
|
115
|
+
New exports from the package root entry: `AmbiguousMetadataStemError`,
|
|
116
|
+
`isAmbiguousMetadataStemError`, `AMBIGUOUS_METADATA_STEM_CODE`,
|
|
117
|
+
`AMBIGUOUS_METADATA_STEM_STATUS`.
|
|
118
|
+
|
|
119
|
+
Measured migration cost, which is what makes this narrowing cheap: **no tree in
|
|
120
|
+
this repository carries the shape.** A walk of all 7,770 tracked files across
|
|
121
|
+
526 directories found zero stem collisions among `.json` / `.yaml` / `.yml` /
|
|
122
|
+
`.ts` / `.js`, confirmed independently by a `git ls-files` pass, and the repo
|
|
123
|
+
holds no `.yaml`/`.yml` metadata file at all outside CI and workspace config.
|
|
124
|
+
No existing tree goes red.
|
|
125
|
+
|
|
126
|
+
<!-- adr-0087: not-required (no-migration-prescription) An accept-set narrowing on a LOADER, not on any authorable key: no property of any spec schema is removed, renamed or re-shaped, so there is no tombstone and nothing for `objectstack migrate meta` to rewrite in a stored document. The affected artifact is a FILESYSTEM LAYOUT — two sibling files — which the ledger cannot address at all: a migration entry rewrites metadata bodies, and neither of the colliding files is wrong on its own. Which one an author wants kept is intent no entry can decide: they may have meant the conversion to `.yaml` to land and forgotten to delete the `.json`, or may have meant the opposite, and the two files carry no evidence of which. The refusal is the channel that reaches them, at the load site, naming both paths, the type, and the remedy. Measured in-repo population of affected trees is zero: a walk of 7,770 tracked files over 526 directories found no directory holding two files with one stem among the registered extensions, and there are no `.yaml`/`.yml` metadata files outside CI and workspace config. -->
|
|
127
|
+
- e9fcd6b: feat(metadata)!: `DatabaseLoaderOptions.cache.ttl` → `cache.ttlMs` — the read-through cache TTL carries its unit in the key name (#14478)
|
|
128
|
+
|
|
129
|
+
<!-- adr-0087: registered metadata-manager-config-cache-ttl-unit-in-key -->
|
|
130
|
+
|
|
131
|
+
**BREAKING** rename on the exported `DatabaseLoaderOptions.cache` shape
|
|
132
|
+
(`DatabaseLoaderCacheOptions.ttl` → `ttlMs`), shipped as `minor` under the
|
|
133
|
+
launch-window convention. `MetadataManager` hands `config.cache.databaseLoader`
|
|
134
|
+
straight to `new DatabaseLoader({ cache })`, so this option is the spec key
|
|
135
|
+
`cache.databaseLoader.ttlMs` one layer down and renames with it: a loader
|
|
136
|
+
configured with `ttlMs: 60_000` expires entries after 60 seconds exactly as
|
|
137
|
+
`ttl: 60_000` did. The README example and the kernel metadata-service docs page
|
|
138
|
+
spell the new key.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// before
|
|
142
|
+
new DatabaseLoader({ driver, cache: { enabled: true, maxSize: 500, ttl: 60_000 } });
|
|
143
|
+
// after
|
|
144
|
+
new DatabaseLoader({ driver, cache: { enabled: true, maxSize: 500, ttlMs: 60_000 } });
|
|
145
|
+
```
|
|
146
|
+
- 3bd9b34: feat(metadata): `deriveViewContainerObject` gets a leaf `/view-container` subpath, so objectql's lean ADR-0076 entry stops loading the manager, chokidar, glob and js-yaml for a six-line pure function
|
|
147
|
+
|
|
148
|
+
`packages/objectql/src/engine.ts` reached `deriveViewContainerObject` through
|
|
149
|
+
`@objectstack/metadata`'s ROOT entry. `core.ts` — the ADR-0076 lean entry —
|
|
150
|
+
re-exports `engine.ts`, so `@objectstack/objectql/core`'s module-init closure
|
|
151
|
+
inherited the whole root entry: `MetadataPlugin` -> `NodeMetadataManager` ->
|
|
152
|
+
`chokidar`, plus `glob`, `js-yaml` and `readdirp`.
|
|
153
|
+
|
|
154
|
+
The same file already carried the answer 79 lines above, at its
|
|
155
|
+
`@objectstack/metadata/errors` import: that leaf subpath exists "precisely so a
|
|
156
|
+
cross-package consumer gets the predicate without the manager, the loaders or
|
|
157
|
+
the YAML/filesystem machinery behind the root entry". This is that pattern,
|
|
158
|
+
taken a second time.
|
|
159
|
+
|
|
160
|
+
**Measured on the built artifacts, not asserted** — every module Node actually
|
|
161
|
+
evaluates when `@objectstack/objectql/core` is loaded in a fresh process,
|
|
162
|
+
recorded through a `module.registerHooks` load hook (ESM and CJS) plus
|
|
163
|
+
`require.cache`, byte sizes from `statSync`:
|
|
164
|
+
|
|
165
|
+
| `@objectstack/objectql/core` | modules | bytes |
|
|
166
|
+
|:---|---:|---:|
|
|
167
|
+
| before (ESM `dist/core.mjs`) | 190 | 12,348,424 |
|
|
168
|
+
| after (ESM `dist/core.mjs`) | 185 | 11,849,808 |
|
|
169
|
+
| **delta** | **-5** | **-498,616 (-486.9 KiB)** |
|
|
170
|
+
| before (CJS `dist/core.js`) | 188 | 12,654,238 |
|
|
171
|
+
| after (CJS `dist/core.js`) | 183 | 12,141,034 |
|
|
172
|
+
| **delta** | **-5** | **-513,204 (-501.2 KiB)** |
|
|
173
|
+
|
|
174
|
+
Six modules stop loading — `packages/metadata/dist/index.js` (237,747 B),
|
|
175
|
+
`js-yaml` (114,610 B), `glob` (82,749 B), `chokidar` (2 files, 54,220 B) and
|
|
176
|
+
`readdirp` (9,836 B) — and one 469-byte module takes their place. Marginal
|
|
177
|
+
module-init time for that root entry, measured on a warm lean closure, was
|
|
178
|
+
~22 ms (median of 7; 20.4-27.5 ms) out of ~630 ms.
|
|
179
|
+
|
|
180
|
+
⚠️ The figure the finding was argued on — "~3.6 KB to ~450 KB" — is right about
|
|
181
|
+
the delta and wrong about the baseline: the lean entry's closure was already
|
|
182
|
+
~11.5 MiB before this import existed, dominated by `@objectstack/spec`
|
|
183
|
+
(9,587,914 B) and `zod` (567,918 B), neither of which the metadata root entry
|
|
184
|
+
contributes. What the root import cost was ~487 KiB *on top of* that, not a
|
|
185
|
+
closure of 450 KB.
|
|
186
|
+
|
|
187
|
+
The derivation itself moves to `packages/metadata/src/view-container.ts`, a
|
|
188
|
+
module with **no imports at all**, and `view-container-expansion.ts` imports
|
|
189
|
+
and re-exports it, so `index.ts`'s root export and `plugin.ts` keep their
|
|
190
|
+
spelling and the symbol stays on the root entry — this subpath is an additional
|
|
191
|
+
door, not a relocation. A re-export shim onto `view-container-expansion.ts` was
|
|
192
|
+
tried first and rejected on measurement: esbuild tree-shakes the unused
|
|
193
|
+
`expandRuntimeViewContainer` but keeps its two `@objectstack/spec` import
|
|
194
|
+
statements, so that shim's own closure was 84 modules / 3,035 KiB. The real
|
|
195
|
+
leaf's is 1 module / 469 B.
|
|
196
|
+
|
|
197
|
+
`expandRuntimeViewContainer` is deliberately not exported from the new subpath:
|
|
198
|
+
`metadata-manager.ts` is its only caller, the root entry does not export it
|
|
199
|
+
either, and it is the half that carries the spec machinery.
|
|
200
|
+
- 8647c87: A completed run of the ADR-0030 notification cut-over now records itself in the `sys_migration` deployment ledger, per the ruled claim matrix.
|
|
201
|
+
|
|
202
|
+
`migrateSysNotificationToEvent` reports `migrated` / `already_done` / `not_applicable` / `error` to its caller and — until now — recorded nothing anywhere. Once that line had scrolled, "did this cut-over run here, and when" had no answer in the deployment even in principle. The ledger row is what answers it, and what a run of this migration may claim under `NOTIFICATION_EVENT_MIGRATION_ID` is stated on that constant in `@objectstack/spec/system`:
|
|
203
|
+
|
|
204
|
+
- `last_run_at` — stamped on every completed non-`error` run (`migrated`, `already_done`, `not_applicable` alike).
|
|
205
|
+
- `applied_at` — stamped only on `migrated`. Never cleared: a later `already_done` leaves an earlier backfill's stamp alone, because the backfill really did happen.
|
|
206
|
+
- `verified_at` — never written, in either direction. This migration has no self-check, and `verified_at` means a self-check passed. On a store created after the cut-over the row already exists and `attestFreshDatastore` set `verified_at` at birth; that certificate survives a run untouched, because the column is omitted from the update rather than sent as `null`.
|
|
207
|
+
- `blocking: 0`, and `details` carrying `{ outcome }` verbatim.
|
|
208
|
+
- An `error` run writes no claim at all — it does not know what it did, so it does not say.
|
|
209
|
+
|
|
210
|
+
**Receipt, not gate.** Nothing reads a row under this id as a precondition and nothing may: a gate would need the self-check that does not exist. The row is what an operator reads, in the shape `sys_migration` already documents for the seed-tenancy repair.
|
|
211
|
+
|
|
212
|
+
Two additions to the published surface of `@objectstack/metadata/migrations`, both driven by that: a new `SysNotificationMigrationReceipt` type, and a new `receipt` member on `SysNotificationMigrationResult` reporting what became of the claim (`inserted` / `updated` / `not-claimed` / `no-ledger` / `failed`, with a reason on the last two). This directory takes no logger and reports to its caller, so the claim's own fate is reported the same way the migration's is — a receipt that could not be written is never swallowed. Reading a result is unaffected; code that CONSTRUCTS a `SysNotificationMigrationResult` by hand (a test double) now supplies `receipt`.
|
|
213
|
+
|
|
214
|
+
### Patch Changes
|
|
215
|
+
|
|
216
|
+
- 0c5d035: fix(metadata): a `HistoryCleanupManager` run that loses deletes now says so, at both of `start()`'s triggers
|
|
217
|
+
|
|
218
|
+
A failing history cleanup was completely silent. Three things composed: every inner `catch` on the delete path is a bare `catch {`, so the error object is discarded; the only `console.error` in `runCleanup()` sits in its OUTER catch, which those inner catches prevent execution from reaching; and `start()` invoked the run as `void this.runCleanup()`, throwing away the `{ deleted, errors }` the run returns — at BOTH call sites, the immediate run and every interval tick. A driver whose deletes failed on every scheduled run therefore produced zero output and no reachable error count, while the history table grew past its retention policy with nothing to find.
|
|
219
|
+
|
|
220
|
+
The repair reads the envelope instead of replacing it. `runCleanup()`'s contract, its inner catches and its counting are unchanged: reporting a failure to the CALLER is the third answer AGENTS.md → "Degradation log levels" allows a durability seam, and that same section names a log per failed write as the mirror-image failure. What was missing was a reader — `start()` is where the chain ends, since it returns `void` and an interval tick has no caller at all. Both call sites now go through one shared pass that reads the returned counts and, when a run lost deletes, prints one `error` line naming the consequence (rows past the retention policy are still in the table, nothing retries them, and the system keeps reporting healthy) and where to look. A run that loses nothing stays quiet, and a direct caller of `runCleanup()` sees exactly the same `{ deleted, errors }` as before.
|
|
221
|
+
- 281bf0d: `HistoryCleanupManager` computes its retention cutoff on one calendar, not two.
|
|
222
|
+
|
|
223
|
+
Both call sites — the age-based delete in `runCleanup()` and the preview count in `getCleanupStats()` — built the cutoff with `cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays)` and then rendered it with `toISOString()`. `setDate`/`getDate` read and write the **local** calendar; `toISOString()` renders **UTC**. They now use `setUTCDate`/`getUTCDate`, so the arithmetic and the rendering agree.
|
|
224
|
+
|
|
225
|
+
`setDate` preserves wall-clock time, so shifting the local calendar back `n` days moves the *instant* by exactly `n × 24h` only while every local day in the window is 24 hours long. When the window straddles a DST transition it is 23 hours (spring-forward) or 25 (fall-back), and the cutoff instant that goes into the `recorded_at: { $lt: … }` **delete** filter is off by the size of that transition — one hour in most zones, thirty minutes on Lord Howe Island. History rows within that slip of the retention boundary were deleted early, or retained too long.
|
|
226
|
+
|
|
227
|
+
The exposure is not limited to the two transition days: the window only has to *straddle* a transition, so it grows with `maxAgeDays`. Measured over a 12-zone × 366-day × 48-half-hour sweep of 2026, in `America/New_York` the old spelling produced a wrong cutoff for 0.6% of instants at `maxAgeDays: 1`, 16.4% at 30, 49.7% at 90 and 69.4% at 180. In zones that do not observe DST (`UTC`, `Asia/Shanghai`, `Asia/Kolkata`, `Australia/Perth`) the rate is 0.0% at every `maxAgeDays` — which is why no test had ever gone red on this.
|
|
228
|
+
|
|
229
|
+
This does **not** make retention timezone-aware, and does not change what `maxAgeDays` means. The cutoff was already intended to be `now − maxAgeDays × 24h`; it is now that in every zone rather than only in zones without DST. Nothing else in either filter moved: the `organization_id` scoping, the ADR-0009 `executionPinned` exclusion and the `maxVersions` path are untouched.
|
|
230
|
+
- 3e7ef9c: Serve an Invalid `Date` from a driver instead of raising `RangeError` in `DatabaseLoader.stat`.
|
|
231
|
+
|
|
232
|
+
`canonicalIsoInstant` reached `value.toISOString()` for any `Date`, and that call raises `RangeError: Invalid time value` for the one `Date` whose time value is `NaN`. `stat()` is a hot read path — REST `/meta/*`, ObjectQL plan resolution, runtime overlay merges — so one legacy `sys_metadata` row answered **500** where the spelling this repair replaced had served a visibly-wrong value.
|
|
233
|
+
|
|
234
|
+
The shape is measured: mysql2 3.23.1 hands back a constant literally named `INVALID_DATE` for a zero `DATETIME`, and postgres-date 1.0.7 builds `new Date(NaN)` for every year in 275760..294276, which Postgres itself stores.
|
|
235
|
+
|
|
236
|
+
The `Date` arm now guards on `Number.isNaN(value.getTime())` and answers `undefined`, so `stat()`'s own `?? new Date().toISOString()` — the branch an absent column already takes — publishes a parseable `MetadataStats.mtime`. `undefined` rather than visible text is deliberate here: `mtime` is declared `z.string().datetime()`, so the text `"Invalid Date"` would not produce a readable cell, it would produce a zod refusal at the consumer, moving the failure instead of removing it. A blank is excluded for the opposite reason — it hides the producer's bug.
|
|
237
|
+
- 09c1d91: docs(metadata): the `@objectstack/metadata/errors` header records what the leaf entry actually loads — 4 packages, 83 modules, 2.2 MB — instead of leaving the reader to infer it is light (#15346)
|
|
238
|
+
|
|
239
|
+
`packages/metadata/src/errors.ts` is the leaf subpath `@objectstack/metadata/errors`, and its header explains why the subpath exists: the root entry pulls the manager, every loader and the YAML/filesystem machinery behind them, and "a consumer that wants a 40-line predicate should not have to load any of that". Measured, that promise half holds — and the header did not say which half.
|
|
240
|
+
|
|
241
|
+
**The half that holds, and stays:** MEASURED 2026-09-08 on `origin/main` `8c1515e847`, with `scripts/check-lean-entry-closure.mjs`'s exported `measure()`/`byPackage()` (one fresh child per published condition; module set collected through `module.registerHooks` unioned with `require.cache`). Both published conditions load exactly four packages, and the manager, every loader, `chokidar`, `glob`, `js-yaml` and `readdirp` are absent from all of them. That is the claim the header actually wrote, and it is accurate.
|
|
242
|
+
|
|
243
|
+
**The half that had drifted:** the general conclusion a reader takes away. `import` loads 83 modules / 2,201,709 bytes; `require` loads 83 / 2,355,405. Since the maintainer's 2026-08-30 ruling the file re-exports from `@objectstack/types`, which builds to one bundled module importing **both** `@objectstack/spec/api` (1,401,253 B) and `@objectstack/spec/security` (195,367 B) at module top, as values — `api` is the dominant edge at 7x `security`, and the two spec entries are independent of each other.
|
|
244
|
+
|
|
245
|
+
The header now separates the two costs the way the finding did, because conflating them is the available mistake: **in-repo the marginal contribution is 143 bytes** — the only current consumer of the subpath, `@objectstack/metadata-protocol`, declares `@objectstack/spec` itself and pays for that closure anyway — while the megabytes are what an **out-of-repo** consumer pays, which nothing here can measure. The figures are labelled PROVENANCE, dated and tree-pinned, in the manner the neighbouring gate keeps its own: nothing derives from them, nothing compares against them, and the header says to re-take them rather than quote them. They rot fast — the same measurement four days earlier at `6e67b86c0` found the same 83 modules but 2,487,842 bytes, 286,133 more, without this file changing at all.
|
|
246
|
+
|
|
247
|
+
**Why `patch` and not `skip-changeset`, measured rather than assumed.** `@objectstack/metadata` is released (17.3.0) and `errors.ts` is a shipped source file, so "a comment publishes nothing" needed checking rather than asserting. Building the package before and after the edit: `dist/errors.js`, `dist/errors.cjs`, `dist/errors.d.ts` and `dist/errors.d.cts` are **byte-identical** (esbuild strips the comment, and there is no `sourcesContent`), but `dist/errors.js.map` and `dist/errors.cjs.map` **change** — the export statement moved from source line 65 to line 113, so the `mappings` VLQ moves with it (`;AAgEA,…` → `;AAgHA,…`), at identical file size. `npm pack --dry-run` lists both maps in the tarball. So this diff does publish bytes from a released package, which is exactly what `skip-changeset` is not for. Nothing executable and no type changes: a consumer's stack traces resolve to the right source line, and that is the whole of it.
|
|
248
|
+
- 7629f4d: feat(spec)!: retire the three inert outer keys of `MetadataManagerConfig.cache` — `enabled`, `ttlSeconds` (formerly `ttl`) and `maxSize` — read by nothing; `cache.databaseLoader` is the only live half (#15624, ADR-0049)
|
|
249
|
+
|
|
250
|
+
<!-- adr-0087: registered metadata-manager-config-inert-cache-keys-retired -->
|
|
251
|
+
|
|
252
|
+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
|
|
253
|
+
launch-window convention ships it as `minor`; the migration prescription is
|
|
254
|
+
registered under protocol major 18, where `os migrate meta` users will look).
|
|
255
|
+
ADR-0049 enforce-or-remove decides it: a declared-but-unenforced key with zero
|
|
256
|
+
measured readers comes off, and the published reference page stops teaching it.
|
|
257
|
+
|
|
258
|
+
`MetadataManagerConfig.cache` declared three outer knobs — `enabled` (default
|
|
259
|
+
`true`), `ttlSeconds` (default 3600; spelled `ttl` until #14478) and `maxSize`
|
|
260
|
+
("Max cache size in bytes") — beside the nested `databaseLoader` block, and
|
|
261
|
+
**nothing read the outer three**. The only runtime consumer of the block is
|
|
262
|
+
`MetadataManager` (`packages/metadata`), which hands `cache.databaseLoader` and
|
|
263
|
+
nothing else to `new DatabaseLoader({ cache })`; a reader census over
|
|
264
|
+
`packages/**` (tests and changelogs excluded) found no runtime reader of any
|
|
265
|
+
outer key, while the same grep shape found the nested `cache?.databaseLoader`
|
|
266
|
+
read twice — the control that makes the zero a measurement. An author writing
|
|
267
|
+
`cache: { enabled: false }` or `cache: { ttlSeconds: 60 }` got a clean parse
|
|
268
|
+
and a cache that behaved exactly as before, with no error and no warning, and
|
|
269
|
+
the published reference page (`references/kernel/metadata-loader`) documented
|
|
270
|
+
all three as if they configured something.
|
|
271
|
+
|
|
272
|
+
**What is refused:** authoring `cache.enabled`, `cache.ttlSeconds`, `cache.ttl`
|
|
273
|
+
or `cache.maxSize` on `MetadataManagerConfig`, with any value — directly, through
|
|
274
|
+
`MetadataManagerOptions`, or through `MetadataPluginConfig.storage`. The nested
|
|
275
|
+
object is not `.strict()`, so each key is a `retiredKey()` tombstone rather than
|
|
276
|
+
a bare deletion (a deletion would have stripped it in silence — the same no-op
|
|
277
|
+
one layer down): authoring it is a `tsc` error (`never`) and a parse error
|
|
278
|
+
carrying the prescription, which names the live nested knob.
|
|
279
|
+
|
|
280
|
+
**What stays, byte-identical:** the DatabaseLoader read-through cache under
|
|
281
|
+
`cache.databaseLoader` — `enabled` (default `true`), `maxSize` (an entry count,
|
|
282
|
+
default 500) and `ttlMs` (milliseconds, default 60000) — and every runtime
|
|
283
|
+
path. Parsed configs no longer carry the two former defaults (`enabled: true`,
|
|
284
|
+
`ttlSeconds: 3600`) that were materialized and never consulted.
|
|
285
|
+
|
|
286
|
+
**The #14478 rename is folded in.** `cache.ttl` → `cache.ttlSeconds` was
|
|
287
|
+
registered under this same unreleased major and never reached a published
|
|
288
|
+
release, so it is absorbed by the removal: `cache.ttl`'s tombstone now
|
|
289
|
+
prescribes deletion (naming `cache.databaseLoader.ttlMs`) instead of a rename to
|
|
290
|
+
a key that is itself retired — an author upgrading from a published 17.x sees
|
|
291
|
+
one hop. The nested `cache.databaseLoader.ttl` → `ttlMs` half of that rename is
|
|
292
|
+
unchanged.
|
|
293
|
+
|
|
294
|
+
## FROM → TO
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
// before — parsed green; no runtime ever read the three outer numbers
|
|
298
|
+
new MetadataManager({
|
|
299
|
+
datasource: 'default',
|
|
300
|
+
cache: { enabled: true, ttlSeconds: 3600, maxSize: 10_485_760, databaseLoader: { ttlMs: 60_000 } },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// after — delete the outer keys; the nested block is the cache that runs
|
|
304
|
+
new MetadataManager({
|
|
305
|
+
datasource: 'default',
|
|
306
|
+
cache: { databaseLoader: { enabled: true, maxSize: 500, ttlMs: 60_000 } },
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Migration.** Delete `cache.enabled`, `cache.ttlSeconds` / `cache.ttl` and
|
|
311
|
+
`cache.maxSize`; nothing replaces them, because nothing ever consumed them. If
|
|
312
|
+
you meant to switch the cache off, cap it or set its TTL, write
|
|
313
|
+
`cache.databaseLoader.enabled` / `.maxSize` (entries) / `.ttlMs` (milliseconds)
|
|
314
|
+
— those are honoured. No `os migrate meta` conversion runs on this surface: a
|
|
315
|
+
`MetadataManager` config is not a stack collection member and never a stored
|
|
316
|
+
row, so the chain has no seam for it; the D3 semantic entry
|
|
317
|
+
`metadata-manager-config-inert-cache-keys-retired` carries the prescription
|
|
318
|
+
into `spec-changes.json`, the upgrade guide and the `spec_changes` MCP tool.
|
|
319
|
+
|
|
320
|
+
The retirement kit: `retiredKey()` tombstones on all three (and the absorbed
|
|
321
|
+
`ttl`), `RETIRED_KEYS_BY_MAJOR[18]` entries for each, the D3 semantic entry
|
|
322
|
+
above (the #14478 entry's outer half is re-worded from a rename to a deletion),
|
|
323
|
+
negative pins asserting each prescription and a positive pin asserting the
|
|
324
|
+
parse output no longer materializes the retired defaults, the published
|
|
325
|
+
reference pages regenerated, and the hand-written docs page and this package's
|
|
326
|
+
README (`@objectstack/metadata` ships `README.md`, hence its `patch`) no longer
|
|
327
|
+
authoring `cache.enabled`.
|
|
328
|
+
- 6f23f0e: fix(metadata): keep the original notification instant when migrating `sys_notification` to the event model (#16312)
|
|
329
|
+
|
|
330
|
+
`migrateSysNotificationToEvent` materializes each legacy inbox row into a
|
|
331
|
+
`sys_inbox_message` and a `sys_notification_receipt`, back-dating both to the
|
|
332
|
+
notification's own `created_at`. Both writes passed no options bag, so they
|
|
333
|
+
relied on the audit binder's create-side `record.created_at ?? now` — the
|
|
334
|
+
laundering #15964 removed, on the maintainer ruling of 2026-09-06. Without
|
|
335
|
+
that accident, every migrated inbox row and receipt is stamped with the moment
|
|
336
|
+
the migration RAN: a user's whole bell history collapses to "all arrived
|
|
337
|
+
today".
|
|
338
|
+
|
|
339
|
+
The two writes now declare `{ context: { preserveAudit: true } }`, the explicit
|
|
340
|
+
historical-import channel the same ruling deliberately kept (#3493; it is what
|
|
341
|
+
REST import's `treatAsHistorical` sets). This is not a bypass of audit — it is
|
|
342
|
+
the door audit left open for a historical import. No exported symbol, schema or
|
|
343
|
+
config key moves.
|
|
344
|
+
|
|
345
|
+
**Release ordering.** `@objectstack/objectql`'s side of #15964 is itself still
|
|
346
|
+
an unreleased changeset, so no published version of this migration has ever
|
|
347
|
+
written the flattened timeline. Releasing the two together keeps it that way.
|
|
348
|
+
|
|
349
|
+
**If a deployment did run it from a build that has both halves**, the original
|
|
350
|
+
timeline is recoverable rather than lost: the source `sys_notification` rows
|
|
351
|
+
are rewritten in place, never deleted or archived, and `created_at` is not
|
|
352
|
+
among the legacy columns the run clears — so the notification's own instant is
|
|
353
|
+
still on the event row and reachable from both new rows through
|
|
354
|
+
`notification_id`.
|
|
355
|
+
- efc5447: `RemoteLoader.list()` no longer reports a nameless remote body as a literal `undefined`.
|
|
356
|
+
|
|
357
|
+
The method declares `Promise<string[]>` and read the collection as `loadMany<{ name: string }>(type)` before mapping `items.map(i => i.name)`. That type argument is an **assertion** about bodies that arrived over HTTP, and nothing checked it: a body with no top-level `name` yielded `undefined`, which went into an array the signature declares as `string[]`. `MetadataManager.listNames()` unions loader `list()` output unfiltered, so the violation reached consumers — measured on this fixture, `listNames()` answered `[ 'account', undefined, 42 ]`.
|
|
358
|
+
|
|
359
|
+
The guard is `DatabaseLoader.list()`'s, one file away: the same cast-then-map spelling with `.filter(name => typeof name === 'string')` behind it. `RemoteLoader` was the only one of the four loaders in that directory with no guard at all — `MemoryLoader` answers with its store keys, and `FilesystemLoader` reports only names `findFile()` resolves. Dropping silently rather than throwing is the direction those siblings already carry: a name in the list that the door answers `null` for is the silent failure an author reads as their own typo, so the list is narrowed to agree with the door.
|
|
360
|
+
|
|
361
|
+
Nothing that was validly returned before stops being returned: the only entries that disappear are the ones whose type the signature already ruled out. A caller that previously received `[undefined]` now receives `[]`. `loadMany()` is deliberately untouched — it keys nothing, so a body carrying no `name` is still served there; this loader reads over HTTP and holds no store key, so `body.name` is the only identity it has and the family's "identity is the store key" rule cannot be satisfied for it.
|
|
362
|
+
- c5d6803: Published `.js.map` files no longer embed the complete original source text (`sourcesContent`) — comments included. `sourcemap: true` was esbuild shorthand, and esbuild's own default for `sourcesContent` is `true`; nobody had decided to publish every package's full source (including `@internal`/test-only comments) to npm inside its source maps, it fell out of a default nobody had looked at. Measured before this change: 55 of 57 publishable packages shipped embedded source text, and maps were roughly half of `@objectstack/spec`'s published bytes.
|
|
363
|
+
|
|
364
|
+
`sourcesContent: false` is now set at one shared place (`scripts/tsup-drop-sources-content.mjs`, wired into every `tsup.config.ts` via tsup's `esbuildOptions` hook — most packages build through the repo-root config directly and pick this up with no config change of their own). `mappings` are untouched, so stack-trace positions still resolve correctly to the original file/line/column; only the embedded source text is gone.
|
|
365
|
+
|
|
366
|
+
`@objectstack/cli` (built with `tsc`, not `tsup`) never embedded source text to begin with — its maps' `sources` entries point at `src/**` paths that are not part of the published tarball either way. That is not a defect unique to `cli`: every `tsup`-built package's `sources` entries are `../src/**`-relative paths that are equally outside `files: ["dist", …]`, and were merely masked by the embedded content that just stopped shipping. Shipping `src/**` in `files[]` to make `sources` resolve was rejected — it would put most of the removed bytes straight back. So `cli`'s maps are left exactly as `tsc` emits them: this is now the fleet-consistent shape (accurate `mappings`, non-resolving-but-honest `sources` labels, no embedded text), not an outlier.
|
|
367
|
+
|
|
368
|
+
A new gate, `pnpm check:sourcemap-no-sources-content`, sweeps every built, non-private package's `dist/**/*.map` and fails if any of them carries a non-empty `sourcesContent` array — so a future `tsup.config.ts` that skips the shared hook, or a toolchain upgrade that changes esbuild's default back, is caught rather than silently re-publishing source text.
|
|
369
|
+
- Updated dependencies [fe0d9a4]
|
|
370
|
+
- Updated dependencies [ecd2158]
|
|
371
|
+
- Updated dependencies [f2b5e46]
|
|
372
|
+
- Updated dependencies [2ed6be6]
|
|
373
|
+
- Updated dependencies [ed7243d]
|
|
374
|
+
- Updated dependencies [6ba0db4]
|
|
375
|
+
- Updated dependencies [625b0c3]
|
|
376
|
+
- Updated dependencies [233222e]
|
|
377
|
+
- Updated dependencies [07f40e5]
|
|
378
|
+
- Updated dependencies [ceb4877]
|
|
379
|
+
- Updated dependencies [e9fcd6b]
|
|
380
|
+
- Updated dependencies [90e7e6d]
|
|
381
|
+
- Updated dependencies [2bdabe6]
|
|
382
|
+
- Updated dependencies [ca326b5]
|
|
383
|
+
- Updated dependencies [8f404a5]
|
|
384
|
+
- Updated dependencies [159dbad]
|
|
385
|
+
- Updated dependencies [68437d4]
|
|
386
|
+
- Updated dependencies [abb140c]
|
|
387
|
+
- Updated dependencies [8333a6c]
|
|
388
|
+
- Updated dependencies [3e3ecb0]
|
|
389
|
+
- Updated dependencies [3030369]
|
|
390
|
+
- Updated dependencies [d5d8d50]
|
|
391
|
+
- Updated dependencies [e08892d]
|
|
392
|
+
- Updated dependencies [ae05f2e]
|
|
393
|
+
- Updated dependencies [b548e43]
|
|
394
|
+
- Updated dependencies [c463d03]
|
|
395
|
+
- Updated dependencies [64bd6a3]
|
|
396
|
+
- Updated dependencies [13c48c2]
|
|
397
|
+
- Updated dependencies [b0529e1]
|
|
398
|
+
- Updated dependencies [66dc6ab]
|
|
399
|
+
- Updated dependencies [6f94458]
|
|
400
|
+
- Updated dependencies [6e67b86]
|
|
401
|
+
- Updated dependencies [132742f]
|
|
402
|
+
- Updated dependencies [85a2459]
|
|
403
|
+
- Updated dependencies [50dc214]
|
|
404
|
+
- Updated dependencies [e89fa92]
|
|
405
|
+
- Updated dependencies [e9fcd6b]
|
|
406
|
+
- Updated dependencies [8976ea1]
|
|
407
|
+
- Updated dependencies [56fe8c2]
|
|
408
|
+
- Updated dependencies [acabd24]
|
|
409
|
+
- Updated dependencies [ab50c8f]
|
|
410
|
+
- Updated dependencies [6491463]
|
|
411
|
+
- Updated dependencies [89cf4d6]
|
|
412
|
+
- Updated dependencies [21c5dcb]
|
|
413
|
+
- Updated dependencies [6d4d5d3]
|
|
414
|
+
- Updated dependencies [ed5d557]
|
|
415
|
+
- Updated dependencies [bca21f7]
|
|
416
|
+
- Updated dependencies [e9fcd6b]
|
|
417
|
+
- Updated dependencies [2025b1f]
|
|
418
|
+
- Updated dependencies [1a7a7c9]
|
|
419
|
+
- Updated dependencies [e9fcd6b]
|
|
420
|
+
- Updated dependencies [cbca47d]
|
|
421
|
+
- Updated dependencies [acf4d38]
|
|
422
|
+
- Updated dependencies [ef3a138]
|
|
423
|
+
- Updated dependencies [68d5dfd]
|
|
424
|
+
- Updated dependencies [3e21cf0]
|
|
425
|
+
- Updated dependencies [4cfc93b]
|
|
426
|
+
- Updated dependencies [efd6b43]
|
|
427
|
+
- Updated dependencies [859ded3]
|
|
428
|
+
- Updated dependencies [fa125f3]
|
|
429
|
+
- Updated dependencies [74628d9]
|
|
430
|
+
- Updated dependencies [a646120]
|
|
431
|
+
- Updated dependencies [6f1ce7d]
|
|
432
|
+
- Updated dependencies [7778115]
|
|
433
|
+
- Updated dependencies [2c753fe]
|
|
434
|
+
- Updated dependencies [52804cd]
|
|
435
|
+
- Updated dependencies [3f89967]
|
|
436
|
+
- Updated dependencies [53cf263]
|
|
437
|
+
- Updated dependencies [21aabbc]
|
|
438
|
+
- Updated dependencies [9c270bb]
|
|
439
|
+
- Updated dependencies [76c8c5a]
|
|
440
|
+
- Updated dependencies [088f761]
|
|
441
|
+
- Updated dependencies [a84e1ce]
|
|
442
|
+
- Updated dependencies [bf1054a]
|
|
443
|
+
- Updated dependencies [d8d2776]
|
|
444
|
+
- Updated dependencies [222dc0f]
|
|
445
|
+
- Updated dependencies [e9fcd6b]
|
|
446
|
+
- Updated dependencies [32c917d]
|
|
447
|
+
- Updated dependencies [f9a3c32]
|
|
448
|
+
- Updated dependencies [f502898]
|
|
449
|
+
- Updated dependencies [51ae731]
|
|
450
|
+
- Updated dependencies [af7edfe]
|
|
451
|
+
- Updated dependencies [b60f48b]
|
|
452
|
+
- Updated dependencies [c78c918]
|
|
453
|
+
- Updated dependencies [4ca358d]
|
|
454
|
+
- Updated dependencies [cf9bda4]
|
|
455
|
+
- Updated dependencies [784cb92]
|
|
456
|
+
- Updated dependencies [7629f4d]
|
|
457
|
+
- Updated dependencies [51df9fd]
|
|
458
|
+
- Updated dependencies [a7da4de]
|
|
459
|
+
- Updated dependencies [de0bcdd]
|
|
460
|
+
- Updated dependencies [70f7d6d]
|
|
461
|
+
- Updated dependencies [c677cda]
|
|
462
|
+
- Updated dependencies [6acb37e]
|
|
463
|
+
- Updated dependencies [7797102]
|
|
464
|
+
- Updated dependencies [554a160]
|
|
465
|
+
- Updated dependencies [f7da71e]
|
|
466
|
+
- Updated dependencies [7f745c3]
|
|
467
|
+
- Updated dependencies [0a038cc]
|
|
468
|
+
- Updated dependencies [e9fcd6b]
|
|
469
|
+
- Updated dependencies [97adce2]
|
|
470
|
+
- Updated dependencies [a83482c]
|
|
471
|
+
- Updated dependencies [5eb24f8]
|
|
472
|
+
- Updated dependencies [2a3decc]
|
|
473
|
+
- Updated dependencies [cc00df2]
|
|
474
|
+
- Updated dependencies [cc00df2]
|
|
475
|
+
- Updated dependencies [f4e6adf]
|
|
476
|
+
- Updated dependencies [ee4a59b]
|
|
477
|
+
- Updated dependencies [4db3c61]
|
|
478
|
+
- Updated dependencies [5ca314a]
|
|
479
|
+
- Updated dependencies [e0af1a8]
|
|
480
|
+
- Updated dependencies [4771bd9]
|
|
481
|
+
- Updated dependencies [414c1fc]
|
|
482
|
+
- Updated dependencies [22c0279]
|
|
483
|
+
- Updated dependencies [c930f85]
|
|
484
|
+
- Updated dependencies [0db2947]
|
|
485
|
+
- Updated dependencies [92b5d7f]
|
|
486
|
+
- Updated dependencies [613bfbd]
|
|
487
|
+
- Updated dependencies [abae16a]
|
|
488
|
+
- Updated dependencies [094b8fd]
|
|
489
|
+
- Updated dependencies [c7aca0d]
|
|
490
|
+
- Updated dependencies [c1d8f98]
|
|
491
|
+
- Updated dependencies [8e0b297]
|
|
492
|
+
- Updated dependencies [d4f9b2a]
|
|
493
|
+
- Updated dependencies [5f7fa1d]
|
|
494
|
+
- Updated dependencies [87f0ccc]
|
|
495
|
+
- Updated dependencies [aedbaef]
|
|
496
|
+
- Updated dependencies [a727043]
|
|
497
|
+
- Updated dependencies [c5d6803]
|
|
498
|
+
- Updated dependencies [10d05bb]
|
|
499
|
+
- Updated dependencies [69602e5]
|
|
500
|
+
- Updated dependencies [c3ce76c]
|
|
501
|
+
- Updated dependencies [7936b29]
|
|
502
|
+
- Updated dependencies [46803fa]
|
|
503
|
+
- Updated dependencies [c2a336c]
|
|
504
|
+
- Updated dependencies [9f890d3]
|
|
505
|
+
- Updated dependencies [0bb2318]
|
|
506
|
+
- Updated dependencies [f7db8f4]
|
|
507
|
+
- Updated dependencies [1ecee3e]
|
|
508
|
+
- Updated dependencies [9408b7f]
|
|
509
|
+
- Updated dependencies [2bb0614]
|
|
510
|
+
- Updated dependencies [b3820c3]
|
|
511
|
+
- Updated dependencies [e9fcd6b]
|
|
512
|
+
- Updated dependencies [4df2a98]
|
|
513
|
+
- Updated dependencies [9bcd9be]
|
|
514
|
+
- Updated dependencies [b398ad2]
|
|
515
|
+
- Updated dependencies [99261a7]
|
|
516
|
+
- Updated dependencies [81b426f]
|
|
517
|
+
- Updated dependencies [001af1c]
|
|
518
|
+
- Updated dependencies [fb77aa5]
|
|
519
|
+
- Updated dependencies [3d3f60e]
|
|
520
|
+
- Updated dependencies [581d8f8]
|
|
521
|
+
- Updated dependencies [f81afe3]
|
|
522
|
+
- Updated dependencies [40a44b9]
|
|
523
|
+
- Updated dependencies [f89812e]
|
|
524
|
+
- Updated dependencies [7a7fb03]
|
|
525
|
+
- Updated dependencies [8fd246d]
|
|
526
|
+
- Updated dependencies [021a735]
|
|
527
|
+
- Updated dependencies [7bdb163]
|
|
528
|
+
- @objectstack/spec@17.4.0
|
|
529
|
+
- @objectstack/core@17.4.0
|
|
530
|
+
- @objectstack/platform-objects@17.4.0
|
|
531
|
+
- @objectstack/types@17.4.0
|
|
532
|
+
- @objectstack/metadata-core@17.4.0
|
|
533
|
+
- @objectstack/metadata-fs@17.4.0
|
|
534
|
+
|
|
535
|
+
## 17.3.0
|
|
536
|
+
|
|
537
|
+
### Minor Changes
|
|
538
|
+
|
|
539
|
+
- 32448d4: feat(metadata): the artifact door registers stack-declared `capabilities` (#12892 step 1)
|
|
540
|
+
|
|
541
|
+
`ARTIFACT_FIELD_TO_TYPE` — the map that decides which collections of a compiled
|
|
542
|
+
artifact reach `MetadataManager` — now carries `capabilities: 'capability'`.
|
|
543
|
+
This is step 1 of the maintainer's 2026-08-29 ruling on #12892 (option 1: *the
|
|
544
|
+
door owns the registration route* for the five artifact security collections).
|
|
545
|
+
|
|
546
|
+
**FROM.** `capabilities` is an authorable top-level stack collection (ADR-0066
|
|
547
|
+
D1), but the door did not map it while `AppPlugin`'s ADR-0057 `SECURITY_FIELDS`
|
|
548
|
+
block did — making that block the collection's **sole registrar on an artifact
|
|
549
|
+
boot**, and it registers the raw bundle bytes with no strict parse, no schema
|
|
550
|
+
default and no ADR-0010 provenance. On a `bootstrap: 'artifact-only'` runtime
|
|
551
|
+
where `AppPlugin` does not run, a package's declared capabilities reached no
|
|
552
|
+
registry at all: `GET /meta/capability` answered **empty**, and
|
|
553
|
+
`bootstrapDeclaredCapabilities` seeded **no `sys_capability` row** for them.
|
|
554
|
+
|
|
555
|
+
**TO.** The door registers them like every other mapped collection: strict
|
|
556
|
+
parse, schema defaults, ADR-0010 provenance. Measured on a real artifact-only
|
|
557
|
+
kernel boot with no `AppPlugin`, over a package declaring
|
|
558
|
+
`{ name: 'crm.export', label: 'Export CRM data' }`:
|
|
559
|
+
|
|
560
|
+
- `GET /meta/capability` went from `[]` to one item carrying `scope:'platform'`
|
|
561
|
+
(the `CapabilitySchema` default) plus `_packageId` / `_packageVersion` /
|
|
562
|
+
`_provenance`;
|
|
563
|
+
- `sys_capability` went from 9 rows (platform-curated only) to 10 — the
|
|
564
|
+
declaration now materializes with `managed_by:'package'` and its `package_id`.
|
|
565
|
+
|
|
566
|
+
**What this does NOT change, deliberately.** On the ordinary artifact boot
|
|
567
|
+
`AppPlugin` still registers `capabilities` and still runs last, so its unparsed
|
|
568
|
+
copy still wins the registry — measured byte-identical before and after this
|
|
569
|
+
change. Two registrars on one route is the interim state the ruling explicitly
|
|
570
|
+
permits while step 2 (that block stops registering the five on the **artifact**
|
|
571
|
+
path, after a census of the non-artifact boots that depend on it) lands. No
|
|
572
|
+
authoring surface moves, and no artifact that parses today stops parsing.
|
|
573
|
+
- 4b4d5a3: fix(metadata): `FilesystemLoader.list()` reports only names `findFile()` / `load()` / `exists()` can resolve
|
|
574
|
+
|
|
575
|
+
**BREAKING (list output narrows).** Two shapes stop appearing in
|
|
576
|
+
`FilesystemLoader.list()`, and therefore in `MetadataManager.listNames()`:
|
|
577
|
+
|
|
578
|
+
- **nested files** — `ROOT/TYPE/crm/account.json` was listed as `account`, a
|
|
579
|
+
name that resolves against `ROOT/TYPE/account.json` and finds nothing;
|
|
580
|
+
- **extension-less files** — `ROOT/TYPE/noext` was listed as `noext`, which
|
|
581
|
+
resolves under no appended extension at all.
|
|
582
|
+
|
|
583
|
+
A third shape follows from the same rule rather than from a rule of its own:
|
|
584
|
+
the extensions a name can be resolved under are now the ones belonging to the
|
|
585
|
+
loader's **registered serializers**, so under the manager's default format set
|
|
586
|
+
(`typescript` / `json` / `yaml`) a `.js` file leaves `list()` too. It was
|
|
587
|
+
previously listed and resolvable while `loadMany()` could never return it and
|
|
588
|
+
`load()` threw `No serializer found for format: javascript`. Register the
|
|
589
|
+
`javascript` serializer and it is listed, resolvable and loadable together.
|
|
590
|
+
|
|
591
|
+
`list()`, `findFile()` and `loadManyKeyed()` now share one name-to-path
|
|
592
|
+
derivation, so `listNames()` and `get()` give the same answer. Previously a name
|
|
593
|
+
could sit in the list while `get()` answered `null` for it — a silent failure an
|
|
594
|
+
author reads as their own typo.
|
|
595
|
+
|
|
596
|
+
Nothing changes for a tree whose metadata is laid out as `ROOT/TYPE/NAME.json`
|
|
597
|
+
(or `.yaml` / `.yml` / `.ts`), which is the layout ADR-0008 §10 already
|
|
598
|
+
prescribes and `metadata-fs`'s `parseItemPath()` already enforces. `.yaml`,
|
|
599
|
+
`.yml` and `.ts` are unaffected: the extension set follows the registered
|
|
600
|
+
serializers, not §10's `.json`-only rule, which governs the `metadata-fs` store.
|
|
601
|
+
|
|
602
|
+
`loadMany()` is unchanged and still returns bodies for nested and
|
|
603
|
+
extension-less files; `findFile()` still resolves an explicitly path-shaped
|
|
604
|
+
name such as `crm/account`, which nothing lists.
|
|
605
|
+
|
|
606
|
+
<!-- adr-0087: not-required (no-migration-prescription) No authorable key, Zod schema or stored row moves: this narrows one runtime loader's `list()` output. The ledger's artifacts project metadata rewrites, and there is nothing here for `objectstack migrate meta` to rewrite — a tree carrying a nested or extension-less file needs the FILE relocated into the two-segment layout ADR-0008 §10 already prescribes, which no migration prescription can express. -->
|
|
607
|
+
- f4e7ae5: fix(security): gate the metadata HMR door on an explicit development posture (#12140)
|
|
608
|
+
|
|
609
|
+
**BREAKING** surface narrowing — `GET`/`POST /api/v1/dev/metadata-events` are no
|
|
610
|
+
longer mounted unless `NODE_ENV` is exactly `development`. Shipped as `minor`
|
|
611
|
+
under the repo's launch-window convention for breaking changes.
|
|
612
|
+
|
|
613
|
+
`MetadataPlugin` mounted both HMR routes whenever a raw-app-capable HTTP server
|
|
614
|
+
was present, with no authentication and no environment condition of its own. The
|
|
615
|
+
dev-only posture the path (`/api/v1/dev/…`) advertises lived only in prose —
|
|
616
|
+
"production deployments simply won't have a CLI POSTing to this endpoint" — which
|
|
617
|
+
is a claim about who is on the network, not a gate that stops them, and the same
|
|
618
|
+
structural shape #9391 closed for the `datasource-admin` family: both mounts take
|
|
619
|
+
the host's framework-native app handle, which is outside REST's `enforceAuth`
|
|
620
|
+
seam by construction.
|
|
621
|
+
|
|
622
|
+
The distributions were enumerated rather than assumed, because "a dev-only
|
|
623
|
+
surface lacking a gate that says so" and "an unauthenticated door on a real
|
|
624
|
+
deployment" want different repairs. The official container image runs `os start`
|
|
625
|
+
under `NODE_ENV=production`; that boot reaches `createStandaloneStack`, which
|
|
626
|
+
composes `MetadataPlugin` unconditionally (only `artifactWatch` was
|
|
627
|
+
NODE_ENV-gated) onto a kernel that registers the Hono server whenever it serves.
|
|
628
|
+
So a production-shaped boot did mount both routes and did answer them — `POST`
|
|
629
|
+
re-reading the compiled artifact from disk and broadcasting a reload frame to
|
|
630
|
+
every connected client, unauthenticated.
|
|
631
|
+
|
|
632
|
+
`registerMetadataHmrRoutes` now refuses at its first statement, ahead of every
|
|
633
|
+
side effect it performs, and returns `null` so "nothing was mounted" is a fact
|
|
634
|
+
the compiler forces its caller to handle. Unset `NODE_ENV` is closed, per the
|
|
635
|
+
maintainer's 2026-08-06 ruling that an absent value reads as `production`; `test`
|
|
636
|
+
and unrecognised spellings (`staging`, `preview`, `qa`) are closed too — a gate
|
|
637
|
+
must not treat a spelling nobody recognises as a key.
|
|
638
|
+
|
|
639
|
+
Who is affected, in both directions:
|
|
640
|
+
|
|
641
|
+
- `os dev` is unchanged. It spawns `os serve --dev`, which sets
|
|
642
|
+
`NODE_ENV='development'` before any plugin starts, so the watch-recompile loop
|
|
643
|
+
still gets its `200` and Studio still gets its reload frames.
|
|
644
|
+
- A deployment that was reaching this endpoint on a production-posture boot now
|
|
645
|
+
gets its host app's `404`. That door was never advertised, never in the SDK
|
|
646
|
+
(`@objectstack/client` builds no such URL) and is a build-tool loopback; if you
|
|
647
|
+
need a supported production reload trigger, that is a product decision, not
|
|
648
|
+
this endpoint.
|
|
649
|
+
- The server-side artifact-file watcher is untouched on every boot shape that had
|
|
650
|
+
it: the reload still happens, only the broadcast to (now absent) SSE clients is
|
|
651
|
+
skipped.
|
|
652
|
+
|
|
653
|
+
<!-- adr-0087: not-required (no-migration-prescription) a mounted HTTP route stops existing under a non-development posture; no authorable key, no spec declaration, no Zod schema and no stored value moves, so `objectstack migrate meta` has nothing to rewrite and no ledger entry could reach anyone this affects -->
|
|
654
|
+
- ac4fefe: fix(metadata): key loader-held items by the row key they were stored under, so a body with no top-level `name` is no longer dropped from `list()` (#14205)
|
|
655
|
+
|
|
656
|
+
`MetadataManager.readListUncached()` — and its no-catch sibling
|
|
657
|
+
`listForIndex()`, which builds the endpoint index — merged each loader's answer
|
|
658
|
+
into the result set keyed by `body.name`, and admitted an item ONLY when the
|
|
659
|
+
stored body carried a string `name`.
|
|
660
|
+
|
|
661
|
+
A metadata body is not required to name itself. `register(type, name, data)`
|
|
662
|
+
takes the key as its ARGUMENT, and `assertMetadataRegisterContract` says so in
|
|
663
|
+
as many words: "A document with NO `name` of its own is fine — the argument is
|
|
664
|
+
the key". An aggregated `defineView` container is exactly that shape — no own
|
|
665
|
+
`name` by design, its identity being the target object, carried in the row's
|
|
666
|
+
`name` COLUMN — and `DatabaseLoader.rowToData()` returns the stored body
|
|
667
|
+
without folding the column into it.
|
|
668
|
+
|
|
669
|
+
So a container written by `register('view', OBJECT, container)` lived in the
|
|
670
|
+
registry for the life of the process and was written to `sys_metadata`, and
|
|
671
|
+
then **disappeared at the next restart**: cold registry, only the loader
|
|
672
|
+
answering, and `list('view')` refused the row. `listDiagnosed()` reported that
|
|
673
|
+
short answer as complete (`degraded: false`) because no loader had thrown. Not
|
|
674
|
+
scoped to views — any loader-held body with no top-level `name` was invisible.
|
|
675
|
+
|
|
676
|
+
**The repair.** A loader-held item's identity is the key its store holds it
|
|
677
|
+
under, so the manager now asks the loader for that key rather than guessing it
|
|
678
|
+
from the body: `MetadataLoader` gains an OPTIONAL `loadManyKeyed()` returning
|
|
679
|
+
`(name, body)` pairs, implemented by `DatabaseLoader` (from the row's `name`
|
|
680
|
+
column) and `MemoryLoader` (from its storage map key). The key travels BESIDE
|
|
681
|
+
the body and is never folded into it, so nothing synthesises a `name` into a
|
|
682
|
+
body that deliberately has none and the register contract's refusal of a
|
|
683
|
+
disagreeing `data.name` keeps meaning what it says.
|
|
684
|
+
|
|
685
|
+
**Nothing consumers see today changes shape.** For any item that went through
|
|
686
|
+
`register()`, a `data.name` that exists is required to equal the key, so the
|
|
687
|
+
keyed merge produces the identical entry; what is new is only the items the old
|
|
688
|
+
gate refused. `loadManyKeyed()` is optional, and a loader without it (a
|
|
689
|
+
`RemoteLoader`, whose wire format carries bodies only) falls back to the
|
|
690
|
+
previous `body.name` keying unchanged — so no implementor of the published
|
|
691
|
+
`MetadataLoader` interface needs to change.
|
|
692
|
+
|
|
693
|
+
`MetadataManager.loadMany()` is deliberately untouched: its `body.name` test is
|
|
694
|
+
a de-duplication guard, not an admission gate — a nameless item already fell
|
|
695
|
+
past it and was returned — so it never carried this defect.
|
|
696
|
+
- 9e0ba21: feat(spec): retire the paper metadata-customization protocol with its full coupling set (#13135, re-charter of #12057; ADR-0049, ADR-0126)
|
|
697
|
+
|
|
698
|
+
<!-- adr-0087: registered metadata-customization-protocol-retired -->
|
|
699
|
+
|
|
700
|
+
**BREAKING** export removal + authorable-key retirement, landing after the
|
|
701
|
+
v17.0.0 cut (the lockstep launch-window convention ships it as `minor`; the
|
|
702
|
+
prescriptions are registered under protocol major 18 —
|
|
703
|
+
`RETIRED_DEFS_BY_MAJOR[18]`, `RETIRED_KEYS_BY_MAJOR[18]` and the D3 semantic
|
|
704
|
+
entry `metadata-customization-protocol-retired` — where `os migrate meta`
|
|
705
|
+
users will look).
|
|
706
|
+
|
|
707
|
+
`kernel/metadata-customization.zod.ts` declared a three-layer platform/user
|
|
708
|
+
patch-overlay protocol (field-level change tracking, customization policies, a
|
|
709
|
+
3-way-merge story) that nothing reachable implemented: no route ever served
|
|
710
|
+
the paper `…/overlay` / `…/effective` endpoints, the only implementation
|
|
711
|
+
(`packages/metadata`'s manager limb) was called solely by its own unit tests,
|
|
712
|
+
no merge engine ever existed, and no code read a `CustomizationPolicy`.
|
|
713
|
+
ADR-0126 §6 wall 4 supersedes the protocol as a matter of record ("nothing may
|
|
714
|
+
build against it"); the maintainer adopted retirement on #12057 (2026-08-29,
|
|
715
|
+
「同意」), and #13135 charters the full coupling set the fork report measured.
|
|
716
|
+
|
|
717
|
+
FROM → TO:
|
|
718
|
+
|
|
719
|
+
- `MetadataOverlaySchema` / `FieldChangeSchema` / `CustomizationOriginSchema` /
|
|
720
|
+
`MergeConflictSchema` / `MergeStrategyConfigSchema` / `MergeResultSchema` /
|
|
721
|
+
`CustomizationPolicySchema` and their `…`/`…Parsed` types
|
|
722
|
+
(`@objectstack/spec/kernel`) → *(removed — no replacement protocol)*. The
|
|
723
|
+
customization that actually ships: ADR-0005's org-scoped overlay
|
|
724
|
+
(`allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, `sys_metadata` org
|
|
725
|
+
rows, layered read `code`/`overlay`/`effective`) and ADR-0126's
|
|
726
|
+
packaged-metadata model (clone + ledger disable).
|
|
727
|
+
- `MetadataOverlayResponseSchema` / `MetadataOverlaySaveRequestSchema` /
|
|
728
|
+
`MetadataEffectiveResponseSchema` (`@objectstack/spec/api` §5) →
|
|
729
|
+
*(removed)* — contracts for endpoints no adapter ever served; the layered
|
|
730
|
+
read's contracts (`getMetaItemLayered`) are the live API.
|
|
731
|
+
- `IMetadataService.getOverlay` / `.saveOverlay` / `.removeOverlay` /
|
|
732
|
+
`.getEffective` optional members (`@objectstack/spec/contracts`) →
|
|
733
|
+
*(removed)*, together with `packages/metadata`'s in-memory limb and its
|
|
734
|
+
`'overlay'` feature log entry.
|
|
735
|
+
- `MetadataPluginConfig.customizationPolicies` / `.mergeStrategy` and
|
|
736
|
+
`MetadataManagerConfig.persistence.overlayWritable` → *(removed — retiredKey
|
|
737
|
+
tombstones)*: authoring one is now a `tsc` error and a parse error carrying
|
|
738
|
+
the prescription. Delete the keys; nothing replaces them (`persistence.writable`
|
|
739
|
+
remains the base write gate).
|
|
740
|
+
|
|
741
|
+
One-line fix: delete the keys and any code building against the removed
|
|
742
|
+
exports — they configured and described nothing that ever ran; org-level
|
|
743
|
+
customization keeps riding the ADR-0005 overlay unchanged.
|
|
744
|
+
|
|
745
|
+
The retirement kit: whole-module deletion + kernel barrel line; 10
|
|
746
|
+
`RETIRED_DEFS_BY_MAJOR[18]` entries (7 kernel defs + 3 api §5 contracts); 3
|
|
747
|
+
`RETIRED_KEYS_BY_MAJOR[18]` tombstone entries (no D2 conversion —
|
|
748
|
+
plugin/manager configs are not stack collection members, the
|
|
749
|
+
`kernel/MetadataPluginConfig:additionalTypes` precedent); D3 semantic entry
|
|
750
|
+
`metadata-customization-protocol-retired`; retirement pin test
|
|
751
|
+
(`kernel/metadata-customization-retirement.test.ts`); type-alias pin rows
|
|
752
|
+
Iso408-411 vacated; api-surface / export-origins / json-schema manifest /
|
|
753
|
+
authorable-surface / reference docs regenerated (the
|
|
754
|
+
`kernel/metadata-customization` reference page disappears with the module).
|
|
755
|
+
- 4cda78c: fix(metadata,objectql,metadata-protocol): require a missing-table error to name the table that was READ (#13324)
|
|
756
|
+
|
|
757
|
+
`isMissingTableError` answers the one question that licenses a fail-soft caller
|
|
758
|
+
to treat an empty result as the truth: "did this read fail because the table has
|
|
759
|
+
not been provisioned yet?". It matched the *shape* of the dialect phrase and
|
|
760
|
+
never asked WHICH table the phrase names.
|
|
761
|
+
|
|
762
|
+
Measured on a real libsql database: a view whose base table is gone fails with
|
|
763
|
+
`no such table: main.<base>` when the view itself is read. The phrase matches,
|
|
764
|
+
so a read of a relation that **exists and may be backed by rows** was classified
|
|
765
|
+
benign, and every fail-soft consumer on that path — `probeInstallOrganizations`,
|
|
766
|
+
`resolveFileReferences`, `seedAutonumber`, the cascade-delete dependents probe,
|
|
767
|
+
`DatabaseLoader`, `SeedLoaderService`, the `sys_metadata` overlay reads —
|
|
768
|
+
computed its answer from data it never read. That is a false "benign", the
|
|
769
|
+
direction the module's own docblock calls far more expensive than a false
|
|
770
|
+
"real".
|
|
771
|
+
|
|
772
|
+
The predicate now takes the object the caller was reading and refuses the
|
|
773
|
+
benign verdict when the phrase names a different relation. Shape alone cannot
|
|
774
|
+
separate the two cases: measured, a view over a missing base table and a
|
|
775
|
+
genuine missing table the caller qualified produce byte-identical messages, so
|
|
776
|
+
the read's name is a parameter rather than another regex.
|
|
777
|
+
|
|
778
|
+
The parameter is **optional** — omitting it reproduces the previous behaviour
|
|
779
|
+
exactly, so no external caller of `@objectstack/metadata/errors` changes. Every
|
|
780
|
+
in-repo call site now passes it. The comparison folds away schema/database
|
|
781
|
+
qualifiers, the legacy `namespace__short` prefix and case, so every shape
|
|
782
|
+
recognised before for a genuine missing table (sqlite `no such table: X`,
|
|
783
|
+
Postgres `relation "x" does not exist`, MySQL `table "x" doesn't exist`,
|
|
784
|
+
`unknown table`, the SQLSTATE and errno limbs) still answers benign.
|
|
785
|
+
- 3c1bbd2: fix(objectql,metadata): the ObjectQL boot loop derives a view container's object through the shared `deriveViewContainerObject`, so the row's own `name` is the LAST term at every SOURCE registrar (#14399)
|
|
786
|
+
|
|
787
|
+
Three sites derive "which object does an aggregated `defineView` container bind
|
|
788
|
+
to". After #13407 / #13913 / #13912 all three read the container's own top-level
|
|
789
|
+
`object` before the `list.data.object` chain, but they still disagreed about the
|
|
790
|
+
row's own `name`:
|
|
791
|
+
|
|
792
|
+
- `packages/objectql/src/engine.ts` `resolveMetadataItemName('views', item)` —
|
|
793
|
+
the boot-loop SOURCE registrar — read `name` FIRST, before `object`;
|
|
794
|
+
- `deriveViewContainerObject` (`@objectstack/metadata`, used by the artifact/HMR
|
|
795
|
+
SOURCE registrar and by `getViewsByObject()`) and `expandRuntimeViewContainer`
|
|
796
|
+
(`@objectstack/metadata-protocol`, the runtime door) both read `name` LAST.
|
|
797
|
+
|
|
798
|
+
A container written as `{ name: 'lead_views', object: 'crm_lead', list: { … } }`
|
|
799
|
+
therefore registered under `lead_views` through the boot loop and under
|
|
800
|
+
`crm_lead` everywhere else, with the whole expansion (`<object>.<key>`) carried
|
|
801
|
+
along — and since `getViewsByObject()` / `GET /meta/view?object=` filter the
|
|
802
|
+
expanded items by their `object`, which registrar loaded the document decided
|
|
803
|
+
whether the views were addressable under the object at all. No error, no
|
|
804
|
+
diagnostic.
|
|
805
|
+
|
|
806
|
+
The boot loop's container branch now calls `deriveViewContainerObject` — by
|
|
807
|
+
import, not by re-spelling: a fourth hand-copy of the chain was the defect, not
|
|
808
|
+
the repair. The direction is the 2026-08-07 meta-rule rather than taste (one
|
|
809
|
+
operation, two inconsistent implementations, the side bound by a DECLARATION
|
|
810
|
+
wins): `ViewSchema.object`'s own `.describe()` names its readers, while the boot
|
|
811
|
+
loop's order argued from item identity, which declares nothing about the
|
|
812
|
+
binding. The two sites that already held the winning order are untouched.
|
|
813
|
+
|
|
814
|
+
**`@objectstack/metadata` — new public export (`minor`).**
|
|
815
|
+
`deriveViewContainerObject` was module-local; it is now on the package's root
|
|
816
|
+
entry, because `packages/objectql` is a SOURCE registrar for the same containers
|
|
817
|
+
and has to mint the same key. `packages/objectql` already declares
|
|
818
|
+
`@objectstack/metadata` as a dependency and nothing in `packages/metadata`
|
|
819
|
+
depends on `objectql`, so the import adds no cycle.
|
|
820
|
+
|
|
821
|
+
**Scope of the behaviour change.** Only the `views` CONTAINER branch moves, gated
|
|
822
|
+
on `isAggregatedViewContainer`: the assembled `viewItems:` channel (standalone
|
|
823
|
+
ViewItems and flattened overlays, every member of `AssembledViewArtifactSchema`
|
|
824
|
+
requiring `viewKind`) still keys by its own `name` first, which is its identity
|
|
825
|
+
and not a binding. `item.id` is untouched and cannot fire for a container —
|
|
826
|
+
`ViewSchema` is a `strictObject` declaring `name` and `object` and no `id`.
|
|
827
|
+
|
|
828
|
+
**No migration surface.** Measured on this tree: of the 54 non-test sources that
|
|
829
|
+
author or carry view containers, ZERO declare a `name` that differs from the
|
|
830
|
+
object they bind to, so every in-tree container derived identically at all three
|
|
831
|
+
sites before this change and does after it. What moves is the latent shape only.
|
|
832
|
+
|
|
833
|
+
⚠️ One card premise was measured false and is recorded in the new pin rather
|
|
834
|
+
than quietly dropped: the artifact/HMR registrar does not silently mint a second
|
|
835
|
+
key for a divergent container. It derives `crm_lead` correctly and then refuses
|
|
836
|
+
the whole artifact load — `assertMetadataRegisterContract` (#7378 row 1),
|
|
837
|
+
`VALIDATION_ERROR` / 400 — because the document's own `data.name` still reads
|
|
838
|
+
`lead_views`. The boot loop reconciles that field and the artifact door does
|
|
839
|
+
not; that residual asymmetry is a separate defect at a separate site and is
|
|
840
|
+
filed as its own card.
|
|
841
|
+
|
|
842
|
+
### Patch Changes
|
|
843
|
+
|
|
844
|
+
- 655b106: fix(metadata): register a `packages[]` artifact per package at the metadata door so every object has one owner across every door (#14599)
|
|
845
|
+
|
|
846
|
+
A release artifact carrying `packages[]` (ADR-0130 D4) was read at the metadata
|
|
847
|
+
door as if it carried one package: `MetadataPlugin._parseAndRegisterArtifact`
|
|
848
|
+
iterated the **flattened top level** and stamped every item with the artifact's
|
|
849
|
+
own `manifest.id`. For an artifact composed with `composeStacks(…, { manifest:
|
|
850
|
+
'preserve' })` that id is one arbitrary member's — `selectManifest`'s `'last'`
|
|
851
|
+
pick — so a two-package artifact registered the **module's** object under the
|
|
852
|
+
**App** package's identity, while the ObjectQL load path, reading the same
|
|
853
|
+
artifact's `packages[]`, owned it under the module's.
|
|
854
|
+
|
|
855
|
+
The platform then held two answers to "who owns this object", and which one a
|
|
856
|
+
consumer saw depended on the door it went through. Measured on a real boot of
|
|
857
|
+
`examples/app-multi-package`:
|
|
858
|
+
|
|
859
|
+
- `GET /api/v1/meta/object` served `crm_order` **twice** — the list merge keys
|
|
860
|
+
slots by `${packageId}${name}`, so the two differently-attributed copies
|
|
861
|
+
landed in two slots;
|
|
862
|
+
- `GET /api/v1/meta/object?package=<the App package>` returned the **module's**
|
|
863
|
+
object, because the App-stamped copy was re-ingested into the registry as that
|
|
864
|
+
package's contribution;
|
|
865
|
+
- the layers door named the App package while the item door and
|
|
866
|
+
`GET /api/v1/packages` named the module;
|
|
867
|
+
- Studio's Data pillar for the App package listed the module's object — ADR-0130
|
|
868
|
+
Consequences §1.3a ("Studio's scope is the package") did not hold.
|
|
869
|
+
|
|
870
|
+
**The door now reads both shapes, and attributes every item to the body it was
|
|
871
|
+
found in.** `packages` present → each assembled package body's collections are
|
|
872
|
+
registered stamped with **that body's** id; `packages` absent → the single
|
|
873
|
+
`manifest` branch runs exactly as before (D7). The owner is read off the body an
|
|
874
|
+
item was found in — never reverse-derived by matching a top-level item's name
|
|
875
|
+
against a name-to-package index, which would be the second metadata-identity
|
|
876
|
+
resolution path #14512's triage rejected by name.
|
|
877
|
+
|
|
878
|
+
**Ordering and the entry gate are reused, not re-derived (D5).** The door calls
|
|
879
|
+
the same `resolveArtifactPackageOrder` the ObjectQL load path calls, so the two
|
|
880
|
+
readers of one `packages[]` cannot disagree about the registration order **or**
|
|
881
|
+
about which artifacts are loadable at all.
|
|
882
|
+
|
|
883
|
+
⚠️ **`resolveArtifactPackageOrder` / `artifactPackageId` moved to
|
|
884
|
+
`@objectstack/core`** — hence the `minor` there. They were in
|
|
885
|
+
`@objectstack/objectql`, which **depends on** `@objectstack/metadata`, so the
|
|
886
|
+
metadata door could not import them from where they lived; `@objectstack/core`
|
|
887
|
+
already owns `resolvePluginOrder` and is already a dependency of both readers,
|
|
888
|
+
so hosting them there adds **no edge** to the package graph. `@objectstack/objectql`
|
|
889
|
+
re-exports both under their existing names — its published surface is unchanged,
|
|
890
|
+
which is why it is graded `patch`. `@objectstack/runtime` is `patch` for the
|
|
891
|
+
dispatcher error vocabulary's `file:` anchors, repointed at the new path.
|
|
892
|
+
|
|
893
|
+
**Single-package artifacts are byte-for-byte unaffected (D7)**, measured rather
|
|
894
|
+
than asserted: the whole `manager.register` sequence for a single-`manifest`
|
|
895
|
+
artifact — every call, in order, with the id and version each item was stamped
|
|
896
|
+
with — is pinned as a literal in
|
|
897
|
+
`packages/metadata/src/plugin-artifact-packages-attribution.test.ts` and was
|
|
898
|
+
recorded identically on both legs of the ablation. A real boot of
|
|
899
|
+
`examples/app-todo` answers every door identically before and after.
|
|
900
|
+
|
|
901
|
+
**Nothing a booted instance can see today disappears.** Every live
|
|
902
|
+
`ARTIFACT_FIELD_TO_TYPE` key is a member of `AssembledPackageBodySchema`
|
|
903
|
+
(measured, not assumed), so iterating bodies loses no collection; and because
|
|
904
|
+
`packages` composes by `concat`, an artifact whose top level carries a
|
|
905
|
+
definition no package body repeats keeps it — registered once, attributed to the
|
|
906
|
+
artifact's own identity, and logged, because it means the artifact's two halves
|
|
907
|
+
disagree about what it ships.
|
|
908
|
+
|
|
909
|
+
⛔ The **producer** half is untouched: `composeStacks` and `os build` keep
|
|
910
|
+
emitting the flattened top level alongside `packages[]`. Whether they should is
|
|
911
|
+
#14512's decision, not this door's.
|
|
912
|
+
- 54e2d36: Artifacts built by released 17.x tooling boot again on ≥17.2 runtimes: the artifact-ingestion door now runs a versioned ADR-0087 forward conversion before the strict parse (#12772).
|
|
913
|
+
|
|
914
|
+
A compiled artifact whose declared `engines.protocol` floor predates the running `@objectstack/spec` version replays the full conversion chain — retired entries included — before validation, exactly the policy the stored-row read path already applies to `sys_metadata` rows. Measured incident: `dist/objectstack.json` built by `@objectstack/cli` 17.1.0 carries the then-legal `allowRestore`/`allowPurge` permission bits (75 of each, injected by the released builder), and spec 17.2.0's `retiredKey` tombstone refused the boot with no operator remedy (`os migrate meta` targets sources, not built artifacts).
|
|
915
|
+
|
|
916
|
+
The conversion is versioned, not a blanket amnesty: an artifact authored at the current (or a newer) spec version converts nothing and still refuses at the tombstone — the retired keys return with the M2 lifecycle initiative (#1883), and artifacts authored against that surface are never stripped by history. Conversion notices surface operator-visibly and deduped, one summary line per conversion per artifact. New exports from `@objectstack/metadata-core`: `applyArtifactForwardConversions`, `resolveInstalledSpecVersion`, `parseRangeFloor`, `resolveDeclaredRange`.
|
|
917
|
+
- 7ef0268: fix(metadata,runtime): retire the `policies` dead pointer in both artifact registrars, and pin the map that carried it (#12894)
|
|
918
|
+
|
|
919
|
+
Zero behaviour change, by construction. Both readers of an artifact boot carried
|
|
920
|
+
a `policies` -> `policy` entry — the artifact door's `ARTIFACT_FIELD_TO_TYPE`
|
|
921
|
+
(`packages/metadata/src/plugin.ts`) and `AppPlugin`'s ADR-0057 `SECURITY_FIELDS`
|
|
922
|
+
list (`packages/runtime/src/app-plugin.ts`) — and **neither could ever match**.
|
|
923
|
+
`ObjectStackDefinitionSchema` is a `strictObject` that declares no top-level
|
|
924
|
+
`policies` key, so a definition carrying a `policies` array is refused outright
|
|
925
|
+
by the door's strict parse and reaches neither registry. The word is real but
|
|
926
|
+
lives one level down: on a permission set `policies` is an alias for
|
|
927
|
+
`rowLevelSecurity` (`PERMISSION_SET_KEY_ALIASES`) — a key on an **item**, never a
|
|
928
|
+
collection. Both entries are removed, each leaving in place the note the map
|
|
929
|
+
already writes for a retirement: what it pointed at, and why it could not match.
|
|
930
|
+
|
|
931
|
+
That was the third entry retired from `ARTIFACT_FIELD_TO_TYPE` for exactly this
|
|
932
|
+
reason (`themes`, then `roles` -> `positions`, which "matched nothing and
|
|
933
|
+
silently dropped compiled positions"). So the deletion ships with the thing the
|
|
934
|
+
two predecessors did not have — a check that fails when the pattern recurs:
|
|
935
|
+
|
|
936
|
+
- `check:stack-collection-maps` now reconciles **eight** hand-maintained
|
|
937
|
+
enumerations against the schema, not seven. `SECURITY_FIELDS` is the new
|
|
938
|
+
eighth, and how it was missing is the finding rather than a footnote: it is
|
|
939
|
+
the only one of the eight that pairs its keys as `[collection, kind]` tuples,
|
|
940
|
+
which neither existing extractor could read, so the site was skipped rather
|
|
941
|
+
than reported. Re-adding `policies` — or any other key the schema does not
|
|
942
|
+
declare — to **either** registrar now fails the gate with the site named.
|
|
943
|
+
- A new `tupleFirstItems` extractor reads that shape, with a self-test case
|
|
944
|
+
(13 assertions, up from 12) covering the comment/nesting cases the flat
|
|
945
|
+
string-array extractor already pins.
|
|
946
|
+
|
|
947
|
+
The mirror-image half of the same measurement is **carried, not shipped**:
|
|
948
|
+
`capabilities` is a declared top-level collection that `SECURITY_FIELDS`
|
|
949
|
+
registers and the door's map does not, making `AppPlugin` its sole registrar on
|
|
950
|
+
an artifact boot. Adding it to the door changes what an artifact boot registers,
|
|
951
|
+
so it is measured and handed to the route-ownership decision (#12892) instead of
|
|
952
|
+
being smuggled in here. The new waiver row records the asymmetry in place.
|
|
953
|
+
- b745157: feat(metadata-core,metadata): warn the operator when a pre-current-era artifact carries form-view predicates that fault open (#12915)
|
|
954
|
+
|
|
955
|
+
A form-view predicate binds `record` (+ `previous`, `parent`) in runtime record
|
|
956
|
+
forms, or `data` in metadata-editing forms. The contract states the failure mode
|
|
957
|
+
beside the vocabulary: **a bare identifier is unbound, the predicate faults, and
|
|
958
|
+
`visibleWhen`'s fault fallback is `true`** — so a field the predicate was
|
|
959
|
+
authored to hide renders for everyone.
|
|
960
|
+
|
|
961
|
+
That is quiet alone and lethal in combination with the authoring pattern it
|
|
962
|
+
serves. Measured on a real deployment: an artifact built by released
|
|
963
|
+
`@objectstack/cli` 17.1.0 authors
|
|
964
|
+
`{ field: 'disqualification_reason', required: true, visibleWhen: 'status == "unqualified"' }`
|
|
965
|
+
— the era's working spelling. On a 17.2 runtime the predicate faults open, the
|
|
966
|
+
conditionally hidden field renders, and its unconditional `required: true`
|
|
967
|
+
blocks **every** record creation through the console, while the same payload
|
|
968
|
+
POSTs 201 through REST. Nothing refused and nothing logged, so the operator —
|
|
969
|
+
the only person who can rebuild the artifact — had no signal at all.
|
|
970
|
+
|
|
971
|
+
The framework artifact door now emits **one deduped `warn` line per artifact**
|
|
972
|
+
naming the authored `engines.protocol` floor and the runtime spec version, how
|
|
973
|
+
many predicates on which views (with the first path as an anchor), the
|
|
974
|
+
fault-open consequence, and the remedy (`os build`). It rides the same funnel
|
|
975
|
+
that already carries the forward-conversion summaries, so both SaaS shapes are
|
|
976
|
+
covered: a single-DB multi-org runtime warns once at boot, and per-tenant-DB
|
|
977
|
+
kernels each warn at their own.
|
|
978
|
+
|
|
979
|
+
**No behaviour change.** No refusal, no rewrite, no schema or contract edit —
|
|
980
|
+
the predicate keeps faulting open exactly as before, and the artifact bytes are
|
|
981
|
+
untouched. Rewriting a bare root to `record.` is a separate, deferred ADR-0087
|
|
982
|
+
conversion.
|
|
983
|
+
|
|
984
|
+
**Scoped to legacy artifacts by construction.** The notice fires only inside the
|
|
985
|
+
versioned window the forward conversion already opens (declared floor below the
|
|
986
|
+
running spec, or undeclared), read off that pass's own verdict rather than
|
|
987
|
+
recomputed. An artifact declaring the current or a newer floor gets zero notices
|
|
988
|
+
from this feature even when it carries bare roots — the boundary that keeps a
|
|
989
|
+
notice about legacy artifacts out of contract territory.
|
|
990
|
+
|
|
991
|
+
Detection is exported from `@objectstack/metadata-core` as
|
|
992
|
+
`detectUnboundFormViewPredicateRoots` (with `BOUND_FORM_VIEW_PREDICATE_ROOTS`)
|
|
993
|
+
so other composed artifact doors can reuse one policy rather than fork it. It is
|
|
994
|
+
pure, read-only, and tuned to prefer silence over a false accusation: string
|
|
995
|
+
literals are stripped before the scan, only root position counts, call targets
|
|
996
|
+
are not roots, comprehension macros (whose iteration variable is locally bound)
|
|
997
|
+
are skipped whole, and AST-only envelopes pass.
|
|
998
|
+
- 6a180e4: fix(core,rest,services)!: a permission-store read failure now fails LOUD instead of resolving as an authenticated caller holding zero capabilities (#13279)
|
|
999
|
+
|
|
1000
|
+
**BREAKING** runtime behaviour change on the shared authorization resolver,
|
|
1001
|
+
shipped as `minor` under the repo's launch-window convention.
|
|
1002
|
+
|
|
1003
|
+
`resolveAuthzContext`'s per-read helper `tryFind` answered a THROWN read exactly
|
|
1004
|
+
the way it answered an EMPTY one: `[]`. So an outage of the permission store
|
|
1005
|
+
resolved as a well-formed context for an authenticated principal holding no
|
|
1006
|
+
capabilities, and the package-management door answered
|
|
1007
|
+
`403 FORBIDDEN` — "Reading packages requires the `studio.access` or
|
|
1008
|
+
`setup.access` capability." That answer was measured byte-identical
|
|
1009
|
+
(`JSON.stringify` equal, against a control that separates two answers which do
|
|
1010
|
+
differ) to what a caller who genuinely holds nothing receives. An administrator
|
|
1011
|
+
was told they lack a capability, during an outage of the store that holds the
|
|
1012
|
+
capability.
|
|
1013
|
+
|
|
1014
|
+
Maintainer ruling 2026-08-30, verbatim 「第一批其余同意」: `tryFind` 区分「无行」
|
|
1015
|
+
与「读失败」,读失败 fail-loud —— 权限库不可达时不再解析为「已认证零能力」,而是
|
|
1016
|
+
响亮拒绝(与真实能力拒绝的 403 可区分)。
|
|
1017
|
+
|
|
1018
|
+
Second maintainer ruling the same day (第 5 场总监席决裁批 #9, verbatim 「同意」),
|
|
1019
|
+
after implementing the first one showed that "the read failed" is two facts:
|
|
1020
|
+
采**选项 A** —— 把 `isMissingTableError` 从 `@objectstack/metadata` 迁至
|
|
1021
|
+
`@objectstack/types`(core 已依赖),metadata 保留 re-export 兼容;`tryFind` 仅对
|
|
1022
|
+
**未被判定为「表未 provision」**的读失败抛 `AuthzStoreUnavailableError`。
|
|
1023
|
+
|
|
1024
|
+
**What changed.** A permission-store read that is issued and throws now raises
|
|
1025
|
+
`AuthzStoreUnavailableError`, which carries the EXISTING ADR-0112 wire code
|
|
1026
|
+
`SERVICE_UNAVAILABLE` and status `503`. No code is added to the closed wire
|
|
1027
|
+
vocabulary and no response envelope gains or loses a key — only which declared
|
|
1028
|
+
code an outage selects. Doors that map thrown errors through
|
|
1029
|
+
`resolveThrownHttpError` answer 503 with no per-door change.
|
|
1030
|
+
|
|
1031
|
+
**What did NOT change**, and is pinned:
|
|
1032
|
+
|
|
1033
|
+
- A reachable, genuinely EMPTY store (reads return no rows) still resolves to
|
|
1034
|
+
zero capabilities.
|
|
1035
|
+
- A genuine capability denial still answers `403 FORBIDDEN` with its message.
|
|
1036
|
+
- An ABSENT engine (`ql` unwired, so no read is ever issued) still resolves to
|
|
1037
|
+
an empty-but-valid envelope.
|
|
1038
|
+
- Anonymous requests never reach the store, so an outage cannot make them loud.
|
|
1039
|
+
- A REAL engine whose `sys_*` tables were never provisioned resolves to zero
|
|
1040
|
+
capabilities, quietly — pinned to be byte-identical to the empty-store
|
|
1041
|
+
envelope, in every dialect spelling and in the production wrapper shape where
|
|
1042
|
+
the driver's phrase is on `cause` rather than the outer message.
|
|
1043
|
+
|
|
1044
|
+
**The boundary between the two kinds of read failure.** An earlier revision of
|
|
1045
|
+
this changeset claimed "embedders without a data plane are unaffected". That
|
|
1046
|
+
claim was too broad; it is retracted here, and the gap it named is now closed
|
|
1047
|
+
rather than merely disclosed. A read also throws when the table was never
|
|
1048
|
+
PROVISIONED — a real engine, wired and reachable, whose `sys_*` tables were
|
|
1049
|
+
never created — and that is a supported deployment shape, not an outage. There
|
|
1050
|
+
"zero capabilities" is the TRUE answer rather than a fabrication: nothing is
|
|
1051
|
+
provisioned, so nothing was withheld. Only an UNREACHABLE store — the ruling's
|
|
1052
|
+
own word 不可达 — leaves the capability set unknown, and only an unknown answer
|
|
1053
|
+
may not be reported as a denial.
|
|
1054
|
+
|
|
1055
|
+
Treating the two alike was measured, not theorised: it turned four CI suites
|
|
1056
|
+
red, all from `no such table` on `sys_user` / `sys_member` /
|
|
1057
|
+
`sys_user_position` / `sys_user_permission_set`. Ordinary CRUD in
|
|
1058
|
+
`@objectstack/client` answered `503`; batch validation errors that owe `400`
|
|
1059
|
+
answered `503`, because authorization refused before validation ran; runtime
|
|
1060
|
+
notifications answered `401` where authenticated callers must be served `200`;
|
|
1061
|
+
and two `.integration.test.ts` noise guards reported that the driver and engine
|
|
1062
|
+
diagnostics for `sys_position` stopped being emitted — the eager throw aborted
|
|
1063
|
+
the resolution before that later read was ever issued, so a change made to stop
|
|
1064
|
+
a failed read being silent had made two other channels silent.
|
|
1065
|
+
|
|
1066
|
+
`tryFind` therefore raises `AuthzStoreUnavailableError` only for a read failure
|
|
1067
|
+
that is NOT positively identified as an unprovisioned table.
|
|
1068
|
+
|
|
1069
|
+
**`isMissingTableError` moved to `@objectstack/types`.** The classifier that
|
|
1070
|
+
draws that boundary already existed and was already right — driver-code based
|
|
1071
|
+
rather than prose-sniffing, documented so that "cannot say" never means "be
|
|
1072
|
+
loud". It lived in `@objectstack/metadata`, which DEPENDS ON `@objectstack/core`,
|
|
1073
|
+
so the resolver could not import it. Rather than keep a second copy of a
|
|
1074
|
+
security-relevant predicate, the ruling relocated the one classifier to
|
|
1075
|
+
`@objectstack/types` — the package core already depends on, and the repo's own
|
|
1076
|
+
stated Home rule for a cross-package error predicate ("every consumer of the
|
|
1077
|
+
question already depends on it, so adopting the predicate never adds an edge",
|
|
1078
|
+
`packages/types/src/unique-violation.ts`). `@objectstack/metadata/errors` still
|
|
1079
|
+
exports `isMissingTableError`, re-exported from the new home, so no consumer of
|
|
1080
|
+
that published subpath changes.
|
|
1081
|
+
|
|
1082
|
+
Its sibling `isSchemaAlreadyExistsError` moved with it — the two are not two
|
|
1083
|
+
modules but two signatures over one matcher, and separating them would have
|
|
1084
|
+
meant re-rolling the matcher, which is the duplication the module exists to
|
|
1085
|
+
prevent. Both are now exported from `@objectstack/types`; the metadata subpath
|
|
1086
|
+
deliberately still publishes only `isMissingTableError`, which is the only one
|
|
1087
|
+
anything imports through it.
|
|
1088
|
+
|
|
1089
|
+
⚠️ **Signed-off risk, recorded because it is load-bearing.** Gating loudness on
|
|
1090
|
+
a driver-error predicate was approved with its false-positive direction stated:
|
|
1091
|
+
mis-reading a genuine outage as "table not provisioned" silently restores the
|
|
1092
|
+
quiet 403 this change removes, with no thrown error and no other failing test.
|
|
1093
|
+
That direction is accepted, not overlooked — the predicate keys on driver codes,
|
|
1094
|
+
SQLSTATEs and errnos first, excludes the known superstring traps up front, and
|
|
1095
|
+
returns `false` for anything it does not positively recognise, so an
|
|
1096
|
+
unrecognised outage stays loud by default. The risk is written beside the
|
|
1097
|
+
predicate in `resolve-authz-context.ts` and both directions are pinned by name
|
|
1098
|
+
in `authz-store-unavailable.test.ts`. ⛔ Do not widen `isMissingTableError` to
|
|
1099
|
+
make a first boot quieter: every widening moves outages into the quiet branch.
|
|
1100
|
+
|
|
1101
|
+
**All-transport, not just REST.** Every transport authorizing through
|
|
1102
|
+
`resolveAuthzContext` inherits this. Six of the eight production transports
|
|
1103
|
+
wrapped the call in a fail-closed `catch` that would have re-silenced the
|
|
1104
|
+
outage — measured, not assumed: with the resolver loud but the nets untouched,
|
|
1105
|
+
the package door answered `401`, i.e. the outage merely changed disguises. Those
|
|
1106
|
+
`catch` blocks now re-raise via `isAuthzStoreUnavailableError` and keep their
|
|
1107
|
+
previous behaviour for every other fault. The transport set is rebuilt from
|
|
1108
|
+
source and audited for set equality on every test run, so a transport added
|
|
1109
|
+
later cannot inherit the old silence unnoticed.
|
|
1110
|
+
|
|
1111
|
+
Callers that treat any throw from `resolveAuthzContext` as "anonymous" should
|
|
1112
|
+
re-raise `isAuthzStoreUnavailableError(err)` instead: degrading it restores the
|
|
1113
|
+
disguise this removes.
|
|
1114
|
+
|
|
1115
|
+
<!-- adr-0087: not-required (runtime-interface-only packages/core/src/security/resolve-authz-context.ts#ResolvedAuthzContext, packages/core/src/security/authz-store-unavailable.ts#AuthzStoreUnavailableError) The breaking surface is runtime TypeScript in `@objectstack/core`'s security module and nothing else: `resolveAuthzContext` stops always-resolving and raises `AuthzStoreUnavailableError` when a permission-store read is issued and throws. NO metadata surface is touched in either direction. No Zod schema changes, no `packages/spec` declaration is added or removed, no authorable key moves, no stored row shape changes, and no object definition is edited — a customer's metadata app is byte-for-byte unaffected, so `objectstack migrate meta` has nothing to visit and there is no tombstone to mint. The wire vocabulary is likewise untouched: `SERVICE_UNAVAILABLE` is an EXISTING `StandardErrorCode` member that `HttpStatusErrorCodeMap` already maps to 503, so this change only selects a different DECLARED code for an outage rather than adding one. Both named symbols resolve at HEAD as exported declarations whose files are not `*.zod.ts`, are not under `packages/spec/src/contracts/`, are not object definitions and are not `z.input` projections; neither is referenced in code by any metadata surface (the `packages/spec` hits for `resolveAuthzContext` are comment prose describing the envelope, which this gate masks). The channel that reaches an affected consumer is therefore code review and this changeset, never the upgrade guide: a ledger entry could not express "your fail-closed catch should re-raise this error", because there is no metadata for a migration to rewrite. -->
|
|
1116
|
+
- e7191ce: fix(build): give each `exports` condition its own `types` target in the 28 dual-build packages (#13112)
|
|
1117
|
+
|
|
1118
|
+
**Published-surface change, zero runtime change.** No emitted byte moves; what
|
|
1119
|
+
moves is which declaration file a resolver READS. Maintainer ruling 2026-08-29
|
|
1120
|
+
(decision batch #3, verbatim 「同意」) chose declaring the files over deleting
|
|
1121
|
+
them.
|
|
1122
|
+
|
|
1123
|
+
## What was wrong
|
|
1124
|
+
|
|
1125
|
+
These 28 packages are `"type": "module"` and dual-built, and each spelled one
|
|
1126
|
+
`types` condition as a **sibling** of `import`/`require`:
|
|
1127
|
+
|
|
1128
|
+
```json
|
|
1129
|
+
"exports": { ".": {
|
|
1130
|
+
"types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs"
|
|
1131
|
+
} }
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
A sibling `types` answers for **both** conditions, so a CommonJS consumer was
|
|
1135
|
+
handed `dist/index.d.ts` — an ES-module declaration, because the package is
|
|
1136
|
+
`"type": "module"` — for an entry point it reaches with `require`. Measured with
|
|
1137
|
+
`tsc --traceResolution` on a `"type": "commonjs"` fixture at `moduleResolution:
|
|
1138
|
+
node16`:
|
|
1139
|
+
|
|
1140
|
+
```
|
|
1141
|
+
error TS1479: The current file is a CommonJS module whose imports will produce
|
|
1142
|
+
'require' calls; however, the referenced file is an ECMAScript module and cannot
|
|
1143
|
+
be imported with 'require'.
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
The JavaScript at `dist/index.cjs` loads perfectly (`check:dual-build-cjs-loads`
|
|
1147
|
+
has asserted that for months). It is the **types** that told the consumer the
|
|
1148
|
+
supported `require` entry point could not be required. The `dist/index.d.cts`
|
|
1149
|
+
twin tsup emits beside it — 36 files, 5,517,701 B on this build — was named by
|
|
1150
|
+
no condition at all and shipped in every tarball unreachable.
|
|
1151
|
+
|
|
1152
|
+
## What changed
|
|
1153
|
+
|
|
1154
|
+
Each condition now names its own declaration, the shape TypeScript documents:
|
|
1155
|
+
|
|
1156
|
+
```json
|
|
1157
|
+
"exports": { ".": {
|
|
1158
|
+
"import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
|
|
1159
|
+
"require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
|
|
1160
|
+
} }
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
33 entry points across 27 packages, subpaths included. The root `types` field is
|
|
1164
|
+
untouched, so `node10` resolvers are unaffected; the `import` condition resolves
|
|
1165
|
+
exactly what it resolved before, measured as an unchanged control in the same
|
|
1166
|
+
run.
|
|
1167
|
+
|
|
1168
|
+
## `@objectstack/core` is deliberately NOT changed
|
|
1169
|
+
|
|
1170
|
+
Splitting a declaration in two makes TypeScript compare it nominally, and
|
|
1171
|
+
`ObjectKernel` carries a `private plugins` member that reaches every plugin
|
|
1172
|
+
through `PluginContext.getKernel()`. With core split, whole-repo `pnpm build`
|
|
1173
|
+
fails in `@objectstack/verify` with 5 × TS2345 ("Types have separate
|
|
1174
|
+
declarations of a private property 'plugins'"); with core held back and the
|
|
1175
|
+
other 27 split, 71/71 tasks pass. So core keeps the sibling-`types` shape and
|
|
1176
|
+
its two `.d.cts` files (220,854 B) stay unreachable, declared as such in
|
|
1177
|
+
`check:dual-build-cjs-loads`. Splitting it needs a decision about core's public
|
|
1178
|
+
types, not about an exports map.
|
|
1179
|
+
|
|
1180
|
+
## For consumers
|
|
1181
|
+
|
|
1182
|
+
- **ESM consumers: nothing changes.** Same declaration file, byte for byte.
|
|
1183
|
+
- **CJS consumers under `node16`/`nodenext`: TS1479 goes away** and the
|
|
1184
|
+
declarations they get are the ones built for CommonJS.
|
|
1185
|
+
- **`node10` / `moduleResolution: node` consumers: nothing changes** — they never
|
|
1186
|
+
read `exports`.
|
|
1187
|
+
- Nothing is removed: every path that resolved before still resolves.
|
|
1188
|
+
|
|
1189
|
+
Packages that are CJS-first (`require` → `./dist/index.js`, no `"type": "module"`)
|
|
1190
|
+
were already correct and are untouched — their `dist/index.d.ts` really is the
|
|
1191
|
+
CommonJS declaration. Their ESM mirror (an unreachable `.d.mts` under the
|
|
1192
|
+
`import` condition) is a separate, larger population and is filed separately per
|
|
1193
|
+
the ruling, not fixed here.
|
|
1194
|
+
|
|
1195
|
+
`check:dual-build-cjs-loads` grew a fourth invariant (TYPED) that reds on the old
|
|
1196
|
+
shape, so the drift cannot return silently.
|
|
1197
|
+
- 2e471e3: `FilesystemLoader` now implements `loadManyKeyed()`, so a metadata file whose
|
|
1198
|
+
body has no top-level `name` is no longer invisible to `MetadataManager.list()`.
|
|
1199
|
+
`loadMany()` globbed files and pushed bodies, discarding the path it had just
|
|
1200
|
+
read; the manager then fell back to keying by `body.name`, which drops every
|
|
1201
|
+
nameless body — an aggregated `defineView` container has none by design. That is
|
|
1202
|
+
the #14205 defect, unrepaired for this loader until now.
|
|
1203
|
+
|
|
1204
|
+
The key is this loader's own name-to-path derivation — the basename minus
|
|
1205
|
+
extension, the same one `list()` reports — but only where that derivation is a
|
|
1206
|
+
bijection for the file: it sits directly under `ROOT/TYPE/` and carries an
|
|
1207
|
+
extension `findFile()` tries, so `findFile(type, key)` resolves back to that same
|
|
1208
|
+
file. Every other shape (a nested path, an extension-less file) keeps the
|
|
1209
|
+
previous behaviour verbatim: keyed by `body.name` when it has one, dropped when
|
|
1210
|
+
it has none. `list()` and `findFile()` disagree outside the flat shape — `list()`
|
|
1211
|
+
reports the bare basename for a nested file and `findFile()` cannot resolve it —
|
|
1212
|
+
so keying those by the basename would mint names `get()` and `exists()` cannot
|
|
1213
|
+
open, and two directories holding one basename would collide silently. Repairing
|
|
1214
|
+
the derivation itself is tracked separately.
|
|
1215
|
+
|
|
1216
|
+
One deliberate consequence: a flat file whose `body.name` disagrees with its
|
|
1217
|
+
basename is now keyed by the basename. That is #14205's rule (identity is the
|
|
1218
|
+
key the store holds an item under, not `body.name`) applied to this loader, and
|
|
1219
|
+
it aligns `list()` with `listNames()` for that shape. `loadMany()`'s own
|
|
1220
|
+
signature and answer are unchanged; both methods now share one file walk so
|
|
1221
|
+
their bodies cannot drift.
|
|
1222
|
+
- 200d255: fix(metadata-core,metadata): a form SECTION binds `current_user` too, so the unbound-root notice stops flagging one (#13072)
|
|
1223
|
+
|
|
1224
|
+
Second correction to the unbound-root boot notice, and the same defect as the
|
|
1225
|
+
first one a surface later. The notice judged a SECTION-level predicate against
|
|
1226
|
+
`record` / `previous` / `parent` / `data`, sourced faithfully from the section
|
|
1227
|
+
contract prose — which was stale.
|
|
1228
|
+
|
|
1229
|
+
`current_user` and its ADR-0068 alias roots (`user`, `ctx.user`, `os.user`)
|
|
1230
|
+
**resolve on a section-level `visibleWhen`**: objectui#6110 threads the host
|
|
1231
|
+
shell's predicate scope into `isSectionVisible` where it used to pass
|
|
1232
|
+
`undefined`, and objectui#6111 copies the authored `visibleWhen` onto the
|
|
1233
|
+
`section-divider` pseudo-field whose predicate the SDUI form renderer evaluates
|
|
1234
|
+
with that scope bound. #12914 re-measured the contract text accordingly. Until
|
|
1235
|
+
this change, a legacy artifact carrying a legitimate section-level
|
|
1236
|
+
`current_user.role == "admin"` predicate was reported at boot as an unbound root
|
|
1237
|
+
that faults open — a notice about a predicate that resolves, which is the
|
|
1238
|
+
cry-wolf failure the module's own doc forbids and the one that trains operators
|
|
1239
|
+
to ignore the channel.
|
|
1240
|
+
|
|
1241
|
+
**What changes:** one vocabulary now serves both form-view predicate surfaces —
|
|
1242
|
+
`record`, `previous`, `parent`, `data`, `current_user`, `user`, `ctx`, `os`. A
|
|
1243
|
+
section predicate rooted at the `current_user` family is silent; a section
|
|
1244
|
+
predicate rooted at a bare field identifier is still reported, and the operator
|
|
1245
|
+
line still prints the rule per surface for the surfaces the findings implicate.
|
|
1246
|
+
|
|
1247
|
+
**Blast radius, stated without inflation:** this is a **notice**, not a refusal
|
|
1248
|
+
— no parse change, no gate, no behaviour change, and it only runs inside the
|
|
1249
|
+
versioned window `applyArtifactForwardConversions` opens. The cost it removes is
|
|
1250
|
+
a false operator signal on legacy artifacts, not a broken runtime.
|
|
1251
|
+
|
|
1252
|
+
**Removed export, with its migration:** `FIELD_ONLY_BOUND_PREDICATE_ROOTS` is
|
|
1253
|
+
gone from `@objectstack/metadata-core`. The section binding empties it, and an
|
|
1254
|
+
exported constant named `FIELD_ONLY_…` holding `[]` asserts a per-surface
|
|
1255
|
+
difference that no renderer makes. FROM → TO: read
|
|
1256
|
+
`BOUND_FORM_VIEW_PREDICATE_ROOTS` (every root bound on any form-view predicate)
|
|
1257
|
+
or `BOUND_FORM_FIELD_PREDICATE_ROOTS` (the field question, the same list today).
|
|
1258
|
+
No consumer can be carrying it: the notice has never shipped — the two
|
|
1259
|
+
changesets that introduce it are still pending in `.changeset/`, the newest
|
|
1260
|
+
published `@objectstack/metadata-core` is 17.2.0, and the commit that added
|
|
1261
|
+
`form-predicate-root-policy.ts` is in no release tag. This was the last moment
|
|
1262
|
+
at which the removal cost nothing.
|
|
1263
|
+
|
|
1264
|
+
**Why the vocabulary is no longer justified by quoting the contract.** Both
|
|
1265
|
+
times this list has been wrong, it was wrong by transcribing a correct-looking
|
|
1266
|
+
sentence that the renderer had already moved past. The prose is a transcription
|
|
1267
|
+
of a renderer and can only lag one, so membership is now stated as the mechanism
|
|
1268
|
+
— *a root is bound on a surface iff some renderer threads a scope carrying it
|
|
1269
|
+
into that surface's evaluator* — with the threading site named per entry, and
|
|
1270
|
+
the module's test reads the LIVE `.describe()` text of
|
|
1271
|
+
`FormFieldSchema.visibleWhen` / `FormSectionSchema.visibleWhen` out of
|
|
1272
|
+
`@objectstack/spec` instead of copying it into a comment. A comment quoting that
|
|
1273
|
+
sentence goes stale in silence, twice now; an assertion that fetches it cannot.
|
|
1274
|
+
|
|
1275
|
+
<!-- adr-0087: not-required (unpublished) the removed export FIELD_ONLY_BOUND_PREDICATE_ROOTS was added after 17.2.0 and is in no release tag, so no upgrader can be holding it and there is nothing to migrate from. -->
|
|
1276
|
+
- 2852acc: fix(metadata-core,metadata): split the form-view predicate root vocabulary per surface, so a field-level `current_user` test is not false-flagged (#12915)
|
|
1277
|
+
|
|
1278
|
+
Same-day correction to the unbound-root boot notice. The notice judged **every**
|
|
1279
|
+
form-view predicate against one vocabulary (`record` / `previous` / `parent` /
|
|
1280
|
+
`data`), sourced faithfully from the contract prose — which, for the field-level
|
|
1281
|
+
slot, was stale.
|
|
1282
|
+
|
|
1283
|
+
`current_user` and its ADR-0068 alias roots (`user`, `ctx.user`, `os.user`)
|
|
1284
|
+
**resolve on a field-level `visibleWhen`** since objectui#6010; three spec text
|
|
1285
|
+
sites still said otherwise until #12930 re-measured them, and one of those sites
|
|
1286
|
+
was the sentence this policy was written against. A legacy artifact carrying a
|
|
1287
|
+
legitimate `current_user.role == "admin"` field predicate was therefore reported
|
|
1288
|
+
as faulting open — the cry-wolf failure the notice is explicitly built to avoid,
|
|
1289
|
+
and the one that trains operators to ignore the channel.
|
|
1290
|
+
|
|
1291
|
+
The vocabulary is now per surface, which is what the contract actually says:
|
|
1292
|
+
|
|
1293
|
+
- **Field-level** (`BOUND_FORM_FIELD_PREDICATE_ROOTS`): the shared base plus
|
|
1294
|
+
`current_user`, `user`, `ctx`, `os`. Silent on all of them.
|
|
1295
|
+
- **Section-level** (`BOUND_FORM_VIEW_PREDICATE_ROOTS`, unchanged in name and
|
|
1296
|
+
value): the base alone. `current_user` is still flagged there — the section
|
|
1297
|
+
docblock states it is unbound at that level and faults open.
|
|
1298
|
+
|
|
1299
|
+
Two limits of the field binding deliberately do **not** change the answer: it is
|
|
1300
|
+
a rendering rule rather than authorization (an authoring hazard, not a
|
|
1301
|
+
version-drift one), and the scope is empty on the console's public `/f/:slug`
|
|
1302
|
+
route (equally true of a freshly built current artifact, so it says nothing
|
|
1303
|
+
about the artifact's era — the only thing this notice claims to detect).
|
|
1304
|
+
|
|
1305
|
+
The emitted warn line now prints the bound roots **per surface, and only for the
|
|
1306
|
+
surfaces the findings implicate**, so an operator is never shown a rule their
|
|
1307
|
+
artifact has no instance of. Findings carry a `surface` field.
|
|
1308
|
+
|
|
1309
|
+
`unboundRootsInCelSource` takes the vocabulary as an optional second argument;
|
|
1310
|
+
its default is unchanged (the stricter base), so existing callers behave exactly
|
|
1311
|
+
as before.
|
|
1312
|
+
- f7be03f: fix(metadata): `getViewsByObject()` expands aggregated view containers instead of answering empty (#13913)
|
|
1313
|
+
|
|
1314
|
+
`MetadataManager.getViewsByObject()` reads `this.list('view')` — the manager's
|
|
1315
|
+
own registry + loader store, which is a completely different store from the
|
|
1316
|
+
`sys_metadata` rows the REST route (`GET /meta/view?object=`) reads through
|
|
1317
|
+
`getMetaItems`. #13407 taught that route to expand a runtime-authored aggregated
|
|
1318
|
+
`defineView` container inline; this exit never called it and had no equivalent
|
|
1319
|
+
step, so a container the REST route now serves still answered **empty** here —
|
|
1320
|
+
for every internal/SDK caller that uses this entry point rather than the route.
|
|
1321
|
+
|
|
1322
|
+
Getting the container into the store was never enough on its own: the filter
|
|
1323
|
+
also requires `viewKind`, and a container has none. Relaxing that requirement
|
|
1324
|
+
would answer with the container itself as a view — the behaviour #7163 ruled
|
|
1325
|
+
wrong — so the repair adds the container's **expansion**, whose items each carry
|
|
1326
|
+
the `viewKind` + `object` pair this filter has always tested. The filter is
|
|
1327
|
+
untouched; it reads the top-level `object` exactly as `ViewSchema.object`
|
|
1328
|
+
declares.
|
|
1329
|
+
|
|
1330
|
+
The expansion is registry-free and per-read, mirroring #13407's choice at the
|
|
1331
|
+
other exit and for the same reason: the registry is process-wide, so a read must
|
|
1332
|
+
not graft rows into it. Already-present names win, so a container whose expanded
|
|
1333
|
+
ViewItems were registered by a source registrar (the ObjectQL boot loop, the
|
|
1334
|
+
artifact/HMR loader) still answers with those registered, fully-enriched items
|
|
1335
|
+
and gains nothing new.
|
|
1336
|
+
|
|
1337
|
+
The object-derivation chain (`object` → `list.data.object` → `form.data.object`
|
|
1338
|
+
→ the row's own `name`) now has one spelling for this package, in the new
|
|
1339
|
+
`view-container-expansion.ts`, rather than a third private copy to fall behind.
|
|
1340
|
+
- e7f56d6: Make the settings engine facade and the metadata database loader bind the row
|
|
1341
|
+
they resolved, not a row the payload names
|
|
1342
|
+
|
|
1343
|
+
Two ingresses resolved an authoritative row id and then folded it into the
|
|
1344
|
+
write payload with the **losing** spread order — `{ id, ...data }` — so a
|
|
1345
|
+
caller-supplied `data.id` spread over the id the ingress had just resolved and
|
|
1346
|
+
silently retargeted the write:
|
|
1347
|
+
|
|
1348
|
+
- `wrapEngineAsSettingsEngine`'s by-id `update` branch
|
|
1349
|
+
(`@objectstack/service-settings`), whose id comes from the caller's
|
|
1350
|
+
`where.id`.
|
|
1351
|
+
- `DatabaseLoader._update` (`@objectstack/metadata`), whose id arrives as a
|
|
1352
|
+
separate parameter every caller resolves first (`existing.id`, from the read
|
|
1353
|
+
immediately above).
|
|
1354
|
+
|
|
1355
|
+
Both now spell it `{ ...data, id }` — the operation's id **after** the spread,
|
|
1356
|
+
so it wins. That is the convention the repo's other two ingresses already
|
|
1357
|
+
document: `rest-server.ts`'s batch update arm ("the operation's id AFTER the
|
|
1358
|
+
spread, so it wins") and `protocol.updateData`'s #6479 fix
|
|
1359
|
+
(`{ ...request.data, id: request.id }`).
|
|
1360
|
+
|
|
1361
|
+
**No wrong write is known to have been reachable.** Both sites' current callers
|
|
1362
|
+
build fresh field literals and never put an `id` inside `data`, so this is
|
|
1363
|
+
hardening a fragile pattern rather than repairing a measured defect. What makes
|
|
1364
|
+
it worth the three characters is that neither site can be caught downstream:
|
|
1365
|
+
both pass **no `where`** to the engine, so the payload is the only id the engine
|
|
1366
|
+
ever sees, and the engine's conflicting-id refusal (`UPDATE_ID_MISMATCH`, 400)
|
|
1367
|
+
needs two disagreeing declarations before it can fire. The fold is the entire
|
|
1368
|
+
trust boundary at both sites, and it is one refactor — a caller handing back a
|
|
1369
|
+
row copy, and rows carry `id` — from the #6479 shape.
|
|
1370
|
+
|
|
1371
|
+
Both are pinned with a payload whose `id` names a **different** row than the
|
|
1372
|
+
one the ingress resolved, asserting the resolved row is still the row bound. A
|
|
1373
|
+
pin exercising a payload without an `id` would have passed against both
|
|
1374
|
+
spellings. The doubles answer "which row does this bind?" with the producer's
|
|
1375
|
+
own `assertEngineUpdateDispatch`, so they cannot be kinder about it than a
|
|
1376
|
+
running server.
|
|
1377
|
+
- 41aa979: Artifact/HMR loader: read a view container's own top-level `object` field
|
|
1378
|
+
|
|
1379
|
+
`MetadataPlugin`'s artifact/HMR registrar derived which object a `defineView`
|
|
1380
|
+
container binds to by walking `list.data.object` then `form.data.object`, and
|
|
1381
|
+
never consulted the container's own top-level `object` — the field
|
|
1382
|
+
`ViewSchema.object` documents as "how a stack-level `views: [...]` entry says
|
|
1383
|
+
which object its views belong to; read by `getViewsByObject()` /
|
|
1384
|
+
`GET /meta/view?object=`".
|
|
1385
|
+
|
|
1386
|
+
A package-shipped `defineView({ object: 'crm_lead', list: { columns: [...] } })`
|
|
1387
|
+
therefore registered nothing at all through this path: the container was dropped
|
|
1388
|
+
before registration, so no expanded `crm_lead.<key>` ViewItems were produced and
|
|
1389
|
+
`getViewsByObject('crm_lead')` / `GET /meta/view?object=crm_lead` answered empty
|
|
1390
|
+
for it. Both derivation sites now call the package's single spelling of that
|
|
1391
|
+
derivation (`deriveViewContainerObject`), which consults the container's own
|
|
1392
|
+
`object` first and keeps the existing `list.data.object` → `form.data.object` →
|
|
1393
|
+
row-name fallback unchanged for every container written before that field was
|
|
1394
|
+
read here. This is the same order #13407 settled at the runtime door.
|
|
1395
|
+
- a7002ce: Canonicalise driver-materialised timestamps at the metadata adapter boundaries
|
|
1396
|
+
|
|
1397
|
+
`MetadataItem.authoredAt` is declared `z.string()` ('ISO-8601 timestamp') and
|
|
1398
|
+
`MetadataStats.mtime` is declared `z.string().datetime()`, but three producers
|
|
1399
|
+
adapted a driver row into those declared types without converting the value.
|
|
1400
|
+
`created_at` / `updated_at` are builtin audit columns and `recorded_at` is a
|
|
1401
|
+
declared `Field.datetime`; `SqlDriver#formatOutput` repairs both only inside its
|
|
1402
|
+
`if (this.isSqlite)` arm, so on Postgres and MySQL a JS `Date` landed in a field
|
|
1403
|
+
every consumer reads as a `string`.
|
|
1404
|
+
|
|
1405
|
+
`SysMetadataRepository#get` / `#getByHash` and `DatabaseLoader#stat` now emit
|
|
1406
|
+
canonical ISO-8601 text on every dialect, matching the sibling producers that
|
|
1407
|
+
already spelled it correctly. Values that were already canonical (SQLite) pass
|
|
1408
|
+
through byte-identically.
|
|
1409
|
+
- 77a532d: Five metadata adapter boundaries now emit the ISO-8601 string their declared type promises
|
|
1410
|
+
when the driver hands them a JS `Date`, instead of asserting `as string` over it
|
|
1411
|
+
|
|
1412
|
+
`MetadataRecord.createdAt` / `.updatedAt` and `MetadataHistoryRecord.recordedAt` are
|
|
1413
|
+
declared `z.string().datetime()`, and `MetadataEvent.ts` is declared `z.string()`. Four
|
|
1414
|
+
producers in `DatabaseLoader` (`rowToRecord`, `getHistoryRecord`, `queryHistory`) and one
|
|
1415
|
+
in `SysMetadataRepository` (`rowToEvent`) reached those fields through an unchecked
|
|
1416
|
+
`row.<column> as string` cast, which is an assertion about a driver row rather than a
|
|
1417
|
+
measurement of one — so nothing type-checked and nothing reported it.
|
|
1418
|
+
|
|
1419
|
+
On Postgres and MySQL the assertion is false for both column classes involved.
|
|
1420
|
+
`SqlDriver#formatOutput` repairs the builtin audit columns and folds declared
|
|
1421
|
+
`Field.datetime` columns only inside its `if (this.isSqlite)` arm, and
|
|
1422
|
+
`withPostgresCalendarDayAsText` leaves `timestamptz` / `timestamp` deliberately untouched
|
|
1423
|
+
because those are instants. A column being declared `Field.datetime` therefore does **not**
|
|
1424
|
+
protect it: on the production default driver both classes come out of the record read door
|
|
1425
|
+
as a `Date`, and `.datetime()` is a refinement a `Date` fails outright. Nothing has failed
|
|
1426
|
+
yet only because no production path parses these values today.
|
|
1427
|
+
|
|
1428
|
+
The repair is producer-side, at the adapter boundary that asserts the declared type — not
|
|
1429
|
+
a tolerant fallback in a consumer, and not a change at the driver's read door, which would
|
|
1430
|
+
reverse a deliberate driver decision. Callers keep their existing behaviour for every other
|
|
1431
|
+
shape: an already-canonical SQLite string passes through byte-identically, an absent column
|
|
1432
|
+
still yields `undefined` so each caller's `?? <default>` chain means what it meant, and an
|
|
1433
|
+
Invalid `Date` is handed through unchanged rather than converted, because what the shared
|
|
1434
|
+
canonical-ISO spelling should do with that one input is still being decided.
|
|
1435
|
+
- 93940d4: `DatabaseLoader`'s private driver-path update helper is typed with the `null` arm `IDataDriver.update()` now declares; both of its callers discard the result. No runtime behaviour changes.
|
|
1436
|
+
- 8225248: fix(metadata): drop the leftover boot-time debug probe from `MetadataPlugin.init` (#14527)
|
|
1437
|
+
|
|
1438
|
+
`MetadataPlugin.init()` printed a bare `console.log` immediately after
|
|
1439
|
+
`ctx.registerService('metadata', this.manager)`, reporting
|
|
1440
|
+
`typeof this.manager.getRegisteredTypes`. Nothing gated it on `NODE_ENV`, a debug
|
|
1441
|
+
flag or a logger level, so every kernel boot that installs `MetadataPlugin` wrote
|
|
1442
|
+
it to stdout: the CLI, the dev server and any embedding host alike.
|
|
1443
|
+
|
|
1444
|
+
What it printed was a probe, not information. `getRegisteredTypes` is a method the
|
|
1445
|
+
class declares statically, so the `typeof` it reported cannot vary in a way an
|
|
1446
|
+
operator could act on, and a repo-wide search finds no reader of the string. The
|
|
1447
|
+
`ctx.logger.info('Initializing Metadata Manager', ...)` call three lines above
|
|
1448
|
+
already announces the same lifecycle step through the plugin's own logger, carrying
|
|
1449
|
+
the fields that are actually actionable (`root`, `watch`, `artifactSource`). Deleted
|
|
1450
|
+
rather than demoted to `ctx.logger.debug` on that reading: once the reported fact is
|
|
1451
|
+
statically known and unread, there is no shape check left worth keeping.
|
|
1452
|
+
|
|
1453
|
+
Observable change: one fewer line on stdout at boot. No API, no types, no behaviour
|
|
1454
|
+
beyond the removed print. On the two paths where a stray stdout line would break a
|
|
1455
|
+
parser — `--json` payloads and the `os serve` protocol channel — the CLI already
|
|
1456
|
+
reserves stdout by redirecting to stderr, so nothing downstream was relying on this
|
|
1457
|
+
line's presence or its absence.
|
|
1458
|
+
|
|
1459
|
+
The file's five other `console.*` calls (the dev HMR path) are deliberately
|
|
1460
|
+
untouched: each carries its own marker, and the one that prints on every
|
|
1461
|
+
non-development boot carries a written rationale for doing so.
|
|
1462
|
+
- 0ae9e1e: fix(metadata): `migrateProjectIdToEnvironmentId`'s raw-driver guard stated its instruction sentence twice (#13219)
|
|
1463
|
+
|
|
1464
|
+
An operator who called `migrateProjectIdToEnvironmentId` with a driver that has
|
|
1465
|
+
no `raw()` was refused correctly, but read the same remedy twice in one message:
|
|
1466
|
+
|
|
1467
|
+
```
|
|
1468
|
+
migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) supports this; cloud-side TursoDriver also conforms.
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
The sentence was concatenated twice, a copy-paste artifact — the sibling
|
|
1472
|
+
`migrateEnvIdToProjectId` carries the correct single-sentence form of the
|
|
1473
|
+
identical guard. Cosmetic and operator-facing only: the guard fires on exactly
|
|
1474
|
+
the same condition, the remedy it names is unchanged, and nothing parses the
|
|
1475
|
+
message. The duplicate line is deleted; the surviving sentence keeps the
|
|
1476
|
+
trailing space that separates it from the one naming the conforming drivers.
|
|
1477
|
+
|
|
1478
|
+
The refusal case in the package's tests now pins the properties — the
|
|
1479
|
+
instruction appears exactly once, no sentence runs into the next, and the
|
|
1480
|
+
supporting sentence is still present — rather than substring-matching the
|
|
1481
|
+
message, which could not see a second copy and so passed either way.
|
|
1482
|
+
- ef76342: fix(metadata): stop `migrateProjectIdToEnvironmentId` renaming into a column no declaration knows about (#13205)
|
|
1483
|
+
|
|
1484
|
+
`AFFECTED_TABLES` was a hand-written list, and it outlived the declarations it
|
|
1485
|
+
described. The branch/project-removal amendment (M1) took `environment_id` out
|
|
1486
|
+
of `sys_metadata_history`'s declaration in `@objectstack/metadata-core`; the
|
|
1487
|
+
migration kept naming that table. Its only guard asks whether `project_id` is
|
|
1488
|
+
present **physically** (`_columnExists`) — which says nothing about the target
|
|
1489
|
+
column being **declared** — so against any database whose physical
|
|
1490
|
+
`sys_metadata_history` still carried the pre-v5 column, the migration renamed it
|
|
1491
|
+
to `environment_id`: a fresh orphan column that no declaration, no `syncSchema`
|
|
1492
|
+
and no reader knows about.
|
|
1493
|
+
|
|
1494
|
+
The list is now **derived from the declarations** rather than restated beside
|
|
1495
|
+
them. A candidate table is migrated only if its current declaration carries the
|
|
1496
|
+
target column, so the two cannot drift apart again — the derivation and the
|
|
1497
|
+
declaration are the same fact. `@objectstack/metadata-core` was already a
|
|
1498
|
+
dependency of this package, so this adds no dependency edge.
|
|
1499
|
+
|
|
1500
|
+
A candidate that does not declare the target column is now **reported** as
|
|
1501
|
+
`status: 'skipped_not_declared'` (an additive member of the result union)
|
|
1502
|
+
instead of silently vanishing from the result array: an operator reading the
|
|
1503
|
+
results can tell "considered and deliberately skipped" from "forgotten again",
|
|
1504
|
+
which is the state this defect started in.
|
|
1505
|
+
|
|
1506
|
+
The sibling `migrateEnvIdToProjectId` is deliberately left alone: its target
|
|
1507
|
+
(`project_id`) is an intermediate column that no current declaration carries by
|
|
1508
|
+
design, so the "target must be declared" rule is sound only for the terminal
|
|
1509
|
+
migration in the chain.
|
|
1510
|
+
|
|
1511
|
+
No behaviour changes for `sys_metadata`, whose declaration does carry
|
|
1512
|
+
`environment_id`: it is renamed exactly as before.
|
|
1513
|
+
- 2a18117: fix(metadata): every migration in `@objectstack/metadata/migrations` refused every driver this repo ships (#14023)
|
|
1514
|
+
|
|
1515
|
+
All four helpers exported from `@objectstack/metadata/migrations` guarded on —
|
|
1516
|
+
and drove through — `driver.raw(sql, bindings?)`. **No data driver in this repo
|
|
1517
|
+
defines `raw`.** `SqlDriver` keeps its knex handle `protected` and declares no
|
|
1518
|
+
`raw` member, and `SqliteWasmDriver` inherits that; the only `raw(` member
|
|
1519
|
+
anywhere outside a test double is an HTTP harness in `packages/verify` whose
|
|
1520
|
+
signature is `(path, init)`. So an operator who passed their platform driver was
|
|
1521
|
+
refused by all four:
|
|
1522
|
+
|
|
1523
|
+
```
|
|
1524
|
+
migrateSysNotificationToEvent({ driver, data }) -> { status: 'error', migrated: 0 }
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
The failure was quiet in the shape that matters. `migrateSysNotificationToEvent`
|
|
1528
|
+
*returns* `{ status: 'error' }` rather than throwing, and the message blamed the
|
|
1529
|
+
caller's driver for lacking a method instead of saying the migration had not
|
|
1530
|
+
run — so someone following the ADR-0030 cut-over runbook, which names this call
|
|
1531
|
+
as the supported way to preserve users' existing bell notifications, would read
|
|
1532
|
+
it as a problem with their own driver.
|
|
1533
|
+
|
|
1534
|
+
It was not only an operator-facing path. `DatabaseLoader` calls
|
|
1535
|
+
`migrateProjectIdToEnvironmentId(driver)` on bootstrap with a real driver, at
|
|
1536
|
+
two call sites, each wrapped in a catch — so the v5.0 `project_id` ->
|
|
1537
|
+
`environment_id` forward migration threw and was swallowed on every boot.
|
|
1538
|
+
|
|
1539
|
+
The four helpers now resolve their raw-SQL entry point through one shared
|
|
1540
|
+
resolver (`src/migrations/driver-exec.ts`) that tries `execute` first and falls
|
|
1541
|
+
back to `raw`. `execute` goes first because it is the surface the contract
|
|
1542
|
+
declares: `IDataDriver` (`@objectstack/spec/contracts`) declares
|
|
1543
|
+
`execute(command, parameters?, options?)` **non-optionally**, with bound
|
|
1544
|
+
parameters as the second positional argument — exactly the shape `raw(sql,
|
|
1545
|
+
bindings?)` was being called in — and has never declared `raw`. `raw` is kept as
|
|
1546
|
+
a fallback so a host or third-party driver that does define it keeps working;
|
|
1547
|
+
nothing that worked before stops working, and the refusal now fires only for a
|
|
1548
|
+
driver offering neither surface.
|
|
1549
|
+
|
|
1550
|
+
Two sibling directories already resolved both surfaces instead of assuming one,
|
|
1551
|
+
in opposite orders (`metadata-protocol`'s `partial-index-probe` tries `raw`
|
|
1552
|
+
first, its `seed-tenancy-backfill` tries `execute` first, and `protocol.ts`'s
|
|
1553
|
+
`ensureOverlayIndex` is a third). One operation with three implementations and
|
|
1554
|
+
two behaviours resolves to the declaration-bound side, which is why this
|
|
1555
|
+
directory adopts `execute`-first uniformly rather than copying either precedent.
|
|
1556
|
+
|
|
1557
|
+
The refusal message now names both surfaces. It keeps the properties pinned
|
|
1558
|
+
after the doubled-sentence defect: the remedy is stated exactly once, the
|
|
1559
|
+
sentences stay separated, and a conforming driver is still named.
|
|
1560
|
+
|
|
1561
|
+
Tests: every pre-existing case in this directory built its own double carrying a
|
|
1562
|
+
`raw` method — including the case asserting the guard fires — so the suite
|
|
1563
|
+
pinned the guard's wording while never exercising a driver the platform ships.
|
|
1564
|
+
Swapping `raw` for `execute` in the helpers and in the doubles would have moved
|
|
1565
|
+
that hole rather than closed it. A new `real-driver-exec-surface.test.ts` drives
|
|
1566
|
+
all four migrations through a real `SqliteWasmDriver` against real in-process
|
|
1567
|
+
SQLite, asserting the physical schema rather than the returned status, and pins
|
|
1568
|
+
the surface reality the file exists for: the real driver has no `raw` and does
|
|
1569
|
+
have `execute`.
|
|
1570
|
+
- c9d4de3: fix(metadata): `migrateSysNotificationToEvent` writes canonical ISO timestamps, not `Date.prototype.toString` (#13998)
|
|
1571
|
+
|
|
1572
|
+
`selectLegacyRows` reads the legacy `sys_notification` table through
|
|
1573
|
+
`driver.raw`/`execute` — a door that does not run `formatOutput`, so none of its
|
|
1574
|
+
repairs apply. On SQLite the legacy stamps come back as canonical ISO text and
|
|
1575
|
+
`String(row.created_at)` is the identity. On Postgres and MySQL an instant
|
|
1576
|
+
column materialises as a JS `Date`, so the migration wrote
|
|
1577
|
+
|
|
1578
|
+
```
|
|
1579
|
+
Sun Aug 30 2026 18:19:25 GMT+0800 (China Standard Time)
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
into `created_at` on the new `sys_inbox_message` row and into `created_at` / `at`
|
|
1583
|
+
on the new `sys_notification_receipt` row — whole seconds in the **migrating
|
|
1584
|
+
host's** zone with the milliseconds dropped, or a value no dialect's timestamp
|
|
1585
|
+
grammar accepts at all. The migration is one-way, so that spelling is what the
|
|
1586
|
+
platform would carry afterwards.
|
|
1587
|
+
|
|
1588
|
+
Both stamps are now canonicalised at the migration, matching the repo's existing
|
|
1589
|
+
correct form: a `Date` is rendered with `toISOString()`, ISO text passes through
|
|
1590
|
+
untouched. Neither column could be repaired further upstream — `created_at` is a
|
|
1591
|
+
builtin audit column that `formatOutput` repairs only in its `if (this.isSqlite)`
|
|
1592
|
+
arm, and `read_at` is a legacy column ADR-0030 removed from the object, so it is
|
|
1593
|
+
not a declared `Field.datetime` either and no coercion could ever reach it.
|
|
1594
|
+
|
|
1595
|
+
Pinned with a hand-made `Date` driven through the migration's read path under a
|
|
1596
|
+
forced process zone, which is what breaks the SQLite identity that kept the
|
|
1597
|
+
existing cases green while the defect was live.
|
|
1598
|
+
- a23603e: fix(runtime,metadata): the default local environment id is `env_local`, not `proj_local` (#13366)
|
|
1599
|
+
|
|
1600
|
+
The v5.0 `project` to `environment` rename changed the default local environment
|
|
1601
|
+
id and shipped that change on the surfaces most people meet: `packages/cli`'s
|
|
1602
|
+
`CHANGELOG.md` records "Default local env id: `proj_local` -> `env_local`", the
|
|
1603
|
+
`os dev` / `os start` / `os serve` commands emit `env_local`, and
|
|
1604
|
+
`content/docs/deployment/cli.mdx` documents `env_local` as the default. Two
|
|
1605
|
+
sites never received it and kept stamping `proj_local`.
|
|
1606
|
+
|
|
1607
|
+
FROM: `createStandaloneStack()` — with no `environmentId` in its config and no
|
|
1608
|
+
`OS_ENVIRONMENT_ID` in the environment — stamped `proj_local` on the kernel it
|
|
1609
|
+
composed, and `MetadataPlugin` used `proj_local` to fill the environment-artifact
|
|
1610
|
+
validation envelope for a bare definition.
|
|
1611
|
+
|
|
1612
|
+
TO: both stamp `env_local`.
|
|
1613
|
+
|
|
1614
|
+
WHO SEES IT. Two audiences, both on the DEFAULT path — no `environmentId` in
|
|
1615
|
+
the config and no `OS_ENVIRONMENT_ID` in the environment:
|
|
1616
|
+
|
|
1617
|
+
1. a host that calls `createStandaloneStack` / `createDefaultHostConfig`
|
|
1618
|
+
**directly**;
|
|
1619
|
+
2. a **bare `os serve`** — one not spawned by `os dev` / `os start`. Those two
|
|
1620
|
+
commands export `OS_ENVIRONMENT_ID=env_local` into the child process, which
|
|
1621
|
+
the fallback yields to, so a boot they start never reached the changed line.
|
|
1622
|
+
`os serve` sets no such variable for its own boot: it only READS one to name
|
|
1623
|
+
the runtime state file. So a bare `os serve` used to run a kernel stamped
|
|
1624
|
+
`proj_local` while publishing `runtime.env_local.json` beside it; the two now
|
|
1625
|
+
agree.
|
|
1626
|
+
|
|
1627
|
+
Where the id is observable — row scoping in `ObjectQLPlugin`, the
|
|
1628
|
+
`X-Environment-Id` header, `sys_metadata.environment_id` — such an embedder now
|
|
1629
|
+
sees `env_local` where it saw `proj_local`, so an install with rows already
|
|
1630
|
+
written under the old id should set `environmentId: 'proj_local'` (or
|
|
1631
|
+
`OS_ENVIRONMENT_ID=proj_local`) explicitly to keep them addressed. That escape
|
|
1632
|
+
hatch is unchanged and still wins over the default.
|
|
1633
|
+
|
|
1634
|
+
NOT CHANGED, deliberately: `@objectstack/cloud-connection` still treats BOTH
|
|
1635
|
+
spellings as the local sentinel, so a persisted `OS_ENVIRONMENT_ID=proj_local`
|
|
1636
|
+
config keeps being recognised as local rather than presented to the control
|
|
1637
|
+
plane as a cloud environment id; and `package-state-store`'s separate `'default'`
|
|
1638
|
+
fallback keeps its own spelling, because renaming it would re-key persisted
|
|
1639
|
+
package-disable state files.
|
|
1640
|
+
- Updated dependencies [809d417]
|
|
1641
|
+
- Updated dependencies [387e231]
|
|
1642
|
+
- Updated dependencies [f794e4e]
|
|
1643
|
+
- Updated dependencies [cae2169]
|
|
1644
|
+
- Updated dependencies [b812a54]
|
|
1645
|
+
- Updated dependencies [2d4fa75]
|
|
1646
|
+
- Updated dependencies [0e4e51b]
|
|
1647
|
+
- Updated dependencies [e84bbf6]
|
|
1648
|
+
- Updated dependencies [effae80]
|
|
1649
|
+
- Updated dependencies [efb3513]
|
|
1650
|
+
- Updated dependencies [d62f990]
|
|
1651
|
+
- Updated dependencies [c45d8e6]
|
|
1652
|
+
- Updated dependencies [2e3e8c7]
|
|
1653
|
+
- Updated dependencies [e621291]
|
|
1654
|
+
- Updated dependencies [655b106]
|
|
1655
|
+
- Updated dependencies [40a93b5]
|
|
1656
|
+
- Updated dependencies [101ad2c]
|
|
1657
|
+
- Updated dependencies [d5b330d]
|
|
1658
|
+
- Updated dependencies [dda969c]
|
|
1659
|
+
- Updated dependencies [1f45690]
|
|
1660
|
+
- Updated dependencies [277948f]
|
|
1661
|
+
- Updated dependencies [8bdd955]
|
|
1662
|
+
- Updated dependencies [54e2d36]
|
|
1663
|
+
- Updated dependencies [b745157]
|
|
1664
|
+
- Updated dependencies [f3bbbef]
|
|
1665
|
+
- Updated dependencies [4f24e9d]
|
|
1666
|
+
- Updated dependencies [e27583e]
|
|
1667
|
+
- Updated dependencies [4bd6faa]
|
|
1668
|
+
- Updated dependencies [86cbe37]
|
|
1669
|
+
- Updated dependencies [6a180e4]
|
|
1670
|
+
- Updated dependencies [474242f]
|
|
1671
|
+
- Updated dependencies [63cd487]
|
|
1672
|
+
- Updated dependencies [bd4aa4e]
|
|
1673
|
+
- Updated dependencies [803eaab]
|
|
1674
|
+
- Updated dependencies [f8e8f03]
|
|
1675
|
+
- Updated dependencies [983edf1]
|
|
1676
|
+
- Updated dependencies [eae824e]
|
|
1677
|
+
- Updated dependencies [f6fa22c]
|
|
1678
|
+
- Updated dependencies [8a483b3]
|
|
1679
|
+
- Updated dependencies [3bc2e38]
|
|
1680
|
+
- Updated dependencies [97bcd99]
|
|
1681
|
+
- Updated dependencies [df59de0]
|
|
1682
|
+
- Updated dependencies [96e25a8]
|
|
1683
|
+
- Updated dependencies [f75a38a]
|
|
1684
|
+
- Updated dependencies [7a25e7d]
|
|
1685
|
+
- Updated dependencies [1fa05a6]
|
|
1686
|
+
- Updated dependencies [c85a265]
|
|
1687
|
+
- Updated dependencies [dcb10a5]
|
|
1688
|
+
- Updated dependencies [773a999]
|
|
1689
|
+
- Updated dependencies [35dffea]
|
|
1690
|
+
- Updated dependencies [d8024f0]
|
|
1691
|
+
- Updated dependencies [8120808]
|
|
1692
|
+
- Updated dependencies [776a098]
|
|
1693
|
+
- Updated dependencies [5060877]
|
|
1694
|
+
- Updated dependencies [4f6325d]
|
|
1695
|
+
- Updated dependencies [52954c0]
|
|
1696
|
+
- Updated dependencies [2aa8456]
|
|
1697
|
+
- Updated dependencies [d23ebb9]
|
|
1698
|
+
- Updated dependencies [93809a3]
|
|
1699
|
+
- Updated dependencies [7c0d0c3]
|
|
1700
|
+
- Updated dependencies [daae7aa]
|
|
1701
|
+
- Updated dependencies [8dc22d6]
|
|
1702
|
+
- Updated dependencies [fa5d137]
|
|
1703
|
+
- Updated dependencies [a392dbf]
|
|
1704
|
+
- Updated dependencies [279431e]
|
|
1705
|
+
- Updated dependencies [948dd6b]
|
|
1706
|
+
- Updated dependencies [3b4c56c]
|
|
1707
|
+
- Updated dependencies [ae8edd2]
|
|
1708
|
+
- Updated dependencies [e25403c]
|
|
1709
|
+
- Updated dependencies [a81aa9d]
|
|
1710
|
+
- Updated dependencies [64baa68]
|
|
1711
|
+
- Updated dependencies [9fa70d7]
|
|
1712
|
+
- Updated dependencies [09db64a]
|
|
1713
|
+
- Updated dependencies [92916e7]
|
|
1714
|
+
- Updated dependencies [a84f3ea]
|
|
1715
|
+
- Updated dependencies [f2eaae8]
|
|
1716
|
+
- Updated dependencies [56c093c]
|
|
1717
|
+
- Updated dependencies [c09451b]
|
|
1718
|
+
- Updated dependencies [ba64877]
|
|
1719
|
+
- Updated dependencies [e7191ce]
|
|
1720
|
+
- Updated dependencies [7345308]
|
|
1721
|
+
- Updated dependencies [79b6a22]
|
|
1722
|
+
- Updated dependencies [30d96ab]
|
|
1723
|
+
- Updated dependencies [f658793]
|
|
1724
|
+
- Updated dependencies [0fd4899]
|
|
1725
|
+
- Updated dependencies [c95ad19]
|
|
1726
|
+
- Updated dependencies [e58ea8b]
|
|
1727
|
+
- Updated dependencies [4a17645]
|
|
1728
|
+
- Updated dependencies [3795c5f]
|
|
1729
|
+
- Updated dependencies [8ab926b]
|
|
1730
|
+
- Updated dependencies [7317cf2]
|
|
1731
|
+
- Updated dependencies [e25e839]
|
|
1732
|
+
- Updated dependencies [5997207]
|
|
1733
|
+
- Updated dependencies [8b13cc8]
|
|
1734
|
+
- Updated dependencies [00d8f65]
|
|
1735
|
+
- Updated dependencies [4a4a35d]
|
|
1736
|
+
- Updated dependencies [86e765a]
|
|
1737
|
+
- Updated dependencies [1d7e76a]
|
|
1738
|
+
- Updated dependencies [53dc739]
|
|
1739
|
+
- Updated dependencies [fd289be]
|
|
1740
|
+
- Updated dependencies [03bf7b1]
|
|
1741
|
+
- Updated dependencies [f90e820]
|
|
1742
|
+
- Updated dependencies [18d816a]
|
|
1743
|
+
- Updated dependencies [e8bd715]
|
|
1744
|
+
- Updated dependencies [b91c351]
|
|
1745
|
+
- Updated dependencies [a28a3c0]
|
|
1746
|
+
- Updated dependencies [200d255]
|
|
1747
|
+
- Updated dependencies [2852acc]
|
|
1748
|
+
- Updated dependencies [daeaaf9]
|
|
1749
|
+
- Updated dependencies [c459da6]
|
|
1750
|
+
- Updated dependencies [e914733]
|
|
1751
|
+
- Updated dependencies [f887e52]
|
|
1752
|
+
- Updated dependencies [881f8d8]
|
|
1753
|
+
- Updated dependencies [3bfa1e6]
|
|
1754
|
+
- Updated dependencies [0a8ebf3]
|
|
1755
|
+
- Updated dependencies [901355c]
|
|
1756
|
+
- Updated dependencies [34ce8e7]
|
|
1757
|
+
- Updated dependencies [33681ea]
|
|
1758
|
+
- Updated dependencies [bfe13c8]
|
|
1759
|
+
- Updated dependencies [0fb3044]
|
|
1760
|
+
- Updated dependencies [4635f3e]
|
|
1761
|
+
- Updated dependencies [fd289be]
|
|
1762
|
+
- Updated dependencies [ee3595c]
|
|
1763
|
+
- Updated dependencies [09b4f4e]
|
|
1764
|
+
- Updated dependencies [b2eab95]
|
|
1765
|
+
- Updated dependencies [93940d4]
|
|
1766
|
+
- Updated dependencies [3a04b01]
|
|
1767
|
+
- Updated dependencies [45b9051]
|
|
1768
|
+
- Updated dependencies [3954fb7]
|
|
1769
|
+
- Updated dependencies [4805b56]
|
|
1770
|
+
- Updated dependencies [b9e9227]
|
|
1771
|
+
- Updated dependencies [d395692]
|
|
1772
|
+
- Updated dependencies [5894d30]
|
|
1773
|
+
- Updated dependencies [a3765f6]
|
|
1774
|
+
- Updated dependencies [2d5cee3]
|
|
1775
|
+
- Updated dependencies [e22158f]
|
|
1776
|
+
- Updated dependencies [7404925]
|
|
1777
|
+
- Updated dependencies [0c2334f]
|
|
1778
|
+
- Updated dependencies [778c59f]
|
|
1779
|
+
- Updated dependencies [d2619fd]
|
|
1780
|
+
- Updated dependencies [af56546]
|
|
1781
|
+
- Updated dependencies [6acb11a]
|
|
1782
|
+
- Updated dependencies [33c5fd3]
|
|
1783
|
+
- Updated dependencies [20b0fdb]
|
|
1784
|
+
- Updated dependencies [905019b]
|
|
1785
|
+
- Updated dependencies [a286411]
|
|
1786
|
+
- Updated dependencies [98c0d33]
|
|
1787
|
+
- Updated dependencies [368a82e]
|
|
1788
|
+
- Updated dependencies [a3d5724]
|
|
1789
|
+
- Updated dependencies [93ea19b]
|
|
1790
|
+
- Updated dependencies [9ee2dcf]
|
|
1791
|
+
- Updated dependencies [8cb96ec]
|
|
1792
|
+
- Updated dependencies [8f10a79]
|
|
1793
|
+
- Updated dependencies [6269a55]
|
|
1794
|
+
- Updated dependencies [a17da05]
|
|
1795
|
+
- Updated dependencies [a8c00e2]
|
|
1796
|
+
- Updated dependencies [22e5236]
|
|
1797
|
+
- Updated dependencies [0fb8760]
|
|
1798
|
+
- Updated dependencies [e5ce2ed]
|
|
1799
|
+
- Updated dependencies [be21955]
|
|
1800
|
+
- Updated dependencies [bc56e18]
|
|
1801
|
+
- Updated dependencies [be21955]
|
|
1802
|
+
- Updated dependencies [a9ee989]
|
|
1803
|
+
- Updated dependencies [4d0d944]
|
|
1804
|
+
- Updated dependencies [15d58db]
|
|
1805
|
+
- Updated dependencies [d63b014]
|
|
1806
|
+
- Updated dependencies [9abe4e4]
|
|
1807
|
+
- Updated dependencies [2cc7122]
|
|
1808
|
+
- Updated dependencies [50d6c92]
|
|
1809
|
+
- Updated dependencies [15d55fb]
|
|
1810
|
+
- Updated dependencies [9e0ba21]
|
|
1811
|
+
- Updated dependencies [a07a831]
|
|
1812
|
+
- Updated dependencies [311433f]
|
|
1813
|
+
- Updated dependencies [3e5ad08]
|
|
1814
|
+
- Updated dependencies [9abe4e4]
|
|
1815
|
+
- Updated dependencies [b7131f3]
|
|
1816
|
+
- Updated dependencies [e5812fa]
|
|
1817
|
+
- Updated dependencies [7085f90]
|
|
1818
|
+
- Updated dependencies [dee4dd4]
|
|
1819
|
+
- Updated dependencies [ce7e497]
|
|
1820
|
+
- Updated dependencies [51ecb2f]
|
|
1821
|
+
- Updated dependencies [9086761]
|
|
1822
|
+
- Updated dependencies [f6344e7]
|
|
1823
|
+
- Updated dependencies [42a117b]
|
|
1824
|
+
- Updated dependencies [1401ae7]
|
|
1825
|
+
- Updated dependencies [4297fe7]
|
|
1826
|
+
- Updated dependencies [e398863]
|
|
1827
|
+
- Updated dependencies [d16df74]
|
|
1828
|
+
- Updated dependencies [d79c602]
|
|
1829
|
+
- Updated dependencies [f11fc61]
|
|
1830
|
+
- Updated dependencies [e808890]
|
|
1831
|
+
- Updated dependencies [8f79379]
|
|
1832
|
+
- Updated dependencies [e6ca40e]
|
|
1833
|
+
- Updated dependencies [0c77ea4]
|
|
1834
|
+
- Updated dependencies [52954c0]
|
|
1835
|
+
- Updated dependencies [89eb997]
|
|
1836
|
+
- Updated dependencies [7131f12]
|
|
1837
|
+
- Updated dependencies [aa5994e]
|
|
1838
|
+
- Updated dependencies [be93457]
|
|
1839
|
+
- Updated dependencies [a65db76]
|
|
1840
|
+
- Updated dependencies [2cf5a96]
|
|
1841
|
+
- Updated dependencies [15eb2c9]
|
|
1842
|
+
- Updated dependencies [5691b07]
|
|
1843
|
+
- Updated dependencies [2a6122b]
|
|
1844
|
+
- Updated dependencies [225e769]
|
|
1845
|
+
- Updated dependencies [8af88dd]
|
|
1846
|
+
- Updated dependencies [fb5fbb8]
|
|
1847
|
+
- Updated dependencies [d7b3963]
|
|
1848
|
+
- Updated dependencies [33184fd]
|
|
1849
|
+
- Updated dependencies [7c41693]
|
|
1850
|
+
- Updated dependencies [b72db01]
|
|
1851
|
+
- Updated dependencies [dce5cd4]
|
|
1852
|
+
- Updated dependencies [9688f58]
|
|
1853
|
+
- Updated dependencies [556ebc1]
|
|
1854
|
+
- Updated dependencies [177ebdc]
|
|
1855
|
+
- Updated dependencies [8d237b4]
|
|
1856
|
+
- Updated dependencies [2d2e6f0]
|
|
1857
|
+
- Updated dependencies [2d8dd8d]
|
|
1858
|
+
- Updated dependencies [22d573e]
|
|
1859
|
+
- Updated dependencies [b5a2398]
|
|
1860
|
+
- Updated dependencies [348860c]
|
|
1861
|
+
- Updated dependencies [5383fa6]
|
|
1862
|
+
- Updated dependencies [5b3ff63]
|
|
1863
|
+
- Updated dependencies [1a6a19c]
|
|
1864
|
+
- Updated dependencies [064d484]
|
|
1865
|
+
- Updated dependencies [527e050]
|
|
1866
|
+
- Updated dependencies [dd33bf9]
|
|
1867
|
+
- Updated dependencies [4cb2a90]
|
|
1868
|
+
- Updated dependencies [74a7804]
|
|
1869
|
+
- Updated dependencies [53d3689]
|
|
1870
|
+
- Updated dependencies [b3a63d3]
|
|
1871
|
+
- Updated dependencies [49f0dcf]
|
|
1872
|
+
- Updated dependencies [033a34c]
|
|
1873
|
+
- Updated dependencies [4d25d22]
|
|
1874
|
+
- Updated dependencies [1ffee51]
|
|
1875
|
+
- Updated dependencies [5ae4303]
|
|
1876
|
+
- Updated dependencies [ece4dad]
|
|
1877
|
+
- Updated dependencies [e9b377e]
|
|
1878
|
+
- Updated dependencies [146f448]
|
|
1879
|
+
- Updated dependencies [735f5c7]
|
|
1880
|
+
- Updated dependencies [a7e18de]
|
|
1881
|
+
- Updated dependencies [366f895]
|
|
1882
|
+
- Updated dependencies [dc75ba8]
|
|
1883
|
+
- Updated dependencies [cce0aa9]
|
|
1884
|
+
- Updated dependencies [e764507]
|
|
1885
|
+
- Updated dependencies [cff17af]
|
|
1886
|
+
- Updated dependencies [39404f3]
|
|
1887
|
+
- Updated dependencies [ca1965f]
|
|
1888
|
+
- Updated dependencies [8619f95]
|
|
1889
|
+
- Updated dependencies [b706af9]
|
|
1890
|
+
- Updated dependencies [db8c288]
|
|
1891
|
+
- Updated dependencies [0e5fe7f]
|
|
1892
|
+
- Updated dependencies [add4360]
|
|
1893
|
+
- Updated dependencies [e0abc38]
|
|
1894
|
+
- Updated dependencies [fc9ba76]
|
|
1895
|
+
- Updated dependencies [1272f0a]
|
|
1896
|
+
- Updated dependencies [0f94cc7]
|
|
1897
|
+
- Updated dependencies [a11c1a5]
|
|
1898
|
+
- Updated dependencies [71f9cd1]
|
|
1899
|
+
- Updated dependencies [ee17d86]
|
|
1900
|
+
- Updated dependencies [cdbd920]
|
|
1901
|
+
- Updated dependencies [18c432e]
|
|
1902
|
+
- Updated dependencies [3c418c4]
|
|
1903
|
+
- Updated dependencies [fa8715a]
|
|
1904
|
+
- Updated dependencies [a933ed7]
|
|
1905
|
+
- Updated dependencies [b3ca463]
|
|
1906
|
+
- Updated dependencies [a933ed7]
|
|
1907
|
+
- Updated dependencies [0d4a6a8]
|
|
1908
|
+
- Updated dependencies [518d5e5]
|
|
1909
|
+
- Updated dependencies [6643ba1]
|
|
1910
|
+
- Updated dependencies [eeba2ef]
|
|
1911
|
+
- Updated dependencies [ec4c4d2]
|
|
1912
|
+
- Updated dependencies [424f73c]
|
|
1913
|
+
- Updated dependencies [cccbe51]
|
|
1914
|
+
- Updated dependencies [a8d6b1d]
|
|
1915
|
+
- Updated dependencies [e4a7695]
|
|
1916
|
+
- Updated dependencies [87075b1]
|
|
1917
|
+
- Updated dependencies [fc58a99]
|
|
1918
|
+
- Updated dependencies [14cfc00]
|
|
1919
|
+
- Updated dependencies [1c6f7b4]
|
|
1920
|
+
- Updated dependencies [e854a53]
|
|
1921
|
+
- Updated dependencies [dfebfc8]
|
|
1922
|
+
- Updated dependencies [598b7ec]
|
|
1923
|
+
- Updated dependencies [d028b37]
|
|
1924
|
+
- Updated dependencies [f7b25c5]
|
|
1925
|
+
- Updated dependencies [122ef38]
|
|
1926
|
+
- Updated dependencies [4a37870]
|
|
1927
|
+
- Updated dependencies [428f9b2]
|
|
1928
|
+
- Updated dependencies [aa7ff56]
|
|
1929
|
+
- Updated dependencies [811a3c2]
|
|
1930
|
+
- Updated dependencies [1401ae7]
|
|
1931
|
+
- Updated dependencies [2fd3f1c]
|
|
1932
|
+
- Updated dependencies [c41b42e]
|
|
1933
|
+
- Updated dependencies [d41d166]
|
|
1934
|
+
- Updated dependencies [c4db311]
|
|
1935
|
+
- Updated dependencies [750fff5]
|
|
1936
|
+
- Updated dependencies [c19035e]
|
|
1937
|
+
- Updated dependencies [ececf7a]
|
|
1938
|
+
- Updated dependencies [d173125]
|
|
1939
|
+
- Updated dependencies [8eeca27]
|
|
1940
|
+
- Updated dependencies [8425c17]
|
|
1941
|
+
- Updated dependencies [a5ef1d8]
|
|
1942
|
+
- Updated dependencies [87ad30c]
|
|
1943
|
+
- Updated dependencies [772d5de]
|
|
1944
|
+
- Updated dependencies [ce80ec2]
|
|
1945
|
+
- Updated dependencies [b372318]
|
|
1946
|
+
- Updated dependencies [97a2263]
|
|
1947
|
+
- Updated dependencies [29d0676]
|
|
1948
|
+
- Updated dependencies [0169d49]
|
|
1949
|
+
- Updated dependencies [6bd3231]
|
|
1950
|
+
- Updated dependencies [d2b5ba8]
|
|
1951
|
+
- Updated dependencies [b799ac5]
|
|
1952
|
+
- Updated dependencies [8f74307]
|
|
1953
|
+
- Updated dependencies [d23dc08]
|
|
1954
|
+
- Updated dependencies [644ad50]
|
|
1955
|
+
- Updated dependencies [9735662]
|
|
1956
|
+
- Updated dependencies [4d5b4f8]
|
|
1957
|
+
- Updated dependencies [3e8f5b0]
|
|
1958
|
+
- Updated dependencies [5d16379]
|
|
1959
|
+
- Updated dependencies [0da7cd2]
|
|
1960
|
+
- Updated dependencies [28a5c3e]
|
|
1961
|
+
- Updated dependencies [4bc18e5]
|
|
1962
|
+
- Updated dependencies [9f57f1e]
|
|
1963
|
+
- @objectstack/spec@17.3.0
|
|
1964
|
+
- @objectstack/platform-objects@17.3.0
|
|
1965
|
+
- @objectstack/core@17.3.0
|
|
1966
|
+
- @objectstack/types@17.3.0
|
|
1967
|
+
- @objectstack/metadata-core@17.3.0
|
|
1968
|
+
- @objectstack/metadata-fs@17.3.0
|
|
1969
|
+
|
|
3
1970
|
## 17.2.0
|
|
4
1971
|
|
|
5
1972
|
### Patch Changes
|