Skip to content

qa(scenarios): refusalMeets never sees a hook's declared refusal; the MCP door serves it as declaredCode beside the status's catalogue code #33497

Open

Description

@objectstack-fleet

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

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.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Nov 14, 2026
  2. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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). 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).

  3. added
    bugSomething isn't working
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  4. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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) · 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).

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingdomain:devxDomain lane — seat card indexed by label:pm:seatfindingRecorded observation — held, not dispatchable until the findings triage round grades itpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions