Skip to content

chore(cli): pin the multi-org runtime name os doctor prints against the spec roster - #12496

Merged
os-litant merged 3 commits into
mainfrom
claude/issue-12464-doctor-organizations-pkg
Aug 26, 2026
Merged

chore(cli): pin the multi-org runtime name os doctor prints against the spec roster#12496
os-litant merged 3 commits into
mainfrom
claude/issue-12464-doctor-organizations-pkg

Conversation

@os-litant

@os-litantos-litant commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Fixes#12464

Implements the PM's ruling B (issue comment 5421042263), taken after the card's own named premise was measured false: the spec roster cannot supply the package name, so "read it from the roster" was not available.

⚠️ What this does NOT do

This does not single-source the spelling, and the card's title is not achieved. The literal is still declared three times — the PLATFORM_PLUGIN_WIRED_RUNTIMES roster key, Serve.ORGANIZATIONS_RUNTIME_PKG, and now doctor.ts's own const. Saying otherwise would misrepresent the change.

The defect being closed is the silent every-gate-green drift, not the duplication as such — which is what the card's own Class section describes: a roster-key rename would leave os doctor naming a package that boot no longer resolves, with every gate green, because the roster pin only ever sees the declaration and nothing read this hint table's text. A duplicate that can drift silently and one that cannot are different things; this makes it the second kind.

Why the name is not read from the roster

Measured on packages/spec/src/kernel/platform-capabilities.ts, and written into the const's docblock so the next reader inherits the measurement rather than the argument:

  • PlatformPluginWiredRuntime has exactly two fields, edition and note. There is no package field, and its docblock records the omission as deliberate: "here the package name is the KEY, so it cannot be null and cannot drift from a duplicate field".
  • The roster is keyed by a plain string, not a literal union, so Object.keys() yields no typed handle.
  • Both rows are edition: 'enterprise' — nothing machine-readable selects this one.
  • The roster's own header settles it: "What this roster deliberately is NOT: a resolution registry … the rows record that fact as prose provenance, they do not encode it as a lookup."

The roster validates a name you already hold; it does not hand you one. Importing Serve.ORGANIZATIONS_RUNTIME_PKG into a diagnostic command was ruled out separately: a diagnostic command depending on a serve command's export in order to spell a package name is a worse coupling than the duplication it removes.

The two legs

(ii) is the load-bearing leg. Leg (i) alone pins doctor against itself: under a roster rename the hint and its expectation move together, so it stays green forever. That is not an argument — it is measured below.

Reverse verification (directions declared before running)

Two mutations, each confirmed on disk before the run and restored with git checkout HEAD -- naming an absolute path, under a trap, with restoration proven by blob-hash comparison (git diff HEAD empty, final blob == HEAD blob dceaad4cc):

mutationleg (i)leg (ii)matches prediction
M1 — rename the const value (simulates a roster-key rename)PASSFAILyes
M2 — re-spell the literal inline, const left intactFAILPASSyes

M1 is the decisive one: leg (i) stayed green exactly as predicted, and only leg (ii) caught it. Without leg (ii) this file would be decorative against the very rename the card is about. M2 shows leg (i) catches an inline re-spelling that leg (ii) cannot see. Neither leg is redundant.

No rebuild is involved in either leg: the mutation target is same-package source reached by a relative specifier (./doctor.js), and the roster is read from @objectstack/spec/kernel, which is untouched.

Verification

All gates below ran on 841494f3f, the final commit. Exit codes were captured before any pipe; each result is the gate's own printed verdict line.

checkresult
pnpm --filter @objectstack/cli exec vitest run (4 files: the new pin, doctor-tenancy-posture-report, doctor-env-provenance, serve-capability-vocabulary)Test Files 4 passed (4) · Tests 53 passed (53)
pnpm --filter @objectstack/cli typecheckEXIT=0
pnpm check:nul-bytesOK (scanned 6906 text file(s) … no raw ASCII control bytes)
pnpm check:test-source-aliasOK — 72 packages with tests scanned
pnpm check:cross-package-test-inputsOK: 20 package(s) read outside themselves, all declared
pnpm check:changeset-gate-self-testsEXIT=0
node scripts/check-empty-changeset.mjs✓ No empty-frontmatter changeset (1 declaring changeset added)
node scripts/check-changeset-no-major.mjs✓ This diff introduces no major bump
pnpm check:type-source-resolutionOK — 93 tsc program(s) across 77 packages
pnpm check:published-files✓ 69 publishable package(s) …

