Repository navigation
fix(pm): check-widening-tells T2 reads the construct that encloses a member — an import list is never a closed set - #21679
Closed
objectstack-fleet[bot] wants to merge 7 commits into
Conversation
…member An import/export specifier list, a call's argument list and an object literal never hold a closed-set member; an `as const` set read only by a refusal predicate is reported as narrowing beside the rows. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
…d the narrowing polarity One fixture per construct the triage ruling names, each with its T2 verdict; PR 21463's hunk read off its closer, with the cut-hunk control; both walks' triggers; the budget on both sides; and the narrowing reading bracketed by the controls that keep firing. The header records the corpus census and the price. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
… counting case Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
A string saying "throw" refuses nothing, and a members-only region that returns a value is an allow-list by another spelling; both now keep T2 firing, each pinned by a control. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
…cblock Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
…file Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
…ady bounds the reads The memo changed no verdict and no read count (its ablation stayed green), so it goes; the two read-count cases keep pinning the per-file read. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 4, 2026
os-project-manager
pushed a commit
that referenced
this pull request
Oct 4, 2026
…not_planned at first grade, citing ruling 208 instrument-discipline.md says a report-only instrument gets no card, no dev and no PR, and that its only in-flight work is deletion (ruling 208). Nothing on the triage path read that line, so the grade never asked it. One sentence in the grading block of references/triage-duties.md now does: a card whose fix lands in a report-only instrument (the widening tell, the half-state patrols) closes not_planned at first touch, citing ruling 208, and the instrument's only in-flight work is deletion. No new gate, label or script. Enumeration pin (the two instances where the line was not read; a third is a red reading of this sentence, not a new card): - #20969 / PR #21016 (landed) - #21465 / PR #21679 (held) Paid in place, net 0 lines (120 -> 120, ceiling unchanged, no re-wrap): the deleted line is the triage-side restatement of the filing door's four classes; its owning copy stays in references/filing-gate.md (the class list and the refusal of everything else, one line, with the four class heads on the lines below it), reached from the pointer line kept right under the deleted one. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 4, 2026
Contributor
Author
Closed, unmerged, on the maintainer's word: 「21679 同意关闭」Triage seat (objectstack-wide, seat post #6015) ·
|
This was referenced Oct 5, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…t_planned at first grade, citing ruling 208 (objectstack-ai#21691) Fixes objectstack-ai#21681 Clause-②: no Tier S (`.claude/**` only). Draft; this run does not flip it ready, queue it or arm auto-merge — the owning seat lands it after its at-tier contract review. ## What changed One protocol sentence in the grading block of `.claude/skills/pm-dispatch/references/triage-duties.md`, as the triage ruling on the card (comment 5976725561) specifies: a card whose fix lands in a report-only instrument (the widening tell, the half-state patrols — the judge-intent class `instrument-discipline.md` names) is closed `not_planned` at first touch, citing ruling 208, and the instrument's only in-flight work is deletion. No new gate, label or script. It is paid in place: one restated line of the same file is deleted, so the file stays at 120 lines under its 120 ceiling. No other line is touched (no re-wrap), and `SKILL.md`, `instrument-discipline.md` and `scripts/pm/check-skill-line-ratchet.mjs` are untouched. The enumeration pin — the two instances where the rule was not read — is named in the commit message: objectstack-ai#20969 / PR objectstack-ai#21016 (landed) and objectstack-ai#21465 / PR objectstack-ai#21679 (held). A third such card is a red reading of this sentence, not a new card. ## Reading 1 (读数一): placement and payment Tree: `objectstack-ai/objectstack` at `bc85776a87` (branch head), base `251a7dd4b4`. - **New sentence, line 64** (120 bytes; the line cap is 120), directly under the two existing close-at-grade lines (the 「无则关」 line and the open-P0/P1 `tooling` line), so the grading block now carries all three close-at-grade cases together: ```text - 只报告仪器(放宽 tell、半状态巡查)的修复卡首触即关 not_planned,引裁决 208;在途只有删除。 ``` - **Deleted line, was line 40** — the triage-side restatement of the filing door's four classes: ```text - 立卡门四类:① 缺陷 / ② 维护者决定 / ③ 直派任务 / ④ 协调节点,⛔ 其余一律不立卡。 ``` - **Its owning copy, kept on the reading path:** `references/filing-gate.md` line 11, the same rule (four classes only, everything else refused), with the four class heads on lines 12 / 16 / 17 / 18: ```text - 立卡只为四类,正文首行写立卡门类别 ①–④;⛔ 其余一律不立卡,不论它叫什么。 - ① 有具名落点或复现的产品缺陷,即 `pm:queue` 的定义;其内的 `finding` 限三类且带 `reach:`。 - ② 只有维护者能做的决定,落卡即带「维护者速读」与四棱块。 - ③ 维护者直派的任务,正文引其原话。 - ④ 协调节点:跨仓/跨层父单与它的逐层子单。 ``` The triage reader reaches it through the pointer line kept right under the deleted one (now line 40): `首行写立卡门类别 ①–④;四类定义、⛔ 清单、三答与配额见 references/filing-gate.md`. The deleted line entered the protocol as the summary of that file, in the same commit that created `filing-gate.md` (`37ed9ae04b`), and moved here verbatim with the principle-only cut; it is a restatement rather than a ruled clause: the ruled clause, with its provenance quotation, lives in `filing-gate.md` and is unchanged. - **Line count:** 120 → 120 (ceiling 120, headroom 0, unchanged). Diff: 1 file, +1 / −1. ## Reading 2 (读数二): gate output Run at `bc85776a87`, after the final commit, exit codes captured before any pipe: ```text pnpm check:pm-skill-ratchet exit 0 ✓ check-skill-line-ratchet self-test: 157 cases pass. ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md: widest table row is 0 bytes (pin 0; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md is 120 lines (ceiling 120; headroom 0). ✓ check-skill-line-ratchet: declared cross-file moves: 2, total ceilings down 477 lines. pnpm check:pm-skill-id-lint exit 0 ✓ check-skill-id-lint self-test: 14 cases pass. ✓ check-skill-id-lint: 34 file(s) clean (pattern /#[0-9]{3,}/g). ``` `ruling 208` is spelled without a hash prefix, the way `instrument-discipline.md` records it; the issue and PR numbers of the pin are in the commit message only. The full derived set (`node scripts/pm/dispatch-gates.mjs --commands`, no paths, change set read from the merge base) is the same 18 commands the dispatch listed, all exit 0. `--ran` reconciliation: `18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN` (a derived zero — every line carried its exit code). One first-run `exit 3` is not a measurement: `check:doc-formula-expressions` refused with PREREQUISITE NOT MET (unbuilt `@objectstack/formula` / `@objectstack/lint`); after the build it names (run under the shared verify lock, `VERDICT command-exit 0`) it exited 0. `check:nul-bytes` exit 0, plus a control-byte self-scan of the edited file: zero hits. No changeset: `.claude/**` publishes nothing. ## Acceptance notes - **Wording differs from the seat's suggested line, meaning unchanged.** The suggestion measured 152 bytes against the 120-byte line cap. The landed line drops the explicit `instrument-discipline.md` pointer; both of its key terms still lead there (「只报告的仪器」 is that file's rule line, and 「裁决 208」 its ruling record), and `reading-discipline.md` keeps its pointer to it. 「在途只有删除」 echoes that file's own 「它的在途工作只有删除」. - **The class is the instrument's role, not the path.** The sentence covers a fix to a report-only, judge-intent instrument. A fix to a hard-gate face that happens to share a file with a patrol is outside it, and the sentence does not widen the class beyond the two instruments the ruling names. Observation only, nothing filed. carrier: the triage seat, applying the sentence at grade time. - **Not decided here, as the ruling lists it:** whether the code PR objectstack-ai#21016 landed is deleted under the report-only rule is the maintainer's question; this PR does not touch `scripts/pm/check-widening-tells.mjs`. ## 维护者速读(草稿) **改了什么:** 分诊职责文件 `triage-duties.md` 的定级段加了一句:修复落在「只报告仪器」(放宽 tell、半状态巡查)上的卡,首次定级就以 not_planned 关闭,并引裁决 208;这类仪器的在途工作只有删除。同时删掉同一文件里一行重复的「立卡门四类」摘要(完整原文仍在 `filing-gate.md`),文件仍是 120 行,行数上限不动。 **为什么改:** 裁决 208 早就规定只报告的仪器不立卡、不派开发、不开 PR,但分诊定级时并不读这条,于是两张这类卡被定级入队、派了开发,一张已经合并,一张被扣住,白花两轮开发。把规则写进分诊每次都读的定级段,首次定级就能拦住。 **风险与代价(含回滚):** 只改内部协议的一行文字,不新增门禁、标签或脚本。风险是分诊把与巡查同文件的硬门禁修复也误关,句子按「只报告」身份判断而不按文件路径,可以避免。已经合并的那份代码要不要删,不在本 PR 范围,留给您另行决定。回滚就是撤销这一个 commit。 **席位意见:** **你要做的:** 无需操作:本 PR 是 Tier S(只改 `.claude/**`),席位复核通过后经合并队列落地;不同意请回一句。 --- _Generated by [Claude Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #21465
Clause-②: no
What
scripts/pm/check-widening-tells.mjsT2 read any bare string or bare…Schema,line on the contract source surface as "a new member of a closed set", whatever enclosed it. It now reads the construct that ENCLOSES the member, from the hunk, as the triage ruling (5961317165) directs. One file changes; no other file, no changeset (scripts/pm/**publishes nothing).enclosingConstruct(side, index)names the innermost opener the hunk shows (through the existingenclosingDelimiters, by the text left of it on its own line), and, only when the hunk shows no opener at all, the first closer the hunk shows ahead of the line (closerAhead, the walk's forward mirror, with the same triggers).T2_CONSTRUCT_VERDICTS, pinned one fixture per construct by the exportedT2_CONSTRUCT_FIXTURES(the ruling's enumeration pin):z.enumz.enum([, orz.literal(z.unionz.union([,z.discriminatedUnion(…, [,.or(as constarrayconst X = […] as constimport {export {({} from '…'(an import or a re-export: silent) and]/] as const(an array: fires). A plain array fires exactly as before, and so do a hunk that shows neither side, a)-only closer (its callee may be.or(), a keyword paren (if (), and a.merge(/.extend(operand (it widens; T2's accidental catch is kept).[.check-widening-tellsT1 fires on a member BOUNDED inside a previously-z.unknown()bag, so the criterion-honestClause-②: no (narrowing)is the blocked declaration and over-declaring is the only unblocked path #19099 left atenclosingDelimiters. The two docblocks and the [finding]check-widening-tellsT1 fires on a member BOUNDED inside a previously-z.unknown()bag, so the criterion-honestClause-②: no (narrowing)is the blocked declaration and over-declaring is the only unblocked path #19099 header paragraphs that said "no reader suppresses on this flag" are updated.as constarray whose every reader is a refusal predicate is T2's narrowing verdict. It is printed under its ownnarrowing:heading beside the rows, and no exit code moves.refusalOnlyReadersreads the head blob (content-addressed, as [finding] check-widening-tells T1 is still blind to FILE-LOCAL declaring factories — a new writable key throughplaceholderFree(on objectstack's own judged surface passes aClause-②: noin silence #18702 reads it) and requires the POLARITY: a guardif (!SET.includes(x)) return;followed, in the rest of its block, by.addIssue(orthrow— only members reach the refusal, so the set narrows when it grows. The allow-list polarity (if (SET.includes(x)) return;, the liveVIEW_FILTER_VALUELESS_OPERATORSshape) keeps firing. The only other readers it accepts are.joinrenders inside that members-only region or inside a.describe(argument.Size — why each block is needed
+802 / −22, one file. By block:
#21465#19099section and theenclosingDelimitersdocblockcloserAhead,enclosingCloser,T2_CONSTRUCT_VERDICTS, the head regexes,enclosingConstruct,codeOfLine,refusalOnlyReaders,t2MemberVerdicttellsInFile/wideningTells/wideningRefusal/verdictLines, andnarrowingLinesT2_CONSTRUCT_FIXTURES(7, the enumeration pin),DENY_SET_SOURCE,PR_21463_HUNKNothing here is a new tell, a new gate, a new flag or a new exit code.
Reading 1 — the repro, before and after
PR #21463's diff at head
52c4c42d72(git diff 086ad0aa68...52c4c42d72; the merge base is themainside of that branch's last merge),--declaration no --diff:fea67065a3ui/component.zod.ts:34,:35,:36(RowHeightSchema,/RowColorConfigSchema,/ListViewSchema,); T1:3805,:3833,:3834,:3835,:383640c0fec9b1:3805,:3833,:3834,:3835,:3836— the three T2 rows are gone and the five true T1 rows still fireThe narrowing instance from PR #21425's ACCEPT (
5955887463), on its squash commit (git diff 32d5769080^ 32d5769080):ui/dashboard.zod.ts:785…:789, the fiveSINGLE_SERIES_CHART_TYPESmembersnarrowing: 5 member line(s) join a module-private as const set whose every reader REFUSES its members …, naming the five lines and the bindingReading 2 — self-test counts and the floor
node scripts/pm/check-widening-tells.mjs --self-test: 553 → 607 cases pass (fea67065a3→40c0fec9b1).T2 — a new member of a closed set. Its pin rises 13 → 67, its measured count, so none of them can stop running silently.SELF_TEST_BATTERY_FLOORstays 16. AGENTS.md's floor rule pins battery NAMES with minimum counts and fails when the roster falls below its pinned battery count. No battery was added or removed (29 before, 29 after), so the rule asks nothing of that constant here. The 16-against-29 gap predates this PR (Acceptance notes).Corpus census — what moves, row for row
The clone is shallow with one boundary (
ce13bb8dc7), excluded. 2,054 non-merge commits touchingpackages/spec/srcare reachable fromfea67065a3; 2,007 have non-test.tsdiffs, giving 6,111 file diffs. Each was judged throughwideningTellswithheadBlobSourcelive (blob resolution asserted through both modules before counting), by the version atfea67065a3(a compare worktree) and by this branch..describe(prose), 93 object-literal values (84 by opener, 9 by closer), 57 import specifiers, 17 export specifiers, 8 specifiers read off} from(PR feat(spec)!: an object-grid page block types the seven members the grid reads, and resizableColumns retires for resizable (#21445) #21463's three among them), and the 5SINGLE_SERIES_CHART_TYPESmembers, now narrowing. Every declined argument and property value is a string; 0 are schema-shaped..describe(argument used to buy the unit that paid for'ROUTE_NOT_FOUND', a value newly admitted toDispatcherErrorCode(03d26f75ab). One is the price, pinned in--self-test: a replaced.describe(argument whose old side the walk reads (so it buys nothing) and whose new side it cannot (two slashes on a JSDoc line above it) fires (dd0f681a30,ui/component.zod.ts:626).check-widening-tellsT1 fires on a member BOUNDED inside a previously-z.unknown()bag, so the criterion-honestClause-②: no (narrowing)is the blocked declaration and over-declaring is the only unblocked path #19099 asked the first suppressing reader to re-measure.Ablations
All from the committed final head
40c0fec9b1(file blobc8af0779bede), throughscripts/ablation-replace.mjsin WRAP mode, under the shared verify lock. Every leg printedok mutation landedandok restored: blob == HEAD (c8af0779bede) and git diff HEAD is empty(10 of 10), andgit status --porcelainwas empty afterwards.--self-testdescription:pins (one side now pays, the other does not).describe(coin gain; the pricefiresas constenumeration fixture, the head-blob closer, the narrowing exit code, the two read-count pins!made optional (polarity ignored)if (unreadable)→if (unreadable && false)}readingAn eleventh leg, run at
121a3f3e0d, dropped a per-binding memo ofrefusalOnlyReadersand stayed GREEN: the memo changed no verdict and no read count. It was therefore deleted (40c0fec9b1) rather than kept unpinned.b5cf545ba7) was a no-op and is not counted: its replacement was a substring of its anchor, so the replacement count did not rise, andablation-replacerefused and restored without running the self-test. Every leg above was re-run at the final head with a countable replacement.Gates
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 30 commands, identical to the dispatch's list, re-derived unchanged at the final head. All 30 ran at40c0fec9b1in one run underscripts/pm/os-verify-lock.sh, each exit code captured before any pipe. The lock held 27m17s on a shared box;check:pm-dispatch-gatesalone took 1,239s (✓ dispatch-gates self-test: 1976 cases pass), andcheck:pm-widening-tellsprinted607 cases pass.node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-declaration-mirrors.mjsnode scripts/check-declaration-mirrors.mjs --self-testnode scripts/check-scripts-symbol-anchors.mjsnode scripts/check-scripts-symbol-anchors.mjs --self-testnode scripts/check-self-test-wired.mjsnode scripts/check-self-test-wired.mjs --self-testnode scripts/check-self-test-workflow-commands.mjsnode scripts/check-self-test-workflow-commands.mjs --self-testnode scripts/check-whole-set-label-write.mjsnode scripts/check-whole-set-label-write.mjs --self-testnode scripts/pm/bare-root-worklist.mjs --self-testpnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:cross-package-test-inputspnpm check:driver-memory-censuspnpm check:entry-guardpnpm check:gitlink-declaredpnpm check:nul-bytespnpm check:parse-guardpnpm check:pm-dispatch-gatespnpm check:pm-widening-tellspnpm check:pnpm-filter-targetspnpm check:ratchet-remedy-authoritypnpm check:refd-timer-probepnpm check:watch-hint-literalnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list(each lineCOMMAND :: exit N, written from the run's own record):Run reconciliation — 30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN./✓ dispatch-gates --ran: 30 derived famil(ies) accounted for — 30 run, 0 NOT-MEASURED, derived at40c0fec9b1, exit 0. Repo-wide lint (pnpm lint) is CI's run and was not run locally: NOT MEASURED here, reason: CI-owned.Self-referential check:
node scripts/pm/check-widening-tells.mjs --declaration no --diffon this branch's own diff (git diff fea67065a3...HEAD) exits 0 —1 changed file(s) — 0 judged against a declared surface (no widening tell), 1 NOT MEASURED.scripts/pm/is on no tell surface, so the instrument says nothing about this PR;Clause-②: norests on the diff publishing nothing.No other test names this script (
git grep -l check-widening-tellsover*.test.*: 0 hits; controlscripts/pm/over the same glob: 5 files), so its suite is its own--self-test.Acceptance notes
Found on the way, not fixed here:
SELF_TEST_BATTERY_FLOOR = 16against 29 declared batteries, so up to 13 batteries could be deleted with no red. The reference instrument (scripts/check-agent-model-declared.mjs) pins its floor equal to its roster (18 and 18). Not touched: this PR adds no battery. Carrier: none.CLOSED_SET_OPENERmatchesz.enum(/z.union(inside string literals, so a prose fragment naming a constructor reads as a new closed set (seen in the census atshared/retired-key.ts:329,migrations/registry.ts:5670andmigrations/entries/semantic/18.analytics-time-dimension-date-range-vocabulary-closed.ts:37). Opener lines are not member lines, so this PR's reading does not reach them; it is the next member of the T2 false-positive family, for the triage ruling's fixture table rather than a new card. Carrier: none.维护者速读(草稿)
改了什么:PM 工具
check-widening-tells的 T2 读数(「闭集新增成员」)以前只看一行长得像不像成员,现在先看这一行被什么结构包着。import / export 列表、函数实参、对象字面量里的元素一律不算闭集成员;z.enum、z.union、as const数组照旧报。另外,一个只被「拒收谓词」读的私有as const集合加成员,现在单独报为「收窄」,不再算放宽。为什么改:PR #21463 只是多 import 了三个 schema,工具就报了三条「闭集新增成员」。按规则,一个只收窄、顺带加 import 的 PR,会被这个假读数逼着把
Clause-②写成yes,往治理记录里写一个并不存在的放宽。在 2,054 个提交上实测:285 条 T2 假读数消失,T1 / T4 一条不动。风险与代价(含回滚):新增 1 条假报(一处 JSDoc 里斜杠让工具读不懂,宁可多报),新增 1 条真报(以前被删掉的说明文字「抵扣」掉的真实新枚举值)。理论上漏报的形状(把实参变成闭集的 helper)在实测中为 0,已写进文件头。只改一个 PM 脚本,不发布任何包;回滚即 revert 本 PR。
席位意见:
你要做的:无需动作;席位达档复核通过后经合并队列落地。
Generated by Claude Code