docs(spec): stop restating a plugin-local union's members, and name the mis-aimed nav-group case - #16958
Conversation
…he mis-aimed nav-group case `AutomationResult.status`'s docblock carried a copy of plugin-approvals' `StrandedRunState` members (`'missing' | 'failed'`). That union has since split `'failed'` three ways, so the copy contradicted the type. Removed rather than refreshed: the union is plugin-local and the spec pin test excludes it on purpose, so a member list here is unpinned prose. `NavigationContributionSchema`'s JSDoc documented only the omitted-`group` case while the `.describe()` below it already named the mis-aimed one; the JSDoc now uses the same spelling. The `app-multi-package` fixture comment credited only `os build` for a finding both `os build` and `os validate` report. Card relationships are declared in the PR body, not here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 131 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 5cd90e3bef75799ca693c5c8f7044897e4f30dfc && git checkout 5cd90e3bef75799ca693c5c8f7044897e4f30dfc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 97adce2faa9d27d7811f1f299f5ed806a467f624 c0143fd839dbf3f92cc39c805b0ce4e328313bf9 && git checkout -B drift-repro 97adce2faa9d27d7811f1f299f5ed806a467f624 && git merge --no-ff c0143fd839dbf3f92cc39c805b0ce4e328313bf9
node scripts/docs-audit/affected-docs.mjs --json 97adce2faa9d27d7811f1f299f5ed806a467f624
|
Fixes #16708
Fixes #16507
Clause-②: no
Both defects are a source comment that states a count or a scope the tree contradicts.
Rewriting them moves no accept set, adds no export, changes no type and puts no new key on
any payload. The whole diff is comment text plus one changeset. 拉回已声明契约 ⇒ 常规档.
Two cards, one family: a comment that stated a count (#16708) or a command scope
(#16507) the tree had already contradicted. One fix method in both — rewrite the sentence to
what the tree does, code untouched.
#16708 — the member list is removed, not refreshed
Re-derived from the type itself, not from the card's count
(
packages/plugins/plugin-approvals/src/approval-service.ts:516, the tree's only declarationof it):
Five members — the card's number is correct. The spec docblock named two.
The copy is deleted rather than corrected, and that is a measurement, not a preference. A
two-leg ablation from the committed state, each leg proved on disk and each restore proved by
blob hash against
HEAD:automation-result-status.pin.test.tsStrandedRunStateremoved from that docblock (occurrences 1 → 0, injected marker present 1×)AssertionError: expected '/**\n * Lifecycle status…' to contain 'StrandedRunState'; 1 failed / 7 passed'missing'then'failed', in its original parentheticalLeg A proves the pin really reads this docblock — the instrument is not blind. Leg B proves
nothing pins the member list: a refreshed five-member copy would sit exactly as unguarded
as the two-member one did, and would go stale the next time the plugin splits an arm. The pin
test says so itself in its header — plugin-approvals'
StrandedRunStateis on its list ofthings "⛔ Not pinned, deliberately". So the docblock now names where the members live and
why they are not restated here, instead of counting them.
Restore after each leg: blob
2d82047493ca63e6b4ea7f945de61ba74370844c==HEAD,git diff HEADempty,git status --porcelainempty.#16507 — both spots, line numbers re-taken from the tree
The card's line numbers were stale; these are this tree's.
packages/spec/src/ui/app.zod.ts— theNavigationContributionSchemaJSDoc (the card said"around :739"; the block is :744-769, the schema at :770). It documented only the
omitted
groupcase while the.describe()at :780 — corrected in docs(spec):navigationContributions[].groupdescribe() documents the omitted case but not the mis-aimed one #14925 — alsonames the case where
groupis present and names no group the target app declares. TheJSDoc now carries that case in the spelling the
.describe()andcontent/docs/ui/setup-app.mdx:99already use, plus the one clause that makes it matter:an author cannot detect it from their own source, because the target app belongs to another
package. ⛔ No third spelling was invented.
examples/app-multi-package/src/packages/orders/index.ts— the fixture comment (the cardsaid
:47; it is :47 still) credited onlyos build.findNavGroupDiagnosticsisimported by
packages/cli/src/commands/compile.ts:49andpackages/cli/src/commands/validate.ts:44(the card said:43) and folded into the samewarningskey fromcompile.ts:506/validate.ts:355. It now names both commands.Closing self-check, with a firing control (triage's acceptance point 4 — ⛔ not a single
grep):
declare is not refused eitherpackages/spec/src/ui/app.zod.tsonly.describe()phrase, same instrument, same treecontent/docs/references/ui/app.mdxandcontent/docs/references/kernel/manifest.mdxpackages/spec/json-schema/.describe()phrase insidepackages/spec/json-schema/The control fires, so the zero is a reading. The edited JSDoc reaches no generated artefact;
the
.describe()beside it does, and it was already correct.Hand-scan of the docs, not an anchor check — every page that carries this claim already
says "
os buildandos validate" (content/docs/ui/setup-app.mdx:99,references/ui/app.mdx:569,references/kernel/manifest.mdx:108).docs/audits/**andcontent/**carry no occurrence ofStrandedRunState,snapshot_droppedorunrepairable(control on the same file set:
nav_contribution_group_missing→ 2 files). ⛔content/docs/releases/**untouched.Changeset — measured in two halves, and they DISAGREE on one file
⛔
skip-changesetwas not assumed. Positive and negative controls on both halves.files[]distpackages/spec/src/contracts/automation-service.tsfiles[]publishessrc/**/*.zod.ts, and this is not a.zod.tsreport-only label→dist/contracts/index.d.ts+.d.mtspackages/spec/src/ui/app.zod.tssrc/**/*.zod.ts, shipped as sourcemirroring object extender ordering→dist/app.zod-CHxLLVKs.d.ts+.d.mtsexamples/app-multi-package/src/packages/orders/index.ts"private": true, nofiles[]Controls for half 2, over the same
dist/: positiveNaming a group the target app does not declare→ 20 files; negativezzz_no_such_string_control→ 0.⇒ Half 1 alone would have called
automation-service.tsunpublished and been wrong: itsTSDoc is emitted into the published
.d.ts. Apatchchangeset for@objectstack/specisowed and is in the diff. Nothing that reaches a generated artefact moved —
pnpm --filter @objectstack/spec check:generatedreports all 15 artifacts up to date and leftgit statusempty.Gates — derived, run, reconciled
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands→ 71families; every one run; reconciled with
--ran:Six of them first refused with PREREQUISITE NOT MET (a stale
packages/spec/dist; unbuilt@objectstack/formula/@objectstack/lint; andcheck:dual-build-cjs-loadswanting everypackage's
dist). ⛔ Those were NOT MEASURED, never red — so the prerequisite was builtrather than declared:
pnpm --filter @objectstack/spec build, thenturbo run build --filter=@objectstack/spec --filter=@objectstack/formula --filter=@objectstack/lint,then a full
turbo run build --concurrency=2(74/74 tasks). All six then ran and returned 0:check:doc-formula-expressions,check:api-surface,check:browser-reachable-entries,check:dual-source-exports,check:entry-nameability,check:dual-build-cjs-loads.Every heavy run went through
scripts/pm/os-verify-lock.sh; exit codes read from its printedVERDICT command-exitline, ⛔ never from a bare$?behind a pipe.Tests
pnpm --filter @objectstack/spec test(full, under the lock)VERDICT command-exit 0— 467 files, 13100 tests passedautomation-result-status.pin.test.ts+ui/app.test.tsVERDICT command-exit 0— 2 files, 110 passedpnpm --filter @objectstack/spec typecheckSPEC_TC_EXIT=0pnpm --filter @objectstack/example-multi-package typecheckEX_TC_EXIT=0Lint — a declared narrowing, with its three readings (all at
c0143fd83, the final commit):ESLint#isPathIgnoredanswersfalsefor all three changed paths, so no part of this diffsits outside the lint population.
--format json—eslint --no-inline-config --format jsonover the threepaths: 3 files linted, 0 errors, 0 warnings, exit 0.
eslint.config.mjs:324-334records that this repo"never enables type-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANY file". A comment-only diff in three files thereforecannot move any untouched file's verdict.
All three are present, so the narrowing is a measurement rather than a skipped run. The
repo-wide
pnpm lintremains CI's.验收备注
Triage's acceptance criteria for #16507, answered in order:
setup-app.mdxand the post-docs(spec):navigationContributions[].groupdescribe() documents the omitted case but not the mis-aimed one #14925.describe()— ⛔ no thirdspelling invented.
os validateadded to the fixture comment, matching "os buildandos validate".findNavGroupDiagnosticsuntouched,nav_contribution_group_missingstillwarn.content/docs/releases/untouched.On triage's 第 4 条 (the "cannot be split" judgement) — reported, ⛔ not acted on. Triage is
right that the two spots are uncoupled: different files, different packages, different facts,
and neither reaches a generated artefact. Splitting was mechanically available. It was not
done, per the dispatch fence. Measured cost of folding, for triage's ledger: the two halves
shared one gate sweep (71 families), one full spec build and one changeset — a split would have
paid all three twice, and
examples/app-multi-packagetypechecks against the built spec anyway,so the "
examples/half need not wait for the spec half" argument does not hold in this tree.⇒ The fold looks cheaper here than the split; triage's instinct is sound in general but the
saving would have been negative on this pair.
Notes, not filed (⛔ neither is a reproducible defect, a contract violation, or an
authoring trap, so ⛔ no card):
packages/spec/CHANGELOG.md:678andpackages/plugins/plugin-approvals/CHANGELOG.md:336still spell the old two-member union. They are historical records and must stay — the
card says so explicitly, and ⛔ they were not touched.
[Decision] Workflow resume ordering: a thrown node today leaves the run terminally unresumable — which of three shapes, given that the current order buys exactly-once across a crash? #13937's / Should a definition-level input-schema refusal be non-retryable? #9889's parity floor makes a flow burn its whole retry budget on a verdict that cannot change #10025's services half … lands" — the same expired-temporal-promise shape as the
defect this PR fixes, but for members that genuinely have not landed yet. Left alone: they
are true today, and rewriting them is a different card's judgement, not this one's. Carrier
for whoever files it:
packages/spec/src/contracts/automation-service.ts, theAutomationResult.statusand.codedocblocks. ⛔ Not filed here — 承接者:无.Generated by Claude Code