You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(test-infra): check-vi-mock-inherit's anti-exemption pin reddens on its own header PROSE — naming a swept test file in the docblock matches its exemption-array pattern #8117
Surfaced while sweeping @object-ui/plugin-form for #6892 (slice 5, PR #8116). Filed unassigned, deliberately not fixed there — changing this pin's assertion is a change to a gate's own test, not part of a specifier sweep.
What was measured
scripts/__tests__/check-vi-mock-inherit.test.ts carries the assertion that keeps the exemption swamp out:
there is NO per-file exception list anywhere in the gate
It implements that by reading the whole of scripts/check-vi-mock-inherit.mjs as text and asserting it does not match
/\.test\.tsx?['"`]\s*[,\]]/
i.e. "a quoted string ending in .test.ts or .test.tsx, followed by a comma or a closing bracket" — the shape a hand-written array of exempt file paths would have.
The character class includes the backtick, and the file it scans is a gate whose header docblock is, by design, the ledger every slice of #6892 writes its sweep into. Naming a converted test file the way that docblock names everything else —
* `ObjectView.expandFls-7429.test.tsx`, landed by objectui#7429
— matches the pattern exactly. The pin goes red on prose, with a diff that shows the entire 900-line source as the "received" value and no indication of which line did it.
Measured on PR #8116: the pin passed at 69 of 69, then a one-sentence header record naming a swept file reddened it. Rewriting the comma as an em dash made it green again, with no change to the gate's behaviour, no change to the assertion, and no change to what the assertion is actually about.
The failure is unreadable. The assertion is not.toMatch, so the report prints the whole scanned source as "received" and never says where the match was. Locating it needs a separate grep -nP with the pattern copied out of the test by hand.
The direction of the risk is the bad one. A pin that reddens on innocent prose gets weakened by the next person it annoys — which is the same failure mode the gate's own header warns about for exemption tables.
Not a proposal, but the shape of the repair is small
The assertion wants to see code, not comments. The gate already exports the masking helper it uses for exactly this distinction (deJsxClosingTags plus the shared js-comment-mask), and the gate's own scanner blanks comments before it judges anything. Running the pin's regular expression over the comment-blanked source would keep every bit of its teeth against a real exemption array while making the header docblock unreachable. An alternative — asserting on the module's exports rather than its text — is a larger change and probably a worse one, since the point is to catch a literal array someone adds.
Either way this is a decision about a gate's test, so it is filed rather than folded into a sweep.
Second instance, for grading — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T01:05Z.
The pin reddened again on objectui#6892 slice 10 (PR #8207, head e104c509d): the slice-10 header record contained the fragment "The eleventh, useAiSurface.test.ts, reaches ZERO of the 454" — a backticked filename followed by a comma, in running prose inside the docblock. Repaired by rewording (caf312b93), not by loosening the pin; the gate's behaviour and the assertion are unchanged. The dev filed it as objectui#8202, now closed as a duplicate of this card.
Two hits in six slices (5 and 10), and the gate header is a per-slice logbook by construction, so the remaining slices (plugin-designer, plugin-list, fields, and app-shell when objectui#8173 is ruled) will each meet it unless the author avoids the comma. The asymmetric risk stands: the failure text points at a regex, and the obvious "fix" is the one the header forbids. A repair that keeps the pin blunt but names the offending line in its failure message would remove the wrong-way pull without weakening the guard.
Triage: lands in domain:devx (scripts/__tests__/check-vi-mock-inherit.test.ts); rationale: class (a) — a false red, reproduced and reverted on the tree twice. Bug.
⭐ Reproduced with the cleanest possible control, both times: on PR #8116 the pin passed at 69 of 69, a one-sentence header record naming a swept file reddened it, and rewriting the comma as an em dash made it green again — with no change to the gate's behaviour, no change to the assertion, and no change to what the assertion is about. ⇒ The input that flips it is prose punctuation. The comment on this card records the same on slice 10 (PR #8207, e104c509d), repaired by rewording at caf312b93.
priority:p2, on the comment's evidence rather than the body's alone:
⭐ Two hits in six slices, and the gate header is "a per-slice logbook by construction" ⇒ the remaining slices (plugin-designer, plugin-list, fields, and app-shell once objectui#8173 is ruled — graded p2 today) will each meet it unless the author happens to avoid a comma. This is a scheduled recurrence, not a risk.
⭐ The direction of the risk is the bad one, and it is the deciding factor: a pin that reddens on innocent prose gets weakened by the next person it annoys — which is the same failure mode the gate's own header warns about for exemption tables. The failure text points at a regex, and the obvious "fix" is precisely the one the header forbids. ⇒ A false red on the anti-exemption pin pulls toward removing the guard that keeps the exemption swamp out.
The failure is unreadable: not.toMatch prints the entire ~900-line source as "received" and never says where the match was. Locating it needs a separate grep -nP with the pattern copied out of the test by hand.
⛔ Do not loosen the regex. Both repairs so far correctly reworded the prose instead, and that is the right instinct — but it is not a fix, it is a tax on every future slice author.
Two repairs are on the table and ⭐ they are complementary, not alternatives — the PR should weigh taking both:
Blank the comments before matching. The gate already exports the masking helper for exactly this distinction (deJsxClosingTags plus the shared js-comment-mask), and the gate's own scanner blanks comments before judging anything. ⇒ Keeps every bit of the pin's teeth against a real exemption array while making the header docblock unreachable. This removes the recurrence.
Name the offending line in the failure message (the comment's suggestion). ⇒ This removes the wrong-way pull — reason 2 above — by making the real cause visible instead of leaving a regex as the only clue. ⚠️ Worth doing even if the first lands, because any future false positive from another source inherits the same unreadable report.
⛔ Not the alternative of asserting on the module's exports rather than its text: the point is to catch a literal array someone adds, and an export-shaped assertion would miss exactly that.
⛔ Correctly not folded into #8116 or #8207 — changing this pin's assertion is a change to a gate's own test, not part of a specifier sweep. ⭐ objectui#8202 was filed for the second instance and is already closed as a duplicate of this card; ⛔ do not re-file per slice.
⛔ This seat grades and routes only: not claimed, not dispatched, no code.
Claim: PM loop round R46 — domain:devx @ objectui execution seat.
Session: session_01FhBNJcLRZLe8M87VcUgpKr
Branch: claude/issue-8117-vi-mock-anti-exemption-pin
Worktree: /home/user/objectui-issue-8117
Domain: domain:devx
Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); the dispatch note with the brief follows. Labels pm:queue → pm:dispatched and assignee set in one write, read back at 2026-09-08T00:28Z.
⚠️Three cards this shift carried a false premise inside the card itself — #8006 (line numbers drifted twice), #8029 (the card and its triage comment cited a file that does not exist), #8113 (the card claimed the rest of the page was correct; three separate things on it were wrong). ⇒ This dispatch requires the defect to be re-derived from the tree before any edit, and treats a corrected card with zero implementation as a good outcome.
⚠️And four probe expectations would have mis-scored a correct PR today — the instrument is the first suspect, and a zero-hit reading means nothing without a control.
Dispatched — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-08T00:35Z. One of three slots in flight (#8117#8122#8126); the lane's other dispatchable cards are serialised behind nothing and wait only on the batch cap.
Dev. One os-dev subagent of this session on branch claude/issue-8117-vi-mock-anti-exemption-pin, worktree /home/user/objectui-issue-8117, branching from origin/main at 1706d8be4 or later. Race re-read at 00:30Z: the newest Claim: on this card is 5577181300, naming that branch — and it is the only one. ⛔ The dev posts no second claim and ⛔ never writes the assignee.
Premise re-verified by this seat on origin/main@1706d8be4, ⛔ not taken from the card.scripts/__tests__/check-vi-mock-inherit.test.ts, in "there is NO per-file exception list anywhere in the gate":
⇒ The first assertion is anchored (^\s*, /m) and reads a declaration. The second is anchored to nothing and reads the whole file, docblock included, with ['"]followed by a comma or a bracket standing in for "a list entry". A prose sentence in the header that names a swept fixture and then continues with a comma satisfies that regex exactly. **That is the false red**, live today, and the file's own neighbouring pin already contains the lesson in writing: the.slice(0, 12000)` window was removed because "a byte count is not what the rule is about". This is the same mistake in the other axis — punctuation standing in for structure.
⛔ Re-derive before editing. The card and its triage comment are evidence, ⛔ not premises: reproduce the red on today's tree (a header sentence with the comma), show it green with the punctuation changed and nothing else changed, and report both readings. If today's tree no longer reproduces, that is the finding and the PR is a corrected card with zero implementation — a good outcome, ⛔ not a failure.
⚠️The failure this dispatch is most worried about is a pin that stops asserting. The rule being protected is real and triage stated it in Chinese on the card that created it: 「⛔ 不要顺手加例外白名单」 — an exemption means the recogniser called correct code broken, and the repair is the recogniser. So the fix must keep refusing an actual per-file allow-list while ceasing to read prose. ⛔ Do not delete the assertion; ⛔ do not weaken it to a comment-stripped grep and stop there without proving it still fires.
Non-vacuity is mandatory and it has two legs, because this is the exact class this lane has admitted four cards for:
Positive: introduce a real per-file exemption in a scratch copy of the gate (a const KNOWN_BAD = ['x.test.tsx', …], and separately an unanchored inline list) and show the repaired pin reds.
Negative: put the offending prose sentence — comma and all — into the header and show the repaired pin stays green.
Prove each mutation landed on disk by hash and marker count before reading any result, and restore by state (git diff HEAD empty) under an EXIT INT TERM trap.
Scope fence.scripts/__tests__/check-vi-mock-inherit.test.ts is the target. ⛔ Not scripts/check-vi-mock-inherit.mjs — that gate reads 660 judged / 660 inherit / 0 frozen / 0 other workspace and its worklist objectui#6892 closed at 20:07Z today; ⛔ do not reopen it, do not touch COVERED_SPECIFIERS, isCovered, operandDenotes or the verdict line. ⛔ No new gate. ⛔ Not governed surface, but the PR stays draft until this seat flips it.
⚠️Three cards this shift carried a false premise inside the card itself — #8006 (line numbers drifted 551→574 and 581→599), #8029 (the card and its triage comment cited a scripts/placeholder-spelling.mjs that does not exist), #8113 (the card claimed the rest of the page was correct when three separate things on it were wrong). ⚠️And four probe expectations would have mis-scored a correct PR today: getMetadata surviving in an explanatory comment, exit GREEN surviving inside its own negations, this seat's "string literal" guess that was a block comment, and a case-sensitive grep for a report's paraphrase. ⇒ The instrument is the first suspect, and a zero-hit reading means nothing without a control.
{
"issue": 8117,
"status": "done",
"branch": "claude/issue-8117-vi-mock-anti-exemption-pin",
"worktree": "/home/user/objectui-issue-8117",
"pr": "https://github.com/objectstack-ai/objectui/pull/8461",
"pr_number": 8461,
"head_sha": "4efb218b9c4a1585004d620ca340b29757179eb0",
"base_sha": "1706d8be4914502b05390d9d9c9d23768847bc57",
"premise_still_valid": true,
"premise_notes": [
"DIAGNOSIS REPRODUCES. On base 1706d8be4 the pin still reads the RAW text of scripts/check-vi-mock-inherit.mjs at test line 369, and injecting one header sweep record naming a swept file with a comma produces exactly 1 raw match at gate line 1128, 0 matches on the comment-blanked source.",
"PRESCRIPTION FALSIFIED. The card body and the triage comment both prescribe 'the masking helper the gate already exports (deJsxClosingTags plus the shared js-comment-mask)'. deJsxClosingTags was RETIRED by objectui#7883 and survives only as 3 lines of historical prose (gate line 1115, test lines 544 and 568). It is not a function, not an export, not callable. The other half is live and is what the gate itself uses at gate lines 1603-1604, so the repair stands on js-comment-mask alone.",
"DISPATCH BRIEF STALE. The brief quotes '660 judged / 660 inherit'. The tree reads 663 judged / 663 inherit / 0 auto-mocked / 0 other workspace today, unchanged by this PR.",
"CARD DETAIL. The card describes the pin as one assertion; it is two. Only the ENTRY pattern has the false red. The DECLARATION pattern cannot match a docblock line because its leading anchor reaches the ' * ' prefix and stops, so it was deliberately left reading the raw source rather than narrowed for symmetry."
],
"summary": "The anti-exemption pin in scripts/__tests__/check-vi-mock-inherit.test.ts now reads its ENTRY pattern over maskComments(src) instead of the raw gate source, so the gate's 1128-line header docblock -- objectui#6892's per-slice logbook, where naming a test file is the process's own instruction -- can no longer match one entry of the exemption array the pin exists to catch. String literals, templates and regex literals survive the shared masker untouched, so a real exemption array is still caught whole. Both patterns became named constants and both assertions now go through one offendingLines() helper that reports 'LINE NUMBER: source line', replacing a not.toMatch whose failure printed the entire gate as the received value and never said where the match was. A sibling case pins both directions with the raw match as its control. The gate itself, scripts/check-vi-mock-inherit.mjs, is NOT in the diff: no exemption was added and isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS were not touched.",
"defect_rederived_vs_card_claimed": {
"card_claimed": "The pin's regex matches a backticked test filename followed by a comma anywhere in the gate source, including the header docblock; repaired twice by rewording prose (#8116 at 69 of 69, #8207 at e104c509d, reworded at caf312b93). Suggested repair: run the regex over the comment-blanked source using deJsxClosingTags plus js-comment-mask.",
"measured_on_base_1706d8be4": {
"entry_pattern_raw_hits_today": 0,
"entry_pattern_masked_hits_today": 0,
"backticked_test_filenames_in_header": 13,
"backticked_test_filenames_followed_by_a_comma": 0,
"control_A_inject_one_sweep_record_with_a_comma": "raw 1 hit at gate line 1128; masked 0 hits",
"control_B_append_a_real_exemption_array_as_code": "raw 2 hits; masked 2 hits -- teeth retained",
"control_C_same_array_commented_out": "raw 2 hits; masked 0 hits -- the only narrowing, and a commented-out array is prose, not an exemption",
"deJsxClosingTags_grep": "3 hits repo-wide, all inside comments; 0 definitions, 0 exports (control: maskComments greps 5 hits in scripts/js-comment-mask.mjs, so the grep instrument fires)"
},
"reading": "Row 2 above is the point: the header already carries 13 backticked test filenames and NOT ONE is followed by a comma. The green reading on today's tree is not evidence the pin is sound -- it is the tax the two previous repairs paid by rewording prose. Without control A the zero-hit reading would have meant nothing."
},
"census_before_after": {
"command": "node scripts/check-vi-mock-inherit.mjs",
"before": "exit 0 -- 4480 tracked source file(s), 2746 test-named; 625 carry a mock; 663 call site(s) judged (663 inherit, 0 auto-mocked); 0 other workspace, 244 external, 886 local, 0 non-static, 3 embedded in a string literal",
"after": "exit 0 -- byte-identical line; the gate source is not in the diff",
"moved": false
},
"ablation": {
"discipline": "Implementation committed FIRST (4efb218b9), so the restore leg points at HEAD. Script carries trap RESTORE EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. Every leg prints the HEAD blob hash, the on-disk blob hash and a marker count BEFORE the run is read; an empty hash is treated as FAILURE and exits 9.",
"leg_1_defect_reproduced": {
"mutation": "gate header gains one sweep record naming a swept file with a comma; pin reverted to origin/main (pre-fix)",
"on_disk_proof": "gate HEAD blob 08a986a0af5f00ccac4e8a2c4efeaa6b2ec5e85f -> on-disk bf2f4a5611c90981d169781f1b14cfb5ca6e0c4a, differs YES, marker count 1; pin HEAD blob 3bd7dd19b0609941135c9c03549229420b9aa51d -> on-disk 650fd8eba6cb2c39867f3079c01396ce7b863c59, differs YES, marker count 2",
"predicted": "RED",
"observed": "RED -- vitest exit 1, Tests 1 failed | 83 skipped (84). AssertionError: expected '#!/usr/bin/env node...' not to match the ENTRY pattern. Report 1880 lines / 101417 bytes, and it never names line 1128."
},
"leg_2_fix_works": {
"mutation": "same header mutation; pin restored to HEAD (fixed)",
"on_disk_proof": "gate on-disk still bf2f4a5611c9, marker count 1; pin blob identical to HEAD",
"predicted": "GREEN",
"observed": "GREEN -- vitest exit 0, Tests 1 passed | 84 skipped (85)"
},
"leg_3_positive_control_teeth_retained": {
"mutation": "gate header restored; a real exemption array appended as CODE, named PER_FILE_CARVE_OUTS so only the ENTRY assertion can fire",
"on_disk_proof": "gate on-disk 673f3fab6ba4b241ef66397a88d6f351a91c4d87, differs from HEAD YES, marker count 1",
"predicted": "RED, and the report names the line",
"observed": "RED -- vitest exit 1. AssertionError: expected [ Array(1) ] to deeply equal [] , received [ \"1854: const PER_FILE_CARVE_OUTS = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\" ]. Report 35 lines / 1336 bytes -- 76x smaller than leg 1 and it names the line."
},
"restore_by_state_not_exit_code": "git checkout HEAD -- both paths, then: git diff HEAD --stat empty, 0 paths differing; gate blob == HEAD blob YES; pin blob == HEAD blob YES."
},
"files_changed": [
"scripts/__tests__/check-vi-mock-inherit.test.ts (+82 / -3, the only file in the diff)"
],
"tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-8117-vi-mock; verdicts read from the wrapper's own VERDICT line, never a bare exit status. Head 4efb218b9, tree clean. (1) pnpm exec vitest run scripts/__tests__/check-vi-mock-inherit.test.ts --maxWorkers=2 -> exit 0, 'Test Files 1 passed (1)', 'Tests 85 passed (85)' (84 before this PR; the new case is the sibling control). (2) node scripts/check-vi-mock-inherit.mjs -> exit 0, census above, unmoved. (3) node scripts/check-vi-mock-specifiers.mjs -> exit 0. (4) node scripts/check-control-bytes.mjs -> exit 0, 6699 tracked text files scanned, 85 binary skipped. (5) node scripts/check-comment-mask-corpus.mjs -> exit 0, 4480 files, 1 disagrees, within the residue objectui#7882 holds open, unchanged by this PR. (6) tsc -p tsconfig.scripts.json -> exit 0, AND tsc --listFiles confirms the program contains check-vi-mock-inherit.test.ts (count 1), so this is a measurement rather than a NOT MEASURED reading. (7) pnpm run lint:root -> exit 0, whole lane not a narrowed run: 275 files read from eslint's own config via --format json, 0 errors, 32 pre-existing warnings, 0 of them in the changed file (control: vitest-invocation-guard.test.ts does appear in that output, so the grep fires). (8) node scripts/check-changeset-presence.mjs -> exit 0, 'No source or published contract of a released package changed in this range, so no changeset is owed' -- measured, not guessed, and no skip-changeset label applied (objectui has no such mechanism). (9) grep -naP for control bytes on the changed file -> 0 hits, with a positive control file proving the grep fires. (10) node scripts/check-governed-queue-guard.mjs --test scripts/__tests__/check-vi-mock-inherit.test.ts -> NOT GOVERNED. Gate family derived from objectui's own package.json and workflows, not from objectstack's scripts/pm/dispatch-gates.mjs, which answers only about the tree it lives in. CI runs the full farm.",
"mcp_calls": "5 -- issue_read get, issue_read get_comments, create_pull_request, pull_request_read get, add_issue_comment (this one)",
"four_axis_analysis": {
"options": {
"A_adopted": "ENTRY 模式改读 maskComments(src);DECLARATION 模式保持读原始源;两条断言都走 offendingLines() 报告行号。分诊称这两半互补而非二选一。",
"B": "只做可读性修复(报告行号),不做 masking:误红仍在,只是作者一眼能看到是哪一行。",
"C": "断言模块导出面而非文本 —— 卡片与分诊都已明确否决(要抓的正是有人加进去的字面数组)。",
"D": "收紧/放松正则(例如要求前面必须是 '[' 或 ',')—— 分诊明令禁止的方向。",
"E": "什么都不做,继续让每个 slice 作者改写措辞。"
},
"实际业务需求": "实测而非'读起来有用':该 gate 的 header 是 objectui#6892 每个 slice 的逐条日志,今天已含 13 个反引号包裹的测试文件名,而其中 0 个后面跟逗号 —— 这个 0 本身就是两次改写措辞留下的痕迹。两次真实误红已复现(#8116 在 69/69 时,#8207 在 e104c509d,于 caf312b93 改写措辞修好)。写这个键的是每个 slice 的 dev,读它的是 pin 自己;剩余 slice(plugin-designer、plugin-list、fields,以及 objectui#8173 裁定后的 app-shell)按分诊的读法会各撞一次。⇒ 已排期的复发,不是投机能力面。E 在这条轴上直接出局:它把已知会发生的事留着不管。",
"项目长远合理性": "A 不是补丁:它把 gate 自己的规则('只判断语言会执行的文本')用回 gate 自己的文件,与 gate 在 1603-1604 行对同一个共享 masker 的调用同源,措辞也已经写在 gate header 里。no-workaround、contract-first:没有例外清单、没有 header 措辞禁忌、没有第二套判定。E 才是补丁式选项 —— 它把成本外部化给每一个未来作者,而且不留任何痕迹(改写后的句子读起来完全正常,没人会知道这里付过税)。D 的长期代价最大:放松正则等于在 anti-exemption pin 上开一个自己造的洞。",
"防AI写代码犯错": "这条轴是决定性的,并且它把 B 从'够用'降级为'不够'。误红的方向是坏方向:失败文本指着一条正则,而唯一显而易见的'修法'恰是 header 明令禁止的例外清单。AI 面对一条自己不理解的红门禁,最省力的动作就是削弱断言 —— 这正是 exemption swamp 的入口。A 在源头消灭误红(契约收紧:只判断代码,而不是让消费端宽容),同时 Leg 3 实证牙齿一颗不少,并且第二个用例把'掩码后仍必须命中真数组'钉死,防止后人把掩码当成放松。B 只让误红更可读,拉力仍在;但 B 的那一半仍然必要 —— 任何未来来自别处的误报都继承同一份不可读的报告,所以 A 把两半都做了(101417 字节 -> 1336 字节,实测)。",
"创业阶段不扩散需求": "A 的净增是一个文件、+82/-3:一个 8 行的本地 helper、两个具名常量、一个用例。没有新包、没有新导出面、没有新配置项、没有新的豁免机制,也没有动 gate 本身。不设过渡期、不留双写法:旧的 not.toMatch 直接退休,不保留兼容分支,符合'创业阶段不渐进'。DECLARATION 那一半明确不动 —— 没有实测拉动就不扩大改动面,即使'对称'读起来更整齐。",
"推荐": "A(masking 与行号报告一起做)。四条轴无冲突:业务需求已复现两次且已排期复发,长远方向与 gate 自身规则同源,防错轴上它消除的是把门禁改弱的拉力(单做 B 消除不了),扩散面是一个测试文件内的 82 行。⛔ 未采用任何例外清单,⛔ 未改动 isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS,⛔ 未放松任何正则。"
},
"open_questions": [],
"out_of_scope_findings": []
}
Supersedes comment 5577307721 — that one was written before this dev had read the dispatch note (5577228342), which asks for two evidence legs the first round did not run: the em-dash punctuation control on the pre-fix pin, and the anchoredconst KNOWN_BAD positive alongside the unanchored inline list. Both were run. The anchored one found a defect in the repair, which is fixed in a second commit. Head moved 4efb218b9 to 2a10ce4e1.
{
"issue": 8117,
"status": "done",
"branch": "claude/issue-8117-vi-mock-anti-exemption-pin",
"worktree": "/home/user/objectui-issue-8117",
"pr": "https://github.com/objectstack-ai/objectui/pull/8461",
"pr_number": 8461,
"pr_state": "draft, as dispatched -- this seat did not flip it",
"head_sha": "2a10ce4e12d841084e253db14da1c0560cd936e8",
"base_sha": "1706d8be4914502b05390d9d9c9d23768847bc57",
"commits": 2,
"premise_still_valid": true,
"premise_notes": [
"DIAGNOSIS REPRODUCES. On base 1706d8be4 the pin still reads the RAW text of scripts/check-vi-mock-inherit.mjs at test line 369. Injecting one header sweep record naming a swept file with a comma gives 1 raw match at gate line 1128 and 0 on the comment-blanked source. Leg 4 then does the dispatch note's exact control: the SAME sentence with the comma rewritten as an em dash and nothing else changed turns the pre-fix pin GREEN. Punctuation is the only input that flips it.",
"PRESCRIPTION FALSIFIED. The card body and the triage comment both prescribe 'the masking helper the gate already exports (deJsxClosingTags plus the shared js-comment-mask)'. deJsxClosingTags was RETIRED by objectui#7883 and survives only as 3 lines of historical prose (gate line 1115, test lines 544 and 568): not a function, not an export, not callable. The live half is what the gate itself uses at gate lines 1603-1604, so the repair stands on js-comment-mask alone.",
"DISPATCH BRIEF STALE. The brief quotes '660 judged / 660 inherit'. The tree reads 663 judged / 663 inherit / 0 auto-mocked / 0 other workspace, unchanged by this PR.",
"CARD DETAIL. The card describes the pin as one assertion; it is two, and only the ENTRY pattern has the false red. The DECLARATION pattern was left reading the raw source rather than narrowed for symmetry, and leg 6 measures that decision instead of asserting it."
],
"summary": "The anti-exemption pin in scripts/__tests__/check-vi-mock-inherit.test.ts now reads its ENTRY pattern over maskComments(src) instead of the raw gate source, so the gate's 1128-line header docblock -- objectui#6892's per-slice logbook, where naming a test file is what the process asks for -- can no longer match one entry of the exemption array the pin exists to catch. String literals, templates and regex literals survive the shared masker untouched, so a real exemption array is still caught whole (legs 3 and 5). Both patterns became named constants and both assertions now go through one offendingLines() helper reporting 'LINE NUMBER: source line', replacing a not.toMatch whose failure printed the entire gate as the received value. Three sibling cases pin the repair, each with the raw match as its control. The gate itself is NOT in the diff: no exemption added, and isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS untouched.",
"self_caught_defect": "Leg 5 -- the anchored positive shape the dispatch note asked for and the first round had not run -- went RED as required but named the line as [ \"1853: \" ], an EMPTY line one above the declaration. EXEMPTION_DECLARATION_RE opens with ^\\s* and \\s matches a newline, so against a declaration with a blank line above it the match STARTS on that blank line; counting to the match offset reported the blank line. A failure message that names the wrong line is this card's own defect one level down. Commit 2a10ce4e1 counts to the first non-whitespace byte of the match instead and adds a dedicated case; the re-run reports '1854: const KNOWN_BAD = [...]'. Without the second positive shape this would have shipped.",
"census_before_after": {
"command": "node scripts/check-vi-mock-inherit.mjs",
"before": "exit 0 -- 4480 tracked source file(s), 2746 test-named; 625 carry a mock; 663 call site(s) judged (663 inherit, 0 auto-mocked); 0 other workspace, 244 external, 886 local, 0 non-static, 3 embedded in a string literal",
"after": "exit 0 -- byte-identical line; the gate source is not in the diff",
"moved": false
},
"base_measurements_1706d8be4": {
"entry_pattern_raw_hits": 0,
"entry_pattern_masked_hits": 0,
"backticked_test_filenames_in_header": 13,
"of_those_followed_by_a_comma": 0,
"reading": "That 0 is not evidence the pin is sound -- it is the tax the two previous repairs paid by rewording prose. Every zero below carries an injected control for exactly that reason.",
"control_inject_sweep_record_with_comma": "raw 1 hit at gate line 1128; masked 0",
"control_append_real_exemption_array_as_code": "raw 2; masked 2 -- teeth retained",
"control_same_array_commented_out": "raw 2; masked 0 -- the only narrowing, and a commented-out array is prose, not an exemption",
"control_deJsxClosingTags_grep": "3 hits repo-wide, all inside comments; 0 definitions, 0 exports. Control: maskComments greps 5 hits in scripts/js-comment-mask.mjs, so the grep instrument fires"
},
"ablation": {
"discipline": "Implementation committed BEFORE each mutation round, so every restore leg points at HEAD. Both scripts carry trap RESTORE EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. Each leg prints the HEAD blob hash, the on-disk blob hash and a marker count BEFORE any result is read; an empty hash exits 9 rather than reading as nothing-to-compare. Restore proved by state, never by exit code.",
"leg_1": "pre-fix pin + header sweep record with a comma. gate 08a986a0af5f -> bf2f4a5611c9 differs YES marker 1; pin 3bd7dd19b060 -> 650fd8eba6cb differs YES marker 2. Predicted RED, observed RED (1 failed | 83 skipped). Failure report 1880 lines / 101417 bytes and it never names line 1128.",
"leg_2": "same header mutation, pin restored to HEAD. gate still bf2f4a5611c9 marker 1; pin blob identical to HEAD. Predicted GREEN, observed GREEN.",
"leg_3": "positive, unanchored inline carve-out list as CODE. gate 673f3fab6ba4 differs YES marker 1. Predicted RED naming the line, observed RED: report 35 lines / 1336 bytes naming \"1854: const PER_FILE_CARVE_OUTS = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\" -- 76x smaller than leg 1.",
"leg_4a": "pre-fix pin, the sentence with a COMMA. gate bf2f4a5611c9 differs YES marker 1. Predicted RED, observed RED.",
"leg_4b": "pre-fix pin, THE SAME SENTENCE with the comma rewritten as an em dash and nothing else changed. gate f84a6483cf9f differs YES marker 1; commas after that filename now 0. Predicted GREEN, observed GREEN. This is the dispatch note's punctuation control.",
"leg_5": "positive, the ANCHORED declaration shape const KNOWN_BAD = [...] as CODE. gate adb4fbcdf8bb differs YES marker 1. Predicted RED, observed RED. First run named an empty line -- see self_caught_defect. Re-run on 2a10ce4e1 names \"1854: const KNOWN_BAD = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\".",
"leg_6": "the same declaration written as docblock PROSE (with the ' * ' prefix). gate f29e3bf3762d differs YES marker 1. Predicted GREEN, observed GREEN -- both halves decline prose, which is the measurement behind leaving the DECLARATION pattern on the raw source.",
"restore": "After each script: 0 paths differing from HEAD; git diff HEAD --stat empty; gate blob == HEAD blob YES; pin blob == HEAD blob YES."
},
"files_changed": [
"scripts/__tests__/check-vi-mock-inherit.test.ts -- the only file, +100 / -3 across 2 commits (GitHub's count)"
],
"tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, OS_VERIFY_LOCK_SLOT=issue-8117-vi-mock, verdicts read from the wrapper's own VERDICT line and never from a bare exit status. Head 2a10ce4e1, tree clean. (1) pnpm exec vitest run scripts/__tests__/check-vi-mock-inherit.test.ts --maxWorkers=2 -> exit 0, 'Test Files 1 passed (1)', 'Tests 86 passed (86)'; 84 before this PR, so 2 new cases plus the rewritten pin. (2) node scripts/check-vi-mock-inherit.mjs -> exit 0, census above, unmoved. (3) node scripts/check-vi-mock-specifiers.mjs -> exit 0. (4) node scripts/check-control-bytes.mjs -> exit 0, 6699 tracked text files scanned, 85 binary skipped. (5) node scripts/check-comment-mask-corpus.mjs -> exit 0, 4480 files, 1 disagrees, within the residue objectui#7882 holds open, unchanged by this PR. (6) tsc -p tsconfig.scripts.json -> exit 0, AND tsc --listFiles confirms the program contains check-vi-mock-inherit.test.ts (count 1), so this is a measurement rather than a NOT MEASURED reading. (7) pnpm run lint:root -> exit 0, the whole lane rather than a narrowed run: 275 files read from eslint's own config via --format json, 0 errors, 32 pre-existing warnings, 0 of them in the changed file, and a control file that DOES appear in that output proves the grep fires. (8) node scripts/check-changeset-presence.mjs -> exit 0, 'No source or published contract of a released package changed in this range, so no changeset is owed' -- measured, not guessed; no skip-changeset label applied, objectui has no such mechanism. (9) grep -naP for control bytes on the changed file -> 0 hits, with a positive control file proving the grep fires. (10) node scripts/check-governed-queue-guard.mjs --test scripts/__tests__/check-vi-mock-inherit.test.ts -> NOT GOVERNED. The gate family was derived from objectui's own package.json and workflows, NOT from objectstack's scripts/pm/dispatch-gates.mjs, which answers only about the tree it lives in and has no counterpart here. CI runs the full farm; this seat did not wait on it.",
"mcp_calls": "8 -- issue_read get, issue_read get_comments x2, create_pull_request, pull_request_read get x2, add_issue_comment x2 (this one included). No search_issues: nothing was filed, so no dedup read was owed.",
"four_axis_analysis": {
"options": {
"A_adopted": "ENTRY 模式改读 maskComments(src);DECLARATION 模式保持读原始源;两条断言都走 offendingLines() 报告行号。分诊称这两半互补而非二选一,实测确认。",
"B": "只做可读性修复(报告行号),不做 masking:误红仍在,只是作者一眼能看到是哪一行。",
"C": "断言模块导出面而非文本 —— 卡片与分诊都已明确否决(要抓的正是有人加进去的字面数组)。",
"D": "放松或收紧正则(例如要求前面必须是 '[' 或 ',')—— 分诊明令禁止的方向。",
"E": "什么都不做,继续让每个 slice 作者改写措辞。"
},
"实际业务需求": "实测而非'读起来有用'。该 gate 的 header 是 objectui#6892 每个 slice 的逐条日志,今天已含 13 个反引号包裹的测试文件名,其中 0 个后面跟逗号 —— 这个 0 正是两次改写措辞留下的痕迹,不是安全的证据。leg 4 用分诊要求的最干净对照钉死了输入:同一句话,逗号换破折号,别的一个字不动,pre-fix pin 就从红变绿。写这个键的是每个 slice 的 dev,读它的是 pin 自己;剩余 slice(plugin-designer、plugin-list、fields,以及 objectui#8173 裁定后的 app-shell)会各撞一次。⇒ 已排期的复发,不是投机能力面。E 在这条轴上直接出局。",
"项目长远合理性": "A 不是补丁:它把 gate 自己的规则('只判断语言会执行的文本')用回 gate 自己的文件,与 gate 在 1603-1604 行对同一个共享 masker 的调用同源,这句话本来就写在 gate header 里。no-workaround、contract-first:没有例外清单、没有 header 措辞禁忌、没有第二套判定。E 才是补丁式选项 —— 它把成本外部化给每一个未来作者,而且改写后的句子读起来完全正常,没人会知道这里付过税。D 的长期代价最大:在 anti-exemption pin 上放松正则,等于自己造一个洞。",
"防AI写代码犯错": "这条轴是决定性的,并且它把 B 从'够用'降到'不够'。误红的方向是坏方向:失败文本指着一条正则,而唯一显而易见的'修法'恰是 header 明令禁止的例外清单;AI 面对一条自己不理解的红门禁,最省力的动作就是削弱断言 —— 这正是 exemption swamp 的入口。A 在源头消灭误红(契约收紧:只判断代码,而不是让消费端宽容),leg 3 与 leg 5 两种形状实证牙齿一颗不少,并且新增用例把'掩码后仍必须命中真数组'钉死,防止后人把掩码当成放松。B 那一半仍然必要 —— 任何未来来自别处的误报都继承同一份不可读的报告 —— 所以 A 两半都做(101417 字节 -> 1336 字节,实测)。⚠️ 这一轮本身就是这条轴的现场演示:第一版 offendingLines 报了空行,是分诊坚持要的第二种正例形状把它抓出来的。",
"创业阶段不扩散需求": "A 的净增是一个文件、+100/-3:一个本地 helper、两个具名常量、两个用例。没有新包、没有新导出面、没有新配置项、没有新的豁免机制,也没有动 gate 本身。不设过渡期、不留双写法:旧的 not.toMatch 直接退休,不保留兼容分支,符合'创业阶段不渐进'。DECLARATION 那一半明确不动 —— 没有实测拉动就不扩大改动面,即使'对称'读起来更整齐,而 leg 6 已经把这个决定量成了读数而不是说法。",
"推荐": "A(masking 与行号报告一起做)。四条轴无冲突:业务需求已复现两次、已用最干净的对照钉死输入、且已排期复发;长远方向与 gate 自身规则同源;防错轴上它消除的是把门禁改弱的拉力(单做 B 消除不了);扩散面是一个测试文件内的 100 行。⛔ 未采用任何例外清单,⛔ 未改动 isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS,⛔ 未放松任何正则。"
},
"notes_for_pm": [
"PR body was PATCHed once to carry the new head and the extra legs. Read back in full: content intact, but the PATCH REMOVED the platform's appended attribution footer entirely rather than downgrading it (a variant of the mutation AGENTS.md records). The session reference survives because it is written into the body as prose plus a code span, which is what AGENTS.md prescribes.",
"PR mergeable_state reads 'behind' -- origin/main moved after this branch was cut. Nothing was rebased; that is the PM's read.",
"The 'tests' label appeared on the PR after creation. Not set by this seat."
],
"open_questions": [],
"out_of_scope_findings": []
}
ACCEPT — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, reviewed 2026-09-08T00:52Z against GitHub and the tree, ⛔ never against the report's narrative.
⭐ The finding of this review: the card's fix AND triage's fix name a helper that does not exist
Both #8117's body and its triage comment prescribe "the masking helper the gate already exports (deJsxClosingTags plus the shared js-comment-mask)". deJsxClosingTags was retired by objectui#7883. Verified here on a clean origin/main@1706d8be4 extract — every occurrence in the tree:
scripts/check-vi-mock-inherit.mjs:1115 * This gate carried a LOCAL workaround for it -- `deJsxClosingTags`, a
scripts/__tests__/check-vi-mock-inherit.test.ts:544 * retired the local `deJsxClosingTags` workaround and its two unit cases
scripts/__tests__/check-vi-mock-inherit.test.ts:568 // `deJsxClosingTags` was deliberately NOT removed in that change — it was
Three mentions, all inside comments; zero definitions, zero exports. ⛔ Not callable. Control on the same grep: maskComments returns 5 hits in scripts/js-comment-mask.mjs, so the instrument fires and the zero is a reading. ⇒ The dev declined the prescription and built the repair on js-comment-mask alone, which is the half the gate itself already calls.
⚠️This is the fourth card this shift whose body and triage comment carried the same false premise — after #8006's drifted line numbers, #8029's citation of a scripts/placeholder-spelling.mjs that does not exist, and #8113's claim that the rest of the page was correct. A dev that copies the card is the failure mode; all four refused.
⚠️ And this seat's own dispatch note carried a stale number — recorded here, not quietly dropped
The dispatch fenced the gate out with "that gate reads 660 judged / 660 inherit / 0 frozen / 0 other workspace". Measured just now on origin/main@1706d8be4, in a detached read-only worktree rather than the shared checkout:
663 call site(s) … and their subpaths judged (663 inherit, 0 auto-mocked); 0 other workspace
663, not 660, and the third field is spelled auto-mocked. The figure came out of this seat's own §1 instead of out of the instrument — which is carry-forward (17) failing in the hand that writes it into every brief. The dev caught it and said so. Nothing was scored on it, because the gate source is not in this diff at all, so the census could not have moved either way.
The repair, checked in the diff rather than taken from the report
One file, +100/-3. The two assertions are now named constants, and only the ENTRY pattern was narrowed:
⭐ The asymmetry is a measurement, not tidiness. The DECLARATION pattern stays on the raw source with the reason written beside it — ^\s* reaches a docblock's * prefix and stops, so it has no false red to fix — and leg 6 measured that by writing the same declaration as prose and observing green on both halves. Narrowing it for symmetry would have bought nothing, and the report says so instead of doing it.
⭐ The non-vacuity control is inside the test permanently, not a run that happened once
expect(EXEMPTION_ENTRY_RE.test(array),'the positive fixture is matchable at all').toBe(true);expect(EXEMPTION_ENTRY_RE.test(prose),'the prose fixture is matchable BEFORE masking').toBe(true);
⇒ A future change to maskComments that blinded this pin cannot pass quietly: the fixtures must match before masking or the case fails first. And the third leg is the one that stops "masking" from being read as a weakening — prose + "\n" + array still reports 4: const ALLOWLIST = [...], so prose above does not shelter code below. The pin keeps every tooth, and that is asserted rather than argued.
⭐ The dispatch asked for a second positive shape, and that is the only reason a defect did not ship
The first offendingLines counted to m.index. EXEMPTION_DECLARATION_RE opens with ^\s*, \s matches a newline, so against a declaration with a blank line above it the match starts on the blank line and the failure reported 1853: — an empty string. A failure message naming the wrong line is this card's own defect one level down. Commit 2a10ce4e1 counts to the first non-whitespace byte and gives it a dedicated case ('a reported line is the DECLARATION, never the blank line above it'). The dev reports this as its own catch rather than presenting a clean first draft, and it is the strongest thing in the run.
The reported ablation is proven by state throughout — blob hash and marker count read before any result, an empty hash exiting 9 rather than reading as nothing-to-compare, RESTORE on EXIT INT TERM with absolute roots, and git diff HEAD empty after each leg. Leg 4 is the dispatch's punctuation control run exactly as specified: same sentence, comma → em dash, nothing else — red then green.
One file. ⛔ scripts/check-vi-mock-inherit.mjs is not in the diff — no exemption added, isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS untouched, and objectui#6892 stays closed. check-changeset-presence run rather than guessed: exit 0, none owed. check-governed-queue-guard --test on the changed path: NOT GOVERNED. PR stays draft until this seat flips it.
⚠️ Two lane facts recorded, neither a falsification: a PR-body PATCH removed the platform's appended attribution footer outright (the session reference survives because it is written as prose in the body, which is what AGENTS.md prescribes); and a tests label arrived on the PR after creation, ⛔ not set by the dev and ⛔ not reverted.
Next: CI convergence → flip ready → post-flip guard → auto-merge SQUASH → queue confirmation → landing probe (maskComments imported and the ENTRY assertion reading through it on re-fetched origin/main; control: the two raw .test(...) non-vacuity lines still present — they are what stop a future masking change from making this pin assert nothing) → LANDED here and on #8117, which closes via Fixes.
PR #8461 at 2a10ce4e1, taken in order and each step read from its own output:
step
reading
CI convergence
28 checks, all green (Live E2E (informational) excluded), read 00:59:41Z
flipped ready
ready_for_review @ 00:59:52Z
post-flip governed-surface guard
completed/success @ 01:00:07Z — ⚠️ re-read after the flip, ⛔ not the pre-flip run
auto-merge
SQUASH
queue
added_to_merge_queue @ 01:00:19Z
Landing probe queued against a re-fetched origin/main once the queue merges it: the pin reads CODE — maskComments imported and the ENTRY assertion reading through it; control: the two raw .test(...) non-vacuity lines still present, which are what stop a future masking change from making the pin assert nothing.
⛔ Not governed surface (Governed Surface Queue Guard green both before and after the flip). LANDED follows here and on the PR.
LANDED — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46. PR #8461 merged 2026-09-08T01:26:49Z as c90395b20; probe run on a clean extract of re-fetched origin/main, ⛔ never on the shared working tree.
leg
reading on origin/main@c90395b20
probe — maskComments imported
:18 import { maskComments, scanSource } from '../js-comment-mask.mjs';
⇒ The pin now reads code and keeps its teeth, and the two raw fixture checks are what will stop a future maskComments change from making it assert nothing.
⚠️One probe leg returned a hit where this seat predicted zero, and the instrument was wrong — again.expect(src).not.toMatch was expected to be gone; it returns 1. The survivor is at :359, inside the docblock headed "## Why not expect(src).not.toMatch(re) (objectui#8117)" — prose explaining the replacement, not an assertion. ⭐ A raw grep that cannot tell code from comment produced a false hit in the very PR that taught this pin to ignore comments. Sixth instrument slip today, third of this seat's own, and caught before it scored anything.
⚠️ A standing belief of this seat is FALSIFIED by this merge commit, and it matters beyond bookkeeping
This seat's §1 has recorded all shift that "squash normalises trailers to plain Co-authored-by: Claude <noreply@anthropic.com> with no model identifier — so the dispatch fence and the mandated trailer never collide at main." Read off c90395b20:
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
---------
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A model identifier is in main's history, twice. ⇒ The belief was wrong, and it was load-bearing: it is why this seat treated the conflict as theoretical. ⭐ It also makes objectui#8471 — filed an hour ago by the #8337 dev, which acted on the stricter reading and omitted the model name — a measured fact rather than a hypothesis, and demonstrates its exact prediction: two seats in one shift resolved the same conflict two different ways, and one machine-readable field at main now carries both shapes. Recorded on that card; ⛔ this seat does not resolve it, since the durable fix is dispatch-contract text and that is governed surface.
pm:dispatched stripped in the same pass; domain:devx, bug, tests, tooling and priority:p2 stay —归属不是状态. The card closed via Fixes.
Activity
baozhoutao commentedon Sep 7, 2026
Second instance, for grading —
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T01:05Z.The pin reddened again on objectui#6892 slice 10 (PR #8207, head
e104c509d): the slice-10 header record contained the fragment "The eleventh,useAiSurface.test.ts, reaches ZERO of the 454" — a backticked filename followed by a comma, in running prose inside the docblock. Repaired by rewording (caf312b93), not by loosening the pin; the gate's behaviour and the assertion are unchanged. The dev filed it as objectui#8202, now closed as a duplicate of this card.Two hits in six slices (5 and 10), and the gate header is a per-slice logbook by construction, so the remaining slices (plugin-designer, plugin-list, fields, and app-shell when objectui#8173 is ruled) will each meet it unless the author avoids the comma. The asymmetric risk stands: the failure text points at a regex, and the obvious "fix" is the one the header forbids. A repair that keeps the pin blunt but names the offending line in its failure message would remove the wrong-way pull without weakening the guard.
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Triage: lands in
domain:devx(scripts/__tests__/check-vi-mock-inherit.test.ts); rationale: class (a) — a false red, reproduced and reverted on the tree twice.Bug.⭐ Reproduced with the cleanest possible control, both times: on PR #8116 the pin passed at 69 of 69, a one-sentence header record naming a swept file reddened it, and rewriting the comma as an em dash made it green again — with no change to the gate's behaviour, no change to the assertion, and no change to what the assertion is about. ⇒ The input that flips it is prose punctuation. The comment on this card records the same on slice 10 (PR #8207,
e104c509d), repaired by rewording atcaf312b93.priority:p2, on the comment's evidence rather than the body's alone:plugin-designer,plugin-list,fields, andapp-shellonce objectui#8173 is ruled — gradedp2today) will each meet it unless the author happens to avoid a comma. This is a scheduled recurrence, not a risk.not.toMatchprints the entire ~900-line source as "received" and never says where the match was. Locating it needs a separategrep -nPwith the pattern copied out of the test by hand.⛔ Do not loosen the regex. Both repairs so far correctly reworded the prose instead, and that is the right instinct — but it is not a fix, it is a tax on every future slice author.
Two repairs are on the table and ⭐ they are complementary, not alternatives — the PR should weigh taking both:
deJsxClosingTagsplus the sharedjs-comment-mask), and the gate's own scanner blanks comments before judging anything. ⇒ Keeps every bit of the pin's teeth against a real exemption array while making the header docblock unreachable. This removes the recurrence.⛔ Not the alternative of asserting on the module's exports rather than its text: the point is to catch a literal array someone adds, and an export-shaped assertion would miss exactly that.
⛔ Correctly not folded into #8116 or #8207 — changing this pin's assertion is a change to a gate's own test, not part of a specifier sweep. ⭐ objectui#8202 was filed for the second instance and is already closed as a duplicate of this card; ⛔ do not re-file per slice.
⛔ This seat grades and routes only: not claimed, not dispatched, no code.
Generated by Claude Code
claude commentedon Sep 8, 2026
Claim: PM loop round R46 —
domain:devx @ objectuiexecution seat.Session:
session_01FhBNJcLRZLe8M87VcUgpKrBranch:
claude/issue-8117-vi-mock-anti-exemption-pinWorktree:
/home/user/objectui-issue-8117Domain:
domain:devxDispatch: one
os-devsubagent of this session (mode:subagent,model: opus); the dispatch note with the brief follows. Labelspm:queue→pm:dispatchedand assignee set in one write, read back at 2026-09-08T00:28Z.Generated by Claude Code
claude commentedon Sep 8, 2026
Dispatched —
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-08T00:35Z. One of three slots in flight (#8117 #8122 #8126); the lane's other dispatchable cards are serialised behind nothing and wait only on the batch cap.Dev. One
os-devsubagent of this session on branchclaude/issue-8117-vi-mock-anti-exemption-pin, worktree/home/user/objectui-issue-8117, branching fromorigin/mainat1706d8be4or later. Race re-read at 00:30Z: the newestClaim:on this card is5577181300, naming that branch — and it is the only one. ⛔ The dev posts no second claim and ⛔ never writes the assignee.Premise re-verified by this seat on
origin/main@1706d8be4, ⛔ not taken from the card.scripts/__tests__/check-vi-mock-inherit.test.ts, in "there is NO per-file exception list anywhere in the gate":⇒ The first assertion is anchored (
^\s*,/m) and reads a declaration. The second is anchored to nothing and reads the whole file, docblock included, with['"]followed by a comma or a bracket standing in for "a list entry". A prose sentence in the header that names a swept fixture and then continues with a comma satisfies that regex exactly. **That is the false red**, live today, and the file's own neighbouring pin already contains the lesson in writing: the.slice(0, 12000)` window was removed because "a byte count is not what the rule is about". This is the same mistake in the other axis — punctuation standing in for structure.⛔ Re-derive before editing. The card and its triage comment are evidence, ⛔ not premises: reproduce the red on today's tree (a header sentence with the comma), show it green with the punctuation changed and nothing else changed, and report both readings. If today's tree no longer reproduces, that is the finding and the PR is a corrected card with zero implementation — a good outcome, ⛔ not a failure.
Non-vacuity is mandatory and it has two legs, because this is the exact class this lane has admitted four cards for:
const KNOWN_BAD = ['x.test.tsx', …], and separately an unanchored inline list) and show the repaired pin reds.Prove each mutation landed on disk by hash and marker count before reading any result, and restore by state (
git diff HEADempty) under anEXIT INT TERMtrap.Scope fence.
scripts/__tests__/check-vi-mock-inherit.test.tsis the target. ⛔ Notscripts/check-vi-mock-inherit.mjs— that gate reads660 judged / 660 inherit / 0 frozen / 0 other workspaceand its worklist objectui#6892 closed at 20:07Z today; ⛔ do not reopen it, do not touchCOVERED_SPECIFIERS,isCovered,operandDenotesor the verdict line. ⛔ No new gate. ⛔ Not governed surface, but the PR stays draft until this seat flips it.scripts/placeholder-spelling.mjsthat does not exist), #8113 (the card claimed the rest of the page was correct when three separate things on it were wrong).getMetadatasurviving in an explanatory comment,exit GREENsurviving inside its own negations, this seat's "string literal" guess that was a block comment, and a case-sensitive grep for a report's paraphrase. ⇒ The instrument is the first suspect, and a zero-hit reading means nothing without a control.Generated by Claude Code
baozhoutao commentedon Sep 8, 2026
os-dev-report
{ "issue": 8117, "status": "done", "branch": "claude/issue-8117-vi-mock-anti-exemption-pin", "worktree": "/home/user/objectui-issue-8117", "pr": "https://github.com/objectstack-ai/objectui/pull/8461", "pr_number": 8461, "head_sha": "4efb218b9c4a1585004d620ca340b29757179eb0", "base_sha": "1706d8be4914502b05390d9d9c9d23768847bc57", "premise_still_valid": true, "premise_notes": [ "DIAGNOSIS REPRODUCES. On base 1706d8be4 the pin still reads the RAW text of scripts/check-vi-mock-inherit.mjs at test line 369, and injecting one header sweep record naming a swept file with a comma produces exactly 1 raw match at gate line 1128, 0 matches on the comment-blanked source.", "PRESCRIPTION FALSIFIED. The card body and the triage comment both prescribe 'the masking helper the gate already exports (deJsxClosingTags plus the shared js-comment-mask)'. deJsxClosingTags was RETIRED by objectui#7883 and survives only as 3 lines of historical prose (gate line 1115, test lines 544 and 568). It is not a function, not an export, not callable. The other half is live and is what the gate itself uses at gate lines 1603-1604, so the repair stands on js-comment-mask alone.", "DISPATCH BRIEF STALE. The brief quotes '660 judged / 660 inherit'. The tree reads 663 judged / 663 inherit / 0 auto-mocked / 0 other workspace today, unchanged by this PR.", "CARD DETAIL. The card describes the pin as one assertion; it is two. Only the ENTRY pattern has the false red. The DECLARATION pattern cannot match a docblock line because its leading anchor reaches the ' * ' prefix and stops, so it was deliberately left reading the raw source rather than narrowed for symmetry." ], "summary": "The anti-exemption pin in scripts/__tests__/check-vi-mock-inherit.test.ts now reads its ENTRY pattern over maskComments(src) instead of the raw gate source, so the gate's 1128-line header docblock -- objectui#6892's per-slice logbook, where naming a test file is the process's own instruction -- can no longer match one entry of the exemption array the pin exists to catch. String literals, templates and regex literals survive the shared masker untouched, so a real exemption array is still caught whole. Both patterns became named constants and both assertions now go through one offendingLines() helper that reports 'LINE NUMBER: source line', replacing a not.toMatch whose failure printed the entire gate as the received value and never said where the match was. A sibling case pins both directions with the raw match as its control. The gate itself, scripts/check-vi-mock-inherit.mjs, is NOT in the diff: no exemption was added and isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS were not touched.", "defect_rederived_vs_card_claimed": { "card_claimed": "The pin's regex matches a backticked test filename followed by a comma anywhere in the gate source, including the header docblock; repaired twice by rewording prose (#8116 at 69 of 69, #8207 at e104c509d, reworded at caf312b93). Suggested repair: run the regex over the comment-blanked source using deJsxClosingTags plus js-comment-mask.", "measured_on_base_1706d8be4": { "entry_pattern_raw_hits_today": 0, "entry_pattern_masked_hits_today": 0, "backticked_test_filenames_in_header": 13, "backticked_test_filenames_followed_by_a_comma": 0, "control_A_inject_one_sweep_record_with_a_comma": "raw 1 hit at gate line 1128; masked 0 hits", "control_B_append_a_real_exemption_array_as_code": "raw 2 hits; masked 2 hits -- teeth retained", "control_C_same_array_commented_out": "raw 2 hits; masked 0 hits -- the only narrowing, and a commented-out array is prose, not an exemption", "deJsxClosingTags_grep": "3 hits repo-wide, all inside comments; 0 definitions, 0 exports (control: maskComments greps 5 hits in scripts/js-comment-mask.mjs, so the grep instrument fires)" }, "reading": "Row 2 above is the point: the header already carries 13 backticked test filenames and NOT ONE is followed by a comma. The green reading on today's tree is not evidence the pin is sound -- it is the tax the two previous repairs paid by rewording prose. Without control A the zero-hit reading would have meant nothing." }, "census_before_after": { "command": "node scripts/check-vi-mock-inherit.mjs", "before": "exit 0 -- 4480 tracked source file(s), 2746 test-named; 625 carry a mock; 663 call site(s) judged (663 inherit, 0 auto-mocked); 0 other workspace, 244 external, 886 local, 0 non-static, 3 embedded in a string literal", "after": "exit 0 -- byte-identical line; the gate source is not in the diff", "moved": false }, "ablation": { "discipline": "Implementation committed FIRST (4efb218b9), so the restore leg points at HEAD. Script carries trap RESTORE EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. Every leg prints the HEAD blob hash, the on-disk blob hash and a marker count BEFORE the run is read; an empty hash is treated as FAILURE and exits 9.", "leg_1_defect_reproduced": { "mutation": "gate header gains one sweep record naming a swept file with a comma; pin reverted to origin/main (pre-fix)", "on_disk_proof": "gate HEAD blob 08a986a0af5f00ccac4e8a2c4efeaa6b2ec5e85f -> on-disk bf2f4a5611c90981d169781f1b14cfb5ca6e0c4a, differs YES, marker count 1; pin HEAD blob 3bd7dd19b0609941135c9c03549229420b9aa51d -> on-disk 650fd8eba6cb2c39867f3079c01396ce7b863c59, differs YES, marker count 2", "predicted": "RED", "observed": "RED -- vitest exit 1, Tests 1 failed | 83 skipped (84). AssertionError: expected '#!/usr/bin/env node...' not to match the ENTRY pattern. Report 1880 lines / 101417 bytes, and it never names line 1128." }, "leg_2_fix_works": { "mutation": "same header mutation; pin restored to HEAD (fixed)", "on_disk_proof": "gate on-disk still bf2f4a5611c9, marker count 1; pin blob identical to HEAD", "predicted": "GREEN", "observed": "GREEN -- vitest exit 0, Tests 1 passed | 84 skipped (85)" }, "leg_3_positive_control_teeth_retained": { "mutation": "gate header restored; a real exemption array appended as CODE, named PER_FILE_CARVE_OUTS so only the ENTRY assertion can fire", "on_disk_proof": "gate on-disk 673f3fab6ba4b241ef66397a88d6f351a91c4d87, differs from HEAD YES, marker count 1", "predicted": "RED, and the report names the line", "observed": "RED -- vitest exit 1. AssertionError: expected [ Array(1) ] to deeply equal [] , received [ \"1854: const PER_FILE_CARVE_OUTS = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\" ]. Report 35 lines / 1336 bytes -- 76x smaller than leg 1 and it names the line." }, "restore_by_state_not_exit_code": "git checkout HEAD -- both paths, then: git diff HEAD --stat empty, 0 paths differing; gate blob == HEAD blob YES; pin blob == HEAD blob YES." }, "files_changed": [ "scripts/__tests__/check-vi-mock-inherit.test.ts (+82 / -3, the only file in the diff)" ], "tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-8117-vi-mock; verdicts read from the wrapper's own VERDICT line, never a bare exit status. Head 4efb218b9, tree clean. (1) pnpm exec vitest run scripts/__tests__/check-vi-mock-inherit.test.ts --maxWorkers=2 -> exit 0, 'Test Files 1 passed (1)', 'Tests 85 passed (85)' (84 before this PR; the new case is the sibling control). (2) node scripts/check-vi-mock-inherit.mjs -> exit 0, census above, unmoved. (3) node scripts/check-vi-mock-specifiers.mjs -> exit 0. (4) node scripts/check-control-bytes.mjs -> exit 0, 6699 tracked text files scanned, 85 binary skipped. (5) node scripts/check-comment-mask-corpus.mjs -> exit 0, 4480 files, 1 disagrees, within the residue objectui#7882 holds open, unchanged by this PR. (6) tsc -p tsconfig.scripts.json -> exit 0, AND tsc --listFiles confirms the program contains check-vi-mock-inherit.test.ts (count 1), so this is a measurement rather than a NOT MEASURED reading. (7) pnpm run lint:root -> exit 0, whole lane not a narrowed run: 275 files read from eslint's own config via --format json, 0 errors, 32 pre-existing warnings, 0 of them in the changed file (control: vitest-invocation-guard.test.ts does appear in that output, so the grep fires). (8) node scripts/check-changeset-presence.mjs -> exit 0, 'No source or published contract of a released package changed in this range, so no changeset is owed' -- measured, not guessed, and no skip-changeset label applied (objectui has no such mechanism). (9) grep -naP for control bytes on the changed file -> 0 hits, with a positive control file proving the grep fires. (10) node scripts/check-governed-queue-guard.mjs --test scripts/__tests__/check-vi-mock-inherit.test.ts -> NOT GOVERNED. Gate family derived from objectui's own package.json and workflows, not from objectstack's scripts/pm/dispatch-gates.mjs, which answers only about the tree it lives in. CI runs the full farm.", "mcp_calls": "5 -- issue_read get, issue_read get_comments, create_pull_request, pull_request_read get, add_issue_comment (this one)", "four_axis_analysis": { "options": { "A_adopted": "ENTRY 模式改读 maskComments(src);DECLARATION 模式保持读原始源;两条断言都走 offendingLines() 报告行号。分诊称这两半互补而非二选一。", "B": "只做可读性修复(报告行号),不做 masking:误红仍在,只是作者一眼能看到是哪一行。", "C": "断言模块导出面而非文本 —— 卡片与分诊都已明确否决(要抓的正是有人加进去的字面数组)。", "D": "收紧/放松正则(例如要求前面必须是 '[' 或 ',')—— 分诊明令禁止的方向。", "E": "什么都不做,继续让每个 slice 作者改写措辞。" }, "实际业务需求": "实测而非'读起来有用':该 gate 的 header 是 objectui#6892 每个 slice 的逐条日志,今天已含 13 个反引号包裹的测试文件名,而其中 0 个后面跟逗号 —— 这个 0 本身就是两次改写措辞留下的痕迹。两次真实误红已复现(#8116 在 69/69 时,#8207 在 e104c509d,于 caf312b93 改写措辞修好)。写这个键的是每个 slice 的 dev,读它的是 pin 自己;剩余 slice(plugin-designer、plugin-list、fields,以及 objectui#8173 裁定后的 app-shell)按分诊的读法会各撞一次。⇒ 已排期的复发,不是投机能力面。E 在这条轴上直接出局:它把已知会发生的事留着不管。", "项目长远合理性": "A 不是补丁:它把 gate 自己的规则('只判断语言会执行的文本')用回 gate 自己的文件,与 gate 在 1603-1604 行对同一个共享 masker 的调用同源,措辞也已经写在 gate header 里。no-workaround、contract-first:没有例外清单、没有 header 措辞禁忌、没有第二套判定。E 才是补丁式选项 —— 它把成本外部化给每一个未来作者,而且不留任何痕迹(改写后的句子读起来完全正常,没人会知道这里付过税)。D 的长期代价最大:放松正则等于在 anti-exemption pin 上开一个自己造的洞。", "防AI写代码犯错": "这条轴是决定性的,并且它把 B 从'够用'降级为'不够'。误红的方向是坏方向:失败文本指着一条正则,而唯一显而易见的'修法'恰是 header 明令禁止的例外清单。AI 面对一条自己不理解的红门禁,最省力的动作就是削弱断言 —— 这正是 exemption swamp 的入口。A 在源头消灭误红(契约收紧:只判断代码,而不是让消费端宽容),同时 Leg 3 实证牙齿一颗不少,并且第二个用例把'掩码后仍必须命中真数组'钉死,防止后人把掩码当成放松。B 只让误红更可读,拉力仍在;但 B 的那一半仍然必要 —— 任何未来来自别处的误报都继承同一份不可读的报告,所以 A 把两半都做了(101417 字节 -> 1336 字节,实测)。", "创业阶段不扩散需求": "A 的净增是一个文件、+82/-3:一个 8 行的本地 helper、两个具名常量、一个用例。没有新包、没有新导出面、没有新配置项、没有新的豁免机制,也没有动 gate 本身。不设过渡期、不留双写法:旧的 not.toMatch 直接退休,不保留兼容分支,符合'创业阶段不渐进'。DECLARATION 那一半明确不动 —— 没有实测拉动就不扩大改动面,即使'对称'读起来更整齐。", "推荐": "A(masking 与行号报告一起做)。四条轴无冲突:业务需求已复现两次且已排期复发,长远方向与 gate 自身规则同源,防错轴上它消除的是把门禁改弱的拉力(单做 B 消除不了),扩散面是一个测试文件内的 82 行。⛔ 未采用任何例外清单,⛔ 未改动 isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS,⛔ 未放松任何正则。" }, "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
baozhoutao commentedon Sep 8, 2026
os-dev-report
Supersedes comment
5577307721— that one was written before this dev had read the dispatch note (5577228342), which asks for two evidence legs the first round did not run: the em-dash punctuation control on the pre-fix pin, and the anchoredconst KNOWN_BADpositive alongside the unanchored inline list. Both were run. The anchored one found a defect in the repair, which is fixed in a second commit. Head moved4efb218b9to2a10ce4e1.{ "issue": 8117, "status": "done", "branch": "claude/issue-8117-vi-mock-anti-exemption-pin", "worktree": "/home/user/objectui-issue-8117", "pr": "https://github.com/objectstack-ai/objectui/pull/8461", "pr_number": 8461, "pr_state": "draft, as dispatched -- this seat did not flip it", "head_sha": "2a10ce4e12d841084e253db14da1c0560cd936e8", "base_sha": "1706d8be4914502b05390d9d9c9d23768847bc57", "commits": 2, "premise_still_valid": true, "premise_notes": [ "DIAGNOSIS REPRODUCES. On base 1706d8be4 the pin still reads the RAW text of scripts/check-vi-mock-inherit.mjs at test line 369. Injecting one header sweep record naming a swept file with a comma gives 1 raw match at gate line 1128 and 0 on the comment-blanked source. Leg 4 then does the dispatch note's exact control: the SAME sentence with the comma rewritten as an em dash and nothing else changed turns the pre-fix pin GREEN. Punctuation is the only input that flips it.", "PRESCRIPTION FALSIFIED. The card body and the triage comment both prescribe 'the masking helper the gate already exports (deJsxClosingTags plus the shared js-comment-mask)'. deJsxClosingTags was RETIRED by objectui#7883 and survives only as 3 lines of historical prose (gate line 1115, test lines 544 and 568): not a function, not an export, not callable. The live half is what the gate itself uses at gate lines 1603-1604, so the repair stands on js-comment-mask alone.", "DISPATCH BRIEF STALE. The brief quotes '660 judged / 660 inherit'. The tree reads 663 judged / 663 inherit / 0 auto-mocked / 0 other workspace, unchanged by this PR.", "CARD DETAIL. The card describes the pin as one assertion; it is two, and only the ENTRY pattern has the false red. The DECLARATION pattern was left reading the raw source rather than narrowed for symmetry, and leg 6 measures that decision instead of asserting it." ], "summary": "The anti-exemption pin in scripts/__tests__/check-vi-mock-inherit.test.ts now reads its ENTRY pattern over maskComments(src) instead of the raw gate source, so the gate's 1128-line header docblock -- objectui#6892's per-slice logbook, where naming a test file is what the process asks for -- can no longer match one entry of the exemption array the pin exists to catch. String literals, templates and regex literals survive the shared masker untouched, so a real exemption array is still caught whole (legs 3 and 5). Both patterns became named constants and both assertions now go through one offendingLines() helper reporting 'LINE NUMBER: source line', replacing a not.toMatch whose failure printed the entire gate as the received value. Three sibling cases pin the repair, each with the raw match as its control. The gate itself is NOT in the diff: no exemption added, and isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS untouched.", "self_caught_defect": "Leg 5 -- the anchored positive shape the dispatch note asked for and the first round had not run -- went RED as required but named the line as [ \"1853: \" ], an EMPTY line one above the declaration. EXEMPTION_DECLARATION_RE opens with ^\\s* and \\s matches a newline, so against a declaration with a blank line above it the match STARTS on that blank line; counting to the match offset reported the blank line. A failure message that names the wrong line is this card's own defect one level down. Commit 2a10ce4e1 counts to the first non-whitespace byte of the match instead and adds a dedicated case; the re-run reports '1854: const KNOWN_BAD = [...]'. Without the second positive shape this would have shipped.", "census_before_after": { "command": "node scripts/check-vi-mock-inherit.mjs", "before": "exit 0 -- 4480 tracked source file(s), 2746 test-named; 625 carry a mock; 663 call site(s) judged (663 inherit, 0 auto-mocked); 0 other workspace, 244 external, 886 local, 0 non-static, 3 embedded in a string literal", "after": "exit 0 -- byte-identical line; the gate source is not in the diff", "moved": false }, "base_measurements_1706d8be4": { "entry_pattern_raw_hits": 0, "entry_pattern_masked_hits": 0, "backticked_test_filenames_in_header": 13, "of_those_followed_by_a_comma": 0, "reading": "That 0 is not evidence the pin is sound -- it is the tax the two previous repairs paid by rewording prose. Every zero below carries an injected control for exactly that reason.", "control_inject_sweep_record_with_comma": "raw 1 hit at gate line 1128; masked 0", "control_append_real_exemption_array_as_code": "raw 2; masked 2 -- teeth retained", "control_same_array_commented_out": "raw 2; masked 0 -- the only narrowing, and a commented-out array is prose, not an exemption", "control_deJsxClosingTags_grep": "3 hits repo-wide, all inside comments; 0 definitions, 0 exports. Control: maskComments greps 5 hits in scripts/js-comment-mask.mjs, so the grep instrument fires" }, "ablation": { "discipline": "Implementation committed BEFORE each mutation round, so every restore leg points at HEAD. Both scripts carry trap RESTORE EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. Each leg prints the HEAD blob hash, the on-disk blob hash and a marker count BEFORE any result is read; an empty hash exits 9 rather than reading as nothing-to-compare. Restore proved by state, never by exit code.", "leg_1": "pre-fix pin + header sweep record with a comma. gate 08a986a0af5f -> bf2f4a5611c9 differs YES marker 1; pin 3bd7dd19b060 -> 650fd8eba6cb differs YES marker 2. Predicted RED, observed RED (1 failed | 83 skipped). Failure report 1880 lines / 101417 bytes and it never names line 1128.", "leg_2": "same header mutation, pin restored to HEAD. gate still bf2f4a5611c9 marker 1; pin blob identical to HEAD. Predicted GREEN, observed GREEN.", "leg_3": "positive, unanchored inline carve-out list as CODE. gate 673f3fab6ba4 differs YES marker 1. Predicted RED naming the line, observed RED: report 35 lines / 1336 bytes naming \"1854: const PER_FILE_CARVE_OUTS = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\" -- 76x smaller than leg 1.", "leg_4a": "pre-fix pin, the sentence with a COMMA. gate bf2f4a5611c9 differs YES marker 1. Predicted RED, observed RED.", "leg_4b": "pre-fix pin, THE SAME SENTENCE with the comma rewritten as an em dash and nothing else changed. gate f84a6483cf9f differs YES marker 1; commas after that filename now 0. Predicted GREEN, observed GREEN. This is the dispatch note's punctuation control.", "leg_5": "positive, the ANCHORED declaration shape const KNOWN_BAD = [...] as CODE. gate adb4fbcdf8bb differs YES marker 1. Predicted RED, observed RED. First run named an empty line -- see self_caught_defect. Re-run on 2a10ce4e1 names \"1854: const KNOWN_BAD = ['packages/x/src/A.test.tsx', 'packages/y/src/B.test.ts'];\".", "leg_6": "the same declaration written as docblock PROSE (with the ' * ' prefix). gate f29e3bf3762d differs YES marker 1. Predicted GREEN, observed GREEN -- both halves decline prose, which is the measurement behind leaving the DECLARATION pattern on the raw source.", "restore": "After each script: 0 paths differing from HEAD; git diff HEAD --stat empty; gate blob == HEAD blob YES; pin blob == HEAD blob YES." }, "files_changed": [ "scripts/__tests__/check-vi-mock-inherit.test.ts -- the only file, +100 / -3 across 2 commits (GitHub's count)" ], "tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, OS_VERIFY_LOCK_SLOT=issue-8117-vi-mock, verdicts read from the wrapper's own VERDICT line and never from a bare exit status. Head 2a10ce4e1, tree clean. (1) pnpm exec vitest run scripts/__tests__/check-vi-mock-inherit.test.ts --maxWorkers=2 -> exit 0, 'Test Files 1 passed (1)', 'Tests 86 passed (86)'; 84 before this PR, so 2 new cases plus the rewritten pin. (2) node scripts/check-vi-mock-inherit.mjs -> exit 0, census above, unmoved. (3) node scripts/check-vi-mock-specifiers.mjs -> exit 0. (4) node scripts/check-control-bytes.mjs -> exit 0, 6699 tracked text files scanned, 85 binary skipped. (5) node scripts/check-comment-mask-corpus.mjs -> exit 0, 4480 files, 1 disagrees, within the residue objectui#7882 holds open, unchanged by this PR. (6) tsc -p tsconfig.scripts.json -> exit 0, AND tsc --listFiles confirms the program contains check-vi-mock-inherit.test.ts (count 1), so this is a measurement rather than a NOT MEASURED reading. (7) pnpm run lint:root -> exit 0, the whole lane rather than a narrowed run: 275 files read from eslint's own config via --format json, 0 errors, 32 pre-existing warnings, 0 of them in the changed file, and a control file that DOES appear in that output proves the grep fires. (8) node scripts/check-changeset-presence.mjs -> exit 0, 'No source or published contract of a released package changed in this range, so no changeset is owed' -- measured, not guessed; no skip-changeset label applied, objectui has no such mechanism. (9) grep -naP for control bytes on the changed file -> 0 hits, with a positive control file proving the grep fires. (10) node scripts/check-governed-queue-guard.mjs --test scripts/__tests__/check-vi-mock-inherit.test.ts -> NOT GOVERNED. The gate family was derived from objectui's own package.json and workflows, NOT from objectstack's scripts/pm/dispatch-gates.mjs, which answers only about the tree it lives in and has no counterpart here. CI runs the full farm; this seat did not wait on it.", "mcp_calls": "8 -- issue_read get, issue_read get_comments x2, create_pull_request, pull_request_read get x2, add_issue_comment x2 (this one included). No search_issues: nothing was filed, so no dedup read was owed.", "four_axis_analysis": { "options": { "A_adopted": "ENTRY 模式改读 maskComments(src);DECLARATION 模式保持读原始源;两条断言都走 offendingLines() 报告行号。分诊称这两半互补而非二选一,实测确认。", "B": "只做可读性修复(报告行号),不做 masking:误红仍在,只是作者一眼能看到是哪一行。", "C": "断言模块导出面而非文本 —— 卡片与分诊都已明确否决(要抓的正是有人加进去的字面数组)。", "D": "放松或收紧正则(例如要求前面必须是 '[' 或 ',')—— 分诊明令禁止的方向。", "E": "什么都不做,继续让每个 slice 作者改写措辞。" }, "实际业务需求": "实测而非'读起来有用'。该 gate 的 header 是 objectui#6892 每个 slice 的逐条日志,今天已含 13 个反引号包裹的测试文件名,其中 0 个后面跟逗号 —— 这个 0 正是两次改写措辞留下的痕迹,不是安全的证据。leg 4 用分诊要求的最干净对照钉死了输入:同一句话,逗号换破折号,别的一个字不动,pre-fix pin 就从红变绿。写这个键的是每个 slice 的 dev,读它的是 pin 自己;剩余 slice(plugin-designer、plugin-list、fields,以及 objectui#8173 裁定后的 app-shell)会各撞一次。⇒ 已排期的复发,不是投机能力面。E 在这条轴上直接出局。", "项目长远合理性": "A 不是补丁:它把 gate 自己的规则('只判断语言会执行的文本')用回 gate 自己的文件,与 gate 在 1603-1604 行对同一个共享 masker 的调用同源,这句话本来就写在 gate header 里。no-workaround、contract-first:没有例外清单、没有 header 措辞禁忌、没有第二套判定。E 才是补丁式选项 —— 它把成本外部化给每一个未来作者,而且改写后的句子读起来完全正常,没人会知道这里付过税。D 的长期代价最大:在 anti-exemption pin 上放松正则,等于自己造一个洞。", "防AI写代码犯错": "这条轴是决定性的,并且它把 B 从'够用'降到'不够'。误红的方向是坏方向:失败文本指着一条正则,而唯一显而易见的'修法'恰是 header 明令禁止的例外清单;AI 面对一条自己不理解的红门禁,最省力的动作就是削弱断言 —— 这正是 exemption swamp 的入口。A 在源头消灭误红(契约收紧:只判断代码,而不是让消费端宽容),leg 3 与 leg 5 两种形状实证牙齿一颗不少,并且新增用例把'掩码后仍必须命中真数组'钉死,防止后人把掩码当成放松。B 那一半仍然必要 —— 任何未来来自别处的误报都继承同一份不可读的报告 —— 所以 A 两半都做(101417 字节 -> 1336 字节,实测)。⚠️ 这一轮本身就是这条轴的现场演示:第一版 offendingLines 报了空行,是分诊坚持要的第二种正例形状把它抓出来的。", "创业阶段不扩散需求": "A 的净增是一个文件、+100/-3:一个本地 helper、两个具名常量、两个用例。没有新包、没有新导出面、没有新配置项、没有新的豁免机制,也没有动 gate 本身。不设过渡期、不留双写法:旧的 not.toMatch 直接退休,不保留兼容分支,符合'创业阶段不渐进'。DECLARATION 那一半明确不动 —— 没有实测拉动就不扩大改动面,即使'对称'读起来更整齐,而 leg 6 已经把这个决定量成了读数而不是说法。", "推荐": "A(masking 与行号报告一起做)。四条轴无冲突:业务需求已复现两次、已用最干净的对照钉死输入、且已排期复发;长远方向与 gate 自身规则同源;防错轴上它消除的是把门禁改弱的拉力(单做 B 消除不了);扩散面是一个测试文件内的 100 行。⛔ 未采用任何例外清单,⛔ 未改动 isCovered / coveredPrefixes / operandDenotes / COVERED_SPECIFIERS,⛔ 未放松任何正则。" }, "notes_for_pm": [ "PR body was PATCHed once to carry the new head and the extra legs. Read back in full: content intact, but the PATCH REMOVED the platform's appended attribution footer entirely rather than downgrading it (a variant of the mutation AGENTS.md records). The session reference survives because it is written into the body as prose plus a code span, which is what AGENTS.md prescribes.", "PR mergeable_state reads 'behind' -- origin/main moved after this branch was cut. Nothing was rebased; that is the PM's read.", "The 'tests' label appeared on the PR after creation. Not set by this seat." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 8, 2026
ACCEPT —
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, reviewed 2026-09-08T00:52Z against GitHub and the tree, ⛔ never against the report's narrative.⭐ The finding of this review: the card's fix AND triage's fix name a helper that does not exist
Both #8117's body and its triage comment prescribe "the masking helper the gate already exports (
deJsxClosingTagsplus the shared js-comment-mask)".deJsxClosingTagswas retired by objectui#7883. Verified here on a cleanorigin/main@1706d8be4extract — every occurrence in the tree:Three mentions, all inside comments; zero definitions, zero exports. ⛔ Not callable. Control on the same grep:
maskCommentsreturns 5 hits inscripts/js-comment-mask.mjs, so the instrument fires and the zero is a reading. ⇒ The dev declined the prescription and built the repair onjs-comment-maskalone, which is the half the gate itself already calls.scripts/placeholder-spelling.mjsthat does not exist, and #8113's claim that the rest of the page was correct. A dev that copies the card is the failure mode; all four refused.The dispatch fenced the gate out with "that gate reads
660 judged / 660 inherit / 0 frozen / 0 other workspace". Measured just now onorigin/main@1706d8be4, in a detached read-only worktree rather than the shared checkout:663, not 660, and the third field is spelled
auto-mocked. The figure came out of this seat's own §1 instead of out of the instrument — which is carry-forward (17) failing in the hand that writes it into every brief. The dev caught it and said so. Nothing was scored on it, because the gate source is not in this diff at all, so the census could not have moved either way.The repair, checked in the diff rather than taken from the report
One file,
+100/-3. The two assertions are now named constants, and only the ENTRY pattern was narrowed:⭐ The asymmetry is a measurement, not tidiness. The DECLARATION pattern stays on the raw source with the reason written beside it —
^\s*reaches a docblock's*prefix and stops, so it has no false red to fix — and leg 6 measured that by writing the same declaration as prose and observing green on both halves. Narrowing it for symmetry would have bought nothing, and the report says so instead of doing it.⭐ The non-vacuity control is inside the test permanently, not a run that happened once
⇒ A future change to
maskCommentsthat blinded this pin cannot pass quietly: the fixtures must match before masking or the case fails first. And the third leg is the one that stops "masking" from being read as a weakening —prose + "\n" + arraystill reports4: const ALLOWLIST = [...], so prose above does not shelter code below. The pin keeps every tooth, and that is asserted rather than argued.⭐ The dispatch asked for a second positive shape, and that is the only reason a defect did not ship
The first
offendingLinescounted tom.index.EXEMPTION_DECLARATION_REopens with^\s*,\smatches a newline, so against a declaration with a blank line above it the match starts on the blank line and the failure reported1853:— an empty string. A failure message naming the wrong line is this card's own defect one level down. Commit2a10ce4e1counts to the first non-whitespace byte and gives it a dedicated case ('a reported line is the DECLARATION, never the blank line above it'). The dev reports this as its own catch rather than presenting a clean first draft, and it is the strongest thing in the run.The reported ablation is proven by state throughout — blob hash and marker count read before any result, an empty hash exiting 9 rather than reading as nothing-to-compare,
RESTOREonEXIT INT TERMwith absolute roots, andgit diff HEADempty after each leg. Leg 4 is the dispatch's punctuation control run exactly as specified: same sentence, comma → em dash, nothing else — red then green.Reported size, checked: the failure report goes
101417 bytes → 1336 bytes.Scope
One file. ⛔
scripts/check-vi-mock-inherit.mjsis not in the diff — no exemption added,isCovered/coveredPrefixes/operandDenotes/COVERED_SPECIFIERSuntouched, and objectui#6892 stays closed.check-changeset-presencerun rather than guessed: exit 0, none owed.check-governed-queue-guard --teston the changed path: NOT GOVERNED. PR stays draft until this seat flips it.testslabel arrived on the PR after creation, ⛔ not set by the dev and ⛔ not reverted.Next: CI convergence → flip ready → post-flip guard → auto-merge SQUASH → queue confirmation → landing probe (
maskCommentsimported and the ENTRY assertion reading through it on re-fetchedorigin/main; control: the two raw.test(...)non-vacuity lines still present — they are what stop a future masking change from making this pin assert nothing) → LANDED here and on #8117, which closes viaFixes.Generated by Claude Code
claude commentedon Sep 8, 2026
Armed —
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46.PR #8461 at
2a10ce4e1, taken in order and each step read from its own output:Live E2E (informational)excluded), read 00:59:41Zready_for_review@ 00:59:52Zcompleted/success@ 01:00:07Z —SQUASHadded_to_merge_queue@ 01:00:19ZLanding probe queued against a re-fetched
origin/mainonce the queue merges it: the pin reads CODE —maskCommentsimported and the ENTRY assertion reading through it; control: the two raw.test(...)non-vacuity lines still present, which are what stop a future masking change from making the pin assert nothing.⛔ Not governed surface (
Governed Surface Queue Guardgreen both before and after the flip). LANDED follows here and on the PR.Generated by Claude Code
claude commentedon Sep 8, 2026
LANDED —
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46. PR #8461 merged 2026-09-08T01:26:49Z asc90395b20; probe run on a clean extract of re-fetchedorigin/main, ⛔ never on the shared working tree.origin/main@c90395b20maskCommentsimported:18 import { maskComments, scanSource } from '../js-comment-mask.mjs';:429 expect(offendingLines(maskComments(src), EXEMPTION_ENTRY_RE, src)).toEqual([]);:428 expect(offendingLines(src, EXEMPTION_DECLARATION_RE)).toEqual([]);:442/:443, both.test(...)assertions presentcheck-vi-mock-inherit× 16, so the grep fires⇒ The pin now reads code and keeps its teeth, and the two raw fixture checks are what will stop a future
maskCommentschange from making it assert nothing.expect(src).not.toMatchwas expected to be gone; it returns 1. The survivor is at:359, inside the docblock headed "## Why notexpect(src).not.toMatch(re)(objectui#8117)" — prose explaining the replacement, not an assertion. ⭐ A raw grep that cannot tell code from comment produced a false hit in the very PR that taught this pin to ignore comments. Sixth instrument slip today, third of this seat's own, and caught before it scored anything.This seat's §1 has recorded all shift that "squash normalises trailers to plain
Co-authored-by: Claude <noreply@anthropic.com>with no model identifier — so the dispatch fence and the mandated trailer never collide atmain." Read offc90395b20:A model identifier is in
main's history, twice. ⇒ The belief was wrong, and it was load-bearing: it is why this seat treated the conflict as theoretical. ⭐ It also makes objectui#8471 — filed an hour ago by the #8337 dev, which acted on the stricter reading and omitted the model name — a measured fact rather than a hypothesis, and demonstrates its exact prediction: two seats in one shift resolved the same conflict two different ways, and one machine-readable field atmainnow carries both shapes. Recorded on that card; ⛔ this seat does not resolve it, since the durable fix is dispatch-contract text and that is governed surface.pm:dispatchedstripped in the same pass;domain:devx,bug,tests,toolingandpriority:p2stay —归属不是状态. The card closed viaFixes.Generated by Claude Code