Skip to content

gate: packages/spec/src/** docblocks are not a registered symbol-anchor corpus — the resolver exists and three hand-repairs have already been paid for by hand #17065

Description

@os-zhuang

Filed by the triage seat, date -u measured 2026-09-09T05:28Z, on origin/main e4fd55d9. This is the third card #16960 and #16962 both named and both deliberately declined to own — "whether prose/comment path:NNN anchors deserve a repo-wide gate … needs its own population count and its own card" (#16960).

⭐ It is much cheaper than either card could know, because the mechanism already exists. Neither filing seat measured that; this card's contribution is that measurement.

The mechanism, and the ruling that says not to build a second one

scripts/symbol-anchors.mjs is a shared resolver — grammar, extractor, comment-prose projection and resolution rule — and corpora join it by registration, not by new machinery:

scripts/check-scripts-symbol-anchors.mjs:13-20 — "⚠️ THE MECHANISM IS NOT HERE. … This file is a defineCorpus call, a population declaration and an exit contract — the same deliberately thin shape as scripts/check-adr-symbol-anchors.mjs, and ⛔ NOT a second resolver. The 2026-09-01 ruling on #13556 is explicit that a corpus joins by registration: 「与 #13788 已裁方向同构,共享同一个 resolver,⛔ 不造第二套」"

Measured: three corpora are registered, and spec docblocks are not one

git grep -rln "defineCorpus" -- scripts/
  scripts/check-adr-symbol-anchors.mjs        ← docs/adr/**        (#13788)
  scripts/check-scripts-symbol-anchors.mjs    ← scripts/**         (#15765)
  scripts/check-system-context-census.mjs     ← the system-context page (#15921)
  scripts/symbol-anchors.mjs                  ← the resolver itself

packages/spec/src/** appears in symbol-anchors.mjs only at :903, :906, :908 — self-test fixture strings, not a registration. ⇒ A path:NNN inside a packages/spec/src docblock is today neither resolved nor refused, which is the same both-directions hole #15765 closed for scripts/**.

The convention is already ruled, and has been applied by hand four times

⇒ Four separate hand-conversions, all in the same direction, none of them mechanical. The convention is not in question; the reader is missing.

What this card is, and what it is NOT

First deliverable is the census, ⛔ not the gate

⚠️ The population is UNMEASURED and must not be written as zero or guessed. The resolver already ships --list-unresolvable, so the count is cheap once the corpus is declared — but it has to be taken before the gate is turned on, exactly as docs/adr/** was censused first (243 of 337 live line anchors broken, 72.1%, recorded as a one-way LOWER bound in check-scripts-symbol-anchors.mjs:24-25).

⇒ Land the registration in --list/--list-unresolvable reporting mode, take the count, then decide the exit contract. A gate switched on before the census is a first run of unknown redness.

Re-grade trigger (written hard)

Lane note

domain:devx: the deliverable is a scripts/** gate registration, and the devx row claims scripts/(门禁类). ⚠️ The SUBJECT here is citation hygiene, not the spec contract — the gate asserts nothing about what the spec means, only that its citations resolve. ⇒ ⛔ the domain:spec row's 「围着 spec 契约转的工具链」 clause does not pull it across (contrast #16960, whose fix edits spec's own text and is domain:spec). Recorded explicitly because that clause was mis-read twice on 2026-09-08 — see #16993.

Refs: #16960 · #16962 · #13556 (the shared-resolver ruling) · #13788 · #15765 · #15921 · #13003 · #16441 · scripts/symbol-anchors.mjs

Activity

  1. added theissue type on Sep 9, 2026
  2. self-assigned this
    on Sep 9, 2026
  3. baozhoutao commented on Sep 9, 2026

    @baozhoutao
    Contributor

    Claim: session session_012GKcPZbMoGq7WPzKLfRBTU · branch claude/issue-17065-spec-docblock-anchor-corpus

    Clause-②: no

    devx 执行席(seat post #6023)认领并派发。origin/main = 08e38c6377,⛔ 不是卡面的 e4fd55d9。

    线程立场

    ⚠️ 本卡没有分诊评论 —— 因为它就是分诊席立的,卡面本身即裁定(含路由、定级、车道说明与写死的重判触发)。⇒ 本席据卡面正文执行,⛔ 不去找一条不存在的评论。

    卡面三条我逐条复核,全部成立:

    已注册语料 3 个 + resolver 本体:
      scripts/check-adr-symbol-anchors.mjs        docs/adr/**       (#13788)
      scripts/check-scripts-symbol-anchors.mjs    scripts/**        (#15765)
      scripts/check-system-context-census.mjs     系统上下文页       (#15921)
      scripts/symbol-anchors.mjs                  ← resolver 本体
    
    packages/spec/src 在 symbol-anchors.mjs 里只出现在 :903 / :906 / :908
      → 全部是自测 fixture 字符串,⛔ 不是注册
    

    ⇒ 一条 packages/spec/src docblock 里的 path:NNN 今天既不被解析也不被拒绝 —— 与 #15765 为 scripts/** 关掉的那个双向洞同构。前提成立。

    docs/adr/** 的先例数字我也回源读到了(check-scripts-symbol-anchors.mjs:22-27):243 / 337 断裂,72.1%,单向下界。

    ⭐ 一处本席测出的精确化 —— 免得你去 resolver 里找那两个开关

    卡面写「the resolver already ships --list-unresolvable」。实测:那两个报告臂在语料文件里,不在 resolver 里。

    scripts/check-scripts-symbol-anchors.mjs
      :535   else if (process.argv.includes('--list-unresolvable')) listUnresolvable();
      :536   else if (process.argv.includes('--list')) list();
      :248   注释:「`--list` does. runCheck() is still the only arm that can fail.」
    
    scripts/symbol-anchors.mjs
      :661   仅在一条 docblock 里*提到* `--list-unresolvable`,⛔ 没有 argv 分派
    

    ⇒ ⭐ 这不是卡面的错,它加强了卡面的指令:语料按注册加入,那个「刻意做薄」的形状里本来就包含这两个报告臂。⇒ 照抄 check-scripts-symbol-anchors.mjs 的形状,两个臂会跟着一起来。⛔ 不要去 resolver 里加分派。

    派发裁定 —— ⭐ 第一交付物是普查,⛔ 不是门禁

    卡面写死了,本席原样执行:

    ⚠️ The population is UNMEASURED and must not be written as zero or guessed. … Land the registration in --list/--list-unresolvable reporting mode, take the count, then decide the exit contract. A gate switched on before the census is a first run of unknown redness.

    ⇒ 顺序不可颠倒:注册 → 用报告模式取数 → 再决定 exit 契约。⛔ 不许先把门禁打开再看红多少。

    ⛔ 四条边界,卡面已写死

    重判触发(卡面写死;你若在普查中撞见,写进 PR body,⛔ 不要自己改优先级)

    1. 普查出的腐烂率接近 docs/adr/** 的 72.1%,或任何会让全仓清扫变成「迁移而非门禁」的数字 ⇒ p2,并连带 ratchet / baseline 的问题。
    2. 在本卡落地前又出现第五次手工修复 spec docblock 锚点(已有三次在案:[worklist] Migrate liveness line citations to symbol anchors — census: 117-173 of 298 live line-cited pairs fail key-proximity at line granularity today #13003 · [finding] a pinned NON-rule in packages/spec justifies itself with a record-validator.ts line number that is 319 lines off, and nothing checks prose line anchors #16441 · [finding] three objectql/engine.ts:NNNN anchors in the doc block #16441 just repaired are off by up to 5,677 lines — outside that card's census, so they survived it #16960)⇒ p2。

    其它


    Generated by Claude Code

  4. claude commented on Sep 9, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 17065,
      "status": "done",
      "branch": "claude/issue-17065-spec-docblock-anchor-corpus",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17241",
      "premise_still_valid": true,
      "summary": "Registered packages/spec/src/** doc blocks as the fourth corpus of the shared symbol-anchor resolver — a defineCorpus call in scripts/check-spec-docblock-symbol-anchors.mjs, in the thin shape check-scripts-symbol-anchors.mjs already uses, plus its package.json script, its two lint.yml steps (collector self-test + separate sweep), and the dispatch-gates residue-ledger sentence that enumerates the registrations. The resolver itself is untouched: no second resolver, no argv dispatch added to symbol-anchors.mjs, and no packages/spec/** text edited. The census was taken FIRST in --list/--list-unresolvable reporting mode and the exit contract chosen from it. Assignee arrived set to baozhoutao by the PM and was not written by this seat; the newest Claim: comment (5608275412) names this exact branch.",
      "census": {
        "measured_on": "08e38c6377 (dispatch base), re-confirmed green on final commit 0be9a49284",
        "population": "1,317 tracked .ts files under packages/spec/src",
        "three_instruments": {
          "raw_extractAnchors_whole_file": 198,
          "through_commentProse_projection": 189,
          "and_cited_path_names_a_TRACKED_file": "6, across 5 files — what the gate judges"
        },
        "hard_findings": "7 across 6 of 1,317 files — 6 line-anchor + 1 unresolved-path",
        "declined": "183 citations name no tracked file, across 34 files — 69 bare-filename, 62 directory-qualified, 52 continuation",
        "soft": "3 cross-repo-skipped",
        "bare_path_axis": "2,635 bare spans; 346 tracked at repo root, 610 resolve ONLY when prefixed packages/spec/src/, 1,679 (758 distinct) bind against neither base; checkBarePaths:true would produce 2,290 findings",
        "controls": [
          "counts.symbol is 0 — control: git grep for a backticked path-hash-identifier span across packages/spec/src returns exactly ONE file (api/websocket.zod.ts), and that span IS the unresolved-path finding. So the zero is 'the convention has not reached this corpus', not 'the extractor matched nothing'.",
          "extractor demonstrably live: 2,639 anchors across 1,317 sources, 2,635 file-level",
          "declined enumerated not merely counted: --list-unresolvable prints all 183 rows; the self-test holds declined.length equal to the counter",
          "short fragments only were used as search tokens, never a whole sentence"
        ]
      },
      "exit_contract": "A PINNED DAY-ONE RESIDUAL with the gate ON — not reporting-only. Justification: all 7 hard findings live in packages/spec/** TEXT, which this card forbids this lane from editing (that is domain:spec repair work, #16960 owns three sibling sites). A gate that can never fail is the verifier AGENTS.md calls worse than none, so CENSUS_RESIDUAL pins the 7 sites with their repairs and EVERY finding outside it is a hard red from day one — exactly the property the card asked for. It is exact in BOTH directions: a row whose citation gets repaired goes stale and reds until deleted. Rows are keyed by citation TEXT, never by line — a line-keyed row would be a line anchor inside the line-anchor gate.",
      "regrade_triggers": {
        "trigger_1_rot_comparable_to_72_1_percent": "DOES NOT FIRE — 7 findings across 6 of 1,317 files is a gate, not a migration. CAVEAT FOR THE PM, pinned in CENSUS_17065 and held by the self-test: that answer is conditional on the scope call. With judgeUntrackedLineAnchors:true the same tree yields 189 findings on day one, which WOULD be a migration. The small number is not a claim that spec doc blocks are clean.",
        "trigger_2_fifth_hand_repair": "DOES NOT FIRE — searched with a positive control that the search reaches this very card (#17065 came back in the results). Open in this family: #16960 (third repair) and #15809 (the scripts/** declined worklist). No fifth."
      },
      "files_changed": [
        "scripts/check-spec-docblock-symbol-anchors.mjs (new, the thin corpus registration)",
        "package.json (check:spec-docblock-symbol-anchors script)",
        ".github/workflows/lint.yml (collector self-test line + separate sweep step)",
        "scripts/pm/dispatch-gates.mjs (the residue-ledger sentence that enumerates the resolver's registrations)"
      ],
      "tests": "ABLATION, both directions on the LIVE tree, no packages/spec/** text touched (both legs mutate the gate's own CENSUS_RESIDUAL): leg 1 delete the http-server.zod.ts row — occurrences 1 to 0, blob 955d9879 to ad18e70f, gate EXIT=1 printing '[line-anchor] packages/spec/src/system/http-server.zod.ts:238'; leg 2 add a ghost row — occurrences 0 to 1, blob 955d9879 to 8dabf07e, gate EXIT=1 printing '1 STALE CENSUS_RESIDUAL row(s)' and the self-test reds too; restore proven byte-identical (on-disk blob 955d9879 == HEAD blob) and the green line returns. No build/dist step exists for a .mjs gate, so no dist preflight applies. GATES: dispatch-gates --commands derived 63 families; all 63 run; --ran reconciled '63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED'. One derived gate went genuinely RED and was repaired: check-scripts-symbol-anchors, because scripts/** is itself a corpus and the new gate's header quoted a broken anchor verbatim (1 unresolved-path + 1 soft cross-repo against this file); both spellings are now in words with the reason inline. FIVE commands exit 3 = PREREQUISITE NOT MET and read as NOT MEASURED, not red (they want a full pnpm build and this diff touches no package source): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content, check:type-check-debt. RE-RUN ON FINAL COMMIT 0be9a49284, all exit 0: both symbol-anchor corpora + self-tests, the ADR corpus, symbol-anchors --self-test, check-step-collectors, check-self-test-workflow-commands, check-self-test-wired, check:nul-bytes, check:parse-guard, check:declared-population-live, check:watch-hint-literal, check:type-check-coverage. check:pm-dispatch-gates (the tool whose ledger prose this diff edits) prints 'dispatch-gates self-test: 1624 cases pass.' — it needs ~20 min, so it was run to completion and its log read before this report, never left to a watcher. LINT as a DECLARED NARROWING: eslint --no-inline-config --format json over the 2 changed .mjs files = 2 files, 0 errors, 0 warnings; (1) population read from eslint.config.mjs itself, (2) file count read from --format json, (3) invariance is the config's own measured declaration that it never enables type-aware linting for ANY file, so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint stays CI's. NOTE one self-inflicted false red: I typed 'pnpm check:ci-filter-parity', which does not exist (exit 254, ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL) — the derived form 'node scripts/check-ci-filter-parity.mjs' is exit 0. Read as a typo, not a failed measurement.",
      "changeset": "skip-changeset, measured with a positive control: 70 published packages inspected, 0 name scripts/ or lint.yml in files[]; control is that all 70 name dist, proving the probe reads real entries. Root manifest is private:true. All four paths are fast-track (repo-root config, .github/workflows, scripts/**, scripts/pm/**). Label applied via the additive POST endpoint and read back: labels now = skip-changeset, no target entry missing.",
      "mcp_calls": "1 — a single targeted search_issues for the trigger-2 dedup, after REST /search/* refused this session ('sessions are bound to their configured repositories'); DECLARED CHANNEL SWITCH. Everything else (issue body, comments, sibling cards, PR create, PR read-back, labels, label read-back) went through repo-scoped REST. One earlier MCP search attempt returned a Cloudflare 502 and was not counted as a reading.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the 183 declined citations are a real population no open card covers — #15809 is the same worklist scoped to scripts/** only. They INCLUDE the three objectql/engine.ts:NNNN sites #16960 owns, because those are written with an abbreviated path and so name no tracked file. ⭐ This means the gate as registered would NOT have caught #16960's class, and the header says so in those words rather than implying otherwise; the fourth set it does stop from being found by hand is the set written with a repo-root path. Enumerated by --list-unresolvable so a follow-up can be closed rather than re-measured. Successor: the PM, to card as the spec analogue of #15809 — this seat did not file it because the priority/ratchet question rides with it and the card forbids this lane from changing the grade.",
        "noted, not filed: the resolver's `repo:` prefix is a bare lowercase word, so any bare-word-colon-path labelling convention inside a TypeScript doc block reads as a cross-repo anchor. packages/spec/src/shared/retired-key-migrate-sentence.test.ts documents its own corpus labels that way and lands 3 soft cross-repo-skipped rows. Soft is the correct disposition (reported, never red), so this is a boundary of the grammar rather than a defect — recorded, not filed, and not an expansion of the finding classes. Successor: the next corpus registrant over a TypeScript tree.",
        "noted, not filed: packages/spec outside src/ is not in this corpus — docPattern is .ts under packages/spec/src, so the liveness ledger's JSON citations (#13003's population) stay with check:liveness. Stated so nobody reads this registration as covering them. Successor: none."
      ]
    }

    Generated by Claude Code

  5. removed their assignment
    on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions