Skip to content

finding: five more gates genuinely read a repo-root instruction file and still score silent for it — the root-file class that #9964 closed for only one gate #9979

Closed

Description

@os-warren

Sibling of #9964, found while measuring it. That card fixed check:pm-skill-ratchet alone; the measurement showed the same hole open in five other gate families, and the fix for each is now a one-line declaration with a landed precedent.

The measurement

Measuring the option-2 extractor widening (114 families x 6326 tracked files) enumerated every (family, root file) pair that a bare top-level *.md literal would create. Nine of the seventeen are genuine — the gate really reads that repo-root file from a module-body literal — and all nine except the ratchet's are still invisible to dispatch-gates:

gate root file it really reads where the literal lives
check:pm-skill-id-lint AGENTS.md EXTRA_FILES = ['.claude/agents/os-dev.md', 'AGENTS.md']
check:pm-governed-merges AGENTS.md, CLAUDE.md GOVERNED_SURFACES rows agents-md / claude-md (exact:)
check:pm-governed-prose AGENTS.md register entry path: 'AGENTS.md'
check:docs-audit-scope AGENTS.md const AGENTS_REL = 'AGENTS.md'
check:required-contexts AGENTS.md INSTRUCTION_SURFACES entry file: 'AGENTS.md'
check:doc-anchors README.md, ARCHITECTURE.md const EXTRA_SOURCES = ['README.md', 'ARCHITECTURE.md']

So an AGENTS.md-only card today derives one gate where six genuinely read the file, and a README.md or ARCHITECTURE.md card still derives zero while check:doc-anchors is REQUIRED in lint.yml and is the repo's only fragment coverage.

check-doc-anchors.mjs already says so about itself, in its own docblock: "README.md or ARCHITECTURE.md still has to reach this gate by judgment". That line was written before the declaration form existed.

Why this is now cheap

The extractor is deliberately closed to bare top-level filenames and should stay closed — re-measured under #9964, widening it costs 17 new pairs of which 8 are fabricated (gates spell README.md / CHANGELOG.md as basenames they join with a package directory), so a README.md card would gain six leads of which five are false. That refusal is correct and should not be revisited.

What changed is that #9964 established the per-gate escape hatch: declare the root-file population as the subtree spelling (AGENTS.md/**), which extractWatchHints already accepts and collapseHint reduces back to the single file. Measured cost of the ratchet's declaration: +1 watch-hint pair, one family, one file, zero fabricated. Each gate above is the same one-line change plus a self-test pin.

Scope note

Deliberately not folded into #9964's PR: those are five other gates, so it is a different file surface and a different set of gate self-tests — outside that card's claimed files. Filing it rather than fixing it in passing.

Each row is independently landable, so this may be worth splitting per gate at triage; check:doc-anchors is the highest-value one (REQUIRED, unique coverage, and its own docblock already flags the gap).


Generated by Claude Code

