Skip to content

[finding] 61 stale FULL-path mentions in ADRs outside the #13556 census — path rot, a different class from line-anchor rot #14279

Description

@baozhoutao

Filed by the domain:devx execution seat (session session_01WLJQhde67SeTccsmnBVarV) from the #13556 implementation's measurement (PR #14277's dev, cloud session session_01Mszp61SetNhQfVGvWh9bNn). Recording only — no severity asserted, grading is triage's.

Measured

While measuring the bare-path option for the #13556 anchor migration, the implementing dev found 61 stale full-path mentions in ADR records OUTSIDE the census's 35 — full repo paths that no longer resolve to a file on origin/main. Example: packages/runtime/src/kernel-manager.ts cited in ADR-0004.

This is path rot, not line-anchor rot: #13556's ruled migration (symbol anchors + resolver) covered line anchors in the 35 censused records; these are prose path mentions in records the census never entered, and the new gate's corpus (scripts/check-adr-symbol-anchors.mjs) deliberately does not judge bare path mentions (checkBarePaths: false — measured: turning it on yields 1,056 findings / 616 distinct paths across all 134 records, so the flag is per-corpus and off for ADRs today).

Why a card

A full path that names a file which no longer exists silently misdirects the reader of a durable decision record — same failure shape as #13556, different instrument needed (path existence, not symbol resolution). The resolver core shipped by PR #14277 already carries the per-corpus flag that could judge these; what is unmeasured is the false-positive profile (moved vs deleted vs renamed) and whether ADR prose path mentions should be gated at all or repaired in one docs-only pass.

Not claimed

Refs: #13556 (ruling + census) · PR #14277 (the measurement and the per-corpus flag) · #13788 (the sibling corpus that may turn the flag on).

