Skip to content

Phase 3: golden contract corpus recorded from the current stack (red suite)#143

Merged
SyniRon merged 8 commits into
developfrom
agent/issue-116
Jun 6, 2026
Merged

Phase 3: golden contract corpus recorded from the current stack (red suite)#143
SyniRon merged 8 commits into
developfrom
agent/issue-116

Conversation

@SyniRon

@SyniRon SyniRon commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Closes #116. Part of #112 (PRD: Goodbye gRPC) — the red suite for the whole rewrite.

What

contract/ — 69 committed goldens across all 16 surviving public routes, plus the replay harness that makes them executable against any http.Handler:

  • Harness: the production chain in-process — real gateway.Service.Server() (auth → sentry → cache → compression, incl. the tickets bypass) over a real loopback gRPC server with the production interceptor order, seeded deterministic fake datastore, always-erroring RESP stub for deterministic cache misses. No docker; full corpus replays in ~0.1s.
  • Comparison is semantic JSON equality: number form (64-bit-as-string vs 32-bit-as-number), array order, and null vs {} vs absent are contractual; bytes informational only (protojson isn't byte-stable). Status + allowlisted headers (Content-Type, X-Content-Type-Options, and WWW-Authenticate — whose absence on the 401 tiers is pinned per fix(auth): two-tier 401 distinguishing bad Bearer scheme from key failure #106) are part of every golden.
  • Exactly one transform, documented: keycloakId stripped symmetrically at record and replay (the field dies at cutover; goldens record truth, the transform documents the break). The keycloak lookup route is deliberately unrecorded and its absence test-enforced.
  • Frozen behavior recorded verbatim: relation-key ≠ user-id on by-id profiles (relation 1 ↔ user 3 divergence proven in the golden), two-tier plain-text 401s, gRPC-status error JSON incl. leaked strconv.ParseUint text, JSON 404, enum name+number path forms (bodies verified identical pairwise), snake/camel dual spellings, repeated filters, unknown-params-ignored, {"profiles":{}} empty search (Position search returns empty profiles for plausible queries - bug or expected? #137), read vs read:tickets scopes.
  • Public surface frozen at five entries for the Phase 3 slices (Phase 3 tracer: GET /api/v1/milpacs/ranks end-to-end on the new net/http stack #125Phase 3: tickets routes - list, by id, by ref, messages, categories #129): Cases, RunCase, CompareGolden, SaveGolden, LoadGolden (+ Case/Auth/Golden). Canon machinery is internal — deep module, small replay-facing interface.
  • Re-record procedure documented and deterministic (re-recording produces a zero-byte diff).

Review hardening (multi-agent review applied, each fix red-proven)

  • CompareGolden self-checks: request metadata (case/method/path/auth), non-allowlisted recorded headers, body/bodyText XOR — golden↔battery de-sync now diffs loudly
  • Route-coverage guard uses anchored patterns (prefix version demonstrably missed deleted routes)
  • LoadGolden validation + goldenPath traversal guard
  • gRPC serve errors surfaced + 10s mount watchdog (startup failure: 1.1s descriptive fail vs 10-min silent hang)

Test plan

  • Corpus green against the current stack, -count=2/-count=3 stable
  • Mutation-verified: tampered goldens fail with pinpoint $.path diffs (6 tamper classes)
  • Zero writes outside contract/; go.mod untouched

🤖 Generated with Claude Code

SyniRon and others added 8 commits June 5, 2026 21:47
…116)

Phase 3 red suite for the Goodbye-gRPC rewrite (PRD #112):

- contract/goldens/: 69 canonicalized goldens covering all 16 surviving
  public routes x happy/edge/error/auth cases, both enum path forms,
  repeated filters and dual-spelling query keys on tickets
- canonicalizer + semantic JSON differ (parse -> canonicalize -> diff;
  number form, array order, null-vs-empty-vs-absent all contractual;
  byte output informational only)
- single documented transform: StripKeycloakID (field dies at cutover);
  keycloak lookup route deliberately unrecorded
- in-process harness: real gateway handler -> real gRPC server with the
  production interceptor chain -> seeded deterministic fake datastore;
  no MySQL/Redis/docker, plain go test, sub-second replay
- re-record procedure documented in contract/README.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… surface

Canonicalize/MarshalCanonical/StripKeycloakID/Diff become canonicalize/
marshalCanonical/stripKeycloakID/diff. Public surface is exactly Cases,
Case, Auth (+constants), Golden, RunCase, CompareGolden, SaveGolden,
LoadGolden. README documents the deep-module rationale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…with a watchdog

Review finding (silent-failure): srv.Serve's error was discarded and
gateway.Service.Server() dials with WithBlock on Background — a startup
failure hung go test for 10 minutes with zero diagnostics. Serve now
reports through a channel; the mount selects on completion / serve
error / 10s timeout and fails fast with the serve error in the message.
Mid-run serve death surfaces per-test via currentStack. Sabotage-
verified: closed listener fails in 1.1s, dead dial port at 10.0s, both
with descriptive messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, body XOR

Recorded case/method/path/auth now diff instead of being write-only;
recorded header keys outside the contractHeaders allowlist are flagged;
a golden carrying both or neither of body/bodyText is a diff. Mutation
tests tamper each field and assert the specific diff fires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add WWW-Authenticate to the contractHeaders allowlist. #106 froze the
401 tiers as challenge-free; RunCase skips empty headers so committed
goldens are unchanged (verified: zero diff), and a rewrite whose auth
middleware adds the header now produces a red ""-vs-set diff —
empirically proven by TestWWWAuthenticateAbsenceIsPinned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
strings.HasPrefix let /api/v1/tickets be satisfied by any tickets case
and /tickets/42 by the messages cases. Each case path (query stripped)
now classifies to the first of 16 anchored patterns, most-specific
first; deleting all cases for one route fails the guard (proven red by
temporarily removing the ticket-by-id cases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LoadGolden rejects goldens violating the body/bodyText XOR or carrying
an undefined auth tier (new Auth.Valid()). goldenPath rejects empty
names and names that are not filepath.IsLocal after FromSlash, with the
error surfaced from both SaveGolden and LoadGolden. A rejection test
covers each case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SyniRon
SyniRon merged commit e610947 into develop Jun 6, 2026
3 checks passed
@SyniRon
SyniRon deleted the agent/issue-116 branch June 6, 2026 03: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.

Phase 3: record the golden contract corpus from the current stack (red suite)

1 participant