Skip to content

refactor(cli): single-source the @objectstack/organizations spelling onto one roster-pinned declaration (#11614) - #12149

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-11614-serve-organizations-pkg-roster
Aug 25, 2026
Merged

refactor(cli): single-source the @objectstack/organizations spelling onto one roster-pinned declaration (#11614)#12149
os-trump merged 2 commits into
mainfrom
claude/issue-11614-serve-organizations-pkg-roster

Conversation

@os-trump

@os-trumpos-trump commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11614

PLATFORM_PLUGIN_WIRED_RUNTIMES (packages/spec) is the provenance roster for plugins[]-wired out-of-repo runtimes, keyed by npm package name. It exists because a fabricated@objectstack/framework sat next to the real @objectstack/organizations in published docs for months, indistinguishable by inspection (#10921). serve is the only runtime that prints one of those names AT OPERATORS — and it spelled the name as a bare literal at the resolution site, under no pin at all.

Body note: two spellings in an earlier revision of this description used angle brackets and were eaten by GitHub's body sanitizer, leaving const X = ''. Rewritten without them.

What changed

packages/cli/src/commands/serve.tsNew Serve.ORGANIZATIONS_RUNTIME_PKG, declared once beside ALWAYS_ON_CAPABILITIES. Both resolution-path uses read it: importFromHost(organizationsPkg) and the readHostDeclaration(...) that picks which of the two absence remedies to print.
packages/cli/test/serve-capability-vocabulary.test.tsThe drift pin, added to the existing #11263 describe block: the value serve resolves is a roster key, and its row is the enterprise edition.
packages/cli/src/commands/serve-cluster-host-resolution.test.tsIts specifier resolver widened one hop — see “The pin that actually bit”.
.changeset/serve-organizations-pkg-roster-key.md@objectstack/cli: patch.

Load semantics are untouched. The roster is not consulted at boot; no posture, no stage classification, no OS_ALLOW_DEGRADED_TENANCY behaviour and no operator-facing byte moves. The declared value is byte-identical to the literal it replaces.

No new import in either file.serve.ts already imports @objectstack/spec/kernel (line 26), and the pin lives in a test that already imports the roster. Zero new cross-package boundaries were crossed, so no ratchet entry of any kind was added.

Premise, re-measured on this branch (not reconciled to the card)

At origin/main2ba4329 the two lines the card names measured at 2797 and 2810, the same offsets the dispatch recorded at a933ed7 — re-derived here, not copied. The literal was still the only spelling, and serve.ts held eight further occurrences of the same package name.

The pin that actually bit

The dispatch flagged the #4409 import ratchet in packages/lint as the hazard. Measured, and it does not apply here: LINT_IMPORT_RATCHET's scan population is exactly COMMAND_FILES = {validate.ts, compile.ts, lint.ts}, and lintImportsIn() matches only the specifier '@objectstack/lint'. serve.ts is in neither set. (The PM has withdrawn that warning on the card.)

The gate that did fail was serve-cluster-host-resolution.test.ts — a source scan over serve.ts that classifies every dynamic load as app-declarable or not, so a package the CLI does not declare can never be reached by a bare import() (cloud#1013, #10645). Its resolveIdentifier() resolved exactly one shape: a const bound directly to a string literal. Moving the spelling onto a class static made the organizations specifier unresolvable, and an unresolved specifier drops out of APP_DECLARABLE_LOADS rather than into it — so the load stopped being swept, silently:

AssertionError: the sweep no longer sees the @objectstack/organizations load
AssertionError: no load site found for @objectstack/organizations: expected 0 to be greater than 0

Both failures came from that file's own named vacuity guard. Its count-only floor (more than 20 resolved packages) absorbed the loss without a word; only the four named packages caught it. That is the guard working exactly as its comment says it should.

The repair is in the detector, not in a declaration — the discipline this repo states for the cross-package-input scanner, applied here: resolveIdentifier() now resolves one further hop, const X = Serve.MEMBER onto that class's static readonly MEMBER, a string literal in the same file. This strictly widens what the sweep judges; it can never excuse a load. No allowlist entry, no ratchet entry, no baseline touched.

Anti-vacuity: the pin shown failing

Three legs, each confirming the mutation on disk with anchored greps in both directions before reading any result. No rebuild is involved or needed: the pin imports ../src/commands/serve.js by relative path, so it reads source, not a package export.

legon-disk checkresult
1 — fix in placeTest Files 1 passed, Tests 13 passed (13)
2 — declaration mutated to @objectstack/organisationsinjected 1, removed 0Tests 1 failed | 12 passed — only the new pin
3 — restoredrestored 1, mutant 0Tests 13 passed (13)
FAIL test/serve-capability-vocabulary.test.ts > … > the package name serve resolves IS a roster key (#11614)
AssertionError: serve loads '@objectstack/organisations', which PLATFORM_PLUGIN_WIRED_RUNTIMES does not declare.
expected [ '@objectstack/organizations', …(1) ] to include '@objectstack/organisations'

The 12 that stayed green through leg 2 include the pre-existing roster test that reads the literal spelled in the test file — which is precisely why it could not catch this class, and why the new pin reads Serve.ORGANIZATIONS_RUNTIME_PKG instead.

A measurement note, kept rather than dropped: the first attempt at leg 2 wrote the mutation with perl -i -pe 's/…/…/', and the / in @objectstack/organizations ate the s/// delimiter. The file was never modified. The on-disk confirmation refused the leg (MUTATION NOT CONFIRMED ON DISK — this ablation did not run) instead of running a no-op ablation and reporting its green as evidence. Recorded because a silent zero-hit edit tool is the failure that check exists for, not because the swap was clumsy.

Verification

All of the below at 3ac21cf, the final commit; the working tree was clean and byte-identical at every run.

Green (each verdict read from the gate's own output, never a piped $?):

pnpm lintfull-repo, not narrowed (eslint . --no-inline-config) exit 0 · @objectstack/cli full suite 178 files / 2023 tests passed · @objectstack/cli typecheck (tsc --noEmit) exit 0 · check:cross-package-test-inputs (“OK: 16 package(s) read outside themselves, all declared”) · check:test-source-alias · check:type-source-resolution · check:published-files · check:route-envelope · check:slot-lookup (“ratchet holds … none new”) · check:type-check-coverage (“OK — 65/78 …”) · check:i18n · check:cli-test-child-env · check-plugin-teardown-shape · check-ci-filter-parity · check:changeset-gate-self-tests · check-adr-0087-registration · check-changeset-no-major · check-empty-changeset · check:objectui-changeset · release-rehearsal-clone --self-test · check:engine-double-contract · check:where-matcher · check:query-options-erasure · docs-audit/check-affected-docs · check-nul-bytes.

Two gates could NOT be measured here — reported as not-measured, never as green. Both refuse loudly on an unbuilt worktree and both need a full workspace build, which is CI's run:

  • check:i18n-coverageCOULD NOT MEASURE: examples/app-showcase/objectstack.config.ts will not load while a workspace connector has no dist/ (connector-mcp, then connector-openapi after that one was built). It states plainly that it compared nothing.
  • check:type-check-debt — refuses --re-measure outright: @objectstack/service-knowledge has no built type entry point, and measuring from there “would silently measure a DIFFERENT WORLD”.

For the second, the number it guards was measured directly: tsc over packages/cli with include: ["src", "test"] reports 146 errors — exactly the frozen TEST_DEBT['@objectstack/cli'] value — and zero in any of the three files this PR edits. So the ledgered count is unmoved by this diff. The exact match is evidence the recipe lined up; it is not a substitute for the gate, which CI runs with the closure built.

Declared residual — not swept

serve.ts still spells @objectstack/organizations in operator-facing message prose (the install remedy, the fatal refusal, the degraded warning) and in three comments. Those are display text, not the resolution path: drift there is visible to whoever reads the message and cannot change which package boots. They were left alone deliberately rather than interpolated, because editing a fatal-path message for a provable no-op is churn on the one text an operator reads at 3am. Filed separately, unlabelled, as #12151.


Generated by Claude Code

…onto one roster-pinned declaration
serve is the only runtime that prints this package name at operators, and it
spelled the name as a bare literal at the resolution site, under no pin. The
spec-owned provenance roster PLATFORM_PLUGIN_WIRED_RUNTIMES is the single
machine-readable answer to whether an out-of-repo @objectstack/* package is
real and where it ships from, but nothing held serve's literal to it.
The name is now declared once as Serve.ORGANIZATIONS_RUNTIME_PKG and read by
both resolution-path uses (importFromHost and readHostDeclaration). A pin in
serve-capability-vocabulary.test.ts asserts that value is a roster KEY whose
row is the enterprise edition. Load semantics are untouched: the roster is not
consulted at boot and no posture, stage classification or degraded-tenancy
behaviour moves.
serve-cluster-host-resolution.test.ts's specifier resolver is widened one hop
to see `const X = Serve.MEMBER`. Without it the organizations load stopped
resolving and dropped OUT of the swept population silently — caught by the
named half of that file's vacuity guard, which is why it names packages
instead of only counting them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 2 documentable anchor(s).

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 2c4c59ed20caf4f6cde3c979ae9205ee69e04256.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 23 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 2c4c59ed20caf4f6cde3c979ae9205ee69e04256packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5596104d3c6710290bada73a83f8156a02b7d608 — the merge of head 3a1d003e614bdccd4b544f5616a503699f33ca5f into base 2c4c59ed20caf4f6cde3c979ae9205ee69e04256, 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 5596104d3c6710290bada73a83f8156a02b7d608 && git checkout 5596104d3c6710290bada73a83f8156a02b7d608
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2c4c59ed20caf4f6cde3c979ae9205ee69e04256 3a1d003e614bdccd4b544f5616a503699f33ca5f && git checkout -B drift-repro 2c4c59ed20caf4f6cde3c979ae9205ee69e04256 && git merge --no-ff 3a1d003e614bdccd4b544f5616a503699f33ca5f
node scripts/docs-audit/affected-docs.mjs --json 2c4c59ed20caf4f6cde3c979ae9205ee69e04256

⚠️ 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 2c4c59ed20caf4f6cde3c979ae9205ee69e04256 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-trump
os-trump added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit f86839dAug 25, 2026
33 checks passed
@os-trump
os-trump deleted the claude/issue-11614-serve-organizations-pkg-roster branch August 25, 2026 15:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] serve.ts 的 organizationsPkg 字面量尚未消费 PLATFORM_PLUGIN_WIRED_RUNTIMES 的 key — roster 落地后的一行闭环

2 participants

@os-trump@claude