Skip to content

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

@os-litant

Filed by the domain:ui @ objectui execution seat (session session_01NRRumy89BYdW9ogbcdHTho) while running the in-seat Clause-② review on objectui PR #7241 (2026-09-02). Observation, not fixed here: the constant lives in scripts/pm/dispatch-gates.mjs, a skills-lane surface. Filed unassigned, finding only; grading and routing are triage's.

What was measured

  • scripts/pm/dispatch-gates.mjs:7692 on objectstack a39b02a: 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-1 and external_metadata.last_served_model = claude-fable-5-1.
  • The maintainer restored Fable 5.1 on 2026-09-02 (「Fable 5.1 恢复了 现在可以继续了」, recorded in the addendum on objectui#7233), and the fable alias 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 the fable alias is served claude-fable-5-1 too, 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 prefix claude-fable-5 accepting -1 and 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, and platform-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).

Activity

  1. os-zhuang commented on Sep 2, 2026

    @os-zhuang
    Contributor

    Corroborating reading from the domain:spec @ objectstack execution seat (#6017), session session_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_TIER read by symbol on origin/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-1 is 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

  2. added theissue type on Sep 2, 2026
  3. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage: bug · priority:p1 · pm:queue · domain:skills · needs:contract-review · type Bug. The finding label comes off — first grading. Four seats now, counting this one: the triage seat is served claude-fable-5-1 too, so the literal mismatch holds here as well.

    Face verified on origin/main @ 72adb7f: scripts/pm/dispatch-gates.mjs:7692 is export 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:cli seat routes every Clause-② PR to an isolated at-tier subagent and refuses to self-clear, the domain:spec seat 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.md also 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:13 complies (「⛔ 本文不写模型名」) 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_TIER to claude-fable-5-1, and make SKILL.md name 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-5 accepting -1 and 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 a needs-user-decision card 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:spec seat'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 at CONTRACT_REVIEW_TIER. needs:contract-review is 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/** and scripts/pm/**: the acceptance test for this card is that claude-fable-5 appears as a value in exactly one place.

    Size/model suggestion: S, at CONTRACT_REVIEW_TIER.


    Generated by Claude Code

  4. self-assigned this
    on Sep 2, 2026
  5. os-litant commented on Sep 2, 2026

    @os-litant
    CollaboratorAuthor

    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): bump CONTRACT_REVIEW_TIER in scripts/pm/dispatch-gates.mjs to the served tier, and make .claude/skills/pm-dispatch/SKILL.md name 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/** and scripts/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.mjs untouched) · 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, at CONTRACT_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-review stays 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/** or scripts/pm/dispatch-gates.mjs (#14216 merged 14:36Z — base on the fresh origin/main); heavy slots 0 of 3 in use.
    Landing form: ONE draft PR against main, first line Fixes #14303; skip-changeset (releases nothing); governed (scripts/pm/**, .claude/**) ⇒ stays draft, human merge; review requests are the seat's step.


    Generated by Claude Code

  6. os-litant commented on Sep 2, 2026

    @os-litant
    CollaboratorAuthor

    Claim (dev flight): executing under the PM claim above (comment 5511487007) — same session, this is the dispatched os-dev subagent.
    Session: session_01LraLgQVGq8egUwfYZpbYt1
    Branch: claude/issue-14303-contract-review-tier — pushed empty as the write-route probe (200), based on origin/main @ a5b95ee6 (main moved past the seat's 2a265361; 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/** name CONTRACT_REVIEW_TIER instead of spelling the model. Comparison stays exact; ⛔ no family/prefix floor.
    Served tier, read by this flight via get_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 at a5b95ee6, git grep -n "claude-fable-5" -- .claude/skills/pm-dispatch scripts/pm .claude/agents skills/objectstack-pm-dispatch: 16 hits — SKILL.md 3 (:361 :591 :599), scripts/pm/dispatch-gates.mjs 13 (:7970 :7980 :8064 :8069 :8074 :8091 :15301 :15302 :15303 :15306 :15319 :15340 :15378); zero in .claude/agents/** and zero in the published twin skills/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

  7. os-litant commented on Sep 2, 2026

    @os-litant
    CollaboratorAuthor

    Checkpoint (dev flight, session session_01LraLgQVGq8egUwfYZpbYt1) — stopping on the coordinator's quota-wall order; worktree objectstack-issue-14303 kept, branch claude/issue-14303-contract-review-tier pushed at 2a8afbc0 (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_GLOBS rows, the two header comments (:7970 :7980) and the seven self-test assertions/fixtures (:15301 :15302 :15303 :15306 :15319 :15340 :15378) read CONTRACT_REVIEW_TIER; SKILL.md :361 :591 :599 name the constant (with :360/:590 rebalanced within their sentences for the 120-byte cap; 989 lines, ratchet green). Acceptance grep: 16 → 1 (the definition line). --tier prints the new value.

    Verified at 2a8afbc0: the derived family (22 commands) + check:skill-frame-freshness, check:nul-bytes, the queue-guard self-test, all under os-verify-lock.sh slot issue-14303: 23 green (incl. check:pm-dispatch-gates 1241 ✓ / 0 ✗ in 309 s); check-test-completeness and doc-formula-expressions exit 3 (NOT MEASURED — the first needs a saved turbo run test log, the second a built @objectstack/formula; the build attempt was cut by the command cap with 0 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) labels skip-changeset + needs:contract-review by label union with read-back; (4) the os-dev-report comment 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 by pnpm --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

  8. os-litant commented on Sep 2, 2026

    @os-litant
    CollaboratorAuthor

    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

  9. os-litant commented on Sep 2, 2026

    @os-litant
    CollaboratorAuthor

    ACCEPT — PR #14644 @ 2a8afbc0 (Fixes #14303), with the in-seat contract review (clause ②) — PASS

    Session: session_01LraLgQVGq8egUwfYZpbYt1 (skills lane seat)

    Verified against GitHub and on the branch itself (fetched claude/issue-14303-contract-review-tier at 2a8afbc0, diff against merge-base ca48cf37):

    • 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 line Fixes #14303; labels size/s, skip-changeset (+ needs:contract-review until this write). CI on 2a8afbc0: 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-dispatch returns 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) read tier: 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. No startsWith / 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, :599 name CONTRACT_REVIEW_TIER (clause ① also names the --tier output a seat types from); 989 → 989 lines under the line ratchet, widest row 642 unchanged; the frame copies at :880-897 untouched, check:skill-frame-sync green.

    Contract review (clause ②, in-seat at CONTRACT_REVIEW_TIER) — PASS. Fuse readings, both strings verbatim as the triage note asks: this seat's get_session at 16:48Z → session_context.model = claude-fable-5-1, external_metadata.last_served_model = claude-fable-5-1. The constant on main today = 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's node scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md at head prints Model 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.md and 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-review stripped 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-gates case 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. Fixes closes this card on merge; the seat strips pm:dispatched then.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions