feat(spec): declare IMetadataService.loadManyKeyed beside its plural-read siblings - #19609
Conversation
…read siblings The keyed plural loader read shipped as a public member on MetadataManager with no declaration on the contract its two siblings loadMany? and loadDiagnosed? are declared on, so the one cross-package caller — the ObjectQL governance audit — narrowed the service slot with a local structural type written beside the call site. Declare the member on IMetadataService, delete the local type, and let the call site read the contract. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
A double holding one fixed body cannot satisfy a caller-chosen `T` — the three object-literal implementations were concrete and `check:test-typecheck` refused them (TS2322, a signature the shrink-only ledger does not record). Declare each double `<T = unknown>` and hand the body back under it. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 216cc3273f3c24c061ee4b87084f52532fdeb14a && git checkout 216cc3273f3c24c061ee4b87084f52532fdeb14a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 b96baa08f273ba99c0986c24f706b693c2061b6a && git checkout -B drift-repro 5c5b67fc4140f76ca3158acea9e0845d9eebfad8 && git merge --no-ff b96baa08f273ba99c0986c24f706b693c2061b6a
node scripts/docs-audit/affected-docs.mjs --json 5c5b67fc4140f76ca3158acea9e0845d9eebfad8
|
…probe-first example Three corrections to the loadManyKeyed declaration and its page. The docblock cited the card that ordered the implementation, which has since been deleted from the board and no longer resolves; check:issue-citations refused it as allocated-but-absent. Cite the merged pull request that is the live record instead, and keep the deleted card's number in prose, without a leading hash, so it stays greppable without minting a reference that dangles. "a customization container" named nothing in the tree and collided with the ADR-0005 sys_metadata overlay, which is a different mechanism this very page documents. The real case is the aggregated defineView container, which the MetadataKeyedItem docblock already names; both carriers now say so. The page's example did an optional call and null-coalesced to an empty array, turning an absent member into an empty set — the opposite of what the callout beneath it says and of what the one real caller does. It now probes first and spells out why absence is not emptiness. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
Ready, green, reviewed — and the last step is blocked with no channelAll three landing preconditions are met and recorded on the card:
This PR has been flipped draft → ready, confirmed by ⛔ ⛔ The seat will not merge this PR, enqueue it by hand, or submit an approving review on it. The only remaining act is enabling auto-merge with Action needed from the maintainer or a seat with the channel: enable auto-merge (SQUASH). Everything else here is finished. Generated by Claude Code |
✅ Correction — this PR is NOT blocked any more. It is in the merge queue.
The maintainer re-granted the permission and the call was retried. ⛔ The earlier standing-down note on this PR — "
|
Carrier stripped on a PASS that is on record — and the seat's own enqueue error, stated plainly
⛔ What I got wrongI enqueued this PR at 00:35Z while
The guard was right and the rule it cites is one I had already read: 「双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队」. I ran the three landing preconditions (at-tier PASS,
✅ Why stripping now is the sanctioned act and ⛔ not a way past the checkThe guard's own text draws the line: "⛔ Stripping the label to get past this check, with no verdict on record, is the defect this leg was built from — not a way through it." There is a verdict on record, and it is cited here rather than asserted: At-tier contract review, ⇒ the condition 「PASS ⇒ 同席剥标并引记录」 is satisfied. The carrier is stripped from both carriers — this PR and its card — by the same seat that adopted the verdict, in the four-step label write, with read-back. What happens nextCarrier stripped on both sides → Generated by Claude Code |
…nsumers after the `PluginSecurityScanner` retirement (objectstack-ai#15932) (objectstack-ai#19610) Fixes objectstack-ai#15932 Clause-②: yes ADR-0049 enforce-or-remove. Executes the ruling on objectstack-ai#15932 (director seat, decision batch objectstack-ai#65, 2026-09-07, maintainer verbatim 「同意」). This is the second half of the `PluginSecurityScanner` retirement, whose live record is **PR objectstack-ai#15930**.⚠️ **Citation corrected:** the number that landing was filed under — 14919, written bare on purpose — **no longer resolves on the board** (404 at 2026-09-21T18:3xZ; lit control: the neighbouring 14920 returns 200), so the digits are kept greppable while the sigil is dropped, which is the same treatment this repo gave the dead `[objectstack-ai#14423]` **docblock citation** that PR objectstack-ai#19609 repaired in `packages/metadata/src/metadata-manager.ts`.⚠️ **Carrier corrected:** an earlier draft of this sentence called that precedent a `Blocked-by: objectstack-ai#14423` line. There is no such line — `Blocked-by:.*14423` returns **0** across `origin/main` (lit control: real `Blocked-by:` lines do exist, in five files and more), and objectstack-ai#19609's own diff removes `* [objectstack-ai#14423] The keyed plural read …` from a docblock. The precedent itself is real and reads verbatim *"the card it was filed under — issue 14423, written here without a leading hash because it no longer resolves"*; only its carrier was misnamed. ⛔ No replacement number is guessed: objectstack-ai#15930 is the PR whose patch did the work, ⛔ not a re-issued card. That change retired `PluginSecurityScanner`, and its type-only import was the scan-result family's only importer of any kind, so the schemas it fed went from one type-only importer to **zero consumers of any kind** while staying fully published. ## What is retired | member | route | | --- | --- | | `KernelSecurityScanResult` (def + 3 exports) | whole-def removal, `RETIRED_DEFS_BY_MAJOR[18]` | | `KernelSecurityVulnerability` (def + 3 exports) | whole-def removal, `RETIRED_DEFS_BY_MAJOR[18]` | | `PluginSecurityManifest.scanResults` | `retiredKey()` tombstone, `RETIRED_KEYS_BY_MAJOR[18]` | | `PluginSecurityManifest.vulnerabilities` | `retiredKey()` tombstone, `RETIRED_KEYS_BY_MAJOR[18]` — see **Scope** below | | `PluginQualityMetrics.securityScan` | `retiredKey()` tombstone, `RETIRED_KEYS_BY_MAJOR[18]` | Two routes because the two questions have different answers. Nothing parses the two **defs**, so there is no author a prescription could reach and a tombstone would be noise. The three **keys** sit on shapes that are not `.strict()`, where a bare deletion is a silent strip (ADR-0104) — so each becomes a `retiredKey()` tombstone, audible in both channels: `tsc` (input type `never`) and the parse, which raises the prescription itself. **No D2 conversion.** A plugin security manifest and a plugin registry entry are package artifacts a publisher ships — never stack collection members, never stored `sys_metadata` rows — so the conversion chain has no seam that would see one. That is the disposition the sibling `kernel-plugin-security-durations-unit-in-key` entry already records for this same manifest. The D3 semantic entry `plugin-security-scan-result-surface-retired` carries the judgement. ## Premise, re-measured first-hand on `origin/main` @ `236cec19a5` | reading | value | | --- | --- | | `KernelSecurityScanResult` / `KernelSecurityVulnerability` in `packages/**/*.ts` outside the declaring module | **0** | | LIT CONTROL — `PluginSecurityManifest` inside `plugin-security-advanced.zod.ts` | **5** ⇒ the file is greppable, so the zero is a reading | | `packages/core/src/security/security-scanner.ts` | **absent** ⇒ the `PluginSecurityScanner` retirement (PR objectstack-ai#15930) landed | | `PluginQualityMetrics.securityScan` in `packages/**/*.ts` | `plugin-registry.test.ts` only — the spec's own self-test | | objectui at the pinned sha `87af769e` — does it import any of this? | **0** hits; LIT CONTROL: `@objectstack/spec` is imported there ⇒ no sibling fix and no pin bump are owed | **The authorable-row count is 27, not the 22 the card carried.** Measured with the playbook's instrument on `authorable-surface/kernel.json`: 8 rows for `KernelSecurityScanResult`, 17 for `KernelSecurityVulnerability`, plus `PluginSecurityManifest:scanResults` and `PluginQualityMetrics:securityScan` — 28 counting the forced-consequence `PluginSecurityManifest:vulnerabilities`. The disagreement is reported, not reconciled: the 22 is superseded, and the FOLLOW-UPS row now says so. ## Scope — one key outside the four names, reported rather than absorbed `PluginSecurityManifest.vulnerabilities` is **not** one of the four names the ruling listed. It is a forced consequence: it was an array of `KernelSecurityVulnerability` and the last authorable referent of a def the ruling retires by name, so it cannot outlive that def, and keeping the def alive only to carry it would be keeping the retired family alive under a second name. It is not a neighbour retired by proximity — the fence's stated concern — and it is named here, in the registry entry, in the changeset and in the hand-back. ⛔ **The outstanding carve-out is ONE enum member wide, not three — and it is NOT recorded as checked.**⚠️ **Coordinates corrected.** The ruling made three carve-outs conditional on a producer grep of `objectstack-ai/cloud`. Two of the three no longer exist in this tree, so only one is still outstanding: | carve-out the ruling named | state at head `3e0a06d0b5` | |:--|:--| | marketplace `'scanning'` status, `marketplace.zod.ts` | **LIVE**, one hit in any `.zod.ts`, at `marketplace.zod.ts:348` — the sole outstanding carve-out | | `marketplace-admin.zod.ts` | **file absent from the tree**; that family's disposition was decided on objectstack-ai#16526 | | incident `'malware'` type, `incident-response.zod.ts` | **file absent from the tree** — the incident family was retired whole by objectstack-ai#15513, ruled **2026-09-05**, two days BEFORE the ruling that made `'malware'` conditional; `malware` returns **0** in any `.zod.ts` | Instrument controls, so the two zeros are readings rather than a dead grep: `marketplace*.zod.ts` on the same `find` returns `marketplace.zod.ts`, and `malware` on the same grep returns 7 non-`.zod.ts` files (ADRs, design docs, records). An earlier draft of this body named all three files in the present tense; that was wrong and is retracted here. `objectstack-ai/cloud` is not reachable from this session, so the producer question for `'scanning'` is genuinely **NOT MEASURED**. ⛔ The absence of these names from this diff is not evidence about them. ## Breaking, for a population that is not measured `@objectstack/spec` is published, so removing six exports and three authorable keys is breaking for consumers no download, dependent or source telemetry was consulted for — exactly as that retirement's own changeset (PR objectstack-ai#15930) says of its own three exports. That was an input to the ruling, not a reason to soften the removal. No deprecation window (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). The release note is written centrally; `content/docs/releases/` is untouched.⚠️ **Runtime behaviour is deliberately unchanged.** Nothing ever read any of these keys, so deleting one removes no check that was running. A consumer that gated on `securityScan.passed === true` was gating on nothing. ##⚠️ Changeset level — the ruling says `major`, a live gate refuses it The ruling and the dispatch both say **`major`**. `scripts/check-changeset-no-major.mjs` hard-refuses a `major` bump for the duration of the launch window (every publishable package is in one Changesets `fixed` group, so one `major` promotes ~70 packages), and the retirement playbook was corrected to say so in objectstack-ai#19446, which is on `main`. A `major` changeset here is a guaranteed-red PR that cannot land. This PR therefore ships **`minor` + a BREAKING banner carrying the FROM → TO mapping and the one-line fix** — the carrier the window designates for breaking-ness — plus the ADR-0087 disposition marker. `check-changeset-no-major.mjs` and `check-adr-0087-registration.mjs` are both green on it. **This is flagged, not silently chosen:** if the seat wants the literal `major`, that is a decision about the launch-window guard, not about this diff. ## Verification | check | result | | --- | --- | | `pnpm --filter @objectstack/spec build` | **pass** (after the two deletion gates fired and were answered, below) | | `pnpm --filter @objectstack/spec test` | **pass** — 510 files, 14897 passed, 1 todo | | `pnpm --filter @objectstack/spec typecheck` | see the hand-back | | `pnpm --filter @objectstack/spec check:generated` | **pass** — 15 artefacts; 5 were stale and were regenerated by `--fix`, never hand-edited | | `check-adr-0087-registration.mjs` | **pass** — 1 declared-breaking changeset, disposition `registered plugin-security-scan-result-surface-retired` | | `check-changeset-no-major.mjs` | **pass** — no `major` bump introduced | **Two gates fired on the way, and both were answered rather than routed around.** The json-schema manifest deletion gate refused the two vanished defs until their keys left `json-schema.manifest/kernel.json` *and* each was declared in `RETIRED_DEFS_BY_MAJOR`; the authorable-surface deletion gate then refused the 25 orphaned key rows until they left `authorable-surface/kernel.json` in the same commit. That sequence is the removal's own evidence and is why the ratchets moved. ⛔ `authorable-surface.base.json` was not touched. **Reverse verification — the refusal pin can fail.** The `scanResults` tombstone was ablated to `z.array(z.unknown()).optional()` with `scripts/ablation-replace.mjs`, which proved the mutation on disk (anchor 1 → 0, blob `0f3af37f5068` → `969efcdaa46a`) before running anything. Result: exactly one test failed — the `scanResults` refusal pin — and the other four passed. The restore leg verified blob == HEAD and `git diff HEAD` empty. ## Acceptance notes Noted, not filed — observed while executing, outside this card's scope, and no in-flight PR or person is known to be heading for these files: - `packages/spec/src/kernel/plugin-security.zod.ts` declares a **parallel, unprefixed** scan-result family — `SecurityVulnerabilitySchema` and `SecurityScanResultSchema`, near-duplicates of the pair retired here, with their own self-test in `plugin-security.test.ts`. It is outside the four names and is deliberately untouched; the pin test asserts both are still exported, so the fence is machine-checked rather than described. Whether it is live is a separate census this card did not take. - `plugin-security-advanced.test.ts`, the declaring module's own self-test, contained **zero** references to the scan-result family. The premise called `plugin-security.test.ts` the family's self-test; it is in fact the *other* family's. The retired family had no self-test at all — a reading slightly stronger than the card's. PR body maintained by the `domain:spec` execution seat, session `session_01UDXER3sdqfeVYpEWZs5mZx`; the diff is the dev's. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #15385
Declares
loadManyKeyed?onIMetadataService, deletes the local structural type the ObjectQL governance audit used to reach it, and documents the member.Execution of the recorded ruling — director seat, decision batch #123 item 5, 2026-09-12, comment
5644711080, maintainer verbatim 「同意」. That ruling picks option 1 of the two the card put to triage and enumerates four items; all four are below. Option 2 (leave it undeclared on purpose) is ruled out and is not re-opened here.Clause-②: yes
The declaration
packages/spec/src/contracts/metadata-service.ts— the new member sits immediately after its unkeyed twinloadMany?(line 675 on the base commit), inside the sameIMetadataServicedeclaration that already carriesloadMany?andloadDiagnosed?.loadManyKeyed?is optional, is generic in one parameterTthat defaults tounknown, takestype: stringplus an optionaloptionsbag typed as a Record from string to unknown, and resolves to an Array of{ name: string; data: T }pairs. That is the signature the ruling names, member for member. The diff is the authority on it, not this paragraph.It is optional, like both siblings, so every existing
IMetadataServiceimplementation still satisfies the contract unchanged and thetypeof ... === 'function'probe stays the way a caller asks for it.Why an inline pair shape rather than the published
MetadataKeyedItemThe ruling writes the return type inline, and that is also the only spelling available.
MetadataKeyedItemis declared inpackages/metadata/src/loaders/loader-interface.tsand exported from@objectstack/metadata, which depends on@objectstack/spec;packages/specdeclares no workspace dependency at all (pg-connection-stringandzod). Importing the named type here would invert that edge and close a cycle. The inline pair is also the file's own precedent:loadDiagnosed?declares its result inline in exactly the same way.The two shapes are structurally identical —
MetadataKeyedItemisreadonly name: stringbesidereadonly data: T, and a readonly property is assignable to a mutable one — soMetadataManager implements IMetadataServicekeeps compiling with no edit topackages/metadata, which is what the green build below shows.The four ruled items
loadDiagnosed's cites#4127 batch 4.packages/objectql/src/plugin.tslosesKeyedPluralMetadataRead(the type and its docblock), and the three service lookups inresolveGovernanceMetadataServicenow ask forIMetadataServicealone instead of intersecting it. Occurrences of that type name underpackages/go 4 to 0 — 1 declaration plus 3 use sites; an earlier draft of this line said 3, corrected against the blob by the at-tier review (lit control:IMetadataServicein the same file = 13, so the zero is a reading).check:slot-lookupstays green — see below.content/docs/kernel/contracts/metadata-service.mdxgains the member, in the interface excerpt'sLoader reads (optional)group and as a newloadManyKeyedsubsection. Section choice and a contrary fact about it are in the acceptance notes.Clause-②: yesabove;minorchangeset (@objectstack/spec, whose changesetsfixedgroup already carries@objectstack/objectql). Theneeds:contract-reviewcarrier was not hung by this branch — the dev never wrote a label.Round 2 — head
b96baa08f2(2 files, +38 / −13)Three corrections, all prose; the PR's file list is unchanged at 5 and no new path was pulled in.
check:issue-citationswas RED at97a639c5b3: the new docblock cited an issue that returns 404 (LIT CONTROL: its neighbour#14424→ 200, so the 404 is a reading). ⭐ The replacement was not guessed —#15378was verified four ways before being named: HTTP 200,merged: true,merged_at, basemain, and the depth-immune one —origin/mainholds the implementation it added (git grep 'async loadManyKeyed' origin/main -- packages/metadata/src/metadata-manager.ts= 1; nonsense control = 0).check-issue-citationsprints a remedy arm its own grammar cannot honour — 「keep the number and say in prose」 still fails, becauseCITATION_REsees the##19614: keeping the#still matchesCITATION_RE, andNON_CITATION_HEADSexcuses only ordinal heads — there is no prose-acknowledgement mechanism in the script. So the dead card is kept as bare digits without a leading hash, with a sentence saying why. Greppable, and nothing dangles.[], turning absence into an empty set — on the one member whose reason for existing is that silent drops are dangerous. It now reads the member into a local, guards ontypeof === 'function', and itselsebranch says why absence is not emptiness, matchingplugin.ts.?? []is gone from the page (0 hits).origin/mainacrosspackages/,content/anddocs/. The right vocabulary came fromMetadataKeyedItem's own docblock: an aggregateddefineViewcontainer "has no ownnameBY DESIGN (its identity is the target object)". Both carriers now say that, each with an explicit disclaimer that it is not the ADR-0005sys_metadataorg customization overlay — which this same page documents separately.Gate readings at⚠️
b96baa08f2—check:issue-citationsis recorded here and ⛔ not in the derived-families row (which is below, in the Local runs table — an earlier draft of this line said "above"), because its root script is--self-testonly while CI runs the self-test and the scan; reporting the alias as a pass is what produced the red in the first place. Run as the SCAN:node scripts/check-issue-citations.mjs --base origin/main→ EXIT=0, captured before any pipe, re-run at the final head → EXIT=0 (4 citations judged across 13 files; 2 resolves, 2 resolves-as-pull-request).pnpm lintwhole repo EXIT=0 · spec build success ·check:generatedall 15 up to date against a fresh build · spec typecheck pass · spec test 509 files / 14901 passed · objectql typecheck EXIT=0 ·check:slot-lookupholds ·check:nul-bytesOK plus a hand control-character scan of both edited files.Mechanical proof the
.tsedit is docblock-only: every added and removed line ingit diff 97a639c5b3..b96baa08f2 -- packages/spec/src/contracts/metadata-service.tsis a comment line — zero non-comment lines. No type or runtime surface moved, so the ablation recorded at97a639c5b3still stands and was not re-run.check:docs-transcript-driftexit 3 (@objectstack/lintunbuilt) → built, re-ran, EXIT=0; objectql typecheck first exit 2 with 42 errors, allTS2307 Cannot find modulefrom an unbuilt dependency closure → built, re-ran, EXIT=0.git merge-base --is-ancestoron #15378's squash commit exited 1, but this checkout is shallow and the control leg was a shallow-window near-relative — so that negative is void, not evidence. The tree read and the API'smerged/merged_atanswer the question without a history walk.Verification
Reverse verification, because this is a cross-package type change and a green typecheck against a stale
.d.tsis indistinguishable from a real one. Run from the committed state throughscripts/ablation-replace.mjs, with the on-disk and in-distevidence the tool produces:bd37483b1715to3a9e85d219ad.scripts/ablation-dist-preflight.mjsfound the mutated marker in 2 built files (packages/spec/dist/contracts/index.d.tsand.d.mts), so the run below read the rebuilt declarations and not a cache.tsc --noEmitinpackages/objectqlwent red with exactly one error, and it is the call site:src/plugin.ts(2593,35): error TS2339: Property 'loadManyKeyed' does not exist on type 'IMetadataService'.bd37483b1715, equal to HEAD,git diff HEADempty, whole-treegit status --porcelainempty. After a rebuild the mutated marker is gone fromdist(0 occurrences) and the real member is back (2), andtsc --noEmitinpackages/objectqlis green with zero output.That is the proof for ruled item 2: the call site now reads the contract, and it reads only the contract.
Local runs.⚠️ Provenance corrected — this table is not all from one head. The nine readings restated in the Round 2 section were taken at the final head
b96baa08f2; every other row here — the objectql typecheck, the changeset gates, the 14 docs gates and the 20 further derived families — was measured at97a639c5b3, before round 2 rewrote the.mdx. ⛔ Nothing is actually unmeasured at the final head: CI ran the whole docs family green there, including "packages/spec/src/**doc-block symbol anchors resolve". It is the sentence that over-claimed its own provenance, not the work.pnpm lint(whole repo,eslint . --no-inline-config)pnpm --filter @objectstack/spec buildpnpm --filter @objectstack/spec check:generatedpnpm --filter @objectstack/spec typecheckcheck:test-typecheck)pnpm --filter @objectstack/spec testpnpm --filter @objectstack/objectql typecheckpnpm --filter @objectstack/objectql testpnpm check:slot-lookup0e658fb: no files addedpnpm check:nul-bytescheck:empty-changeset,check:adr-0087-registration,check:changeset-no-major,check:changeset-gate-self-tests)check:doc-anchors,check:doc-authoring,check:doc-frontmatter,check:docs-section-name,check:docs-single-h1,check:docs-redirects,check:docs-spec-enumerations,check:docs-transcript-drift,check:docs-audit-scope,check:doc-route-spelling,docs-audit/check-affected-docs,docs-audit/check-drift-comment,check:section-landing-index,check:keyed-text-bounds)check:type-check-coverage,check:test-source-alias,check:published-files,check:dts-closure,check:lean-entry-closure,check:cross-package-test-inputs,check:spec-docblock-symbol-anchors,check:comment-mask-adoption,check:comment-mask-corpus,check:undeclared-dep-imports,check:query-options-erasure,check:spec-parsed-alias,check:objectql-double-limit,check:engine-double-contract,check:durability-log-level,check:published-readme-links,check:pm-prior-rulings,check:sourcemap-no-sources-content,check:strictness-ledger,check:skill-refs)check:type-check-debt,check:dual-build-cjs-loadsPREREQUISITE NOT MET); each needs a whole-workspace build this branch did not run. Neither a pass nor a finding. Declared to CI.What the generators actually moved: nothing
Measured rather than inferred, and this was the one prediction worth testing. Six generators were run against the built tree —
gen:api-surface,gen:export-origins,gen:spec-changes,gen:schema,gen:docs,gen:declaration-map— each exiting 0, after whichgit status --porcelainlisted no generated artefact.check:generatedindependently reports all 15 up to date. So an optional member on a published interface moves none of the four artefacts that nameIMetadataService, exactly as the claim predicted.check:api-surfacefirst reported stale withPREREQUISITE NOT MET — this gate reads built output, and what is on disk predates the sources. Thedisthad been built before a later edit to the test file, which is a build input. Rebuilding cleared it. It was never an artefact move.Acceptance notes
⛔ Noted, not filed, and deliberately not fixed here — each is outside this card's ruled four items.
loadManyKeyedahead of its own declared siblingloadMany?. Oncontent/docs/kernel/contracts/metadata-service.mdx,loadManyappeared 0 times before this change (lit control on the same page:loadDiagnosed= 5, so the zero is a reading). The page's interface excerpt is explicitly partial and says so — its line 29 points atIMetadataServicein the source for the full member list — so this is a documentation gap rather than a contradiction, but the ordering is odd for a reader and it is being handed to the seat to file as its own card. Widening this PR to also documentloadManywas declined on purpose.loadManyKeyedsubsection underCore CRUD, immediately afterload / loadDiagnosedand beforelist / listNames.Bulk Operationswas considered and rejected: despite the name, that section on this page documents bulk writes (bulkRegister/bulkUnregister), so a plural loader read filed there would sit in the write section. The chosen spot is the page's loader-read run, one step from the plural registry reads a reader would be comparing it against.loadDiagnosedexample still teaches the shape this PR's new example refuses. Atcontent/docs/kernel/contracts/metadata-service.mdx:138, two sections above the new probe-first example, the pre-existingloadDiagnosedsnippet spells an optional call plus?? {}— absence collapsing into a value, which is exactly what the new example'selsebranch says not to do ("Do NOT fall through to an empty set") and what the info Callout restates ("never as an empty set"). It is present at this PR's merge base and untouched here (?? []on this page at head: 0, git grep exit 1 captured before any pipe; lit control?? {}on the same page: 1, at:138, so the zero is a reading). The page is now internally inconsistent in style rather than wrong. ⛔ Recorded here rather than filed as a card, per the standing rule: the question "which PR will touch this file?" has an answer, and it is this one — so the note belongs where the next editor of the page will read it. Widening this PR to rewrite a snippet outside its four ruled items was declined on purpose.#16090serialisation caveat recorded in the ruling's item 3 is spent. That issue is closed, and no open pull request holds the page. Nothing was serialised against and nothing waited.Landing
⛔ Draft on purpose, and it stays that way from this branch. No flip to ready, no enqueue, no auto-merge. Landing is the owning seat's act after an at-tier contract review.
Generated by Claude Code