You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
qa(scenarios): refusalMeets never sees a hook's declared refusal; the MCP door serves it as declaredCode beside the status's catalogue code #33497
Filing gate: ① (found while reviewing in-flight work). Contract review 6885289276 of PR #33488 (card #33257, a hook's declared refusal) found this outside its diff. The PR's dev probed the live answer.
What is wrong
refusalMeets in packages/qa/scenarios/src/score/scenario-operation-grade.ts lets a declared VALIDATION_FAILED be met by "the app's own rule": a 4xx answer whose code is not in the spec's ErrorCode catalogue. Its docblock says a hook's declared refusal (OVER_RECEIPT at 422, ASSET_NOT_IN_STOCK at 409) "reaches the MCP door with its own code".
On main it does not. The MCP data tools serve a hook's declared refusal with code set to the status's catalogue code (for example RESOURCE_CONFLICT at 409) and the hook's own code as declaredCode (#31407, packages/mcp/src/mcp-server-runtime.ts). The runner (packages/qa/scenarios/src/runner/observe.ts) keeps code and status but not declaredCode. So refusalMeets sees a catalogue code and returns false. No declared hook refusal, at any status, meets VALIDATION_FAILED on a live run. The grader's unit tests miss this because they pass ASSET_NOT_IN_STOCK in as code directly.
Live probe (PR #33488's dev, on main): a sandboxed hook declaring ASSET_NOT_IN_STOCK at 409 answers 409 RESOURCE_CONFLICT with declaredCode: ASSET_NOT_IN_STOCK.
Fix
The runner keeps declaredCode on a refused answer. refusalMeets treats a present declaredCode at a 4xx status as the app's own rule. Re-pin the unit tests on the wire shape the door actually serves. Correct the docblock and packages/qa/scenarios/README.md to match.
Reach
The scenario lane's grade of every card whose build refuses through a hook's declared refusal. Skills now teach declared refusals (PR #33488), so builds will hit this more often.
This is a scenario grader defect, the same family as #33414 and #33415 (devx). refusalMeets never sees a hook's declared refusal, because the runner drops declaredCode. So every declared hook refusal in an operations card grades as a miss. p2: it gives false failures that can block a gallery export (#30349, now held by eval#1).
Claim: PM loop round 9 · 2026-11-14T23:43Z
Session: session_01FEkQ5nw3MDkRymEzv6FDJs (card thread, dev; its previous PR #33453 merged) · seat session_01FEkQ5ooRyyS95kdyC2dPEX
Account: os-project-manager
Branch: claude/project-thread-rl158j
Worktree: objectstack-issue-33497
Domain: domain:devx
Seat: domain:devx#4
File surface: packages/qa/scenarios/src/score/scenario-operation-grade.ts, packages/qa/scenarios/src/runner/observe.ts (and the file declaring ObservedAnswer), their tests under packages/qa/scenarios/, packages/qa/scenarios/README.md, root .changeset/. ⛔ No packages/mcp/**.
Container & model: S/M, mode:thread, model: default tier, effort: default
Clause-②: no
Metadata-protocol: none
Responsibility: the scenario runner drops the door's declaredCode, so the grader's refusalMeets never sees a hook's declared refusal | none | the MCP door's refusal wire shape (#31407, packages/mcp), unchanged
Thread-read: none
Premise: holds: on origin/main the MCP runtime serves declaredCode beside code (mcp-server-runtime.ts L2179–2187); refusedBy in observe.ts (L208) keeps only code and status; refusalMeets (scenario-operation-grade.ts L213) reads code only
Serial constraints cleared: no open PR touches packages/qa/scenarios/**
Scope: the card's Fix, one PR, Fixes #33497, a patch changeset for @objectstack/scenarios (file name unique to this card). Order: /mnt/project-files/pm-orders/devx/order-33497.md. Summons: the maintainer's 「在开一个 devx」「都处理 p1 p2 卡」 (this project, 2026-11-14T15:37Z).
Activity
objectstack-fleet commentedon Nov 14, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: first touch →
domain:devx· p2 ·pm:queue· 2026-11-14T22:45Z⛔ Not a claim.
This is a scenario grader defect, the same family as #33414 and #33415 (devx).
refusalMeetsnever sees a hook's declared refusal, because the runner dropsdeclaredCode. So every declared hook refusal in an operations card grades as a miss. p2: it gives false failures that can block a gallery export (#30349, now held byeval#1).objectstack-fleet commentedon Nov 14, 2026
domain:devx#4 · os-project-manager · session_01FEkQ5ooRyyS95kdyC2dPEX
Claim: PM loop round 9 · 2026-11-14T23:43Z
Session:
session_01FEkQ5nw3MDkRymEzv6FDJs(card thread, dev; its previous PR #33453 merged) · seatsession_01FEkQ5ooRyyS95kdyC2dPEXAccount:
os-project-managerBranch:
claude/project-thread-rl158jWorktree:
objectstack-issue-33497Domain:
domain:devxSeat:
domain:devx#4File surface:
packages/qa/scenarios/src/score/scenario-operation-grade.ts,packages/qa/scenarios/src/runner/observe.ts(and the file declaringObservedAnswer), their tests underpackages/qa/scenarios/,packages/qa/scenarios/README.md, root.changeset/. ⛔ Nopackages/mcp/**.Container & model: S/M,
mode:thread,model: default tier,effort: defaultClause-②: no
Metadata-protocol: none
Responsibility: the scenario runner drops the door's
declaredCode, so the grader'srefusalMeetsnever sees a hook's declared refusal | none | the MCP door's refusal wire shape (#31407,packages/mcp), unchangedThread-read: none
Premise: holds: on
origin/mainthe MCP runtime servesdeclaredCodebesidecode(mcp-server-runtime.ts L2179–2187);refusedByin observe.ts (L208) keeps onlycodeandstatus;refusalMeets(scenario-operation-grade.ts L213) readscodeonlySerial constraints cleared: no open PR touches
packages/qa/scenarios/**Scope: the card's Fix, one PR,
Fixes #33497, apatchchangeset for@objectstack/scenarios(file name unique to this card). Order:/mnt/project-files/pm-orders/devx/order-33497.md. Summons: the maintainer's 「在开一个 devx」「都处理 p1 p2 卡」 (this project, 2026-11-14T15:37Z).