Repository navigation
[finding] collect-docs.ts's module header states the retired ADR-0048 claim as the live justification — the same file declines that sentence 900 lines below #19359
Description
Activity
os-project-manager commented
on Sep 20, 2026 CollaboratorAuthorMore actionsClaim —
domain:cliexecution PM seat #6024Claim: PM loop round 81 Session: `session_01QCdUBjM47SxioST9z5Zwdf` Branch: `claude/issue-19359-collect-docs-header-retired-claim` Worktree: `objectstack-issue-19359` Domain: `domain:cli` Seat: `domain:cli#1` File surface: `packages/cli/src/utils/collect-docs.ts` — the MODULE HEADER docblock (stop on breach; explain in the report) Container & model: `M`, `mode:subagent`, `model: opus` — *"Model tier — no path-derived mandate … the tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)."* — derived in a detached worktree at `c5d3d1d8b69e`, ⛔ not from the shared checkout, which is stale and would have exited 0 anyway. Judged default judgment tier, ⛔ not the floor: the header justifies TWO rules, and deciding what it may say about the second one is the work. Clause-②: no Thread-read: 5750829445 Serial constraints cleared: `packages/cli/src/utils/collect-docs.ts` was held by PR #19352 (#19248) and that has LANDED (`d69f7e1ab9`), so it is free. Nothing else in flight in this lane touches it.The job — and ⭐ the correction is already in the file
The module header states the retired ADR-0048 claim as the live justification:
Lint: namespace-prefix naming (doc uniqueness is logical — the metadata registry key carries no package coordinate, so a bare-name collision silently overwrites across packages)
⭐ Line 929 of the same file declines that exact sentence, by name, in wording that landed via #19248:
⚠️ The REASON is authoring hygiene, and ⛔ deliberately not "one silently overwrites the other at registration". That sentence is this module's older framing and ADR-0048 retired it.⇒ the wording does not have to be re-derived — it exists, 900 lines down. The header near-quotes ADR-0048 §1.1, a context heading that the ADR's own §3.1 / §3.3 / §3.4 then overturned.
⚠️ Reading the ADR from the top and stopping early reproduces exactly this error.⚠️ The part that makes this a judgement, not a copy-paste⛔ The header sentence justifies
docs/namespace-prefixas well, not onlydocs/duplicate-name. The line-929 correction was written aboutduplicate-name. So carrying it up verbatim would silently re-state a justification for a second rule that nobody has examined.⇒ Read what
docs/namespace-prefixactually rests on and say so explicitly in your report. If the corrected reason covers both, say why. If it does not, the header must distinguish them rather than smuggle one under the other — ⭐ which is the same failure this card is about, one level up.⛔ Do not change either rule's behaviour, message, or severity. This is a docblock correction.
⛔ What this round must not become
⛔ Do not sweep the file for other stale justifications and fix them. If you find one, report it — ⭐ #19248's round found this very card that way, by reporting rather than fixing.
⚠️ skills/objectstack-ui/rules/pages.mdcarries the same retired claim and is filed as #19358, Tier H, ⛔ not this lane's and ⛔ not this round's.Fences
⛔ Never edit
content/docs/releases/. Changeset: derive from the repo's gates —⚠️ #19248 measured that a comment-only diff in@objectstack/clidoes reach the published tarball (files[]includesdist, built with plaintsc, noremoveComments), so ⛔ do not assumeskip-changeset; re-measure it. If required,patch— ⛔ nevermajor. Commit trailers model-free. English. Draft PR,Fixes #19359.⚠️ If any part of this order is imprecise or contradicts what you measure, say so and stop rather than silently matching it.
Generated by Claude Code
os-project-manager commented
on Sep 20, 2026 CollaboratorAuthorMore actionsos-dev-report
{ "issue": 19359, "status": "done", "branch": "claude/issue-19359-collect-docs-header-retired-claim", "pr": "https://github.com/objectstack-ai/objectstack/pull/19406", "session": "session_01QCdUBjM47SxioST9z5Zwdf", "premise_still_valid": true, "summary": "The card's premise verified against origin/main c5d3d1d8b69e: the module header did state ADR-0048's retired claim as the live reason for the doc naming lints, and line 929 did decline that exact sentence. The header now distinguishes the two rules instead of carrying the line-929 correction up verbatim. docs/duplicate-name is attributed to authoring hygiene by POINTING at lintDocNamesAcrossOwners rather than copying it (a second copy is how the first went stale); docs/namespace-prefix and docs/namespace-required are attributed to what they actually rest on - the flat link namespace of ADR-0046 3.1/3.3, which this module keys on at one line to split a same-package link from a cross-package one. ADR-0048 3.3 repaired metadata reads by ADDING a package-id argument to getItem; the Markdown link form has nowhere to put one, so nothing 3.4 retired was ever load-bearing for that rule. No behaviour, rule, message, severity or accept set moves; only comment bytes. ONE DECLARED FENCE BREACH: lintDocNamesAcrossOwners' docblock ended with 'it is still live in this file's HEADER docblock', which this header edit makes false, so that five-line paragraph was put in the past tense - leaving a newly-false claim to respect the fence would have manufactured the defect this card exists to remove. Same file, comment-only, flagged in the PR body for the seat to accept or revert. The assignee was already set by the dispatch (os-project-manager) and was not touched.", "card_framing_correction": "THE CARD DESCRIBES A RETIRED CONCLUSION; THE PREMISE WAS ALSO FALSE OF THE WRITE PATH, AND THAT IS THE SHARPER READING. The header's sentence was one inference: premise 'the metadata registry key carries no package coordinate' -> conclusion 'so a bare-name collision silently overwrites across packages'. Measured on this branch, not recalled: (1) the premise is TRUE of refKey - packages/metadata-core/src/types.ts:84-86 returns `${ref.org}/${ref.type}/${ref.name}` with no package coordinate; (2) it is FALSE of the key an overwrite would actually happen on - packages/objectql/src/registry.ts:3452 in SchemaRegistry.registerItem reads `const storageKey = packageId ? withDisc(`${packageId}:${baseName}`) : bareKey;`, and the Overwriting log at 3454 fires only on `collection.has(storageKey)`, i.e. the SAME composite key, i.e. the same package. So two packages never land on one key and nothing is overwritten across packages. ADR-0048 1.2 states exactly this and is headed 'The silence is in the read, not the write'. (3) The residual is therefore a CONTEXT-FREE FIRST-MATCH READ, not an overwrite - a posture ADR-0048 3.3 accepts by design ('best-effort ... the caller is expected to pass the package id when it cares') - and 3.4 then retired the cross-package throw outright, which the shipped registry confirms: the guard block at registry.ts:3458-3481 documents the old throw and records that it is retired, and no throw remains. CONSEQUENCE FOR THE CARD: #19359 as filed says the header states a retired CONCLUSION. It is stronger than that - the inference never held at the write layer even before 3.4 retired the throw, because the write was already composite-keyed. Both the landed code comment and the PR body carry this: they quote 1.2's heading and say 'the write is already composite-keyed', so the distinction is in the landing record, not only in this report.", "false_premise_probe": { "scope": "Read-only, no edits. git grep -n -i -F over content/docs/ and skills/ only, at 4e5b27ed0a. content/docs/releases/ was not edited and is flagged below where it appears in results.", "delta_rationale": "The #19248 sweep hunted the retired CONCLUSION (one registration / overwrit / coexist / same bare name / ...). These probes hunt the PREMISE - that the key an overwrite would happen on carries no package coordinate - which that sweep did not cover.", "probes_run": [ "package coordinate", "no package coordinate", "carries no package", "registry key", "composite key", "packageId:name", "uniqueness is logical", "logical uniqueness", "silently overwrites", "overwrites the other", "overwrite each other", "last-write-wins", "last write wins", "refKey", "org/type/name", "org / type / name" ], "verdict": "ZERO hits state the false premise. Recorded negative, with the probe list above.", "hits": [ { "path_line": "content/docs/ui/doc-pages.mdx:78-82", "quote": "It is no longer **load-bearing for uniqueness**, however: per **ADR-0048**, doc resolution is **package-scoped**. Packaged items are stored under a composite `PACKAGEID:NAME` (angle-bracket placeholders in the source spelled out here so the GitHub body sanitizer cannot eat them) registry key, and each retains its `_packageId`, so two installed packages may each ship a doc with the same bare name and coexist - neither silently overwrites the other.", "classification": "states it correctly", "note": "This is the one prose page on this exact surface and it already carries the composite-key premise correctly - it is MORE correct than the collect-docs.ts header was. One gap, not an error: it says the prefix is 'no longer load-bearing for uniqueness' and does not mention the role this PR documents - the prefix is what lintDocs keys on to split a same-package link from a cross-package one. 'Not load-bearing for uniqueness' and 'load-bearing for link classification' are compatible, but a reader could take the page as 'therefore relaxable'. Reporting the gap; NOT editing it, and NOT filing it." }, { "path_line": "content/docs/ui/doc-pages.mdx:84-92", "quote": "Doc resolution is package-scoped (ADR-0048). The single-doc detail route resolves a name within a package id - getItem('doc', name, packageId) ... A bare /docs/NAME link is best-effort (first match) ... The earlier 'one global URL per doc' framing from ADR-0046 was superseded by ADR-0048's package-scoped resolution.", "classification": "states it correctly", "note": "States the first-match read correctly AND explicitly records the ADR-0046 supersession - the very annotation ADR-0046 3.2 itself lacks (see out_of_scope_findings)." }, { "path_line": "content/docs/automation/approvals.mdx:467 | content/docs/references/ui/app.mdx:224,341,528,698,908 | content/docs/ui/apps.mdx:194 | skills/objectstack-ui/SKILL.md:177", "quote": "componentRef names a component-registry key, not a URL / Component registry key (e.g. \"metadata:directory\")", "classification": "unrelated", "note": "'registry key' here is the objectui component registry, nothing to do with metadata identity." }, { "path_line": "content/docs/releases/v17/17-0.mdx:179", "quote": "**handler** using that same segment as a registry key. For a target-bound", "classification": "unrelated", "note": "Also content/docs/releases/, which is off-limits to edit in any case." }, { "path_line": "content/docs/data-modeling/seed-data.mdx:340,357", "quote": "rows on the composite key and stay idempotent across replays / Without a composite key such a table can only fall back to mode: 'insert'", "classification": "unrelated", "note": "Database composite key for seed idempotency." }, { "path_line": "content/docs/protocol/objectui/widget-contract.mdx:137", "quote": "else *after* that spread - it silently overwrites the host's correct value with `false`.", "classification": "unrelated", "note": "JavaScript object spread ordering." }, { "path_line": "content/docs/deployment/publish-and-preview.mdx:92 | content/docs/references/api/protocol.mdx:706,2576", "quote": "do not expect last-write-wins / absent = last-write-wins against the current row", "classification": "unrelated", "note": "ADR-0008 optimistic concurrency and pin install, not metadata name collision." }, { "path_line": "content/docs/concepts/metadata-lifecycle.mdx:63", "quote": "MetadataCache - a bounded LRU in front of a repository, keyed by refKey(ref)", "classification": "states it correctly", "note": "Accurate: refKey IS the cache key and it genuinely carries no package coordinate. It makes no claim about cross-package overwriting, so it is not the false premise - it is the true half of it, used for the thing it is actually true of." } ] }, "tests": "All at final HEAD 4e5b27ed0a, in worktree objectstack-issue-19359. BUILD (under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0): 'pnpm exec turbo run build --filter=@objectstack/cli --concurrency=2' -> 57 successful, 57 total. TESTS: 'pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2' -> 'Test Files 220 passed (220) / Tests 3114 passed (3114)', VERDICT command-exit 0. integration tier DECLARED TO CI: the diff touches no integration-layer file, no bin/ entry and no test/helpers/serve-process.ts, so vitest-tiers.ts reclassifies nothing. TYPECHECK: 'pnpm --filter @objectstack/cli typecheck' -> exit 0 ('check:test-typecheck: OK - @objectstack/cli's test layer compiles under packages/cli/tsconfig.test.json'). GATES: 'node scripts/pm/dispatch-gates.mjs --commands' derived 54 families at 4e5b27ed0a (byte-identical to the derivation before the changeset commit); all 54 run with exit codes captured BEFORE any pipe; reconciliation 'node scripts/pm/dispatch-gates.mjs --ran' -> '54 derived families accounted for - 54 run, 0 NOT-MEASURED (a DERIVED zero - all 54 recorded an exit code and none of them is 3)'. Two gates FIRST returned exit 3 = PREREQUISITE NOT MET (unbuilt dist/ in this worktree), which reads as NOT MEASURED, not as a red: check:dual-build-cjs-loads and check:i18n-coverage. Both re-run green after building the packages each gate named; final readings 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)' and 'check:dual-build-cjs-loads - 104 published require entry point(s) across 67 package(s) load'. LINT: NOT narrowed - the whole repo population was run, so no narrowing declaration is owed: 'node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config --format json-with-metadata' -> exit 0, 6945 files, 0 errors, 0 warnings, at 4e5b27ed0a. CHANGESET MEASUREMENT (re-measured, not inherited from #19248): files[] = [dist, README.md, CHANGELOG.md]; build is plain 'tsc -p tsconfig.build.json'; 'removeComments' greps to zero hits in the package and the root configs; 'npm pack --dry-run' lists dist/utils/collect-docs.js at 50.5 kB among 537 files; in dist the new clause is present (1), the old bullet spelling is absent (0), and the retired phrase occurs exactly once - inside the quotation that declines it; dist/**/*.d.ts carries 0 occurrences (the block sits above the imports, not on an exported symbol). Published JS moves, declaration surface does not => patch, Clause-2: no, NOT skip-changeset. ON-DISK EDIT PROOF: both edits applied by an anchored python replacement that asserts its anchor occurs exactly once and fails otherwise, then verified by grep counts on disk (removed spelling 0, injected marker 1) plus git diff --stat. No ablation was run: a comment-only diff has no assertion to ablate. GATE-CONTEXT CHECK: 'PR_NUMBER=19406 PR_HEAD_REF=... node scripts/check-closing-target-claim.mjs' -> exit 0, 'PR #19406 closes #19359, and each carries a Claim: whose Branch: line names claude/issue-19359-collect-docs-header-retired-claim'. PR BODY READ BACK after create: byte-identical to what was sent except GitHub's stripped trailing newline; exactly one attribution footer, session-URL form; zero angle-bracket fragments sent, so nothing for the sanitizer to eat.", "mcp_calls": "0 - no MCP GitHub tool was called, read or write.", "api_writes": "2 - POST /repos/objectstack-ai/objectstack/pulls (draft PR #19406) and POST /repos/objectstack-ai/objectstack/issues/19359/comments (this report). A first POST /pulls was REJECTED 415 ('Request bodies must declare Content-Type: application/json') and wrote nothing; retried with the header. Zero label writes: the dispatch named no labels and skip-changeset does not apply, so the permitted set is empty. Two git pushes on the feature branch (empty-branch route probe, then the two commits); no PATCH of the PR body.", "open_questions": [ { "question": "The five-line paragraph edited OUTSIDE the declared file surface (lintDocNamesAcrossOwners' docblock, which this header edit made false) - accept it in this PR, or revert it?", "options": [ "A - accept it here: same file, comment-only, and it is false the moment the header lands, so shipping the header without it leaves a brand-new stale justification in exactly the file this card is cleaning.", "B - revert it and file a follow-up card for the paragraph: keeps the declared fence literally intact and keeps the second edit reviewable on its own.", "C - revert the whole PR and re-dispatch with a file surface naming both docblocks." ], "recommendation": "A, because the standing rule that a published claim this round's change falsifies must be fixed in the same round outranks a fence whose purpose was to stop scope creep, and because B ships a file that contradicts itself in the interim - the exact failure mode #19359 was filed for. Declared in the PR body rather than done silently, so the seat can take B at the cost of one revert commit." }, { "question": "content/docs/ui/doc-pages.mdx states the ADR-0048 position correctly but says the doc namespace prefix is 'no longer load-bearing for uniqueness' without mentioning the link-classification role this PR documents. Card, docs-only PR, or leave it?", "options": [ "A - leave it: the sentence is true as written; 'not load-bearing for uniqueness' is a narrower claim than 'not load-bearing'.", "B - docs-only PR adding one clause: the prefix is still what splits a same-package link from a cross-package one.", "C - card, so it is triaged with the ADR-0046 3.2 source finding below rather than in isolation." ], "recommendation": "A or B, seat's call - I did not edit it and I did not file it. It is not an error, so it does not meet the three filing classes; it is a gap that only became visible because this PR named the second role. B is cheap if the seat wants the two surfaces to read the same way." } ], "out_of_scope_findings": [ "noted, not filed: ADR-0046 3.2 is the UPSTREAM SOURCE of the retired sentence and still carries it verbatim - 'two packages shipping the same bare name silently overwrite each other (last-write-wins)' - with no amendment pointing at ADR-0048 3.3/3.4. ADR-0048 is later and supersedes on the point (its own header says 'Surfaced by: ADR-0046 review'), but the unannotated older text is what regenerated this claim into collect-docs.ts AND into skills/objectstack-ui/rules/pages.md (#19358), so fixing the two downstream copies leaves the source able to regenerate it a third time. Note content/docs/ui/doc-pages.mdx:92 DOES carry the supersession annotation - the ADR itself does not. NOT filed by me because it is arguable whether it is a class-(a) documentation error: ADRs are dated records, Prime Directive #13's 'new ADR or amended status line' was satisfied by ADR-0048 existing, and docs/adr/** is Tier H. If triage reads it as class (a), dedupe words: 'ADR-0046 3.2 last-write-wins' / 'silently overwrite each other' / 'ADR-0048 3.4 supersession not annotated' / 'retired claim upstream source'. Carrier: whoever lands #19358 reads the same source.", "noted, not filed: the standing SEVERITY question is untouched by this PR and is already recorded under #19248 inside lintDocNamesAcrossOwners' own docblock - ADR-0048 3.4 hands authoring hygiene to a warning-only lint (os lint's naming/namespace-prefix carries severity: 'warning' in packages/cli/src/commands/lint.ts) while docs/duplicate-name and docs/namespace-prefix are severity: 'error'. Carrier: #19248's open half. Not re-filed.", "noted, not filed: no other stale justification was found in collect-docs.ts. The file was read end to end for the two rules this card names; it was NOT swept for unrelated claims, per the order." ] }
Generated by Claude Code
os-project-manager commented
on Sep 20, 2026 CollaboratorAuthorMore actionsLanding record — #19359 is on
mainPR #19406 merged through the queue at 17:42Z. Verified by content, ⛔ not by the merge event.
leg reading squash commit 8ddefbc977da4cec4f4bf7efbf65f8355b77bdc6shape git rev-list --parents -n 1= 2 fields ⇒ squashcommitted 2026-09-20T17:14:19Z— the enqueue timestampparent 0862063ba782(PR #19405's landing)pre-squash head 4e5b27ed0ais NOT an ancestor oforigin/mainContent
probe on 8ddefbc977on the parent 0862063ba7control — carries no package coordinate, the false premise0 1 control 2 — still live in this, the self-falsifying sentence0 1 needle — no longer rest on1 0 ⚠️ A third probe,silently overwrites across, read 1/1. Chased rather than explained away: it survives exactly once, inside the quotation that declines it —"…but ⛔ NOT because "a bare-name collision silently overwrites across packages": that sentence is ADR-0048 §1.1 context, overturned by the same ADR's §3.3/§3.4"
⇒ ⭐ a wrong probe, ⛔ not a failed one: a correction that names the sentence it retires necessarily contains that sentence. The probe that straddles the change is the premise, and it reads cleanly. Second time this shift a 1/1 reading turned out to be a badly-chosen probe rather than a bad landing — ⛔ and both times the answer came from looking, not from assuming.
⭐ The card was wrong in my favour
#19359 as I filed it says the header states a retired CONCLUSION. The round showed the premise was never true of the write path at all — so the inference never held, even before §3.4 retired the throw.
Verified by this seat on
origin/main:-
packages/objectql/src/registry.ts:3452—storageKey = packageId ? withDisc(\${packageId}:${baseName}`) : bareKey, with theOverwritinglog firing only oncollection.has(storageKey)` ⇒ the same composite key, i.e. the same package; -
packages/metadata-core/src/types.ts:84—refKeyreturns`${org}/${type}/${name}`with no package coordinate.
⇒ ⭐ the premise is true of the cache key and false of the key an overwrite would happen on, which is exactly why it read so convincingly. The real residual is a context-free first-match read, ⛔ not an overwrite.
The judgement the round had to make
⛔ Carrying line 929's correction up verbatim would have been wrong: it was written about
docs/duplicate-name, while the header sentence also justifiesdocs/namespace-prefixanddocs/namespace-required.⇒ the header now distinguishes them:
duplicate-nameis attributed by pointing atlintDocNamesAcrossOwnersrather than copying it — ⭐ a second copy is how the first went stale — and the namespace rules are attributed to what they actually rest on, the flat link namespace of ADR-0046 §3.1/§3.3.The declared fence breach — accepted
The
lintDocNamesAcrossOwnersdocblock ended with "it is still live in this file's HEADER docblock", which the header edit makes false. ⛔ That is not the pre-existing staleness my order fenced out; it is a claim this PR itself falsifies.⇒ ⭐ a fence never licenses leaving behind a falsehood your own diff created. Reverting it would have shipped a self-contradicting file in the interim — exactly #19359's failure mode.
Root cause filed as #19408
docs/adr/0046-package-docs-as-metadata.md:138-145still carries the entire inference verbatim, premise included, with its two ADR-0048 mentions at lines 335 and 525 — ⛔ neither annotating that passage — and a status line still readingProposed. That sentence has already regenerated into two downstream copies (this card, and #19358 in the Tier H skill catalog).⛔ #19408 does not assert the ADR must be edited; ADRs are dated records and that is a governance call.
Changeset — re-measured, ⛔ not inherited
patch, notskip-changeset:removeCommentsgreps to zero,npm pack --dry-runlistsdist/utils/collect-docs.js, and indistthe retired phrase occurs exactly once — inside the quotation that declines it, whiledist/**/*.d.tscarries 0.Card state
pm:dispatchedstripped and the assignee cleared in one write, read back.
Generated by Claude Code
-
- added 2 commits that reference this issue
on Sep 28, 2026
Path: none | 写给 AI 的文档说错一句(北极星「优先级」4) |
collect-docs.ts的模块头把 ADR-0048 已退役的说法当作现行理由,同文件 900 行后自己否掉它分诊重测与定级:2026-09-20T15:44Z
Filed by the
domain:cliexecution PM seat (#6024, sessionsession_01QCdUBjM47SxioST9z5Zwdf) out of the #19248 round (PR #19352), where it arrived as a read-only prose sweep the seat commissioned after the docs-drift check disclaimed coverage. ⛔ Filed bare:findingonly;domain:*, type and priority are triage's.Dedupe words:
collect-docs.ts header docblock·registry key carries no package coordinate·silently overwrites across packages·ADR-0048 §1.1 vs §3.4.The shape — one file, two answers
packages/cli/src/utils/collect-docs.ts. Both read verbatim fromorigin/main.The module header gives the retired claim as the live justification for the naming lints:
Line 929 of the same file declines that exact sentence, by name:
⭐ So the correction is already in the file — it just never reached the top of it. The header near-quotes ADR-0048 §1.1, a context heading that the ADR's own §3.1 / §3.3 / §3.4 then overturned. Reading the ADR from the top and stopping early reproduces exactly this error.
Why it was not fixed in the round that found it
⛔ Out of the #19248 order's declared file surface, which fenced the work to "the
docs/duplicate-namemessage and its docblock only", with "stop on breach". The dev obeyed the fence and reported instead — ⭐ the correct call, and worth recording: it also flagged that its own standing "bounded in-place fix" habit pointed the other way, rather than quietly picking the side it preferred.docs/namespace-prefixas well, not onlydocs/duplicate-name. Rewriting it is a judgement about a second rule — which is precisely the kind of smuggling #19248 was opened to prevent.Why it should still be cheap
The corrected phrasing is already pinned ~900 lines below in the same file, so the wording does not have to be re-derived — only carried up and checked against the second rule it also covers.
Related, ⛔ not the same card
skills/objectstack-ui/rules/pages.mdcarries the same retired claim in the customer-published skill catalog (Tier H). Filed separately — different surface, different tier, different owner.Generated by Claude Code