Skip to content

Commit a56baa2

Browse files
claude[bot]claude
andauthored
fix(metadata,objectql): the action audit reads the store key and asks the plane by name, and listNames gains loadMany fault parity (#15378)
* feat(metadata): listNames gains loadMany's per-loader fault parity, and a keyed plural read lands beside loadMany Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * fix(objectql): both halves of the D5 bijection read the store key, and the handler half asks the plane by name Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(runtime): the #14423 fixture pins the agreement, and C4 as a boundary Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * chore: changeset for the keyed audit read and listNames fault parity, and the measured ablation outcome Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(metadata): the outage double is a separate double, not a failure flag, and IDataDriver comes from the contracts entry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(metadata): pin the by-name rung's per-name cost — one findOne per probe Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 222dc0f commit a56baa2

7 files changed

Lines changed: 1292 additions & 85 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
'@objectstack/metadata': minor
3+
'@objectstack/objectql': minor
4+
---
5+
6+
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
7+
8+
Two plural reads of one metadata plane could disagree with a by-name read of
9+
that same plane, and the ADR-0110 D5 action-governance audit stood on the
10+
disagreement — reporting `registered handler with NO declaration … REFUSED at
11+
dispatch` about a route the router was resolving and dispatching in the same
12+
boot.
13+
14+
**`MetadataManager.listNames` gains the per-loader `try`/`catch` that
15+
`loadMany` and `list()` have carried since #5108.** One loader fault used to
16+
produce two different facts depending only on which plural read a caller
17+
reached for: `loadMany` swallowed it and answered short, `listNames` threw. It
18+
now degrades the same way, through the same `reportLoaderReadFailure` /
19+
`reportLoaderReadRecovered` helpers — one outage, one line, one vocabulary.
20+
Callers that relied on `listNames` throwing to detect an outage should read
21+
`listDiagnosed()`, which reports `degraded` explicitly.
22+
23+
**New: `MetadataManager.loadManyKeyed(type, options?)`**`loadMany` read under
24+
the identity the STORE holds each item by, returning `{ name, data }` pairs. It
25+
delegates to a loader's own `loadManyKeyed` where one is offered (on
26+
`DatabaseLoader` that shares `loadMany`'s single query, so it costs nothing
27+
extra) and otherwise falls back to that loader's `list()` + per-name `load()`.
28+
**`loadMany`'s published return shape does not change**, and no existing
29+
consumer is touched: the key travels *beside* the body, never inside it, so a
30+
body that deliberately carries no `name` stays byte-identical to what was
31+
stored (#14205).
32+
33+
**The action-governance audit now mirrors the router on both halves of the D5
34+
bijection.** The declaration half enumerates the plane keyed
35+
(`loadStandaloneActionsKeyed`), so a row whose body does not name itself — a
36+
`sys_metadata` row keyed by its `name` column, or a `FilesystemLoader` file
37+
whose identity is its path — is a declaration to the audit exactly as it is to
38+
the router; the handler half also probes the plane BY NAME
39+
(`lookupMetadataAction`, `loadDiagnosed`/`load`, injected like the existing
40+
registry rung), so a loader fault a plural read swallows can no longer turn a
41+
dispatchable handler into an accusation. Both probes stay conservative in one
42+
direction only: a source that throws leaves the handler on the list.
43+
44+
Additive on every published signature. `runActionGovernanceInventory` and
45+
`collectEngineActionDeclarations` gain optional parameters and keep their old
46+
ones working unchanged; declaration rows gain an optional `storeKey` (the new
47+
exported `ActionDeclarationRow`).
48+
49+
**Population change, reported:** `unboundDeclarations` now sees declarations
50+
whose identity is the store key. Its BEFORE was **0, structurally rather than
51+
by sampling** — a nameless row was dropped before reconciliation ran, so it
52+
could never be reported however many a plane held. Its one deliberate
53+
subtraction: a row with neither an own `name` nor a store key is no longer
54+
reported as `actionName: undefined`, which read as a parse failure in the
55+
warning rather than as a finding.
56+
57+
Known boundary, stated in the audit's docblock rather than left to be
58+
rediscovered: a boot-time audit runs outside any request scope, so if a
59+
composition ever registered `metadata` as `SCOPED` the audit could not reach
60+
that instance at all — before any read method runs. No shipped composition does
61+
(`packages/metadata/src/plugin.ts` registers a static instance), and reaching a
62+
request-scoped service from a boot-time audit is a separate change.

0 commit comments

Comments
 (0)