Gate families were derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack on the merged tree (no staleness warning), which reported the change set as exactly the three files in this PR.

Not a vacuous typecheck:tsc --noEmit --listFiles confirms both edited files are in the CLI's typecheck program (the new test file and doctor.ts each present), so the green covers this diff rather than excluding it.

ESLint — narrowed, and the narrowing is measured: repo-wide pnpm lint is CI's run. Locally the two changed TS files linted clean under eslint --no-inline-config (2 files, 0 errors, 0 warnings, count read from --format json; the population read from eslint's own --print-config, not guessed). The narrowing excludes nothing because this repo runs one eslint.config.mjs which, in its own words, "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not" — so this diff cannot move any untouched file's verdict.

No behaviour change. The declared value is byte-identical to the literal it replaces; the rendered bullet is unchanged.

Deletion condition

The const carries its own expiry. The whole TENANCY_POSTURE_FIX_HINTS table is duplicated between doctor.ts and serve.tssingle and group byte-identical too, and those two touch no roster, so nothing could ever notice them drift. A shared hint table would single-source all three sentences and the package name at once; that work is tracked at #12492, which stays open and is not addressed here. This PR is a step toward it, not the end state.

serve.ts was not touched — it is off-limits under the card's file surface.


Generated by Claude Code

`doctor.ts`'s `TENANCY_POSTURE_FIX_HINTS` repeated serve's `isolated` posture
sentence verbatim with its own bare `@objectstack/organizations` literal, under
no check at all — so a roster-key rename would leave `os doctor` naming a
package boot no longer resolves, with every gate green.
The hint now interpolates a module-level `ORGANIZATIONS_RUNTIME_PKG`, pinned on
two legs: the RENDERED bullet compared whitespace-included against text built
from that declaration, and a roster leg asserting the declaration is a key of
the spec-owned `PLATFORM_PLUGIN_WIRED_RUNTIMES`. The roster leg is the
load-bearing one — without it the hint and its expectation move together under a
rename and nothing goes red.
This does not single-source the spelling and the const's docblock says so at the
site: the literal is still declared three times, the roster cannot supply the
name (keyed BY package name, no `package` field by design, "not a resolution
registry"), and importing serve's export into a diagnostic command would be a
worse coupling. What changes is that this copy can no longer drift in silence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
@github-actions

github-actionsBot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/data-modeling/indexing.mdx(via os doctor (command))
  • content/docs/deployment/cli.mdx(via os doctor (command))
  • content/docs/protocol/backward-compatibility.mdx(via os doctor (command))
  • content/docs/protocol/kernel/config-resolution.mdx(via os doctor (command))
  • content/docs/protocol/kernel/http-protocol.mdx(via os doctor (command))

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

  • content/docs/releases/v16.mdx(via os doctor (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
  • 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 ce6d4e9abfeac3298f06dcc8c42003289db26c22packageMentionDocs.

Which tree this was computed on

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

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

The new `ORGANIZATIONS_RUNTIME_PKG` const and its docblock were inserted
BETWEEN the `/** One-line descriptions of the accepted postures … */` block and
`TENANCY_POSTURE_FIX_HINTS`, the declaration that block documents. Nearest
preceding block wins, so the table was left with no docblock at all and a
paragraph about posture descriptions introduced a package-name constant.
That is precisely the defect class this branch exists to close — a comment
describing something other than what it sits on, invisible to every gate — so
it does not get to ship inside the fix for it.
Pure relocation: the const and its docblock now sit immediately after the
Tenancy Posture banner, ahead of the posture block, which is adjacent to its
table again. No wording changed on either block, and the file's sorted line
multiset is identical to the previous commit's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
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

2 participants

@os-litant@claude