Uh oh!
There was an error while loading. Please reload this page.
docs(kernel): name the registry slot on every runtime-services page, and gate it - #9681
Conversation
…and gate it (#9630) `services.storage` was the only documented runtime accessor whose registry slot has a different name (`file-storage`), and the page never said so. The chapter's binding note tells the reader that no literal `services.*` object is injected and that plugin code resolves through `ctx.getService(...)`; `storage-service.mdx` then spelled its whole surface `services.storage.upload(...)`. Following both together produces `ctx.getService('storage')`, which throws `[Kernel] Service 'storage' not found` — the name they need appears nowhere on the page. Seven of the eight pages were fine only because their accessor and their slot happen to be the same word. The slot is not what is wrong. `file-storage` is canonical: it is the `CoreServiceName` member, `CORE_SERVICE_PROVIDER` maps it to `@objectstack/service-storage`, and the CLI, plugin-email and the HTTP dispatcher all resolve it. So this names the real key on the page rather than renaming the slot or adding a `registerService('storage', ...)` alias. - every chapter page gains a `- **Registry slot:** <key>` bullet, matching the `Service name:` convention the `kernel/contracts/` pages already use - `storage-service.mdx` gains an "Accessor name vs registry slot" section, and a warning separating the server-side `IStorageService` from the client SDK's differently-shaped `ObjectStackClient.storage` - the chapter binding note states the accessor/slot distinction chapter-wide check-runtime-services-index gains a fifth check so the class cannot recur: the declared slot must be a key some production `registerService` call really registers. Its four existing enumerations are untouched — they read page existence and order, never a line of `packages/`, which is why this shipped green. The sweep is multiline-aware because `plugin-audit` puts its key on the line after the `(`; the self-test pins that shape, and narrowing the sweep to one line makes `audit` report a false absence on the real tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
✅ PM ACCEPT — #9630 / PR #9681Verified independently: 10 files H1 = (a), established rather than assumed — and you found the prior artZero ⭐ The nuance you added makes the trap materially worse than the card described
Separating the two on the page is the fix the card could not have specified, because the card did not know the second surface existed. This is what H1 was for. Ruling 1 respected under real temptationSlot not renamed, no The fix shape is right too: every chapter page gains a ⭐ H4 delivered, additively — and ablation 3 is the best measurement in this reportThe gate gains a fifth check holding each declared slot to a production Ablation 3 is the one I want on the record:
I warned you about that trap because I fell into it on this repo myself, and a single-line grep returning a false zero is exactly how this class of bug survives. You did not just avoid it — you turned it into a permanent regression test that fires on the real tree, so the next person who "simplifies" that regex gets caught by production code rather than only by a fixture. A fixture proves the code handles the case; the real tree proves the case exists. All three ablations predicted RED and observed RED, gate restored byte-identical. #9683 — H2 answered, and the answer is "nobody wrote it down"
#9684 — a fourth enumeration, one location over
Also correct: no changeset (docs plus one CI gate script, nothing published changes), Verdict: ACCEPT. Arming once the two running gates converge. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9630
H1 verdict: (a) —
services.storageis a chapter-wide documentation shorthandMeasured before deciding the shape, because (a) and (b) call for different fixes.
registerService('storage', ...)exists — multiline-aware sweep overpackages/,apps/,examples/, production and test files: zero. The same sweep foundplugin-audit's two-line registration ataudit-plugin.ts:117, which is the exact shape a line-at-a-time grep misses — so the zero is a real absence, measured with a tool proven to catch the trap.services.storagein any code file — only docs, plus one historicalCHANGELOG.mdline. NoRuntimeServices/ServicesBindingtype exists.'file-storage'(http-dispatcher.ts:1619), perCoreServiceName.docs/audits/2026-06-handwritten-docs-accuracy-followups.md: "services. is a chapter-wide docs abstraction ... flag for follow-up only if the whole chapter ever reconciles docs notation with real registration keys."* This PR is that reconciliation.So the shorthand is fine for the other seven, where shorthand and slot coincide; it is broken only for storage, and the fix is to name the real slot on the page. The chapter-wide convention stays.
One nuance that makes the trap worse, not better: a real
.storageaccessor does exist —ObjectStackClient.storage(packages/client/src/index.ts:2839). It is not this page's surface: it is the browser/HTTP client (upload(file, scope),getDownloadUrl(fileId)), disjoint in shape from the server-sideIStorageServicethis page documents (upload(key, data, options),download(key): Buffer). A reader searching the tree forstoragefinds a real but wrong-shaped surface, so the page now separates the two explicitly.H2: the divergence is stated in one place, but never explained
packages/spec/src/contracts/storage-service.ts:13says "Aligned with CoreServiceName 'file-storage'", andkernel/contracts/storage-service.mdx:12already names the slot correctly. But no stated reason forfile-storageoverstorageexists anywhere: not at the registration site, not in theCoreServiceNameenum comment (// Storage Driver (Local/S3)), not in an ADR, not in a changeset. Filed as a finding rather than invented — see the report.H3 sweep: the defect is confined to this one chapter
content/docs/kernel/runtime-services/kernel/contracts/storage-service.mdxkernel.getService('file-storage'))service-storage,plugin-dev)examples/,apps/getService('storage')granted-permissions.test.ts:87, asserts a denial — the key is arbitrary there and the test passes because it throws.claude/**,skills/**(governed)What changed
Ruling 1 respected: the slot is not renamed and no
registerService('storage', ...)alias is added.file-storageis canonical —CoreServiceName,CORE_SERVICE_PROVIDER, and three internal consumers — so the page is what gets corrected.- **Registry slot:** ...bullet, matching theService name:convention thekernel/contracts/pages already use.storage-service.mdxgains an Accessor name vs registry slot section showing thatctx.getService('storage')throws andctx.getService('file-storage')is the real key, plus the client-SDK warning above.H4: the gate extension — done, because it measured as contained
Confirmed first that #9634's gate does not catch this class: its four enumerations read page existence and order, never a line of
packages/. That is exactly why this shipped green.Extending it was contained because the primitives already existed: the recursive
packages/source walk is lifted fromscripts/check-init-service-contract.mjs:92-107, and the gate already had a--self-testharness with exported pure functions. A fifth check now holds each page's declared slot to a productionregisterService/registerServiceFactorycall.The existing four checks are untouched — this is additive, per ruling 3. Deliberately not required: that the slot equal the accessor. The rule is only "say which key you mean, and be right", so a legitimate divergence like
file-storagepasses while a fictional one fails.Reverse-verification, all three predicted red and observed red:
no Registry slot bullet -- state the key ctx.getService() resolves this surface bystorage) — the defect itselfdeclares registry slot storage, but no production registerService() call ... -- did you mean file-storage?auditreports a false absence on the real tree — the multiline handling is load-bearing on real code, not decorativeSelf-test grew 11 → 17 assertions, including a fixture whose registration key sits on the line after the
(.Verification
Full local gate union, re-run on final commit
6cf12acaf, all green:check:nul-bytes·check:docs-audit-scope·check:docs-redirects·check:role-word·check:runtime-services-index· speccheck:empty-state·check:liveness·check:strictness-ledger·check:variant-docsGate family derived from the actual changed paths via
node scripts/pm/dispatch-gates.mjs, not recalled. MDX compiles (fumadocs-mdx).No changeset: docs and a CI gate script only — nothing published changes.
Generated by Claude Code
Generated by Claude Code