Activity

  1. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    首触定级(own-lane,skills 座,session ae0d8bd1-5667-5f58-8ff8-fa22ec154325):pm:queue,S。#9978 已确立的同一声明模式逐门禁复制(每家族一行声明 + 各自自测钉),不动抽取器。

    串行与折叠注记:清单里 check-required-contexts.mjs 是热文件 —— PR #9940(治理面,等人合)在改它,#9678(workflow 接线 + 头部注记)串行其后。本卡与 #9678 是折叠候选(同文件、同批小改):#9940 落地解锁时按折叠五门审一次,过则并单派发(共享分支、逐卡 commit、多 Fixes),不过则 #9678 → #9979 串行。其余五个文件(pm-skill-id-lint / governed-merges / governed-prose / docs-audit-scope / doc-anchors)当前空闲,若拆开派发可先行。


    Generated by Claude Code

  2. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Claim: session ae0d8bd1-5667-5f58-8ff8-fa22ec154325 · family dispatch #9678+#9979 · shared branch claude/family-9678-9979-required-contexts

    首触定级 + 方向(own-lane):pm:queue→随族派发。方向 = 逐门套用 #9978 已落地的声明模式:每个真读根指令文件的门禁在自身源码加 <file>/** 形声明(dispatch-gates 抽取器现认的写法),各按该文件自测惯例钉住(声明⊆真实人口、不虚报);零 extractor 改动。doc-anchors 的「by judgment」注记可改指声明,覆盖范围不动。折叠依据与排除清单见 #9678 claim。


    Generated by Claude Code

  3. self-assigned this
    on Aug 19, 2026
  4. added a commit that references this issue on Aug 19, 2026
  5. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Family dispatch report for #9678 + #9979 — same report posted on both cards; PR #10011 (draft).

    {
      "family": [9678, 9979],
      "status": "done",
      "branch": "claude/family-9678-9979-required-contexts",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10011",
      "pr_state": "draft",
      "head": "9b479c512",
      "premise_still_valid": true,
      "cards": {
        "9678": {
          "status": "done",
          "narrowed": false,
          "commits": [
            "ab5f3bfba ci(#9678): give --verify-required-set a standing scheduled caller",
            "9b479c512 fix(#9678): the patrol wiring pins report their own verdict when it is missing"
          ],
          "files": [
            ".github/workflows/required-set-patrol.yml (new)",
            "scripts/check-required-contexts.mjs (header note + wiring self-test)"
          ],
          "half_A": "New scheduled workflow required-set-patrol.yml. Cron '23 4,16 * * *' (twice daily) + workflow_dispatch + a pull_request run scoped to the patrol and the script it calls, per half-state-patrol.yml's posture. permissions: contents: read only. Findings NEVER fail: a completed sweep exits 0 whether it found 0 or N. The job fails ONLY on cannot-run/cannot-deliver; exit code captured with no pipe in between so the script's own 0-swept / 2-environment split survives. NEVER required and structurally so: no merge_group trigger, no REQUIRED_CONTEXTS row, both machine-pinned.",
          "report_destination": "Run log + run step summary, and nowhere else. Decided and stated in the workflow header: a NEW tracker is refused, and the one existing anchor (#9857) is owned end-to-end by half-state-patrol's generator, so a second writer would make each patrol silently discard the other's findings. Stated cost: a completed sweep that finds drift exits 0 and shows a green tick, so the run emits a ::warning:: annotation (annotations render on the Actions run list) keyed on the ABSENCE of the report's own clean mark — failing toward a false alarm, never a false all-clear. Routing findings to a durable surface is a governed-surface change and was left alone.",
          "half_B": "Header note in scripts/check-required-contexts.mjs: the live half is NOT runnable from a dev seat, with both measurements quoted. The pre-existing 'the ruleset answers 200 to an ordinary agent seat' paragraph is corrected in place rather than deleted (it was true for the seat class that measured it), and the usage block's now-misleading 'the same, from an agent container' line is replaced."
        },
        "9979": {
          "status": "done",
          "narrowed": false,
          "commits": [
            "95c175de2 chore(#9979): five more gates declare the repo-root file they really read"
          ],
          "files": [
            "scripts/pm/check-skill-id-lint.mjs        -> ROOT_FILE_WATCH_HINTS = ['AGENTS.md/**']",
            "scripts/pm/check-governed-merges.mjs      -> ['AGENTS.md/**', 'CLAUDE.md/**']",
            "scripts/pm/check-governed-prose.mjs       -> ['AGENTS.md/**']",
            "scripts/docs-audit/check-audit-scope.mjs  -> ['AGENTS.md/**']",
            "scripts/check-required-contexts.mjs       -> ['AGENTS.md/**']",
            "scripts/check-doc-anchors.mjs             -> ['README.md/**', 'ARCHITECTURE.md/**'] + the 'by judgment' docblock repointed",
            "scripts/pm/dispatch-gates.mjs             -> SELF-TEST ONLY (18 new cases); zero extractor / hintCovers changes"
          ],
          "note": "Every declaration is provenance, never a lookup key: each gate opens its real constant and the glob spelling appearing THERE would send it reading a path that does not exist — silently in doc-anchors' case, since existsSync filters the extra sources out. Both halves pinned per file (declaration covers the real population; nothing declared is a value the gate looks up). doc-anchors COVERAGE unchanged: 251 fragment links across 399 sources, identical before and after."
        }
      },
      "derivation_before_after": {
        "command": "node scripts/pm/dispatch-gates.mjs FILE",
        "AGENTS.md": "1 -> 7 (check:pm-skill-ratchet was the only one; now + docs-audit-scope, pm-governed-merges, pm-governed-prose, pm-skill-id-lint, required-contexts, and the new required-set-patrol.yml family from #9678)",
        "README.md": "0 -> 1 (check:doc-anchors, REQUIRED in lint.yml)",
        "ARCHITECTURE.md": "0 -> 1 (check:doc-anchors)",
        "CLAUDE.md": "0 -> 1 (check:pm-governed-merges)",
        "LICENSE (control)": "0 -> 0",
        "examples/AGENTS.md (control)": "0 -> 0",
        "totals": "114 families across 26 workflow files -> 115 across 27; the added family is the #9678 patrol"
      },
      "self_test_counts_before_after": {
        "scripts/pm/dispatch-gates.mjs": "314 -> 332 cases",
        "scripts/check-required-contexts.mjs": "115 -> 124 assertions (119 after #9678's first commit, 123 after #9979, 124 after the ablation fix)",
        "scripts/pm/check-governed-merges.mjs": "77 -> 81 assertions",
        "scripts/pm/check-governed-prose.mjs": "24 -> 28 cases",
        "scripts/docs-audit/check-audit-scope.mjs": "22 -> 24 cases (its sibling affected-docs untouched at 262)",
        "scripts/pm/check-skill-id-lint.mjs": "10 -> 14 cases",
        "scripts/check-doc-anchors.mjs": "17 -> 22 assertions (this file's self-test prints no case counter; counted from source)"
      },
      "gates": {
        "union_source": "node scripts/pm/dispatch-gates.mjs with NO path arguments, run at final head 9b479c512 — 8 paths vs merge base 6de17ea93, 15 families matched. Exit codes captured by redirect-then-$?, never across a pipe.",
        "verdict_lines": [
          "check:cross-package-test-inputs EXIT=0 — 'All 52 self-test cases passed.' / 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'",
          "check:doc-anchors EXIT=0 — '✅ check-doc-anchors: 251 internal #fragment link(s) across 399 source file(s) all resolve to a real heading'",
          "check:docs-audit-scope EXIT=0 — '✓ check-audit-scope self-test: 24 cases pass.' / '✓ docs-accuracy-audit scope is in sync with content/docs/: 180 hand-written doc(s).'",
          "check:node-version EXIT=0 — 'check-node-version: OK (30 setup-node step(s) across 27 workflow(s), all on Node 22).'",
          "check:pm-dispatch-gates EXIT=0 — '✓ dispatch-gates self-test: 332 cases pass.'",
          "check:pm-governed-merges EXIT=0 — '✓ check-governed-merges --self-test: 81 assertions (...)'",
          "check:pm-governed-prose EXIT=0 — '✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.'",
          "check:pm-skill-id-lint EXIT=0 — '✓ check-skill-id-lint: 15 file(s) clean (pattern /#[0-9]{3,}/g).'",
          "check:required-contexts EXIT=0 — '✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491).'",
          "check:shard-attestation EXIT=0 — '✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).'",
          "check:workflow-status-functions EXIT=0 — 'check-workflow-status-functions: OK (scanned 27 workflow file(s), 46 job(s), 24 job-level if: expression(s); 9 read needs.*.outputs.*, all naming a status function).'",
          "check:nul-bytes EXIT=0 — 'check-nul-bytes: OK (scanned 6341 text file(s) -- 6341 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'",
          "node scripts/check-cross-package-test-inputs.mjs EXIT=0 — same verdict as its pnpm form",
          "node scripts/check-shard-attestation.mjs EXIT=0 — '✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).'",
          "node scripts/docs-audit/check-affected-docs.mjs EXIT=0 — completed, printed its unreachable-row census",
          "node scripts/check-required-contexts.mjs [required-set-patrol.yml family] EXIT=0 — the offline pin, same verdict as check:required-contexts"
        ],
        "workflow_lint": "NO actionlint exists in this repo — grepped lint.yml and package.json. Its workflow gates are check:workflow-status-functions, check:node-version, check:shard-attestation and check:required-contexts, all listed above and all green. Additional mechanical validation is in reverse_verification (iii)."
      },
      "reverse_verification": {
        "predictions_written_first": "Recorded to scratchpad BEFORE any ablation ran; five numbered predictions.",
        "(i) doc-anchors declaration emptied": {
          "predicted": "README/ARCHITECTURE derivation returns to zero; check:doc-anchors reddens in its SELF-TEST not its sweep; dispatch-gates reddens on the two positive halves only, negatives stay green.",
          "observed": "All three, exactly. 'No check family names the given paths in its own source' for both files; '❌ check-doc-anchors --self-test: every extra source declares a root-file watch hint: README.md, ARCHITECTURE.md vs ' (empty right side); '✗ dispatch-gates self-test: 2 of 332 case(s) failed' naming exactly the two doc-anchors positive halves.",
          "direction_note": "NOT uniform, and predicted as such: dispatch-gates' self-test reports MORE failures while README.md's derivation reports FEWER families.",
          "restored": "git checkout HEAD -- ; both gates re-run green (332 cases, 251 links)."
        },
        "(ii) patrol workflow deleted": {
          "predicted": "the new #9678 presence assertions redden by name; AGENTS.md derivation drops 7 -> 6.",
          "observed_first_run": "Derivation dropped to 6 as predicted. The assertions did NOT report — the block died on an uncaught ENOENT, so the 'exactly one workflow' failure was recorded and never printed, every later assertion never ran, and !/merge_group/ on an unread file is vacuously TRUE (a false green about a workflow that does not exist). That is a real defect in code this branch added; the ablation is what found it.",
          "fix": "9b479c512 — existsSync guard, absence is its own named assertion, each downstream case carries patrolPresent.",
          "observed_after_fix": "'✗ check-required-contexts --self-test — 4 failure(s)', each naming its own condition (exactly-one-caller / standing-caller-missing / no-merge_group / clean-mark-coupling). No throw.",
          "restored": "file moved back, self-test green at 124 assertions, git status clean."
        },
        "(iii) workflow validated mechanically (Actions cannot run locally)": {
          "yaml": "parses under the repo's own `yaml` package — 3 triggers, permissions {contents: read}, 1 job, 6 steps, merge_group absent.",
          "shell": "all four run: blocks pass `bash -n`.",
          "annotate_step_executed": "the step's real bash was run against real rendered reports from judgeRequiredSet: clean -> '::notice::required-set sweep: the live required set and REQUIRED_CONTEXTS agree in both directions.'; drift -> '::warning::required-set sweep found a registry-vs-live DISAGREEMENT. ...'"
        },
        "(iv) the #9678 defect class reproduced here (the card's own half)": {
          "bare": "node scripts/check-required-contexts.mjs --verify-required-set -> EXIT=2, 'required-set sweep: NOT VERIFIED — GET https://api.github.com/repos/objectstack-ai/objectstack answered HTTP 401', with the proxy hint printed.",
          "with_documented_remedy": "NODE_OPTIONS=--use-env-proxy ... -> EXIT=2, '... answered HTTP 403', and the proxy hint correctly goes SILENT — so a dev sees a bare 403 with no explanation of its seat class. That is the classified NOT VERIFIED the card asked for, and it is the container class, not the tree."
        }
      },
      "labels_read_back": {
        "method": "union write via issue_write (labels is a full PUT), then read back via search_pull_requests fields=[labels] — issue_read get_labels errors on PR numbers ('Could not resolve to an Issue with the number of 10011'), and pull_request_read get returns no labels field.",
        "before_write": ["ci/cd", "size/l"],
        "written": ["ci/cd", "size/l", "skip-changeset"],
        "after_read_back": ["ci/cd", "size/l", "skip-changeset"],
        "verdict": "skip-changeset present, no bot label lost. No changeset file: nothing here is published."
      },
      "exclusion_list_compliance": {
        "extractor_hintCovers": "untouched — dispatch-gates changes are self-test cases only; the bare-*.md widening stays refused and the new negative halves pin that these declarations bought no part of it (examples/AGENTS.md and the cross-root pairs stay unreached).",
        "workflow_never_required": "no merge_group trigger, no REQUIRED_CONTEXTS row, sole caller of the flag — all three machine-pinned, and the pins are shown reddening in reverse verification (ii).",
        "doc_anchors_coverage": "unchanged — 251 fragment links across 399 sources before and after; only the docblock prose and the declaration were added.",
        "governed_surfaces": "nothing under .claude/ or skills/ touched; this family is entirely non-governed."
      },
      "open_questions": [
        {
          "question": "Card #9678's original maintainer ruling (2026-08-18) put the standing caller in the PM round-report checklist under .claude/skills/pm-dispatch/** — a governed surface. The PM's later direction comment converged on A+B (scheduled workflow) and explicitly did not adopt C. I implemented A+B as dispatched, so the round-report line from the earlier ruling does NOT exist. Should it still be added?",
          "options": [
            "A. Leave as-is. The workflow is the standing caller; the round report would be a second, human-cadence caller of the same read, and a dev seat cannot run it anyway (measured 403), so a PM line would say 'read the patrol's run log'.",
            "B. File a follow-up governed-surface card adding one round-report line that POINTS AT the patrol's latest run rather than re-running the sweep — costs the skill-line ratchet one line, and the ratchet's own rule requires a deletion elsewhere or a net reduction.",
            "C. Treat the earlier ruling as superseded by the direction comment and record that explicitly on the card so the next reader does not re-open it."
          ],
          "recommendation": "C, plus A. The direction comment is the later ruling from the dispatching seat and it converged deliberately; the value the round-report line was meant to buy — 'something runs this on a cadence' — is now bought by the workflow, at zero governed-surface cost and zero ratchet cost. B remains available if drift is ever found to sit unread in run logs, but filing it now would spend a governed-surface merge on a line whose content is 'go look at the workflow'. What I did NOT do is silently drop the earlier ruling: it is flagged here for the PM to record on the card."
        },
        {
          "question": "The patrol adds a SECOND dispatch-gates family for the same script — an AGENTS.md card now derives both `pnpm check:required-contexts [lint.yml]` and `node scripts/check-required-contexts.mjs [required-set-patrol.yml]`. dispatch-gates drops flags from a direct invocation, so the second renders without `--verify-required-set`.",
          "options": [
            "A. Accept. half-state-patrol.yml set exactly this precedent (`node scripts/pm/check-half-states.mjs [half-state-patrol.yml]`), the provenance column names the workflow, and running the bare script is the offline pin — correct and green, never misleading.",
            "B. File a finding against dispatch-gates' runnableInvocation for dropping flags that change what a script does."
          ],
          "recommendation": "A, and I did not file B. The rendered command is harmless and correct here, and B would be a general claim about runnableInvocation that I have measured on exactly one instance — the header's own doctrine is that a fabricated or misleading lead must be priced before it is filed. Flagging it here rather than filing it is the honest middle."
        }
      ],
      "out_of_scope_findings": [
        "filed as #10012: dispatch-gates' residue prose still names `README.md` as the live specimen of an unreachable top-level-FILE population — after this PR that is the one root file in the repo that IS reachable, so a reader who tests the claim gets the opposite answer. Rule unchanged and correct; only the example went stale. Labelled `finding` + `domain:skills`, unassigned, no pm:queue. Two remedies with a recommendation are on the card."
      ],
      "cleanup": "worktree /home/user/objectstack-f9678 removed without --force after node_modules deletion; no background processes were started."
    }

    Generated by Claude Code

  6. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    ACCEPT — 随族 PR #10011 复核通过并入队(完整复核记录在 #9678 的 ACCEPT 评论,本卡半的要点:五门禁 + doc-anchors 声明逐文件双向钉、抽取器零改动、派生前后实测 AGENTS.md 1→7 / README·ARCH·CLAUDE 0→1 / 双对照恒 0、排除清单合规)。session ae0d8bd1-5667-5f58-8ff8-fa22ec154325


    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

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions