Uh oh!
There was an error while loading. Please reload this page.
refactor(cli): single-source the @objectstack/organizations spelling onto one roster-pinned declaration (#11614) - #12149
Conversation
…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
📓 Docs Drift CheckThis PR changes 1 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11614
PLATFORM_PLUGIN_WIRED_RUNTIMES(packages/spec) is the provenance roster forplugins[]-wired out-of-repo runtimes, keyed by npm package name. It exists because a fabricated@objectstack/frameworksat next to the real@objectstack/organizationsin published docs for months, indistinguishable by inspection (#10921).serveis 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.What changed
packages/cli/src/commands/serve.tsServe.ORGANIZATIONS_RUNTIME_PKG, declared once besideALWAYS_ON_CAPABILITIES. Both resolution-path uses read it:importFromHost(organizationsPkg)and thereadHostDeclaration(...)that picks which of the two absence remedies to print.packages/cli/test/serve-capability-vocabulary.test.ts#11263describe block: the valueserveresolves is a roster key, and its row is theenterpriseedition.packages/cli/src/commands/serve-cluster-host-resolution.test.ts.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_TENANCYbehaviour and no operator-facing byte moves. The declared value is byte-identical to the literal it replaces.No new import in either file.
serve.tsalready 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/main2ba4329the two lines the card names measured at 2797 and 2810, the same offsets the dispatch recorded ata933ed7— re-derived here, not copied. The literal was still the only spelling, andserve.tsheld eight further occurrences of the same package name.The pin that actually bit
The dispatch flagged the #4409 import ratchet in
packages/lintas the hazard. Measured, and it does not apply here:LINT_IMPORT_RATCHET's scan population is exactlyCOMMAND_FILES={validate.ts, compile.ts, lint.ts}, andlintImportsIn()matches only the specifier'@objectstack/lint'.serve.tsis 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 overserve.tsthat classifies every dynamic load as app-declarable or not, so a package the CLI does not declare can never be reached by a bareimport()(cloud#1013, #10645). ItsresolveIdentifier()resolved exactly one shape: aconstbound directly to a string literal. Moving the spelling onto a class static made the organizations specifier unresolvable, and an unresolved specifier drops out ofAPP_DECLARABLE_LOADSrather than into it — so the load stopped being swept, silently: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.MEMBERonto that class'sstatic 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.jsby relative path, so it reads source, not a package export.Test Files 1 passed,Tests 13 passed (13)@objectstack/organisations1, removed0Tests 1 failed | 12 passed— only the new pin1, mutant0Tests 13 passed (13)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_PKGinstead.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/organizationsate thes///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/clifull 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-coverage—COULD NOT MEASURE:examples/app-showcase/objectstack.config.tswill not load while a workspace connector has nodist/(connector-mcp, thenconnector-openapiafter that one was built). It states plainly that it compared nothing.check:type-check-debt— refuses--re-measureoutright:@objectstack/service-knowledgehas 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:
tscoverpackages/cliwithinclude: ["src", "test"]reports 146 errors — exactly the frozenTEST_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.tsstill spells@objectstack/organizationsin 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