Repository navigation
services.storage is the only documented runtime accessor whose registry slot has a different name (file-storage), and the page never says so #9630
Copy link
Copy link
Closed
Description
Activity
Claiming this card.
- session:
session_01XqDQYVU5smx29ts9pAErja - branch:
claude/issue-9630-storage-slot-name(pushed, empty) - worktree:
objectstack-9630
Starting with the H1 measurement (what
services.storageactually denotes) before any edit, since it decides the shape of the fix.
Generated by Claude Code
- session:
- added a commit that references this issue
on 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):
surface verdict content/docs/kernel/runtime-services/the defect — fixed in #9681 kernel/contracts/storage-service.mdx:12already correct ( kernel.getService('file-storage'))Published READMEs ( service-storage:39,150,plugin-dev:52,70)already correct — no npm-shipped README carries the wrong key examples/,apps/no storage service resolution at all Runnable code calling getService('storage')none. Lone hit granted-permissions.test.ts:87asserts a denial — key is arbitrary there, test passes because it throws.claude/**,skills/**(governed)clean — zero mentions of any storage service key, nothing for a maintainer to review 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
- added a commit that references this issue
on Aug 18, 2026 - added 3 commits that reference this issue
on Aug 23, 2026
Found during the #9604 sweep (H3: check every
ctx.registerServicename against the runtime-services chapter). Reported rather than ridden along — #9604's dispatch scoped the diff to theservices.smsomission, and this one carries a judgment call.What was measured
Sweeping every production
registerServicecall onmainate8dba8a8a(test files, fixtures and mocks excluded) and matching it against the eight*-service.mdxpages incontent/docs/kernel/runtime-services/:services.datadatapackages/objectql/src/plugin.ts:312services.sharingsharingpackages/plugins/plugin-sharing/src/sharing-plugin.ts:494services.auditauditpackages/plugins/plugin-audit/src/audit-plugin.ts:117services.queuequeuepackages/services/service-queue/src/queue-service-plugin.ts:82services.emailemailpackages/plugins/plugin-email/src/email-plugin.ts:370services.smssmspackages/services/service-sms/src/sms-plugin.ts:181services.settingssettingspackages/services/service-settings/src/settings-service-plugin.ts:130services.storagefile-storagepackages/services/service-storage/src/storage-service-plugin.ts:237Seven of eight map one-to-one.
storageis the exception: there is noregisterService('storage', ...)anywhere in the tree (checked multiline-aware). The slot isfile-storage, and that spelling is canonical rather than incidental —packages/spec/src/system/core-services.zod.ts:27lists'file-storage'in theCoreServiceNameenum, andCORE_SERVICE_PROVIDERmaps 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 literalservices.*object is not injected by the open framework, and that plugin code resolves services throughctx.getService(...). It then sends the reader tostorage-service.mdx, whose entire surface is spelledservices.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 theservices.*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:
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.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.mdxnext to the canonical source, and consider extending the chapter's binding note to say that theservices.*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