Skip to content

test: dispatch the nine new grant fixtures, and sharpen the package gate - #129

Merged
cuibonobo merged 1 commit into
mainfrom
claude/dispatch-grant-fixtures
Sep 19, 2026
Merged

cuibonobo merged 1 commit into
mainfrom
claude/dispatch-grant-fixtures

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

The follow-up haverstack/core#320 unblocked. Bumps @haverstack/conformance-fixtures to ^0.31.0 and handles the nine fixtures it adds.

The bump is the forcing act, not a formality. The range was ^0.30.0, and a caret on a 0.x version pins the minor — so 0.31.0 would never have been resolved, the coverage gate would never have fired, and CI would have stayed green with nine fixtures unhandled. Nothing about a release reaches this repo until the range moves.

Seven of the nine are plain POST /records bodies and share one dispatch loop. The other two need prior state, and each carries a control that makes it mean what it claims:

Fixture The refusal The control beside it
…grant-on-an-ungrantable-family-confers-nothing a stored grant naming _app@1 confers no create → 403 the same grant shape on comment@1 does confer the create → 200
…mutate-grant-with-no-read-companion update-any with no read-any conveys nothing → 404 the same grant with read-any beside it conveys the update → 200

Without those controls both tests would pass against a server holding no grant for the requester at all, since a missing grant answers 403 on the create and 404 on the patch exactly the same way. The control is the whole difference between pinning the rule and pinning a coincidence.

Both grants are written directly rather than through stack.grant(), which refuses each of these outright — which is the point. These fixtures pin the second half of the two-sided rule: what happens when a grant reaches storage some other way and is read back.

Gate fixes

Two problems with the whole-package gate added in #128, both visible the first time it fired in anger rather than in a simulation:

  1. It recorded a block's names only after that block's own assertions passed. So a block that failed its coverage reported nothing, and the gate blamed it for every fixture it does dispatch — 90 names for 9 real ones. Recording first and asserting second keeps one block's failure from cascading.

  2. It read the allConformanceFixtures aggregate, which is a union of the arrays beside it, so every name arrived twice under two different export names.

The failure now names exactly what is unhandled:

+   "errorResponseFixtures: error-validation-grant-entity-grantee-without-entity-id"
+   "errorResponseFixtures: error-validation-grant-entity-grantee-with-empty-entity-id"
    … 7 more

That is the output an unread block should produce, and it is what I checked against before adding the dispatch arms.

Docs

No behavior change — tests and a devDependency bump. No route, status code, error code, auth rule or environment variable moved.

Verification

pnpm run format:check   ✓
pnpm run lint           ✓
pnpm typecheck          ✓
pnpm test               ✓  579 passed across 29 files (was 570 / 29)

I ran the gate against the bumped dependency before writing any dispatch, and it failed as designed on both the errorResponseFixtures block coverage and the whole-package test, naming all nine. The two noise problems above are what that run surfaced.

The two evaluation-time fixtures passing here is independent confirmation of core#320 through this repo's conformance harness — those expectations were originally observed with a throwaway wire client, and this is the first time they have been checked by the gate that will keep checking them.

Notes for reviewers

pnpm-lock.yaml moves for the single devDependency bump; nothing else in the tree changed version.

The nine fixtures assert status and error.code only, via the block's existing expectError(). Their details — the field paths and messages, which I probed off a live server when writing them in core — remain documentation rather than an asserted contract on either side of the wire. Whether that should change is the open question I raised in core#320, and I have deliberately not pre-empted it here.

Completes the split: docs in #127, the server-specific coverage in #128, the cross-implementation rules in core#320, and their dispatch here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01St5i1czsAp26y8H56pmANC


Generated by Claude Code

Bumps @haverstack/conformance-fixtures to ^0.31.0 and handles what it adds.
The caret pins the minor on a 0.x range, so 0.30.0 would never have picked
these up on its own — the bump is what puts the coverage gate to work.

Seven of the nine are plain POST /records bodies and share one dispatch. The
other two need prior state, and each carries the control that makes it mean
what it says: a grant naming _app@1 confers no create *while the same shape
on a grantable family does*, and update-any with no read companion conveys
nothing *while the same grant with read-any beside it conveys the update*.
Without those, both tests would pass against a server holding no grant for
the requester at all, since a missing grant answers 403 and 404 the same way.

Two fixes to the whole-package gate, both visible the first time it fired in
anger. It recorded a block's names only after that block's own assertions
passed, so one failing block reported nothing and the gate blamed it for
every fixture it does dispatch — 90 names for 9 real ones. And it read the
allConformanceFixtures aggregate, which is a union of the arrays beside it,
so every name arrived twice. Recording first and skipping the aggregate
leaves the failure naming exactly what is unhandled.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01St5i1czsAp26y8H56pmANC
@cuibonobo
cuibonobo merged commit c540e4d into main Sep 19, 2026
8 checks passed
@cuibonobo
cuibonobo deleted the claude/dispatch-grant-fixtures branch September 19, 2026 22:14
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.

2 participants