@objectstack/metadata 17.3.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 CHANGED
@@ -1,5 +1,537 @@
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
+
3
535
  ## 17.3.0
4
536
 
5
537
  ### Minor Changes
package/README.md CHANGED
@@ -165,19 +165,21 @@ new MetadataManager({
165
165
  `DatabaseLoader` wraps `load` / `loadMany` / `list` / `stat` results in a
166
166
  generic LRU cache (see `src/utils/lru-cache.ts`). Writes invalidate the
167
167
  affected entries, so reads always observe writes made through the same loader
168
- instance; out-of-band SQL writes are honored within `ttl` milliseconds.
168
+ instance; out-of-band SQL writes are honored within `ttlMs` milliseconds.
169
169
 
170
- Configuration lives under `cache.databaseLoader`:
170
+ Configuration lives under `cache.databaseLoader` — the only live member of the
171
+ `cache` block (the outer `cache.enabled` / `cache.ttlSeconds` / `cache.maxSize`
172
+ were removed in #15624: nothing ever read them, and authoring one is now a
173
+ compile-time and parse-time error naming this nested half):
171
174
 
172
175
  ```typescript
173
176
  new MetadataManager({
174
177
  datasource: 'default',
175
178
  cache: {
176
- enabled: true,
177
179
  databaseLoader: {
178
180
  enabled: true,
179
181
  maxSize: 500, // Max cached (type, name) entries
180
- ttl: 60_000, // Cache TTL in milliseconds
182
+ ttlMs: 60_000, // Cache TTL in milliseconds
181
183
  },
182
184
  },
183
185
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/metadata/errors` — the shared driver-error discriminators for\n * the metadata storage seams (#4728 / #4825 / #4867 family).\n *\n * ## Why this subpath exists\n *\n * The \"which driver failures may be silenced?\" question is not local to one\n * package. It was answered first for DDL in `@objectstack/metadata`\n * (`ensureSchema`, #4728), then for reads on the legacy `DatabaseLoader` path\n * (`nextEventSeq`, #4825) — and the *canonical* transactional producer of the\n * very same numbers, `SysMetadataRepository`, lives in a different package\n * (`@objectstack/metadata-protocol`, #4867) and carried the identical defect.\n *\n * Three ways to serve that second package were considered; the third is the\n * one taken, and the first is the one this module exists to prevent:\n *\n * 1. **Copy the predicate.** Rejected. Two hand-rolled vocabularies of\n * \"benign driver error\" is precisely the dual-source debt #4825 killed:\n * a driver quirk taught to one copy and not the other produces two\n * packages that disagree about whether data may be silently invented.\n * 2. **Sink it into a common dependency** (`@objectstack/types`,\n * `@objectstack/spec/shared`). Architecturally attractive and explicitly\n * *not* precluded by this module — but out of scope on the round that\n * needed it (spec was frozen; types was under concurrent change).\n * ⇒ **TAKEN, by the maintainer's 2026-08-30 ruling on #13279.** The\n * predicate now lives in `@objectstack/types`\n * (`driver-error-classification.ts`); what forced it was a consumer this\n * file could never serve — `resolveAuthzContext` in `@objectstack/core`,\n * which metadata **depends on**, so the edge could not point that way.\n * 3. **Export it deliberately from its current home** — this file. One\n * declaration, one implementation, one place a new driver quirk is taught.\n *\n * ## What this file is now\n *\n * Option 2 is taken, so this is the compatibility seam it always said it would\n * become — \"a single, greppable seam to delete if the maintainer later takes\n * option 2\". It is NOT deleted: `@objectstack/metadata/errors` is a published\n * subpath with out-of-repo consumers, and #13279 is a fix, not a removal. It\n * re-exports the one symbol it always exported, from the new home. Everything\n * below still describes why the subpath exists and why it stays narrow.\n *\n * ## Why a subpath and not the package entry\n *\n * `@objectstack/metadata`'s root entry pulls the manager, every loader and the\n * YAML/filesystem machinery behind them. A consumer that wants a 40-line\n * predicate should not have to load any of that, and the weight is exactly\n * what would tempt the next author back to option 1. This entry re-exports\n * one leaf module and nothing else, so the cross-package edge stays a leaf\n * edge — and stays a single, greppable seam to delete if the maintainer later\n * takes option 2.\n *\n * ## Scope of the promise\n *\n * Only {@link isMissingTableError} is re-exported HERE. Its sibling\n * `isSchemaAlreadyExistsError` moved to `@objectstack/types` with it (they are\n * two signatures over one matcher and cannot be separated without re-rolling\n * it), but it does not need a second door: nothing imports it through\n * `@objectstack/metadata/errors`, and an exported symbol nobody imports is a\n * promise made for nothing (Prime Directive #10, pointed at our own API\n * surface). Anything that needs it reads `@objectstack/types` directly.\n */\n\nexport { isMissingTableError } from '@objectstack/types';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgEA,mBAAoC;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgHA,mBAAoC;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/metadata/errors` — the shared driver-error discriminators for\n * the metadata storage seams (#4728 / #4825 / #4867 family).\n *\n * ## Why this subpath exists\n *\n * The \"which driver failures may be silenced?\" question is not local to one\n * package. It was answered first for DDL in `@objectstack/metadata`\n * (`ensureSchema`, #4728), then for reads on the legacy `DatabaseLoader` path\n * (`nextEventSeq`, #4825) — and the *canonical* transactional producer of the\n * very same numbers, `SysMetadataRepository`, lives in a different package\n * (`@objectstack/metadata-protocol`, #4867) and carried the identical defect.\n *\n * Three ways to serve that second package were considered; the third is the\n * one taken, and the first is the one this module exists to prevent:\n *\n * 1. **Copy the predicate.** Rejected. Two hand-rolled vocabularies of\n * \"benign driver error\" is precisely the dual-source debt #4825 killed:\n * a driver quirk taught to one copy and not the other produces two\n * packages that disagree about whether data may be silently invented.\n * 2. **Sink it into a common dependency** (`@objectstack/types`,\n * `@objectstack/spec/shared`). Architecturally attractive and explicitly\n * *not* precluded by this module — but out of scope on the round that\n * needed it (spec was frozen; types was under concurrent change).\n * ⇒ **TAKEN, by the maintainer's 2026-08-30 ruling on #13279.** The\n * predicate now lives in `@objectstack/types`\n * (`driver-error-classification.ts`); what forced it was a consumer this\n * file could never serve — `resolveAuthzContext` in `@objectstack/core`,\n * which metadata **depends on**, so the edge could not point that way.\n * 3. **Export it deliberately from its current home** — this file. One\n * declaration, one implementation, one place a new driver quirk is taught.\n *\n * ## What this file is now\n *\n * Option 2 is taken, so this is the compatibility seam it always said it would\n * become — \"a single, greppable seam to delete if the maintainer later takes\n * option 2\". It is NOT deleted: `@objectstack/metadata/errors` is a published\n * subpath with out-of-repo consumers, and #13279 is a fix, not a removal. It\n * re-exports the one symbol it always exported, from the new home. Everything\n * below still describes why the subpath exists and why it stays narrow.\n *\n * ## Why a subpath and not the package entry\n *\n * `@objectstack/metadata`'s root entry pulls the manager, every loader and the\n * YAML/filesystem machinery behind them. A consumer that wants a 40-line\n * predicate should not have to load any of that, and the weight is exactly\n * what would tempt the next author back to option 1. This entry re-exports\n * one leaf module and nothing else, so the cross-package edge stays a leaf\n * edge — and stays a single, greppable seam to delete if the maintainer later\n * takes option 2.\n *\n * ## Scope of the promise\n *\n * Only {@link isMissingTableError} is re-exported HERE. Its sibling\n * `isSchemaAlreadyExistsError` moved to `@objectstack/types` with it (they are\n * two signatures over one matcher and cannot be separated without re-rolling\n * it), but it does not need a second door: nothing imports it through\n * `@objectstack/metadata/errors`, and an exported symbol nobody imports is a\n * promise made for nothing (Prime Directive #10, pointed at our own API\n * surface). Anything that needs it reads `@objectstack/types` directly.\n */\n\nexport { isMissingTableError } from '@objectstack/types';\n"],"mappings":";AAgEA,SAAS,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts"],"mappings":";AAgHA,SAAS,2BAA2B;","names":[]}