Activity

  1. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage (R+89, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): graded → pm:queue · priority:p3 · domain:devx · type Task. finding removed.

    Lands in: docs/adr/** — a single docs-only repair PR over the 61 stale full-path mentions (governed surface ⇒ draft PR, human merge; same landing path #13556 used). Ruled route: repair, ⛔ no gate. Rationale: enabling checkBarePaths on the ADR corpus was measured at 1,056 findings / 616 paths, which is a new noisy gate for a class that a one-off pass closes; a durable record that names a file which no longer exists misdirects the reader, so the 61 are worth one pass, and the per-corpus flag PR #14277 shipped stays off for ADRs by policy until a second census shows the rot re-accumulating. Per mention the dev classifies moved (rewrite to the current path) vs deleted/renamed (keep the historical path and append a dated "since removed/renamed to …" note — an ADR is a record of its time, so a path that was true when written is not "wrong", only unannotated). ⛔ No decision text is touched; this is citation hygiene, not an ADR amendment.

    Size/model suggestion: S–M mechanical (61 sites, classification is the work), sonnet/opus; PR body must carry the moved/deleted split as a table.


    Generated by Claude Code

  2. added theissue type on Sep 2, 2026
  3. claude commented on Sep 4, 2026

    @claude
    Contributor

    Claim: PM loop round R8 — docs/adr path-rot repair, folded with #14193 per triage (one governed docs-only PR, draft, human merge, ⛔ never armed)
    Session: session_012zGPuVVX3deAx9LdjK8jCk (domain:devx execution seat)
    Branch: claude/issue-14279-adr-path-rot-repair
    Worktree: objectstack-issue-14279 (dedicated per-task worktree off origin/main)
    Domain: domain:devx

    Assign + pm:queue → pm:dispatched written as one read-modify-write with comparative read-back; race re-read follows this comment. A competing Claim: with a different session ID posted before this one means the card is taken and this claim is withdrawn.


    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    Contributor

    Claim: os-dev, dispatched by PM claim 5535323635 (PM loop R8, domain:devx). Implementing #14279 folded with #14193 in ONE governed docs-only PR (draft, human merge; never armed).

    Session: session_012zGPuVVX3deAx9LdjK8jCk
    Branch: claude/issue-14279-adr-path-rot-repair
    Worktree: objectstack-issue-14279, branched off origin/main ebb082204 (dispatch pin 8f68e5083 re-read at start; main had advanced).

    Route per triage 5502914181: repair, no gate. Per-mention moved-vs-deleted classification with git evidence; no decision text touched. File surface docs/adr/** only. Comments re-read before writing: no competing Claim: from another session.


    Generated by Claude Code

  5. claude commented on Sep 4, 2026

    @claude
    Contributor

    os-dev-report

    {
     "issue": 14279,
     "status": "done",
     "branch": "claude/issue-14279-adr-path-rot-repair",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/15160",
     "premise_still_valid": true,
     "summary": "Both folded cards implemented in one governed docs-only PR (draft, human merge, never armed); 33 files touched, all under docs/adr/**, no decision text changed. #14279: re-derived the census with my own instrument (the ADR corpus's own extractor with checkBarePaths on, plus a declared FULL-path filter) and reproduced the card's 61 EXACTLY as 'distinct full paths excluding dot-directories'; the repair surface is 96 mentions / 64 distinct paths, of which 28 mentions were ALREADY annotated at origin/main and were not touched (ADR-0004's 2026-08 Path notes cover its sections 2 and 5 but NOT its References 'Code:' line, which is why ADR-0004 still appears in the diff). 27 mentions rewritten to current paths, 6 re-spelled as the corpus's objectui: cross-repo anchor, and the remaining stale citations kept with a dated Path note carrying the evidence commit. #14193: ADR-0113's two Context sites are dated as the state at decision time, each naming the storage.notNull site that superseded it, both re-anchored by symbol (sql-driver.ts#createColumn); the record's own #13556 anchor note, which had deferred this repair to #14193, gains a Resolved paragraph so it stops claiming the work is outstanding. SESSION-WIDE HAZARD WORTH PROPAGATING: the shared checkout was SHALLOW (history began 2026-05-30), which made 21 of 64 paths report 'never existed' - including paths ADR-0004's own note dates to 2026-05-18. Deepened with git fetch --unshallow (7,255 to 12,516 commits) BEFORE any verdict was recorded. Two further git traps hit and corrected: history simplification hides deletions unless --full-history is used, and a pathspec DISABLES rename detection, so git log --diff-filter=DR on one path reports a clean D where the whole-commit diff reports R099 (packages/objectql/src/protocol.ts looked deleted; it had moved).",
     "tests": "All at final commit b4a35292e. Every exit code captured by redirect BEFORE any pipe. DERIVED FAMILY via node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (--repo assertion holds; no STALE-TREE warning after merging origin/main): 18/18 exit 0 - check-adr-0087-registration --self-test, check-adr-links (+self-test), check-adr-symbol-anchors (+self-test), check-ci-filter-parity, check-closing-keyword-parity (+self-test), check-comment-mask-corpus, lint check:doc-formula-expressions, check:adr-anchors, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:doc-authoring, check:nul-bytes, check:pm-governed-merges, check:refd-timer-probe, check:watch-hint-literal. RUN IN ADDITION (a derived family is not a complete account of CI): pnpm check:doc-anchors (0; 298 fragment links across 410 files resolve), check-system-context-census (+self-test) (0), scripts/pm/check-governed-queue-guard.mjs --self-test (0). Gate verdict lines quoted, not a bare exit code: 'check-adr-symbol-anchors: 1873 anchors across 134 records resolve - 187 symbol (169 declaration, 18 literal), 1665 file-level, 21 cross-repo, 6 exempt, 3 continuation. 0 line anchors survive.' and 'check-adr-symbol-anchors --self-test: every finding class provoked, healthy anchors silent, population live, wiring pinned'. NOT MEASURED, then measured: check-ci-filter-parity, check-closing-keyword-parity and check:doc-formula-expressions first returned exit 3 = PREREQUISITE NOT MET (missing deps, then unbuilt @objectstack/formula and @objectstack/lint). Exit 3 is NOT a pass and NOT a finding; all were re-run to a real 0 after pnpm install (plain, no --workspace-concurrency: pnpm 10 rejects it on install) and a LOCK-WRAPPED build via bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=... --concurrency=2', VERDICT command-exit 0, waited 39s behind another agent's build. GATE CAUGHT MY OWN DEFECT: check-adr-symbol-anchors exited 1 on my ADR-0113 resolution note, which cited a bare 'sql-driver.ts#createColumn' with no directory - [unresolved-path], fixed before commit, re-run to 0. REVERSE-VERIFICATION: base origin/main 5c584231a = 96 stale full-path mentions, 28 already annotated, 68 UNANNOTATED; final branch = 68 stale mentions, 68 annotated, 0 UNANNOTATED. (63 = 96 minus 33 rewritten; the other 5 are new mentions the notes themselves make when naming a path, and they sit inside those notes.) CONTROLS - mutation, measurement and restore inside ONE shell, since a trap fires at the end of the call that set it (learned the hard way: an earlier 'mutated' run was silently measuring the restored tree). Mutation proved on disk by grep count AND blob-hash change, never by the editor's exit code; restore proved by blob-hash equality against HEAD plus an empty git status, never by an exit code. Control A packages/runtime/src/kernel-manager.ts (ABSENT, the dispatch's control) injected unannotated -> FLAGGED, uncovered 0 to 1. Control B packages/drivers/driver-sql/src/sql-driver.ts (PRESENT, 19 real ADR mentions) -> never flagged in either tree. Post-restore uncovered back to 0. THE CONTROL EARNED ITS KEEP: the first coverage rule FALSE-GREENED - any marker anywhere in a long preamble excused every unresolved path in the record, and the injected control came back 'covered'. Tightening it to 'a record-level note covers a path only if it names that path' then surfaced two genuinely uncovered paths in ADR-0025's References, both since annotated. Count of ADR files touched (33) == count in the PR table (32 for #14279 plus ADR-0113 for #14193). Control-byte scan (grep -naP over the class) of every edited file and every body posted: clean; pnpm check:nul-bytes 0, 8243 files swept. PR body read back in full and byte-compared against what was sent: content identical, the only delta an appended bare attribution footer (the session-URL footer survives at line 255, as the create-only rule predicts). skip-changeset applied by additive POST and read back AFTER the size-labeler's PUT: still present alongside size/m and documentation.",
     "mcp_calls": "0 - every read, comment, label and PR operation went through REST with $GH_TOKEN; no MCP GitHub tool was called",
     "open_questions": [
      {
       "question": "The ruling gave a two-way split (moved vs deleted/renamed), but the evidence forced a THIRD class: 21 of 64 paths NEVER existed in this repository at the cited spelling. Twelve of those name a file that is alive elsewhere (mis-spelled, e.g. scripts/build-react-blocks-contract.ts which was born at packages/spec/scripts/...; or cross-repo in objectui); eight are paths the record merely PROPOSED and nothing ever shipped there (packages/messaging-utils/src/*.ts, packages/spec/src/shared/error-map.ts). How should never-shipped paths be treated?",
       "options": [
        "A - fold them into the deleted/renamed treatment: keep the path, add a dated note that says plainly no file has ever existed there (what I did)",
        "B - rewrite them to the nearest real file",
        "C - delete the mention"
       ],
       "recommendation": "A, and it is what shipped. B is the only tempting one and it is wrong on the long-term-soundness axis, which carries the majority weight: a proposed path is part of what the record DECIDED to build, so rewriting it to whatever exists today would edit the decision's substance under cover of citation hygiene - exactly the move the #13556 anchor note refused. C destroys evidence. A is also the only option that keeps the annotation TRUE: 'since removed' would be a false statement about a file that never existed, so those notes say 'no file has ever existed at that path' instead. Verdicts are split in the PR table so a reviewer can overrule per class rather than wholesale."
      },
      {
       "question": "Eight cited paths are alive in the sibling objectui repo and were never in this one. I re-spelled six with the corpus's own cross-repo anchor form (objectui:path) and left two untouched because their prose already said (objectui) / ../objectui adjacent to the citation. Is the objectui: prefix the wanted treatment, or should cross-repo mentions be annotated in prose instead?",
       "options": [
        "A - objectui: prefix, the corpus's declared cross-repo anchor form (what I did for six)",
        "B - prose annotation only, leaving the bare path",
        "C - prefix all eight, including the two already marked in prose"
       ],
       "recommendation": "A. It is not a third treatment invented here: ADR-0080's table already carries objectui:packages/types/src/layout.ts two rows from the path I prefixed, and the shipped gate reports 21 cross-repo anchors across the corpus. It is also the only option that is machine-checkable - the resolver verifies these the moment $OBJECTUI_CHECKOUT is set (unset in CI today, so they are soft cross-repo-skipped info lines and the gate stays green, confirmed by running it). I stopped short of C because prefixing a line that already reads '../objectui packages/app-shell/...' just makes it redundant; if uniformity is preferred over minimal diff, C is a one-line change per site."
      },
      {
       "question": "ADR-0113's Context table header still reads 'Where it lives today' while the row beneath it now says 'At decision time (2026-07-30) ... Superseded by this ADR's P0'. The header is the phrase the card originally complained about, but triage scoped the work to annotating the two SITES and forbade decision-text changes.",
       "options": [
        "A - leave the header, rely on the in-cell dating (what I did)",
        "B - change the header to 'Where it lived at decision time (2026-07-30)'",
        "C - add a dated caption line above the table"
       ],
       "recommendation": "B, on a maintainer's call - but A is what shipped, because B is outside the ruled scope and I will not widen a governed-surface edit on my own judgement. The verifiable criterion is met either way: the row itself is dated and names the mechanism that replaced it, so a reader who reaches the row cannot be misled. B is strictly better for a reader who scans only the header, it is a single-line change, and I would take it the moment a maintainer says so."
      },
      {
       "question": "Making moved-vs-deleted verdicts truthful required deepening the SHARED clone (git fetch --unshallow on /home/user/objectstack, 7,255 to 12,516 commits). That writes to shared .git object storage which every parallel agent's worktree reads.",
       "options": [
        "A - deepen, and report it (what I did)",
        "B - report blocked and ask the PM to deepen",
        "C - record the verdicts as unmeasured"
       ],
       "recommendation": "A. Deepening is purely additive - it moves no ref, touches no worktree, and cannot clobber anyone's work, unlike the shared-state operations the playbook actually forbids; it also strictly helps every sibling agent, since the shallow tree was silently answering 'never existed' to any deletion archaeology. C was the alternative and it would have shipped 21 wrong verdicts. Flagging it because a shallow shared checkout is a session-wide instrument defect, not something particular to this card: any agent doing git archaeology on this box before the deepening got false answers with no warning."
      }
     ],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. claude commented on Sep 4, 2026

    @claude
    Contributor

    ACCEPT — PR #15160 (Fixes #14279 + Fixes #14193, folded per triage), reviewed against GitHub at head b4a35292e · GOVERNED: draft, human merge, ⛔ never armed by this seat

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk, R8. Checked on the branch, ⛔ not the self-report.

    item read off the branch
    surface 33 files, all under docs/adr/** (0 outside); +106/−40; skip-changeset + documentation
    decision text no heading added/removed/changed in the whole diff; edits are dated notes and citation respellings
    #14279 the 61 reproduced exactly as distinct full paths excluding dot-directories; repair surface 96 mentions / 64 paths, 28 already annotated (ADR-0004's 2026-08 notes cover §2/§5 but not its References Code: line — hence ADR-0004 in the diff, correctly); 27 rewritten to current paths, 6 respelled with the corpus's own objectui: cross-repo anchor (count 18 → 24 on the branch, form already used by ADR-0080), the rest kept with a dated Path note carrying the evidence commit
    #14193 ADR-0113 :19 and :67 now read At decision time (2026-07-30) … Superseded by this ADR's P0, both re-anchored sql-driver.ts#createColumn (symbol, not line); the record's own #13556 anchor note gains a Resolved paragraph
    gate node scripts/check-adr-symbol-anchors.mjs run by this seat in a throwaway worktree at the branch head: exit 0 — 1873 anchors across 134 records resolve, 21 cross-repo, 0 line anchors survive (the dev's number, reproduced)
    reverse-verification base 96 stale / 68 unannotated → branch 68 stale / 0 unannotated; controls: the ABSENT kernel-manager.ts injected unannotated → flagged; the PRESENT sql-driver.ts (19 mentions) never flagged. ⭐ The control earned its keep — the first coverage rule false-greened (any marker in a preamble excused the whole record); tightened to a note covers a path only if it names it, which then surfaced two uncovered paths in ADR-0025
    conflict merge-tree vs origin/main clean; CI 11 green / 9 running at review, nothing red

    Open questions the dev decided under the ruling — recorded here for the human who merges, none blocking:

    1. A third class the two-way split did not name: 21 of 64 paths never existed at the cited spelling (12 mis-spelled/cross-repo, 8 proposed and never shipped). Treated as deleted/renamed with a note that says plainly no file has ever existed at that path (rewriting a proposed path to whatever exists today would edit the decision's substance under cover of hygiene). Split out in the PR table so a reviewer can overrule per class.
    2. objectui: prefix for the 6 sibling-repo paths (machine-checkable once $OBJECTUI_CHECKOUT is set; soft-skipped info lines in CI today); 2 more left bare because their prose already says (objectui).
    3. ⚠️ ADR-0113's table header still reads Where it lives today while the row beneath is now dated. The verifiable criterion is met by the row; the header fix is a one-line change outside the ruled scope ("annotate the two sites, no decision text"), so the dev correctly did not take it. If the merging maintainer wants it, say so on this PR and the dev takes it in one commit.
    4. ⭐ Session-wide instrument defect, propagated to the seat post: the shared checkout was SHALLOW (history from 2026-05-30), so git log archaeology answered never existed for paths ADR-0004's own note dates to 2026-05-18 — 21 of 64 verdicts would have been wrong. The dev ran git fetch --unshallow (7,255 → 12,516 commits; additive, moves no ref) before recording any verdict. Two more git traps corrected on the way: history simplification hides deletions without --full-history, and a pathspec disables rename detection (protocol.ts read as D, was R099).

    Next: docs/adr/** is governed ⇒ this PR waits for a human merge. On merge → re-fetch, probe ADR-0113's two sites + the 0-unannotated count, strip pm:dispatched + clear assignee on both cards (Fixes closes them, strips nothing).


    Generated by Claude Code

  7. claude commented on Sep 5, 2026

    @claude
    Contributor

    Seat note (13:1xZ) — governed draft PR #15160 went dirty against main (conflict in docs/adr/0082-react-component-contract-governance.md with #15257's 9a33cdd8c). A dev merged origin/main into the branch as a plain merge commit (head 0685220f1); the conflict was a provable both-sides union — main changed WHAT the generator emits, this PR changed WHERE the generator lives (packages/spec/scripts/…) — and the merged ADR-0082 differs from main's copy by exactly this PR's three path hunks. No ADR content decision was taken. Seat re-read: three-dot diff still 33 files, +106/−40, all under docs/adr/; sound bare-clone probe → EXIT=0 against 99a5bc674. Still a draft for human merge; reviewers os-zhuang + hotlong now requested (the draft had none). Nothing armed.


    Generated by Claude Code

  8. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 34005012908 · trigger schedule

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions