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 noregisterService('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 on storage-service.mdx (and possibly the chapter's binding note) naming file-storage as the slot for ctx.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 published CoreServiceName member, 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
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