Skip to content

spec(conformance): rejection-arm vs errors[] mutual-exclusion test + storyboard alignment (closes #3998, #3914)#4006

Merged
bokelley merged 1 commit intomainfrom
bokelley/storyboard-rejection-arm-mutex-3998
May 3, 2026
Merged

spec(conformance): rejection-arm vs errors[] mutual-exclusion test + storyboard alignment (closes #3998, #3914)#4006
bokelley merged 1 commit intomainfrom
bokelley/storyboard-rejection-arm-mutex-3998

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 3, 2026

Summary

The wire-placement guidance on `GOVERNANCE_DENIED` (shipped to `main` via #3929 and to 3.0.x via #3996) is normative MUST-language: when a task response defines a structured rejection arm (`AcquireRightsRejected`, `CreativeRejected`), the arm IS the canonical denial shape — sellers MUST NOT additionally emit the error code in `errors[]` or `adcp_error`. The schema enforces this with `not: { required: ["errors"] }` on each rejection arm.

Until now the rule was asserted only in prose. This PR adds executable conformance and aligns the existing storyboards with the rule.

Changes

`tests/rejection-arm-mutual-exclusion.test.cjs` — schema-validation conformance check (new)

Asserts both directions of the rule for both rejection arms:

  • Accept: canonical rejection-arm shape (status + reason, no errors[])
  • Reject: rejection-arm with errors[] populated — the schema's `not: { required: ["errors"] }` clause must fire

Catches regression on the constraint before any storyboard does. Wired into the aggregate `npm test` run via `test:rejection-arm-mutex`.

`brand_rights/governance_denied` storyboard — assertions corrected

Was asserting:
```yaml

  • check: error_code
    value: "GOVERNANCE_DENIED"
    ```

…on a task whose canonical denial shape is the `AcquireRightsRejected` arm (`status: "rejected"` + `reason`). Adopters returning the spec-correct shape were failing the storyboard. Now asserts:

```yaml

  • check: response_schema # AcquireRightsRejected validates; not: required: errors fires on dual-emission
  • check: field_value
    path: "status"
    value: "rejected" # discriminator on the rejection arm
  • check: field_present
    path: "reason" # governance findings propagated verbatim
    ```

Also drops the `expect_error: true` and `negative_path: payload_well_formed` flags — Case-1 of the rule explicitly says transport-level success markers MUST NOT be flipped (HTTP 200 / MCP `isError: false` / A2A `succeeded`). The narrative is updated to spell out that the schema's `not: { required: ["errors"] }` rule is what enforces the no-dual-emission discipline.

Closes the storyboard portion of #3914 (the storyboard was non-spec; adopters returning the rejection arm were failing it).

`media_buy_seller/governance_denied` storyboard — narrative tightened

The assertions were already correct (Case-2 of the rule: `create_media_buy` has no rejection arm, so `errors[].code: GOVERNANCE_DENIED` + `adcp_error.code: GOVERNANCE_DENIED` + flipped transport markers is the canonical denial shape). Updated the `expected` prose to make Case-2 explicit and cross-reference the brand-rights scenario as the Case-1 counterpart, so a reader sees both arms of the rule from either entry point.

Wire-format impact

None. Schema constraints unchanged. Pure conformance + documentation: the schema rule was already in place; this PR makes it discoverable from a failing test and aligns the existing storyboards with the rule.

Test plan

  • `npm run test:rejection-arm-mutex` — 4/4 passing locally (canonical accept + dual-emission reject for both rejection arms)
  • Compliance bundle rebuilds cleanly (`node scripts/build-compliance.cjs`)
  • CI green
  • Conformance test runs as part of the aggregate `npm test` suite

Refs

🤖 Generated with Claude Code

…storyboard alignment (closes #3998)

The wire-placement guidance on GOVERNANCE_DENIED (shipped via #3929 to main and #3996 to 3.0.x) is normative MUST-language: when a task response defines a structured rejection arm (AcquireRightsRejected, CreativeRejected), the arm IS the canonical denial shape — sellers MUST NOT additionally emit the error code in errors[] or adcp_error. The schema enforces this with not: required: errors on each rejection arm.

Until now the rule was asserted only in prose. This change adds executable conformance:

- tests/rejection-arm-mutual-exclusion.test.cjs — new schema-validation conformance check. Asserts both directions: canonical rejection-arm shape accepts; rejection-arm with errors[] populated rejects. Catches regression on the not block before storyboards do. Wired into the aggregate npm test run via test:rejection-arm-mutex.
- brand_rights/governance_denied storyboard — was asserting check: error_code, value: GOVERNANCE_DENIED on a task whose canonical denial shape is status: rejected + reason. Now asserts field_value status=rejected plus field_present reason. Closes the storyboard portion of #3914 (storyboard was rejecting spec-correct adopter responses).
- media_buy_seller/governance_denied storyboard — narrative tightened to make Case-2 of the rule explicit (no rejection arm → errors[] + adcp_error populated; transport markers flipped). Cross-references brand-rights as the Case-1 counterpart.

Wire format unchanged. Schema constraints unchanged. Pure conformance + documentation: the schema rule was already in place; this change makes it discoverable from a failing test and aligns the existing storyboards with the rule.

Closes #3998 (rejection-arm mutex conformance).
Closes #3914 (storyboard portion — storyboard was non-spec, now matches the rejection-arm path).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit f74aa81 into main May 3, 2026
20 checks passed
@bokelley bokelley deleted the bokelley/storyboard-rejection-arm-mutex-3998 branch May 3, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant