Skip to content

feat(spec): storage becomes the canonical core-service slot; file-storage stays a deprecated v17 alias - #9820

Merged
os-elon merged 5 commits into
mainfrom
claude/issue-9683-storage-slot-rename
Aug 19, 2026
Merged

feat(spec): storage becomes the canonical core-service slot; file-storage stays a deprecated v17 alias#9820
os-elon merged 5 commits into
mainfrom
claude/issue-9683-storage-slot-rename

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#9683

Ruling implemented

Maintainer ruling, 2026-08-18, verbatim: 「9683 file-storage 可以叫 storage」 — recorded in issue comment 5334896426. storage becomes the canonical CoreServiceName slot; the resolution is a rename, superseding the card body's "not proposing a rename" framing and the #9630 dispatch-time no-rename note. file-storage was the only member of the enum whose spelling diverged from its documented accessor, with no recorded reason anywhere in the tree.

Falsifiable-premise measurement (verdict: NO structural conflict — premise valid)

The ruling required measuring whether the /api/v1/storage route namespace and ObjectStackClient.storage structurally conflict with a service slot named storage before renaming. Measured:

  • Slot names are pure registry keys.registerService(name: string, ...) / getService(name) key a plain Map in the kernel (packages/core/src/kernel.ts); nothing derives a route path or accessor from a slot name.
  • Route paths are literal constants. service-storage mounts /api/v1/storage from a hardcoded basePath default; metadata-protocol's SERVICE_CONFIG and the dispatcher's route ledger carry the path as literals.
  • ObjectStackClient.storage reads the storage ROUTE key from the discovery routes map (this.getRoute('storage')), which is produced by an explicit serviceToRouteKey table — a namespace separate from service slots. After the rename that table maps storage → storage; the coincidence of names is an identity across two explicit records, not a collision.
  • Discovery services is an open record (z.record(z.string(), ServiceInfoSchema)), so the key set is not closed against the new member.

One additional consumer was found and handled (see the discovery mirror below): objectui's console endpoint catalog reads the file-storage key off the discovery document.

What changed

packages/spec (minor):

  • CoreServiceName gains storage as the canonical member; file-storage stays an accepted member, JSDoc-marked as the deprecated v17 alias pointing at storage and the ruling. CORE_SERVICE_PROVIDER and ServiceRequirementDef carry both keys.
  • CoreServiceContracts gains storage: IStorageService; the file-storage entry is annotated as the deprecated alias — deliberately the same pattern as the existing http.server / http-server pair in the same file.
  • contracts/storage-service.ts header and api/discovery.zod.tsfiles-capability JSDoc updated.

Alias mechanism — registration-side, the smallest shim (@objectstack/service-storage): the plugin registers the same SwappableStorageService instance under both names and declares providesServices = ['storage', 'file-storage']. Pinned by an alias-equivalence test: getService('storage') and getService('file-storage') return the SAME instance. This follows the repo's own http.server/http-server precedent; no kernel/lookup changes, no consumer-side fallbacks.

Internal resolvers move to the canonical name: packages/runtime/src/http-dispatcher.ts (discovery resolution), packages/plugins/plugin-email/src/email-plugin.ts (attachment store), packages/cli/src/commands/migrate/files-to-references.ts.

Discovery reports storage — and mirrors the row verbatim under file-storage for v17. Both producers (dispatcher getDiscoveryInfo, metadata-protocol getDiscovery) key the canonical services.storage row and emit a byte-equal copy under the alias key. Reason: objectui's console endpoint catalog (useApiDiscovery.ts, shaped by objectui#4240) reads the file-storage services key; dropping the key would be a silent inside-a-major break of a shipped reader — the same class the ruling forbids for getService. Pinned in both producers' tests (filled and empty slot alike). The mirror retires with the alias at the next major (#9819 tracks the full footprint; #9819 remains open).

plugin-dev: the storage service toggle accepts both spellings (services: { storage: false } or the pre-rename 'file-storage': false); a test pins the alias-toggle compatibility.

Docs: kernel/runtime-services/storage-service.mdx now declares Registry slot storage with the alias section (the "Accessor name vs registry slot" exception section is gone — accessor and slot are the same word again); kernel/runtime-services/index.mdx, kernel/contracts/storage-service.mdx, kernel/services-checklist.mdx, api/plugin-endpoints.mdx, READMEs updated; content/docs/references/ pages regenerated via gen:docs (never hand-edited). scripts/check-runtime-services-index.mjs header prose updated to record the ruling (its old prose declared file-storage canonical and warned against exactly this rename; the gate's mechanics are unchanged and green — the storage page's declared slot is held to the real registrations, which now include both keys).

Changeset: spec minor (enum member addition + deprecation annotation — deliberately not major; the alias keeps v17 compatibility), patch for service-storage, runtime, metadata-protocol, cli, plugin-email, plugin-dev. ADR-0087: not-required (a registry slot name is not authorable metadata; compatibility is carried by the enum + dual registration, retirement at the next major).

Remaining 'file-storage' references (whole-tree grep, each deliberate)

  • Alias machinery + pins: enum member, CORE_SERVICE_PROVIDER / ServiceRequirementDef rows, contracts entry, dual registration, discovery mirror rows, dev-plugin toggle, and the tests pinning each.
  • Deprecation annotations pointing at storage and the ruling (spec, docs, READMEs, gate-script history notes).
  • Historical records, untouched by design: CHANGELOGs, docs/audits/2026-06-handwritten-docs-accuracy-followups.md, spec migration-registry entries (e.g. 17.storage-service-list-retired), retired-bridge narration in dispatcher-plugin.ts / http-dispatcher.test.ts comments, ADR-0076/0115 (governed surface).
  • Different namespaces, not the slot: the integration connector family prose ("SaaS / database / file-storage" in spec/integration, build-docs.ts, protocol/objectql docs) and the sys_email attachment help text plus its generated translation bundles (prose about the capability; churning them would dirty three hand-translated locales for zero contract value).

Verification (all at head 395e5c4, after merging origin/main)

  • Alias-equivalence pin (both spellings → same instance) and canonical-name pins at each moved resolver: green (service-storage 24 files / 362 tests).
  • Package suites, all green at head or on unchanged-by-merge packages: spec 413/10989, runtime 174/2599, metadata-protocol 123/1697 (+10 skipped), objectql 219/3879, plugin-email 26/418, plugin-dev 5/54, cli 134/1461.
  • Full consumer sweep: pnpm build --concurrency=2 over the whole workspace (71 packages, the downstream closure of spec) green at head.
  • Reverse verification of the rebuilt d.ts: a tsc probe in packages/runtime accepted CoreServiceName.enum.storage and the alias, and a @ts-expect-error on a bogus member was satisfied — proving consumers read the rebuilt declarations, not a stale dist.
  • pnpm --filter @objectstack/spec check:generated green after gen:docs regen; check:runtime-services-index (incl. the new runtime-services/versioning.mdx's stability matrix omits services.sms — a fourth enumeration of the chapter that #9634's gate does not hold #9684 stability-matrix claim) and its --self-test green; check:service-providers green (14 slots).
  • node scripts/pm/dispatch-gates.mjs re-derived on the actual diff; every named gate run and green at head, including check:i18n, check:type-check-debt --re-measure (ratchet exactly at its measurements), check:engine-double-contract, check:where-matcher, check:adr-0087-registration, check:changeset-no-major. One transient red was diagnosed as a stale pre-merge CLI dist (the i18n extractor banner), fixed by rebuilding — no bundle changes were committed.

Out of scope, filed unassigned


Generated by Claude Code

…rage stays a deprecated v17 alias (#9683)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw
…l storage slot name (#9683)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw
…TS2347 debt ledger (#9683)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016D9wdJR14KKCxz1WgdAzcw
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 8 package(s): @objectstack/cli, @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/plugin-dev, @objectstack/plugin-email, @objectstack/runtime, @objectstack/service-storage, @objectstack/spec, touching 34 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/wire-format.mdx(via /api/v1/storage (route))
  • content/docs/deployment/cli.mdx(via os migrate files-to-references (command))
  • content/docs/kernel/contracts/auth-service.mdx(via CoreServiceName (symbol))
  • content/docs/kernel/contracts/data-engine.mdx(via CoreServiceName (symbol))
  • content/docs/kernel/contracts/index.mdx(via CoreServiceName (symbol))
  • content/docs/kernel/runtime-services/storage-service.mdx(via CORE_SERVICE_PROVIDER (symbol), CoreServiceName (symbol), /api/v1/storage (route))
  • content/docs/kernel/services-checklist.mdx(via CORE_SERVICE_PROVIDER (symbol), CoreServiceName (symbol), DevPlugin (symbol), getDiscovery (symbol), providesServices (symbol), /api/v1/storage (route))
  • content/docs/plugins/anatomy.mdx(via providesServices (symbol))
  • content/docs/protocol/kernel/lifecycle.mdx(via providesServices (symbol))
  • content/docs/protocol/kernel/plugin-spec.mdx(via EmailService (symbol))
  • content/docs/protocol/objectql/types.mdx(via os migrate files-to-references (command))
  • content/docs/upgrading.mdx(via os migrate files-to-references (command))

3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx(via CoreServiceName (symbol))
  • content/docs/releases/v16.mdx(via WellKnownCapabilitiesSchema (symbol))
  • content/docs/releases/v17.mdx(via CoreServiceName (symbol), DevPlugin (symbol), DevPluginOptions (symbol), providesServices (symbol), os migrate files-to-references (command))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/metadata/ROADMAP.md, packages/plugins/plugin-dev/README.md, packages/services/service-storage/README.md, …) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e124e58f9525e278bba7583cd9a505ab5681d92cpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 6d5e5993a23384c7b5b5d85209c8b8a5f40a1acb — the merge of head 395e5c4a92d4eed5f652dde1ffac96e256e85f74 into base e124e58f9525e278bba7583cd9a505ab5681d92c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6d5e5993a23384c7b5b5d85209c8b8a5f40a1acb && git checkout 6d5e5993a23384c7b5b5d85209c8b8a5f40a1acb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e124e58f9525e278bba7583cd9a505ab5681d92c 395e5c4a92d4eed5f652dde1ffac96e256e85f74 && git checkout -B drift-repro e124e58f9525e278bba7583cd9a505ab5681d92c && git merge --no-ff 395e5c4a92d4eed5f652dde1ffac96e256e85f74
node scripts/docs-audit/affected-docs.mjs --json e124e58f9525e278bba7583cd9a505ab5681d92c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e124e58f9525e278bba7583cd9a505ab5681d92c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CoreServiceName's file-storage member is the only slot whose name diverges from its accessor, and no source anywhere states why

2 participants

@os-elon@claude