Repository navigation
Description
Found during the #9604 sweep (H3: check every ctx.registerService name against the runtime-services chapter). Reported rather than ridden along — #9604's dispatch scoped the diff to the services.sms omission, and this one carries a judgment call.
What was measured
Sweeping every production registerService call on main at e8dba8a8a (test files, fixtures and mocks excluded) and matching it against the eight *-service.mdx pages in content/docs/kernel/runtime-services/:
| documented accessor | registered slot | site |
|---|---|---|
services.data |
data |
packages/objectql/src/plugin.ts:312 |
services.sharing |
sharing |
packages/plugins/plugin-sharing/src/sharing-plugin.ts:494 |
services.audit |
audit |
packages/plugins/plugin-audit/src/audit-plugin.ts:117 |
services.queue |
queue |
packages/services/service-queue/src/queue-service-plugin.ts:82 |
services.email |
email |
packages/plugins/plugin-email/src/email-plugin.ts:370 |
services.sms |
sms |
packages/services/service-sms/src/sms-plugin.ts:181 |
services.settings |
settings |
packages/services/service-settings/src/settings-service-plugin.ts:130 |
services.storage |
file-storage |
packages/services/service-storage/src/storage-service-plugin.ts:237 |
Seven of eight map one-to-one. storage is the exception: there is no registerService('storage', ...) anywhere in the tree (checked multiline-aware). The slot is file-storage, and that spelling is canonical rather than incidental — packages/spec/src/system/core-services.zod.ts:27 lists 'file-storage' in the CoreServiceName enum, and CORE_SERVICE_PROVIDER maps it to @objectstack/service-storage. Every internal consumer uses it: packages/cli/src/commands/migrate/files-to-references.ts:197, packages/plugins/plugin-email/src/email-plugin.ts:223, packages/runtime/src/http-dispatcher.ts:1135.
Why it may be a defect rather than a legitimate difference
The chapter's own binding note (runtime-services/index.mdx:8-15) says a literal services.* object is not injected by the open framework, and that plugin code resolves services through ctx.getService(...). It then sends the reader to storage-service.mdx, whose entire surface is spelled services.storage.upload(...), services.storage.download(...), and so on.
A reader who follows both instructions together writes ctx.getService('storage') and gets nothing back. The name they need — file-storage — appears nowhere on the page. That trap exists only for this one accessor, which is what makes it worth a card: it is not a general property of the services.* naming, it is a single unannounced exception.
The judgment (why this is not in #9604's PR)
Two readings, and they call for different fixes:
- Legitimate. The managed runtime's
services.*binding is its own namespace and need not equal the registry slot. Then the docs are correct and the fix is a one-line note onstorage-service.mdx(and possibly the chapter's binding note) namingfile-storageas the slot forctx.getService. - Drift. The accessor was meant to match the slot. Then either the page should document
services.fileStorage/ the slot name, or the slot is misnamed — the second being a breaking change to a publishedCoreServiceNamemember, so almost certainly not the route.
The first reading looks much more likely, which makes this a small docs fix, but "which namespace is authoritative for services.*" is a spec question rather than a copy-editing one, so it is filed rather than guessed.
Suggested resolution
Add the slot name to storage-service.mdx next to the canonical source, and consider extending the chapter's binding note to say that the services.* accessor name and the registry slot name are separate namespaces which coincide for every accessor except this one. Cheap, and it removes the only unannounced exception in the table above.
Refs: #9604
Activity
os-steve commentedon Aug 18, 2026
Claiming this card.
session_01XqDQYVU5smx29ts9pAErjaclaude/issue-9630-storage-slot-name(pushed, empty)objectstack-9630Starting with the H1 measurement (what
services.storageactually denotes) before any edit, since it decides the shape of the fix.Generated by Claude Code
docs(kernel): name the registry slot on every runtime-services page, …
os-steve commentedon Aug 18, 2026
{ "issue": 9630, "status": "done", "branch": "claude/issue-9630-storage-slot-name", "pr": "https://github.com/objectstack-ai/objectstack/pull/9681", "premise_still_valid": true, "summary": "H1 VERDICT: (a) — services.storage is a chapter-wide documentation shorthand, not a real accessor. Evidence: zero registerService('storage') anywhere (multiline-aware, prod AND test); zero literal `services.storage` in any code file (docs + one historical CHANGELOG line only); no RuntimeServices/ServicesBinding type; the discovery document keys the service 'file-storage' (http-dispatcher.ts:1619); and a prior in-repo audit (docs/audits/2026-06-handwritten-docs-accuracy-followups.md) already recorded 'services.* is a chapter-wide docs abstraction'. IMPORTANT NUANCE that makes the trap worse: a real .storage accessor DOES exist — ObjectStackClient.storage (packages/client/src/index.ts:2839) — but it is the browser/HTTP client surface (upload(file,scope), getDownloadUrl(fileId)), disjoint in shape from the server-side IStorageService this page documents (upload(key,data,options), download(key):Buffer). So a reader searching for 'storage' finds a real but wrong-shaped surface; the page now separates the two. Ruling 1 respected: slot NOT renamed, NO alias added. Fix = every chapter page gains a '- **Registry slot:** <key>' bullet (matching the Service name: convention kernel/contracts/ pages already use), storage-service.mdx gains an 'Accessor name vs registry slot' section showing ctx.getService('storage') throws [Kernel] Service 'storage' not found while ctx.getService('file-storage') resolves, and the chapter binding note states the distinction chapter-wide. H4 DELIVERED: check-runtime-services-index.mjs gains a FIFTH check holding each declared slot to a production registerService call — additive, the four existing enumerations untouched.", "tests": "All at final commit 6cf12acaf (union re-run on that exact tree; local HEAD == remote HEAD == 6cf12acaf). Gate family derived from actual changed paths via `node scripts/pm/dispatch-gates.mjs`, not recalled; every run echoed its script name (no zero-match silent pass). GREEN: check:nul-bytes (75 self-test assertions; 6185 files scanned) · check:docs-audit-scope (242+22 self-test cases; 179 hand-written docs in sync) · check:docs-redirects (48 assertions) · check:role-word · check:runtime-services-index · spec check:empty-state · check:liveness · check:strictness-ledger · check:variant-docs. Extended gate: self-test 11 -> 17 assertions; real run now reports '8 chapter page(s) ... and 8 declared registry slot(s) vs 54 registered key(s)'. REVERSE VERIFICATION — 3 ablations, direction predicted RED and observed RED each time, run from the committed state: (1) drop storage's slot bullet => 'no `- **Registry slot:** <key>` bullet -- state the key ctx.getService() resolves this surface by'; (2) declare the wrong slot 'storage' (the defect itself) => 'declares registry slot `storage`, but no production registerService() call under packages/ registers that key -- did you mean `file-storage`?'; (3) narrow the sweep regex from \\s* to [ \\t]* (line-at-a-time) => `audit` reports a FALSE absence ON THE REAL TREE, because plugin-audit's registration at audit-plugin.ts:117 puts its key on the line after the '(' — proving the multiline handling is load-bearing on real code, not decorative; the self-test's split-line fixture went red in the same run. Gate restored byte-identical (git status clean, regex line matches original). MDX compiles (fumadocs-mdx). No changeset: docs + one CI gate script, nothing published changes — `skip-changeset` label applied as the UNION with the bot's `documentation`/`size/m` and READ BACK (all three present).", "open_questions": [], "out_of_scope_findings": [ "filed as #9683: CoreServiceName's `file-storage` member is the only slot whose name diverges from its accessor, and no source anywhere states why (H2 result — searched the registration site, the enum comment, ADRs 0076/0115, all changesets, and the contract file; the contract file records the alignment but not the reason). Explicitly does NOT propose renaming the slot.", "filed as #9684: runtime-services/versioning.mdx's stability matrix omits `services.sms` — 7 rows for 8 pages. A FOURTH enumeration of the chapter that #9634's gate does not hold; same class as #9604, one location over. Suggests extending the gate to that matrix and to each row's label vs the page's own Stability bullet." ] }H3 sweep table (the defect is confined to one chapter — nothing ships wrong to npm):
content/docs/kernel/runtime-services/kernel/contracts/storage-service.mdx:12kernel.getService('file-storage'))service-storage:39,150,plugin-dev:52,70)examples/,apps/getService('storage')granted-permissions.test.ts:87asserts a denial — key is arbitrary there, test passes because it throws.claude/**,skills/**(governed)Card's 8-row table re-verified on current
main(c07d6e8b9, all line numbers still exact): dataobjectql/src/plugin.ts:312· sharing:494· audit:117· queue:82· email:370· sms:181· settings:130· storage →file-storage:237. Seven map 1:1; storage is the lone exception, as filed.Generated by Claude Code
Generated by Claude Code
docs(kernel): list services.sms in the runtime-services stability mat…
docs(kernel): name the registry slot on every runtime-services page, …
fix(spec): correct false CoreServiceName JSDoc claims in contracts/*.…
docs(kernel): list services.sms in the runtime-services stability mat…