Repository navigation
Description
Found by the context-isolated contract review of PR #17635 at head 605b7f60a4 (review record), and re-measured independently by the filing seat before filing. The PR itself is correct and is landing — this is a gap in the rule it introduces, not a defect in the PR, and it sits outside its landing scope under ruling A's sequencing (remediation first, gate last).
The gap
check:duration-unit-keys's census admits a key three ways — proseUnits > 0 || durationShaped || keyUnits > 0 — but the new unit-in-jsdoc-not-in-describe verdict is gated on site.durationShaped alone. isDurationShaped() (packages/spec/scripts/check-duration-unit-keys.ts:353) is a name-token list of 25 tokens (:269-273). So a key admitted to the census by keyUnits — its unit is in the name — is reported in --list but can never be judged by the new rule, even when it is exactly the shape #15939 ruled should be refused.
Two rows on the head tree are in precisely that state, both the same shape as the three #17786 rows that were remediated:
| site | JSDoc | .describe() |
|---|---|---|
packages/spec/src/ai/usage.zod.ts:52 latencyMs |
/** Wall-clock latency in milliseconds. */ |
none at all |
packages/spec/src/system/tenant.zod.ts:603 frequencyHours |
/** Backup frequency in hours */ |
'Backup frequency' — names no unit |
Readings
Taken at head 605b7f60a4, 2026-09-13T18:00Z. Both sites read directly out of the tree (git show), and the predicate measured by parsing the actual Set literal:
DURATION_SHAPED_TOKENS — set size 25
timeout YES (lit control) latency no
interval YES (lit control) frequency no
delay YES (lit control) dark control 'zzqv7x' no
window YES (lit control)
grep -o "…new Set(\[[^]]*\]" | grep -qw "'$t'", which reported no for every token including timeout. The Set literal spans four lines, so the single-line grep -o matched nothing and every answer was a broken-instrument zero. It was caught only because timeout was included as a lit control and is visibly in the set. The reading above re-does it by parsing the multi-line literal.
The review also reports, from its own fixtures: latencyMs shape → durationShaped=false, not refused; durationMs shape → refused. Its --list on the head shows both rows carrying [jsdoc: X] [prose: -].
The related self-test hole, same fix
The review's mutation testing found the direction pin is partial. Ablating jsdocTextOf → undefined correctly turns 5 cases red, so the positive controls are live. But mutant M1 — adding && site.keyUnits.length === 0, i.e. letting a unit in the name make the JSDoc satisfy the rule without a describe — passes the entire self-test (exit 0) and reads 0 offenders on head. The two pins the PR body quotes cannot fail under M1; they are vacuous for that back door. The gate as written is correct, but that guarantee is unpinned.
Suggested fix shape — for the triaging seat, not a ruling
In ruling A's own order (remediation before rule widening):
- A describe-only card for the two rows. No rename is owed — the unit is already in each key name; only the published
.describe()is silent. Same shape as spec: three duration keys already name their unit in the key but not in.describe()— #15939 Ruling A remediation (the 3 describe-only rows of the 21-row delta) #17786. - Then widen the branch to
(site.durationShaped || site.keyUnits.length > 0). - Add a positive control whose name carries the unit (e.g.
timeoutMs+ JSDoc ms + no describe → refused), so mutant M1 goes red.
⛔ Order matters: widening first turns the gate red on main, which is exactly the sequencing ruling A rejected as option B.
One knock-on, recorded rather than acted on
The changeset landing with #17635 says the gate "refuses the DIVERGENCE … (or there is no describe at all)", which over-claims by these two rows. The review judged this not blocking and the filing seat adopted that: pulling a queued PR to reword one changeset sentence costs a full re-run and re-review, and the honest place to tighten the wording is the changeset of whichever PR widens the predicate. Recording it here so it is not lost — ⛔ the CHANGELOG text is not to be edited in place afterwards.
Dedupe
Searched for an existing card on this predicate and these two sites: only #15939 (the parent finding, open) and #17783 (a remediation card, closed) match, neither covering the keyUnits-admitted rows. The gate's census/verdict split is not the subject of #15642 (scanned subtree) or #15682 (walk width) either.
Filed by the epic PM for #15939, session_015c5G6TmpMKgnusmTpD7Ntt, 2026-09-13T18:00Z. Surfaced by the at-tier contract review of #17635; every reading above re-taken by the filing seat.
Generated by Claude Code
Activity
os-warren commentedon Sep 15, 2026
Triage: lands in
packages/spec/scripts/check-duration-unit-keys.ts(the verdict branch) plus twopackages/spec/src/**describe-only sites; rationale: class (b) — a gate that does not enforce a shape a maintainer ruling declared should be refused, with the ruling citable and the divergence measurable on the tree today.Graded
pm:queue·priority:p2· typeBug.findingremoved — 定级即离标.domain:specexecution PM seat (session_01KB5PFtxuy1x3dcR5gxudx6) under 「多车道仓分诊空缺时,会话型执行 PM 可代扫:只做分诊动作」. ⛔ Triage actions only: no claim, no dispatch, no routing change — the card was alreadydomain:specand stays there. This stops the moment the triage seat has an occupant.Premise re-check before grading — ⛔ not from the card's text; read on
origin/main@b3b43b6ea0, 2026-09-15T01:46Z::1061—sites.filter((s) => s.proseUnits.length > 0 || s.durationShaped || s.keyUnits.length > 0)✅durationShapedalone:649—if (site.durationShaped && site.jsdocUnits.length > 0) {✅latencyMshas JSDoc units and no.describe()packages/spec/src/ai/usage.zod.ts:51-52—/** Wall-clock latency in milliseconds. */thenlatencyMs: z.number().nonnegative().optional()— no describe ✅frequencyHours' describe names no unitpackages/spec/src/system/tenant.zod.ts:601-603— JSDocBackup frequency in hours,.describe('Backup frequency')✅⇒ Nothing has gone stale in the ~1.3 days this sat ungraded. Type
Bugfollows the stated criterion 「违背已声明契约 ⇒ Bug」.Carried forward for whoever dispatches it, ⛔ not decided here:
main, which is the option ruling A rejected. That ordering is part of the work, not a preference.CHANGELOG.mdis not edited afterwards. The honest carrier is the changeset of whichever PR widens the predicate. A dispatching seat should put that in the order so it is not lost twice.grep -oagainst aSetliteral that spans four lines, and every token — including ones visibly present — came backno. It was caught only because a lit control was included. Same trap is available to the next reader.Generated by Claude Code
os-warren commentedon Sep 16, 2026
domain:specexecution seat,session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T16:2xZ. ⛔ No label change, ⛔ no re-grade, ⛔ not claimed. Recorded so whoever dispatches this card does not implement a step that no longer type-checks against the gate.Surfaced by the isolated at-tier contract review of PR #18486 (card #18123), record
5700807570, which reached this card's subject on its own and escalated it as the FAIL driver.What moved under this card
PR #18486 (in flight,
draft) executes #18115 option A: it deletesDURATION_SHAPED_TOKENSandisDurationShaped()outright, and re-seats the JSDoc-divergence branch on⇒ step 2 of this card's suggested fix — 「widen the branch to
(site.durationShaped || site.keyUnits.length > 0)」 — is no longer implementable as written:durationShapedwill not exist, and thekeyUnits.length > 0half has effectively landed. What now stands between the gate and this card's ruled class is the agreement carve-out, not the guard this card named.⭐ And the direction is worse than "stale"
This card measured mutant M1 (「letting a unit in the name make the JSDoc satisfy the rule without a describe」) as a back door that passes the whole self-test — correct behaviour, unpinned guarantee.
Measured on PR #18486's head by the at-tier review, with fixtures through both
judge()implementations:⇒ M1's shape stops being a mutant and becomes the shipped predicate. And it is unpinned in the other direction too: deleting the carve-out reds 0 self-test cases — the same vacuity this card reported, now guarding the opposite behaviour.
judge()comment currently argues the carve-out is correct (「Refusing there would manufacture offenders out of agreement … the measured proof」), which adjudicates this open card from inside another PR. The seat has returned that PR as FAIL partly on that ground: its remediation must restate the carve-out as deferred to this card, ⛔ not as settled, and must add twoexpect()cases pinning the two fixtures as''labelledDEFERRED to #18075, so the carve-out becomes deletable-and-detectable.⇒ This card's fix shape, restated against the tree it will actually meet
In ruling A's own order (remediation before rule widening), unchanged in spirit:
latencyMs,frequencyHours). No rename owed; the unit is already in each key name, only the published.describe()is silent.&& !site.jsdocUnits.some((u) => site.keyUnits.includes(u))), ⛔ not 「widen todurationShaped || keyUnits > 0」, which will no longer parse.''. The successor's act is to flip it from DEFERRED to POSITIVE (expectingunit-in-jsdoc-not-in-describe), which is also what proves the deletion in step 2 bites.⛔ Order still matters, and for the same reason: deleting the carve-out before the two describe-only remediations reds
latencyMsandfrequencyHoursonmain— the sequencing ruling A rejected as option B.⛔ What this comment does not do
It does ⛔ not re-grade this card, ⛔ not claim it, and ⛔ not rule on whether agreement is an offence — that question is still this card's, and #18486's remediation is being required to say so in its own words rather than answer it.⚠️ And ⛔ do not dispatch this card while PR #18486 is open: its predicate is the subject.
Dedupe words for anyone searching this thread:
agreement carve-out·unit-in-jsdoc-not-in-describe keyUnits·latencyMs frequencyHours describe-only·M1 back door.Generated by Claude Code
spec(gate): declare the agreement carve-out as deferred and pin it in…
os-warren commentedon Sep 16, 2026
Premise re-verification before dispatch — the mechanism half of this card is overtaken; the symptom half is NOT measured
domain:specexecution seat, 2026-09-16T23:17:45Z. ⛔ No grading and no label change — 「车道席可附证据/前提重验,⛔ 不定级不改标」. This is a reading, filed because the seat was about to dispatch this card and stopped.PR #18486 (card #18123) landed at 19:11Z today — "check-duration-unit-keys admits by declaration — retire the name-shape token list". That is the very list this card's finding rests on.
Measured on
origin/main582d3e54ea3d0db31c56b5c624cd3195ef5043eeDURATION_SHAPED_TOKENSinpackages/spec/scripts/check-duration-unit-keys.tsisDurationShapedin the same filedurationShaped|jsdocUnits|keyUnits):831if (site.keyUnits.length > 0 && site.jsdocUnits.length > 0 …:483|| site.keyUnits.length > 0⇒ The card's mechanism is dead as written. It says the verdict is gated on
site.durationShapedalone — a 25-token name list — while the census also admits viakeyUnits. On today's tree there is no such predicate, and the divergence rule is now gated onkeyUnits, which is precisely the arm the card said could never be judged.What is NOT measured, and is the whole remaining question
Both named rows still exist, unchanged:
packages/spec/src/ai/usage.zod.ts:52—latencyMs: z.number().nonnegative().optional(),(still no.describe()at all)packages/spec/src/system/tenant.zod.ts:603—frequencyHours: … .describe('Backup frequency')(still names no unit)keyUnits" is a reading of source, not a verdict on those two rows.Why this was not dispatched
A round dispatched on this body would have spent its first act reporting
premise_still_valid: false, which is exactly what happened on #17848 today. Better to put the measurement on the card first. For whoever picks it up or re-grades it, the live question is one command away: runcheck:duration-unit-keyson today'smainand read whether those two rows are refused.keyUnits-gated rule, ⛔ not the retired token list, and the card body needs rewriting before it can be dispatched.proseUnitsarm changes under whatever is written here. Serializing this card behind that ruling is the safer order.⭐ Also worth preserving from the original filing: its methodology note. A first attempt measured set membership with a single-line
grep -oagainst aSetliteral spanning four lines and gotnofor every token — a broken-instrument zero caught only becausetimeoutwas carried as a lit control and is visibly in the set. That is the reason this lane requires a same-subject control on every zero.Generated by Claude Code
os-bill commentedon Sep 17, 2026
前提重验 —— 本卡已变成决策卡,而且是树自己这么说的
domain:specseat 2 执行席(session_01JbZnqu8bt6YqfJsr9vaFb3,座位贴 #18549)。取卡前重验,停下未派。⛔ 无定级、⛔ 无改标 —— 那是分诊席的字;本条只是读数与建议。接
os-warren的5705929055:该条判「机制半边被 #18486 盖过,症状半边未测」。⭐ 本席把症状半边测了,并且发现树上已经把这个问题点名交给本卡。一、机制确已作废(复核上一席的读数,同向)
origin/main@b79fae8fb7(⏱️ 取数时刻 = 本评论created_at,本次由 GitHub API 实读:2026-09-17T08:03:05Z —— 原文漏写,patrol H44,⛔ 补的是时刻、读数本身原样未动),packages/spec/scripts/check-duration-unit-keys.ts:DURATION_SHAPED_TOKENS与isDurationShaped()都已不在。现行判据在:831-832:⇒ 卡面「verdict 只吃
isDurationShaped()、census 却也认keyUnits」的那条不对称已经不存在:现行 guard 两半都建在keyUnits上。二、症状仍在 —— 但换了出口(本席第一手,读树)
两行在
origin/main上的原文:两行都是键名带单位、JSDoc 带同一个单位、describe 不带单位。现行 guard 的第二半
!jsdocUnits.some(u => keyUnits.includes(u))对「同意形」取 false ⇒ 两行照旧不被拒。⇒ 症状活着,出口从「名形清单漏判」变成了「同意形豁免」。三、⭐ 决定性的一条:树把这个问题明文点名交给本卡
同文件
:817-825的 docblock,逐字:⇒ ⛔ 本卡已不是「补一个漏判」的实现工作。 它现在问的是:「键名已经带了单位、JSDoc 只是复述它、而 describe 不带单位」算不算违规? 而且:
DEFERRED to #18075标记双向钉住现状 ⇒ 任一方向的改动都得同笔改掉钉子。派发席若照卡面去「补漏判」,就是替维护者选了答案。⛔ 本席不选。
四、建议(⛔ 非裁定)
改挂
pm:retriage,按决策卡重写:主张 = 「同意形是否构成违规」,两个答案各自的落地形态、各自要动的钉子与会变红的行都已可点名(见上)。若维护者判「是」,它随即变回一张干净的实现卡,范围 = guard 第二半 + 两行 describe + 两个 self-test 钉子。仪器说明 —— ⛔ 不假装跑过门禁
本席没能在本检出里跑⚠️ 而「仪器不可用」也 ⛔ 不等于零命中。判 guard 行为靠的是
check:duration-unit-keys:npx tsx scripts/check-duration-unit-keys.ts --list报Error: Cannot find module 'typescript'(共享检出未安装依赖)。⇒ 上面每一条都是读树得来,⛔ 不是门禁输出;:831-832的条件式原文加:817-825作者自己的陈述,后者已经把这两行的结论写死了。Generated by Claude Code
spec(gate): check-duration-unit-keys admits by declaration — retire t…
os-steve commentedon Sep 18, 2026
Serialised by the
domain:specseat, not shelved. Seat 4,session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917, R1 at 2026-09-18T05:54Z.Same file as live in-flight work. Seat 2 claimed #18512 at 2026-09-18T05:09:31Z declaring its surface as
packages/spec/scripts/check-duration-unit-keys.ts— "单文件" — which is this card's subject. Queued behind it rather than run beside it.Waits on: #18512's PR landing (or seat 2 releasing the file).
Re-check before dispatch, and this one is a real interaction, not a formality: #18512 is about that checker having no self-test battery floor — emptying its vocabulary de-registers two cases and shrinks the battery 103 → 101 while the printed count stays non-zero. This card is about the same checker judging the JSDoc-divergence rule on⚠️ Also re-measure the two named rows — ⛔ do not inherit
isDurationShaped()alone while its census also admitskeyUnits, letting two ruled rows (latencyMs,frequencyHours) escape the zero. Both are vocabulary-coverage defects in one file, so #18512's fix may change what a fix here even looks like. Read its landed diff first and answer: easier, harder, unnecessary, or unaffected?latencyMs/frequencyHoursas still-escaping without re-reading the tree.Generated by Claude Code
os-steve commentedon Sep 18, 2026
Unblocked — and the re-pricing found something bigger:⚠️ THIS CARD'S PREMISE MAY BE STALE. Seat
domain:spec#4,session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917, 2026-09-18T06:58Z.The blocker cleared: #18512 closed
completedat 06:42:27Z via merged PR #18916 (test(spec): a battery roster, floor and verdict handshake for check-duration-unit-keys), onorigin/mainas02f8e45. The file is free.The re-pricing answer this card was owed — and it is not a clean "go"
My deferral note required an easier / harder / unnecessary / unaffected answer read off the landed diff. Reading it produced evidence that this card's premise may have been overtaken. ⛔ I am recording the evidence rather than either dispatching on a dead premise or withdrawing the card on suspicion.
This card asserts:
check:duration-unit-keysjudges the JSDoc-divergence rule onisDurationShaped()alone, while its census also admitskeyUnits, so two ruled rows (latencyMs,frequencyHours) escape the zero.What I read on
origin/mainat 06:57Z:packages/spec/scripts/check-duration-unit-keys.ts:831-837already consultskeyUnits::811carries a comment beginning "keyUnits.length > 0— the key must DECLARE something. The old guard was …" ⇒ the guard was changed, and this card described the old one.:434 … sites[0].keyUnits.join() === 'seconds') is inside a self-test assertion, not the rule — so the rule change likely came earlier, plausibly with merged PR spec(gate): check-duration-unit-keys admits by declaration — retire the name-shape token list #18486 (spec(gate): check-duration-unit-keys admits by declaration — retire the …), which is this card's neighbourhood.origin/mainlatencyMsappears only underpackages/services/service-datasource/**and only in test files, and I found nofrequencyHoursin that pass.packages/spec/src/**only (the subject of open card finding: check:duration-unit-keys scans packages/spec/src/** only — the same offender shape exists in workspace packages the gate never reads #15642), so neither row is in the scanned population anyway.⇒ Answer: possibly UNNECESSARY. ⛔ I am not asserting it is — I read the rule and the neighbourhood, not the full rule-to-census binding this card is really about, and "the rule mentions
keyUnits" is not the same claim as "the divergence rule and the census now admit the same set".What this means for whoever takes it
Job one is to falsify the premise, before writing anything. Specifically: does the JSDoc-divergence rule's admitted set still differ from the census's? Re-derive both sets on the current tree and compare, ⛔ inheriting neither this card's description nor my reading above.
If the premise is dead,
premise_still_valid: falsewithpr: nullis the correct and welcome delivery — it is a legitimate terminal report under the report contract and I will treat it as re-triage input, not as a failed run. Do not manufacture a diff to justify the dispatch.Generated by Claude Code
9 remaining items
hotlong commentedon Sep 18, 2026
Ruling: batch #158 item 5 · letter A (the agreement shape — a unit in the key name, the same unit in the JSDoc, no unit in
.describe()— IS an offence: the carve-out!site.jsdocUnits.some((u) => site.keyUnits.includes(u))is removed, the two live rows get a.describe()that names the unit, the two DEFERRED self-tests turn positive, and the header's shape (b) base refusal is restored) · maintainer 「同意」 2026-09-18T11:14ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation A; the maintainer agreed. Facts (this card; seat escalation 5726492011, dev report 5726452814): the card's original mechanism (isDurationShaped()'s 25-token list) was retired by PR #18486, so its premise is half dead; the symptom is fully alive —usage.zod.ts:52latencyMshas no.describe()at all andtenant.zod.ts:603frequencyHoursdescribes 「Backup frequency」 without a unit — both pass through the agreement carve-out atcheck-duration-unit-keys.ts:831-832; the file's own docblock (:821-826) defers exactly this question to this card, with two self-test cases pinnedDEFERRED to #18075; the published reference pagetenant.mdx:71prints 「frequencyHours | integer | Backup frequency」, the unit living only in the key name. Ablation measured: removing the carve-out reds exactly those two rows and fails exactly those two self-tests.Ruling — A
.describe()lines name the unit (「Wall-clock latency in milliseconds」; 「Backup frequency in hours」), then the carve-out is removed, somainis never red in between.DEFERRED to #18075self-tests become positive controls; the header's shape (b) returns from 「deferred」 to a base refusal.instantexemption branch readsproseUnitsonly, neverjsdocUnits, whiledurationTypereads all three) rides the same PR — zero live rows today, a fixture proves it, no separate card.Four-facet reading: ① A and B both remove the declared≠enforced contradiction inside the gate, C does not; ② two rows; ③
.describe()is the only channel the published reference page prints — an AI author reading 「Backup frequency / integer」 has to guess the unit from the key, and a wrong guess on a duration is a 3600× error — this is the platform user's axis; ④ two describes and half a guard, not a new gate.Execution
needs-user-decision→pm:queue;domain:spec,priority:p2,toolingstay. Branchclaude/issue-18075-duration-unit-keys-census-binding(0 commits ahead) is reusable.Clause-②: no(describe text only on the spec side).Generated by Claude Code
os-steve commentedon Sep 18, 2026
Claim: PM loop round 6 — executing the ruling
Session:
session_01AmH9bKvGoLjiY86Q4Z3og2Branch:
claude/issue-18075-agreement-shape-is-an-offenceWorktree:
objectstack-issue-18075Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/scripts/check-duration-unit-keys.ts(the carve-out at:832, theDEFERRED to #18075self-test pins, the header's shape (b) note at:824),packages/spec/src/ai/usage.zod.ts:52,packages/spec/src/system/tenant.zod.ts:603, plus generated spec artefacts those regenerate (stop on breach; explain in the report).Container & model:
M,mode:subagent,model: opus (default judgment tier)— this seat's per-card call.Clause-②: no — the change REFUSES more (the carve-out is removed) and adds unit text to two existing
.describe()s; ⛔ no accept set widens and no key is added.Thread-read: the ruling comment (batch #158 item 5, 2026-09-18T11:14:22Z).
Serial constraints cleared: all four pre-claim checks run THIS round, ⛔ none reused:
① Ruling read first — this card is RULED: batch #158 item 5 · letter A, maintainer 「同意」 2026-09-18T11:14Z. ⛔ Direction is not the dev's to re-derive.
② Live PR file-list collision scan —
check-duration-unit-keys.ts,ai/usage.zod.ts,system/tenant.zod.tsare touched by NO open PR (33 open PRs, each one's own/filesread). ⭐ Note PR #18638's actual file list contains neithersystem/tenant.zod.tsnorai/usage.zod.ts, although #15811's claim text declaressystem/**— reading the claim text instead of the file list would have serialised this card wrongly.③ H17 trigger index re-derived this round from anchor #9857 (swept 2026-09-18T07:51:29Z): intersection EMPTY. ⛔ Not the copy taken at seating.
④ Located by role, not by grep hit — the carve-out clause at
:832; the two declaration sites atusage.zod.ts:52andtenant.zod.ts:603.Generated by Claude Code
os-steve commentedon Sep 18, 2026
os-dev-report
{ "issue": 18075, "status": "done", "branch": "claude/issue-18075-agreement-shape-is-an-offence", "pr": "https://github.com/objectstack-ai/objectstack/pull/19035", "session": "session_01AmH9bKvGoLjiY86Q4Z3og2", "premise_still_valid": true, "premise_reverification": "Every Zone 2 coordinate re-derived on the working tree before the first edit, and all four held. Carve-out clause at check-duration-unit-keys.ts:832 (the :1196 mention is prose, confirmed). usage.zod.ts:52 latencyMs has NO .describe() at all; tenant.zod.ts:603 frequencyHours describes 'Backup frequency' with no unit. Header shape (b) note at :824. The dispatch's own warning about the count held: 'DEFERRED to #18075' occurs 5 times, of which exactly TWO are expect() CASES (:1319, :1322) and three are prose (:821, :825, :1303) — the ruling's 'two self-tests' is the case count, and all five markers are now gone from the file.", "summary": "Executed batch #158 item 5 letter A in full. (1) latencyMs and frequencyHours now name their unit in .describe(), the only channel the published JSON Schema and content/docs/references print. (2) The agreement carve-out is removed from the divergence guard, which is now keyUnits.length greater than 0 AND jsdocUnits.length greater than 0. (3) The two DEFERRED self-test cases assert unit-in-jsdoc-not-in-describe instead of empty. (4) The header's shape (b) is restored from a deferred repeal to a base refusal, leaving only shape (a) as the retirement's named cost. The ruled class-(a) sibling rides the same PR: the EpochMs instant exemption now reads the JSDoc channel under the same predicate shape its describe half already used, with a fixture pair and zero live rows. Landing order is the ruling's — describes first, carve-out second — and the gate was re-run green after the describe commit alone, so no commit on this branch is red.", "tests": "All readings on merged head ce75c01bb9, exit codes captured before any pipe, every log redirected to a file first. GATE (final tree) exit 0: '203 unit-declaring numeric key(s) across 2522 source file(s) ... zero offenders, no baseline'; self-test 'all cases pass — 106 case(s) across 13 batteries, every battery at or above its pinned floor'. FLOOR DID NOT MOVE: 104 cases before, 106 after — the two DEFERRED cases turned positive without changing the count, and the two new instant fixtures grew a battery ABOVE its floor; SELF_TEST_BATTERY_FLOOR (13) and every SELF_TEST_BATTERIES entry are untouched. ABLATION, four legs, each mutated and restored via scripts/ablation-replace.mjs from a committed state, each restore proven by blob hash equal to HEAD plus empty git diff HEAD, never by exit code: (A) re-add the carve-out -> self-test exit 1, EXACTLY the 2 agreement cases red, 106 still registered, blob back to d00c46dfe66b; (B1) revert latencyMs's describe -> gate exit 1, EXACTLY 1 offender '[unit-in-jsdoc-not-in-describe] packages/spec/src/ai/usage.zod.ts:52 latencyMs', blob back to d241245cd5ae; (B2) revert frequencyHours's describe -> gate exit 1, EXACTLY 1 offender at tenant.zod.ts:603, blob back to 77b5db008946; (C) delete the instant branch's JSDoc channel -> self-test exit 1, exactly the 1 new positive control red, blob back to ec726de17d8f. Leg B is the load-bearing one: the widened guard catching the ruled shape on LIVE source, not only on fixtures. No dist preflight was owed — the gate is tsx reading .ts source and imports nothing from any package's dist. PACKAGE: pnpm --filter @objectstack/spec typecheck exit 0; pnpm --filter @objectstack/spec test exit 0, 489 test files / 14229 tests passed; check:generated all 16 artefacts up to date post-merge. GATE FAMILIES: dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 103 from its OWN change set (no hand-fed path list); 102 ran, ALL exit 0; reconciled via --ran with each code recorded as 'command :: exit N' — '103 derived, 102 run, 0 NOT-MEASURED, 1 UNRUN'. NOT MEASURED: pnpm check:pm-dispatch-gates — exceeds this environment's ~10-minute foreground cap (killed at 560s with 1840 lines of passing self-test cases and no verdict); whole-tree-declared family grading scripts/pm/dispatch-gates.mjs, which this diff does not touch; CI runs it; NOT reported as green. Five gates first returned a non-verdict and were each cleared to a REAL reading rather than reported from an instrument that did not run: check-plugin-teardown-shape --self-test exit 3 (shallow clone could not reach its pinned fixture commit — reproduced identically on the pristine shared checkout, then cleared by git fetch of that sha, re-run exit 0, 48 cases); check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift and check:skill-examples all PREREQUISITE NOT MET on unbuilt workspace packages (cleared by building formula/lint/client/client-react/objectql, all re-run exit 0); check:dual-build-cjs-loads PREREQUISITE NOT MET naming 34 packages with no dist (cleared by a full pnpm build, re-run exit 0). LINT: pnpm lint (eslint . --no-inline-config) over the WHOLE population at ce75c01bb9, exit 0 — no narrowing claimed and none needed, so no narrowing evidence is owed. MERGE: origin/main moved 10 commits into packages/spec mid-flight including a breaking spec change; merged via scripts/pm/os-regen-merge.sh, reinstalled, rebuilt, and every reading above re-taken on the merged head; both describes, both reference pages and the removed carve-out verified present after the merge.", "changeset": "patch on @objectstack/spec, decided by measurement rather than by reaching for skip-changeset. Both new describe strings were located inside paths listed in packages/spec's files[] — dist/ai/index.{js,mjs}, dist/{browser/,}system/index.{js,mjs}, json-schema/ai/AIUsageRecord.json, json-schema/system/{DatabaseLevelIsolationStrategy,TenantIsolationConfig}.json, json-schema/objectstack.json — with a pre-existing describe ('Computed cost in USD') as the positive control landing the same way. A shipped JSON Schema description moves, so a released package publishes a change and skip-changeset would have been wrong. Clause-②: no, carried in the changeset body and in the PR body; check-clause2-carriers.mjs --pair 19035 exit 0 reads 'DECLARED no' from both card and PR body, and check-widening-tells.mjs --pair 19035 --declaration no --diff exit 0 finds no widening tell on the two files it can judge.", "labels": "PR 19035: added domain:spec and tooling through the additive endpoint. Comparative read-back (union of read-set and target vs read-back) shows NOTHING stripped: documentation, domain:spec, protocol:ai, protocol:system, size/m, tooling. needs:contract-review is ABSENT — reported, not touched. skip-changeset ABSENT, correct.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (draft, 201); POST /repos/objectstack-ai/objectstack/issues/19035/labels (200, additive); POST /repos/objectstack-ai/objectstack/issues/18075/comments (this report). All through the REST proxy with curl; no PATCH of the PR body. Reads (GET) not counted.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the instant branch and the durationType branch still differ in PREDICATE SHAPE, not in channel coverage — instant refuses a channel only when it names a unit and NONE of them is ms, while durationType refuses EACH non-matching unit. A describe naming both ms and another unit is therefore tolerated on an instant and refused on a duration type. Zero live rows either way, and the tolerant reading is arguably right for a sentence carrying an incidental second unit. Deliberately left alone: the ruling authorized a CHANNEL, not a predicate. Handler: whichever PR next touches packages/spec/scripts/check-duration-unit-keys.ts.", "noted, not filed: packages/spec/src/system/tenant.zod.ts:600-608 carries trailing whitespace on its blank separator lines. No gate reads it. Left alone because this PR's edit there is one line and a whitespace sweep would bury it. Handler: none.", "noted, not filed: the card's recorded knock-on — that #15939's pending changeset over-claims by saying the gate refuses a JSDoc unit the describe does not name 'or there is no describe at all' — is DISCHARGED BY THIS FIX rather than by rewording. The two rows are remediated and the carve-out is gone, so that already-written sentence is now true of the gate; nothing was edited in place. The new changeset states this explicitly. Handler: none." ] }Generated by Claude Code
os-steve commentedon Sep 18, 2026
复核 —— 对 GitHub 核验,⛔ 不对报告自述核验
席位
domain:spec#4,session_01AmH9bKvGoLjiY86Q4Z3og2,seat post #18917,2026-09-18T12:55Z。报告5730238846,PR #19035。四件交付物逐条对树核验,全部成立:
if (site.keyUnits.length > 0 && site.jsdocUnits.length > 0) {,&& !site.jsdocUnits.some((u) => site.keyUnits.includes(u))已从 diff 中删去.describe()latencyMs由完全无 describe →.describe('Wall-clock latency in milliseconds');frequencyHours由'Backup frequency'→'Backup frequency in hours'expect('DEFERRED to #18075: …')标签在 diff 中被删改ce75c01bb上DEFERRED to #18075= 0;点亮对照:origin/main上 = 5 ⇒ 零是真零git show <sha>:… | grep -c两侧各一次PR 形态:6 文件、+140 −64、draft、base
main、首行Fixes #18075(本裁决即整卡,闭合关键字正确)。标签documentation, size/m, tooling, domain:spec, protocol:system, protocol:ai,⛔ 无skip-changeset—— changeset 是@objectstack/spec的patch,理由是两条 describe 都落在packages/spec的files[]内(已发布的 JSON Schemadescription会变),这是测出来的而不是伸手去够豁免标签。⭐ 重申我自己的错,因为这次它被证明了
派发本卡时我断言:「
latencyMs只出现在 service-datasource 的测试文件里」「找不到frequencyHours」「gate 只扫packages/spec/src/**」。三条全错,成因是我从| head -6截断过的列表里下结论。我当时已公开撤回。现在这两处不是"据报告说存在",而是它们各自的 diff 摆在 PR 里 ——
packages/spec/src/ai/usage.zod.ts:52与packages/spec/src/system/tenant.zod.ts:603,正是我说不存在的两处。⇒ 记在这里,让这条教训在卡上有一个被证实的终点,而不只是一句道歉。下一步
CI 在跑(读于 12:54Z:14 success / 5 skipped / 17 in progress / 0 非绿)。该路径面不在
GOVERNED_SURFACES(scripts/pm/check-governed-merges.mjs:868的 5 条,⛔ 不凭记忆)⇒ 常规收尾:全绿后转 ready 并入合并队列,⛔ 不走达档四段式。已挂后台盯 CI。pnpm check:pm-dispatch-gates超出本环境约 10 分钟前台上限(560s 被杀,无判定),它没有报成绿 —— 该家族由 CI 跑。我采信这个口径:⛔ 不把"没测"读成"通过"。Generated by Claude Code
Generated by Claude Code
os-steve commentedon Sep 18, 2026
Release: PR #19035 MERGED 2026-09-18T13:54:13Z — 批次 #158 item 5 letter A 四件交付物全部落地。席位
domain:spec#4,session_01AmH9bKvGoLjiY86Q4Z3og2,seat post #18917。卡关completed,pm:dispatched同笔摘除,assignee 清空;tooling/priority:p2/domain:spec留下(归属不是状态)。落地按 内容 核验,⛔ 不按 API 的
merged字段origin/mainfetch 后 tipabb01f105c。仪器可达半径:内容形 grep,只看追踪文件里的源码文本 —— 本次四个目标全在追踪的.ts里,⇒ 读数在半径内(新条款 SKILL.md:165)。usage.zod.ts含Wall-clock latency in milliseconds'tenant.zod.ts含Backup frequency in hourssite.keyUnits.length > 0 && site.jsdocUnits.length > 0(点亮对照)DEFERRED to #18075DEFERRED to #99999同仪器⭐ 一次差点误判,记下来因为它是本班的惯犯形状
探针「
jsdocUnits.some应为 0」读回 2。我没有据此判定豁免还在 —— 读了那两行::855—— 注释:「⛔ THERE IS NO AGREEMENT CARVE-OUT HERE, and the absence is the decision, not an omission. A second half spelled!jsdocUnits.some(…)sat on this guard for one release…」:1358—— 注释:自测正控的说明,同样在复述那个已删的半句。⇒ 两处都在
//注释里,活代码里一处没有,而 ③ 的点亮对照证明新判据确实就位。⛔ 命中数不是事实;注释里的字面量不是活代码 —— 与 #18376 同族(那次我把/** */里的字面量读成了打印路径)。这次在下结论前读了行。交付物
latencyMs与frequencyHours在.describe()里点名单位 —— 这是已发布 JSON Schema 与content/docs/references/**唯一会印出来的通道。被裁的 class-(a) 同族(EpochMs instant 豁免读 JSDoc 通道)同 PR 搭载。changeset:
@objectstack/specpatch,⛔ 非skip-changeset—— 两条 describe 都落在该包files[]实际发运的内容里(已发布的 JSON Schemadescription会移动),这是测出来的。pnpm check:pm-dispatch-gates超出其环境前台上限(560s 被杀、无判定),未报成绿,该家族由 CI 覆盖。本席采信该口径,⛔ 不把「没测」读成「通过」。Generated by Claude Code
Generated by Claude Code
test(spec): a battery roster, floor and verdict handshake for check-d…
fix(spec): the agreement shape is an offence — name the unit in two d…