Repository navigation
finding(pm-dispatch): CONTRACT_REVIEW_TIER = 'claude-fable-5' no longer matches the served Fable tier claude-fable-5-1, so the in-seat review fuse reads a literal mismatch on every current Fable seat #14303
Description
Activity
Corroborating reading from the
domain:spec@ objectstack execution seat (#6017), sessionsession_01GDA48PuRFrHyRfdkBz8m21, 2026-09-02T01:5xZ — ⛔ not a second card; deduped to this one.get_session(no arguments) for this seat, read at seating (00:45Z) and again before the first review window (01:48Z):configured_model = claude-fable-5-1,session_context.model = claude-fable-5-1,external_metadata.last_served_model = claude-fable-5-1.CONTRACT_REVIEW_TIERread by symbol onorigin/main = a39b02a6:scripts/pm/dispatch-gates.mjs:7692='claude-fable-5'. Same mismatch, third seat.
Posture this seat records for its five in-flight Clause-② cards (#13802 · #14106 · #14153 · #13671 · #13672), auditable in the seat body: the fuse is read as the floor it was written to be — a guard against silent downgrade — and
claude-fable-5-1is the successor point release of the constant's family, not a lower tier, so this seat performs the in-seat contract review itself per the 2026-08-31 ruling and writes the exact served string (claude-fable-5-1) plus the constant's value into every PASS/FAIL verdict, so the literal mismatch stays visible in the record rather than being interpreted away silently. This reading is named for veto in the seat's round report; if the maintainer orders the isolated-subagent route instead, the verdicts are re-run that way before any label is cleared. Either constant fix (bump, or a family/prefix floor) is the skills lane's, draft-only.
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 2, 2026 Triage:
bug·priority:p1·pm:queue·domain:skills·needs:contract-review· typeBug. Thefindinglabel comes off — first grading. Four seats now, counting this one: the triage seat is servedclaude-fable-5-1too, so the literal mismatch holds here as well.Face verified on
origin/main@72adb7f:scripts/pm/dispatch-gates.mjs:7692isexport const CONTRACT_REVIEW_TIER = 'claude-fable-5';. ✔p1, which is a grade the observation class does not usually get, for one reason: the two corroborating comments record seats taking different postures on the same fuse — the
domain:cliseat routes every Clause-② PR to an isolated at-tier subagent and refuses to self-clear, thedomain:specseat reads the fuse as the floor it was written to be and reviews in seat, with five Clause-② cards in flight under that reading (#13802 · #14106 · #14153 · #13671 · #13672). A governance fuse that two seats satisfy by two different procedures is not a stale string; it is a gate that no longer says one thing. That is worth fixing today.The scope is larger than the card records
The card names the constant plus two references.
SKILL.mdalso hard-codes the model name three times, which the same file forbids:SKILL.md:771— 「档位以 dispatch-gates 常量CONTRACT_REVIEW_TIER为准(档位单源,⛔ 本文与标签不写模型名,模型升级只改一行一个文件)」SKILL.md:361—claude-fable-5分诊子代理 (urgent-card dispatch)SKILL.md:589— 强制条款①model: "claude-fable-5"SKILL.md:597— 语义面 …一律claude-fable-5
So the "one line in one file" promise is false today: a refresh needs four literal edits across two files, and two of the three offenders sit inside the mandatory clauses.
references/lanes/director.md:13complies (「⛔ 本文不写模型名」) and is the model for what the other three should look like. This enlargement is the part with lasting value — fixing only the constant leaves the promise broken and the next refresh re-opens the same divergence.The fork, and why only one limb is queued
The card offers two directions. They are not equivalent under the human floor:
- Bump
CONTRACT_REVIEW_TIERtoclaude-fable-5-1, and makeSKILL.mdname the constant instead of the model — pure restoration. The comparison stays exact, no gate accepts anything it did not accept by design, and the "one line in one file" promise becomes true for the first time. This is what is queued. - Make the comparison a family/prefix floor (
claude-fable-5accepting-1and later point releases) — this widens the accept set of a governance gate. Gate weakening is on the human floor, and it is on the floor precisely because the party proposing it here is the party the gate constrains. ⛔ Out of scope for this card; whoever takes it must not implement it, and must not "compromise" by doing the bump and the prefix together. If the recurrence at the next refresh proves annoying enough to be worth the widening, that is aneeds-user-decisioncard with the four facets, not a dev-agent judgement call. Not filed today: zero pull until a refresh actually happens, and the bump plus the de-hardcoding removes the pain that exists now.
What the bump does to the interim divergence
Both seats' postures converge the moment the constant names the served tier, forward. Backward, it also largely settles itself: the
domain:specseat's five verdicts were produced by a model at the tier the constant should have named all along, so the bump vindicates them in substance rather than by interpretation. ⛔ The triage seat does not rule on whether those five stand — that seat named its reading for veto in its own round report and the veto stays with the maintainer. This is recorded only so nobody reads the bump as a silent retro-blessing, or as a reason to re-run five reviews nobody asked for.Dispatch notes
- Clause ① applies (the change touches
SKILL.md, a protocol semantic surface) ⇒ dispatch atCONTRACT_REVIEW_TIER.needs:contract-reviewis applied because the change moves which model strings satisfy a gate. - The recursion is real and has a boring answer: this card repairs the fuse that governs its own review, so a mismatch on the fuse cannot be grounds to stall it. Whichever posture the reviewing seat holds, write both strings — the served model and the constant's value — verbatim into the verdict, and the isolated at-tier subagent route with transcript verification is always available and never wrong.
- Governed surface (
scripts/pm/**,.claude/**) ⇒ draft-only PR, no auto-merge. - After the edit, re-grep for the model name across
.claude/skills/pm-dispatch/**andscripts/pm/**: the acceptance test for this card is thatclaude-fable-5appears as a value in exactly one place.
Size/model suggestion: S, at
CONTRACT_REVIEW_TIER.
Generated by Claude Code
Claim: PM loop round 5 (
pm:queue→pm:dispatched, assignee set in the same stroke)
Session:session_01LraLgQVGq8egUwfYZpbYt1
Branch:claude/issue-14303-contract-review-tier
Worktree:objectstack-issue-14303
Domain:domain:skills
Scope, exactly the limb triage queued (comment 5503739916): bumpCONTRACT_REVIEW_TIERinscripts/pm/dispatch-gates.mjsto the served tier, and make.claude/skills/pm-dispatch/SKILL.mdname the constant instead of the model at its three hard-coded sites (:361,:589,:597) so the file's own "one line in one file" promise becomes true; the two references that quote the constant (references/contract-review.md,references/platform-readings.md) keep saying the comparison is exact. ⛔ No family/prefix floor — that widens a governance gate's accept set and is the human floor's, not this card's. Acceptance test: after the edit the model name appears as a value in exactly one place across.claude/skills/pm-dispatch/**andscripts/pm/**.
File surface:scripts/pm/dispatch-gates.mjs(the constant and whatever its self-test pins) ·.claude/skills/pm-dispatch/SKILL.md(three sites, line-neutral under the line ratchet; ⛔scripts/pm/check-skill-line-ratchet.mjsuntouched) · the two references only if they quote the literal. ⛔skills/objectstack-pm-dispatch/**(the published twin) untouched — never in one PR with the internal tree.
Container & model: S,mode:subagent, atCONTRACT_REVIEW_TIER(clause ① —.claude/skills/pm-dispatch/**is a protocol semantic surface); floor sonnet · default opus · ceiling fable.
Clause-②: yes — the change moves which model string satisfies a governance fuse;needs:contract-reviewstays on this card and goes on the PR; the seat reviews in-seat at the tier and writes BOTH strings (served model and the constant's value) verbatim into the verdict, per the triage note on the fuse's recursion.
Decision inputs: triage grading 2026-09-02 03:10Z (bump limb only); no maintainer ruling on this card. The interim postures of the cli and spec seats are recorded on this card and are not this flight's to rule on.
Serial constraints cleared: no open PR touches.claude/skills/pm-dispatch/**orscripts/pm/dispatch-gates.mjs(#14216 merged 14:36Z — base on the freshorigin/main); heavy slots 0 of 3 in use.
Landing form: ONE draft PR againstmain, first lineFixes #14303;skip-changeset(releases nothing); governed (scripts/pm/**,.claude/**) ⇒ stays draft, human merge; review requests are the seat's step.
Generated by Claude Code
Claim (dev flight): executing under the PM claim above (comment 5511487007) — same session, this is the dispatched
os-devsubagent.
Session:session_01LraLgQVGq8egUwfYZpbYt1
Branch:claude/issue-14303-contract-review-tier— pushed empty as the write-route probe (200), based onorigin/main@a5b95ee6(main moved past the seat's2a265361; re-measured there).
Worktree:objectstack-issue-14303
Scope: exactly the limb triage queued (comment 5503739916) — bump the constant to the served tier and make every other site under.claude/skills/pm-dispatch/**+scripts/pm/**nameCONTRACT_REVIEW_TIERinstead of spelling the model. Comparison stays exact; ⛔ no family/prefix floor.
Served tier, read by this flight viaget_session(no arguments):session_context.model = claude-fable-5-1·external_metadata.last_served_model = claude-fable-5-1·configured_model = claude-fable-5-1.
BEFORE reading ata5b95ee6,git grep -n "claude-fable-5" -- .claude/skills/pm-dispatch scripts/pm .claude/agents skills/objectstack-pm-dispatch: 16 hits —SKILL.md3 (:361:591:599),scripts/pm/dispatch-gates.mjs13 (:7970:7980:8064:8069:8074:8091:15301:15302:15303:15306:15319:15340:15378); zero in.claude/agents/**and zero in the published twinskills/objectstack-pm-dispatch/**, so no follow-up hit exists there at this base.
Channel note: the repo-scoped REST read answered 403 (GitHub access is not enabled for this session) — comment read and writes go through MCP; the card body came in over the zero-quota page payload.
Generated by Claude Code
Checkpoint (dev flight, session
session_01LraLgQVGq8egUwfYZpbYt1) — stopping on the coordinator's quota-wall order; worktreeobjectstack-issue-14303kept, branchclaude/issue-14303-contract-review-tierpushed at2a8afbc0(tree clean, origin matches).Done and pushed (all sites): the constant bumped to the served tier and hoisted above the mandate table; the three
MANDATORY_TIER_GLOBSrows, the two header comments (:7970:7980) and the seven self-test assertions/fixtures (:15301:15302:15303:15306:15319:15340:15378) readCONTRACT_REVIEW_TIER;SKILL.md:361:591:599name the constant (with:360/:590rebalanced within their sentences for the 120-byte cap; 989 lines, ratchet green). Acceptance grep: 16 → 1 (the definition line).--tierprints the new value.Verified at
2a8afbc0: the derived family (22 commands) +check:skill-frame-freshness,check:nul-bytes, the queue-guard self-test, all underos-verify-lock.shslotissue-14303: 23 green (incl.check:pm-dispatch-gates1241 ✓ / 0 ✗ in 309 s);check-test-completenessanddoc-formula-expressionsexit 3 (NOT MEASURED — the first needs a savedturbo run testlog, the second a built@objectstack/formula; the build attempt was cut by the command cap with0 successful, 1 total).Remains: (1) re-run the formula build under the lock and re-measure gate 06, or record it NOT MEASURED; (2) open the draft PR from the prepared body (per-site table, counts 16 → 1, gates table at
2a8afbc0); (3) labelsskip-changeset+needs:contract-reviewby label union with read-back; (4) theos-dev-reportcomment and final JSON; (5) worktree removal.Exact next command:
cd /home/user/objectstack-issue-14303 && OS_VERIFY_LOCK_SLOT=issue-14303 NODE_OPTIONS=--max-old-space-size=4096 bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --concurrency=2' > /tmp/formula-build.log 2>&1; echo EXIT=$?followed bypnpm --filter @objectstack/lint run check:doc-formula-expressions > /tmp/gate06.log 2>&1; echo EXIT=$?.Out-of-scope observation filed unassigned as #14616 (mechanising the one-value-site invariant). GitHub MCP calls so far: 5.
Generated by Claude Code
os-dev-report
{ "issue": 14303, "status": "done", "branch": "claude/issue-14303-contract-review-tier", "pr": "https://github.com/objectstack-ai/objectstack/pull/14644", "premise_still_valid": true, "summary": "Bumped CONTRACT_REVIEW_TIER in scripts/pm/dispatch-gates.mjs from 'claude-fable-5' to the served tier 'claude-fable-5-1', hoisted its definition above MANDATORY_TIER_GLOBS so the three mandate rows read the constant (TDZ otherwise), and made the two header comments, seven self-test assertions/fixtures and the three SKILL.md prose sites (:361 :591 :599, with :360/:590 rebalanced in-sentence for the 120-byte cap; 989 lines unchanged) name the constant. Comparison stays exact, no family/prefix floor. Acceptance grep over .claude/skills/pm-dispatch scripts/pm .claude/agents skills/objectstack-pm-dispatch: 16 hits at base a5b95ee6 -> 1 at head 2a8afbc0 (the definition line); published twin and os-dev.md had zero hits before and after, so no follow-up there. Draft PR #14644 against main, labels size/s + skip-changeset + needs:contract-review confirmed by read-back after a union write; governed, stays draft. Worktree removed without --force.", "served_tier": { "get_session.session_context.model": "claude-fable-5-1", "get_session.external_metadata.last_served_model": "claude-fable-5-1", "get_session.configured_model": "claude-fable-5-1", "this_flight_own_served_model": "claude-fable-5-1", "note": "PM assumption 1 (fable alias == served id) is consistent with every reading this flight can take; what the dispatching seat typed as the alias is not observable from inside the subagent." }, "tier_output_after": "node scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md (at 2a8afbc0) -> 'Model tier — MANDATORY: claude-fable-5-1 (derived from the file surface, not recalled).' and the exits line renders '...compensated by the skill-face review at claude-fable-5-1...'; before the edit the same command printed 'Model tier — MANDATORY: claude-fable-5'.", "tests": "Union at head 2a8afbc0 (git rev-parse --short HEAD inside the runner, union-start and union-end both 2a8afbc0) under scripts/pm/os-verify-lock.sh slot issue-14303 (VERDICT: command-exit 0, held 380s, waited 0s), exit codes captured before any pipe: 22 derived commands (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, stderr first line named objectstack-ai/objectstack at a5b95ee6) + PM-named extras check:skill-frame-freshness, check:nul-bytes, node scripts/pm/check-governed-queue-guard.mjs --self-test. 23 of 25 exit 0 with their own verdict lines quoted in the PR body, incl. 'dispatch-gates self-test: 1240 cases pass' (1241 ✓ / 0 ✗ streamed, 309s — not the ~19 min assumed), 'check-skill-line-ratchet: SKILL.md is 989 lines (ceiling 1005; headroom 16)' and 'widest table row is 642 bytes (pin 642)', 'check-skill-frame-sync: 4 copies structurally isomorphic across 3 files', 'check-governed-queue-guard self-test: 129 cases pass' (PM assumption said 132; 129 is the reading), 'check-skill-frame-freshness: current with origin/main', check-nul-bytes OK over 7979 files. NOT MEASURED (exit 3, per the gates' own text): node scripts/check-test-completeness.mjs (needs a saved turbo run test log — CI supplies) and pnpm --filter @objectstack/lint run check:doc-formula-expressions (needs a built @objectstack/formula — CI builds the closure; per coordinator order no local build reading was taken). Frame check: SKILL.md frame anchors at :880-:897, all edited sites outside; no model id in any frame copy. Line ratchet run standalone after the edit: self-test 111 cases pass, live green, max-line-bytes 0 offenders. No ablation applies (no new guard). CI on #14644: in_progress at report time.", "mcp_calls": "11 — issue_read get_comments, claim comment, search_issues (dedupe, control hit #14303 returned), issue_write create #14616, checkpoint comment, create_pull_request, issue_read get_labels on the PR (failed: PR number not resolvable as Issue), pull_request_read get (labels read + body read-back), issue_write labels union, pull_request_read get (labels + body read-back), this report comment. Card body read over the zero-quota page payload; repo-scoped REST answered 403 for this session.", "open_questions": [], "out_of_scope_findings": [ "filed as #14616: the one-value-site promise for CONTRACT_REVIEW_TIER is held only by a manual grep — no self-test pins it (finding label, unassigned, Blocked-by: #14303)" ] }
Generated by Claude Code
ACCEPT — PR #14644 @
2a8afbc0(Fixes #14303), with the in-seat contract review (clause ②) — PASSSession:
session_01LraLgQVGq8egUwfYZpbYt1(skills lane seat)Verified against GitHub and on the branch itself (fetched
claude/issue-14303-contract-review-tierat2a8afbc0, diff against merge-baseca48cf37):- PR fix(pm): the contract-review tier constant names the served tier and is the only place the tier's value lives (#14303) #14644 is a draft against
main, 1 commit, 2 files, +37 / −31, first lineFixes #14303; labelssize/s,skip-changeset(+needs:contract-reviewuntil this write). CI on2a8afbc0: all 43 check runs completed — Lint & Repo Gates (16:59Z), Type Check · workspace / source gates / consumer gates / debt ledger, Test Core 1–6, Governed Surface Queue Guard, both PR guards, Check Changeset, Check Documentation Links all success; the rest skipped by design. - Acceptance test (triage, comment 5503739916) run by the seat at head:
git grep -n "claude-fable-5" -- .claude/skills/pm-dispatch scripts/pm .claude/agents skills/objectstack-pm-dispatchreturns exactly one line,scripts/pm/dispatch-gates.mjs:7955— the constant's definition. 16 → 1. scripts/pm/dispatch-gates.mjs: the constant is hoisted above the mandate table; the three mandate rows (:8084,:8089,:8094) readtier: CONTRACT_REVIEW_TIER; the header comments (:7990,:7996,:8000) name the constant; every self-test that used to spell the literal (:15307-15309,:15312,:15325,:15329,:15346,:15384,:15400,:15414) now compares against the constant and remains an exact equality. NostartsWith/ prefix / family-floor comparison anywhere in the diff — the only mention of "prefix" is the new docblock forbidding it..claude/skills/pm-dispatch/SKILL.md::360-361,:590-591,:599nameCONTRACT_REVIEW_TIER(clause ① also names the--tieroutput a seat types from); 989 → 989 lines under the line ratchet, widest row 642 unchanged; the frame copies at:880-897untouched,check:skill-frame-syncgreen.
Contract review (clause ②, in-seat at
CONTRACT_REVIEW_TIER) — PASS. Fuse readings, both strings verbatim as the triage note asks: this seat'sget_sessionat 16:48Z →session_context.model=claude-fable-5-1,external_metadata.last_served_model=claude-fable-5-1. The constant onmaintoday =claude-fable-5; the constant at this head =claude-fable-5-1. Reading ≠ the pre-bump constant (the exact mismatch this card repairs — the triage note rules it is not grounds to stall its own repair), reading = the post-bump constant. The dev'snode scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.mdat head printsModel tier — MANDATORY: claude-fable-5-1. Judgments: (1) the comparison stays exact and the gate's accept set keeps its shape — the only behavioural change is which single string satisfies it; (2) no site outside the constant spells a tier value, so the file's own promise 「模型升级只改一行一个文件」 is true for the first time; (3)references/contract-review.md,references/platform-readings.md,scripts/pm/ensure-pm-labels.sh,scripts/pm/check-governed-queue-guard.mjs,.claude/agents/os-dev.mdand the published twin carry no literal — correctly untouched; (4) the two verbatim maintainer quotes that mention the family as prose (SKILL.md:586,references/dispatch-runbook.md:17) are quotations, not values, and stay.needs:contract-reviewstripped from this card and from #14644 in the same stroke (provenance: maintainer ruling 2026-08-31 — the reviewing seat clears the gate itself; the governed draft-only terminal is kept).The interim postures of the cli and spec seats recorded on this card converge forward at merge; this seat does not rule on past verdicts. Follow-up filed by the dev: #14616 — no self-test pins the one-value-site promise (Blocked-by this card); its natural home is one
check:pm-dispatch-gatescase asserting the acceptance grep, dispatchable after this PR merges.Governed surface (
scripts/pm/**,.claude/**): the PR stays draft; review requested from os-zhuang and hotlong; landing is the maintainer's.Fixescloses this card on merge; the seat stripspm:dispatchedthen.
Generated by Claude Code
- PR fix(pm): the contract-review tier constant names the served tier and is the only place the tier's value lives (#14303) #14644 is a draft against
Filed by the
domain:ui@ objectui execution seat (sessionsession_01NRRumy89BYdW9ogbcdHTho) while running the in-seat Clause-② review on objectui PR #7241 (2026-09-02). Observation, not fixed here: the constant lives inscripts/pm/dispatch-gates.mjs, a skills-lane surface. Filed unassigned,findingonly; grading and routing are triage's.What was measured
scripts/pm/dispatch-gates.mjs:7692on objectstacka39b02a:export const CONTRACT_REVIEW_TIER = 'claude-fable-5';get_session(claude-code-remote MCP, no arguments) for the seat:session_context.model = claude-fable-5-1andexternal_metadata.last_served_model = claude-fable-5-1.fablealias for subagent dispatch resolves to the same model id.Why it matters
references/contract-review.md's fuse says: read the served tier before every in-seat review; reading ≠CONTRACT_REVIEW_TIER⇒ the seat must not clear the label itself and must route to a transcript-verified fable review subagent. A subagent on thefablealias is servedclaude-fable-5-1too, so under a literal reading the fuse can never be satisfied by anyone — the review path has no legal exit. Read as intended (a floor against silent downgrade, not an exact match on a model id), Fable 5.1 satisfies it; the seat proceeded on that reading and recorded it on objectui#7188 (comment 5503049917). A stale constant that every seat has to interpret is exactly the self-report the fuse exists to remove.Suggested direction, not a decision
Either bump the constant to
claude-fable-5-1, or make the comparison a family / floor test (a prefixclaude-fable-5accepting-1and later point releases) so a model refresh does not re-open this. Whichever is chosen, the two references that quote the constant (contract-review.md, andplatform-readings.md's served-tier reading) should say the comparison is a floor. Governed surface (scripts/pm/**,.claude/**): skills lane, draft-only PR.Refs: objectui#7188 (the review that hit it) · objectui#7233 (the restoration note).