Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
## Context
The four packages targeted by this change all sit on a network boundary: `oauth-client` calls Authorization Code / PKCE / token / discovery endpoints; `webhook-client` both signs outbound webhook deliveries (consumer of the receiver) and ships a receiver shape (provider for downstream consumers); `schema-registry` is itself a registry server but is also a consumer of upstream registries during compatibility checks; `api-schemas` defines the wire shapes that every API plugin consumer sends. Each of these surfaces has a real, reproducible failure mode where the provider shape mutates while consumer expectations do not — exactly the failure class Pact-style contract testing is designed to catch.
The audit (`/mnt/s/web/genesis/docs/superpowers/specs/2026-04-25-test-suite-audit.md` §2 row "No contract tests") flags this as a critical missing tier. The audit also explicitly names these four packages as the canonical first-wave consumers.
## Goals / Non-Goals
**Goals:**
- A self-contained `@suaveplan/testing-contract` package with zero `@suaveplan/*` runtime deps (testing-infrastructure exception per `CLAUDE.md` rule 8) that produces and verifies Pact Specification v3 files.
- A normative requirement on each of the four consumer packages: ship `*.contract.test.ts`, commit pact files to `pacts/`, run provider verification where a server is present.
- Optional Pact Broker integration so multi-repo deployments can publish/pull pacts without the file commit.
**Non-Goals:**
- Replicating the entire `@pact-foundation/pact` feature surface (matchers like `like`/`term`/`eachLike` are out of scope for v0; we ship exact-shape recording first and add matcher syntax in a follow-up).
- Pulling `@pact-foundation/pact` itself as a dependency — it is Node-only and ships a Ruby standalone, neither of which fit the universal package constraint.
- Bidirectional contract testing (Pactflow's bi-di flow). Classical consumer-driven only.
## Decisions
- **Pact Specification v3 as the wire format.** Future migration to v4 is plausible but v3 is the broadest-supported version across brokers (Pactflow, OSS Pact Broker) and matches the field shapes our `oauth-client` / `webhook-client` consumers already think in.
- **`pacts/` committed in-repo.** Cross-package contract files live next to the consumer, not in a broker-only flow, because the genesis monorepo gives us atomic provider+consumer commits — broker integration is opt-in for downstream multi-repo deployments.
- **`recordPactInteraction(httpClient)` rather than middleware.** A `fetch`-shaped wrapper composes with every consumer's existing injectable-fetch DI surface (every package on genesis exposes `{ fetch?: FetchLike }` or equivalent). No instrumentation of the consumer's internals is required.
- **`verifyProviderAgainstPact` is HTTP-only.** Replaying a recorded pact against a live provider URL covers OAuth, webhook receivers, schema registry, and API-schemas consumers. Message contract testing (queues, pubsub) is deferred until a `messaging` package lands.
- **Errors via local `TestingContractError`.** Mirrors the `TestingError` pattern in `@suaveplan/testing` — testing-infrastructure exception keeps the package free of `@suaveplan/error`.
## Risks / Trade-offs
- **No matcher syntax in v0** means consumer pacts are exact-shape; provider drift on incidental fields (timestamps, request ids) will trigger spurious failures. Mitigated by an explicit `ignoreFields: string[]` option on `verifyProviderAgainstPact` for v0; full matcher support tracked as follow-up.
- **Pact files committed in-repo** add line-noise to PRs that touch wire formats. This is the point — wire-format drift becomes reviewable diff, not silent runtime breakage.
- **Broker auth via `PACT_BROKER_TOKEN`** keeps secrets out of pact files but requires CI secret plumbing; documented as a follow-up in `tasks.md`.
- **Universal-package constraint** means the package cannot use `@pact-foundation/pact`'s native verifier; the in-house verifier must be kept simple and well-tested (100% coverage gate enforces this).

View File

@@ -0,0 +1,53 @@
## Why
Section 2 of the test-suite audit (`/mnt/s/web/genesis/docs/superpowers/specs/2026-04-25-test-suite-audit.md` row "No contract tests") lists consumer/provider contract testing as a critical missing tier of the test pyramid: `@suaveplan/oauth-client`, `@suaveplan/webhook-client`, `@suaveplan/schema-registry`, and `@suaveplan/api-schemas` are textbook Pact candidates whose failure modes (provider drifting from consumer expectations, schema breaking changes, OAuth token-endpoint shape mutations, webhook signature drift) are not catchable by today's unit, integration, or E2E tiers.
Today the only schema-shaped guard is hand-written Zod validation in each package's tests, which proves the consumer's encoder agrees with itself, not that a real provider's responses still match what the consumer was built against. This change introduces a project-wide Pact-style consumer/provider contract testing capability and wires it into every consumer package whose surface crosses a network boundary.
## What Changes
### New package — `@suaveplan/testing-contract`
- Lives at `packages/testing/testing-contract` and follows the same dependency-graph exception as `@suaveplan/testing-integration`: zero `@suaveplan/*` runtime deps except `@suaveplan/types` (per `CLAUDE.md` rule 8 testing-infrastructure carve-out).
- Public API:
- `defineConsumerInteraction({ provider, request, response, state? })` — declarative interaction record.
- `recordPactInteraction(httpClient)` — wraps a `fetch`-shaped client; while wrapped, every request/response pair is recorded against the matching `defineConsumerInteraction` declarations into a `.pact.json` file.
- `verifyProviderAgainstPact(pactFile, providerUrl, options)` — replays a pact file's interactions against a running provider and asserts each response matches the recorded shape.
- `publishPact(brokerUrl, version)` and `pullPactsForVerification(brokerUrl, providerName)` — optional Pact Broker integration; auth via `PACT_BROKER_TOKEN` env.
- Pact file shape conforms to Pact Specification v3 `interactions[]` (consumer/provider names, request, response, providerStates).
- Errors prefixed `TESTING_CONTRACT.*`. Coverage gate at 100% (Istanbul, all four metrics).
### Cross-cutting requirements on consumer packages
Each of the four packages SHALL ship a `*.contract.test.ts` that records interactions against its public API surface using `recordPactInteraction`. The recorded `.pact.json` files are committed to `pacts/<consumer>__<provider>.json` so PR review surfaces wire-format drift. Provider-side specs (where the package itself implements a server / receiver — `webhook-client` receivers, `schema-registry` server) SHALL run `verifyProviderAgainstPact` in CI against the same pact files.
### Capability deltas
Each consumer package gets an `## ADDED Requirements` block carrying:
- A **Consumer-side pact recording** requirement (must produce a pact file in `pacts/`).
- For server-shaped packages (`webhook-client` receiver, `schema-registry` server), a **Provider-side pact verification** requirement.
## Capabilities
### New Capabilities
- `testing-contract` — Pact-style consumer/provider contract testing primitives (`defineConsumerInteraction`, `recordPactInteraction`, `verifyProviderAgainstPact`, `publishPact`, `pullPactsForVerification`).
### Modified Capabilities
- `oauth-client` — adds consumer-side pact requirement against the OAuth/OIDC provider's token + discovery endpoints.
- `webhook-client` — adds consumer-side (signing) and provider-side (receiver) pact requirements.
- `schema-registry` — adds consumer-side (schema lookup) and provider-side (registry server) pact requirements.
- `api-schemas` — adds consumer-side pact recording over the `common`/`auth`/`admin`/`events` envelope shapes that consumers send to API plugins.
(None of these capabilities have a pre-existing `openspec/specs/<capability>/spec.md` yet; the deltas in this change land them as new files when folded.)
## Impact
- **Affected code:** new package at `/mnt/s/web/genesis/packages/testing/testing-contract/**`; new `*.contract.test.ts` files inside the four consumer packages; new `pacts/` directories per consumer package.
- **Affected consumers:** every `@suaveplan/*` package whose surface crosses a network boundary inherits the requirement on its first OpenSpec change after this one lands.
- **Catalog additions (root `package.json`):** none — `testing-contract` synthesises pact JSON itself rather than depending on `@pact-foundation/pact` (which is Node-only and pulls a Ruby toolchain). The optional broker-publish path uses native `fetch`.
- **CI:** a new `pact:verify` Turborepo task runs `verifyProviderAgainstPact` on every commit for the provider-shaped packages.
- **Pre-publish gate:** every package with contract tests must pass `pact:verify` in addition to `lint typecheck test` and 100% coverage.
- **Cross-cutting:** five new spec files land on archive (`testing-contract`, `oauth-client`, `webhook-client`, `schema-registry`, `api-schemas`).

View File

@@ -0,0 +1,24 @@
## ADDED Requirements
### Requirement: Consumer-side pact recording over envelope shapes
The `@suaveplan/api-schemas` package SHALL ship an `api-schemas.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record interactions exercising every envelope shape defined in the package: `common` (pagination, sort, filter, id-param, error-envelope), `auth` (login, register, refresh, logout, context), `admin` (ping, health, status), and `events` (domain-event, envelope, metadata). The resulting pact file MUST be committed to `pacts/api-schemas__enterprise-api.json`.
#### Scenario: Every envelope shape is exercised
- **WHEN** the consumer-side test suite runs
- **THEN** `pacts/api-schemas__enterprise-api.json` contains at least one interaction whose request or response body matches each of `common`, `auth`, `admin`, and `events`
#### Scenario: Schema drift produces reviewable diff
- **WHEN** an `api-schemas` change renames a field on the `auth.login` request envelope
- **THEN** the diff against `pacts/api-schemas__enterprise-api.json` is non-empty AND surfaces in PR review before the corresponding API plugin is updated
### Requirement: Pact-recorded shapes parse cleanly under the package's Zod schemas
Every recorded request and response body in the committed pact file MUST parse successfully under the matching Zod schema exported from `@suaveplan/api-schemas`. A validation step in the consumer-side test SHALL enforce this contract.
#### Scenario: Recorded body conforms to its Zod schema
- **WHEN** the test parses each `interactions[].request.body` and `interactions[].response.body` under the matching exported Zod schema
- **THEN** every parse succeeds AND no `ZodError` is thrown

View File

@@ -0,0 +1,24 @@
## ADDED Requirements
### Requirement: Consumer-side pact recording
The `@suaveplan/oauth-client` package SHALL ship a `oauth-client.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record at minimum the following interactions against any conforming OAuth 2.0 / OIDC provider: OIDC discovery (`GET /.well-known/openid-configuration`), Authorization Code token exchange (`POST /token` with `grant_type=authorization_code`), Client Credentials token exchange (`POST /token` with `grant_type=client_credentials`), token refresh (`POST /token` with `grant_type=refresh_token`). The resulting pact file MUST be committed to `pacts/oauth-client__oidc-issuer.json`.
#### Scenario: Pact file committed
- **WHEN** the consumer-side test suite runs
- **THEN** `pacts/oauth-client__oidc-issuer.json` is produced AND its `interactions[]` covers discovery, authorization-code exchange, client-credentials exchange, and refresh
#### Scenario: Wire-format drift is reviewable
- **WHEN** an `oauth-client` change alters a request body field name
- **THEN** the diff against `pacts/oauth-client__oidc-issuer.json` is non-empty AND surfaces in PR review
### Requirement: Injectable fetch surfaces pact wrapping
The package's public functions accepting `{ fetch?: FetchLike }` SHALL accept the `recordPactInteraction(fetch)` wrapper without requiring any public-API change.
#### Scenario: Wrapped fetch records all token endpoint traffic
- **WHEN** the consumer test wraps the package's injectable fetch via `recordPactInteraction` AND drives an Authorization Code exchange
- **THEN** every request the package issues is captured into the pact file

View File

@@ -0,0 +1,24 @@
## ADDED Requirements
### Requirement: Consumer-side pact recording
The `@suaveplan/schema-registry` package SHALL ship a `schema-registry.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record interactions against an upstream registry's lookup, register, and compatibility-check endpoints. The resulting pact file MUST be committed to `pacts/schema-registry__upstream-registry.json`.
#### Scenario: Lookup, register, compatibility recorded
- **WHEN** the consumer-side test exercises `getSchema`, `registerSchema`, and `checkCompatibility`
- **THEN** `pacts/schema-registry__upstream-registry.json` contains at least one interaction per public method
### Requirement: Provider-side pact verification
The `@suaveplan/schema-registry` package SHALL ship a `schema-registry.provider.test.ts` that uses `verifyProviderAgainstPact` from `@suaveplan/testing-contract` to replay `pacts/schema-registry__upstream-registry.json` against the registry server implemented by the package itself. Provider states for "subject exists with N versions" SHALL be registered via `options.stateHandlers`.
#### Scenario: Provider state seeds version history
- **WHEN** an interaction declares `providerStates:[{ name:"subject 'orders' has 3 versions" }]` AND the corresponding state handler runs
- **THEN** the registry under test reports `versions.length === 3` for subject `'orders'` before the interaction is replayed
#### Scenario: Compatibility break fails verification
- **WHEN** the registry server's `BACKWARD` compatibility check returns `false` for an interaction recorded as `true`
- **THEN** `verifyProviderAgainstPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `pact:verify` fails the build

View File

@@ -0,0 +1,94 @@
## ADDED Requirements
### Requirement: Package isolation
The `@suaveplan/testing-contract` package `dependencies`, `peerDependencies`, and `devDependencies` MUST NOT list any `@suaveplan/*` scoped package other than `@suaveplan/types`. External catalog deps only (`zod`, build tooling). This mirrors the testing-infrastructure carve-out documented in `CLAUDE.md` rule 8.
#### Scenario: Dep-graph guard
- **WHEN** `bunx turbo ls --filter=@suaveplan/testing-contract...` is run
- **THEN** no `@suaveplan/*` package other than `@suaveplan/types` appears in the forward closure of `@suaveplan/testing-contract`
### Requirement: Pact Specification v3 file shape
Pact files emitted and consumed by the package SHALL conform to Pact Specification v3 — a JSON document with `consumer.name`, `provider.name`, `interactions[]` (each carrying `description`, optional `providerStates[]`, `request`, `response`), and `metadata.pactSpecification.version === "3.0.0"`. Field order in serialised output MUST be deterministic so file diffs are reviewable.
#### Scenario: Round-trip preserves byte equality
- **WHEN** a pact file is read via `readPactFile`, then written via `writePactFile` to a new path
- **THEN** the new file is byte-identical to the original
#### Scenario: Spec version asserted on read
- **WHEN** `readPactFile` is given a file whose `metadata.pactSpecification.version !== "3.0.0"`
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.PACT_FILE.UNSUPPORTED_VERSION"`
### Requirement: Consumer-side recording API
`defineConsumerInteraction({ provider, request, response, state? })` SHALL register an expected interaction. `recordPactInteraction(httpClient)` SHALL wrap a `FetchLike` such that every request issued through the wrapped client is matched against registered interactions and accumulated into a pact file emitted by `flushPact(path)`. Unmatched interactions and interactions with no observed traffic MUST both be reported, distinctly.
#### Scenario: Recorded interaction lands in pact file
- **WHEN** `defineConsumerInteraction({ provider:"P", request:{ method:"GET", path:"/x" }, response:{ status:200, body:{ ok:true } } })` is registered AND a `recordPactInteraction`-wrapped fetch hits `GET /x` AND `flushPact("p.json")` runs
- **THEN** `p.json` contains exactly one interaction matching the registered shape
#### Scenario: Unmatched traffic surfaces as error
- **WHEN** the wrapped fetch issues a request that matches no registered interaction AND `flushPact` is called
- **THEN** `flushPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.RECORD.UNMATCHED_REQUEST"` AND `context.method` and `context.path` identify the unmatched request
#### Scenario: Unused interaction surfaces as error
- **WHEN** an interaction is registered AND the wrapped fetch issues no matching request AND `flushPact` is called
- **THEN** `flushPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.RECORD.UNUSED_INTERACTION"` AND `context.description` identifies the interaction
### Requirement: Provider-side verification
`verifyProviderAgainstPact(pactFile, providerUrl, options)` SHALL replay each interaction in the pact file against the supplied `providerUrl`, asserting that each response's status, headers (where recorded), and body shape match the recorded shape modulo `options.ignoreFields`. The function MUST use an injectable `fetchFn`. State handlers registered via `options.stateHandlers` MUST run before each interaction whose `providerStates[]` includes the matching state name.
#### Scenario: Provider response matches recorded shape
- **WHEN** the provider returns `{ status:200, body:{ ok:true } }` for the interaction `GET /x` AND `verifyProviderAgainstPact` is called
- **THEN** the function resolves with `{ passed: true, results: [{ description, ok: true }] }`
#### Scenario: Provider response diverges
- **WHEN** the provider returns `{ status:500 }` instead of the recorded `200`
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `context.diff` contains the structured path → expected/actual diff
#### Scenario: State handler runs before matching interaction
- **WHEN** an interaction declares `providerStates:[{ name:"user-exists" }]` AND `options.stateHandlers["user-exists"]` is registered
- **THEN** the registered handler is awaited exactly once before the interaction's request is replayed
### Requirement: Pact Broker integration
`publishPact(brokerUrl, version)` SHALL POST the pact file to the broker's `pacts/provider/<P>/consumer/<C>/version/<v>` endpoint. `pullPactsForVerification(brokerUrl, providerName)` SHALL GET the broker's verification endpoint and return `PactFile[]`. Both functions MUST authenticate via the `PACT_BROKER_TOKEN` env var (Bearer scheme); when unset, both MUST throw before any HTTP call.
#### Scenario: Publish includes Bearer token
- **WHEN** `PACT_BROKER_TOKEN=abc` is set AND `publishPact("https://broker", "1.2.3")` runs
- **THEN** the issued POST has header `Authorization: Bearer abc`
#### Scenario: Missing token aborts before HTTP
- **WHEN** `PACT_BROKER_TOKEN` is unset AND `publishPact(...)` is called
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.BROKER.MISSING_TOKEN"` AND no HTTP request is issued
### Requirement: Error code prefix
Every error thrown by the package SHALL be a `TestingContractError` whose `code` begins with `TESTING_CONTRACT.`. Plain `Error` / `TypeError` MUST NOT be thrown from any public code path.
#### Scenario: Code namespace enforced
- **WHEN** any public function throws
- **THEN** the thrown value `instanceof TestingContractError === true` AND `error.code.startsWith("TESTING_CONTRACT.") === true`
### Requirement: Coverage gate
Istanbul statement, branch, function, and line coverage across `packages/testing/testing-contract/src/` (excluding `*.bench.ts` and `*.schemas.ts`) MUST be 100%. The pre-publish gate MUST hard-fail below 100%.
#### Scenario: Coverage gate trips below 100%
- **WHEN** any line, branch, function, or statement is uncovered
- **THEN** the pre-publish gate fails AND the package MUST NOT be published

View File

@@ -0,0 +1,24 @@
## ADDED Requirements
### Requirement: Consumer-side pact recording
The `@suaveplan/webhook-client` package SHALL ship a `webhook-client.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record signed-delivery POSTs to a webhook receiver. Recorded interactions MUST include the `X-Signature` HMAC-SHA256 header so receiver implementations can be verified against the canonical signing scheme. The resulting pact file MUST be committed to `pacts/webhook-client__webhook-receiver.json`.
#### Scenario: Pact records signed delivery
- **WHEN** the consumer-side test drives a signed delivery
- **THEN** `pacts/webhook-client__webhook-receiver.json` contains an interaction whose `request.headers["X-Signature"]` is a valid HMAC-SHA256 of the recorded body under the recorded secret
### Requirement: Provider-side pact verification
The `@suaveplan/webhook-client` package SHALL ship a `webhook-client.provider.test.ts` that uses `verifyProviderAgainstPact` from `@suaveplan/testing-contract` to replay `pacts/webhook-client__webhook-receiver.json` against a running webhook-receiver instance and assert each response matches the recorded shape. Verification MUST run on every commit via the `pact:verify` Turborepo task.
#### Scenario: Receiver passes verification
- **WHEN** the receiver under test returns the recorded response shape for every interaction
- **THEN** `verifyProviderAgainstPact` resolves with `passed: true`
#### Scenario: Receiver drift trips verification
- **WHEN** the receiver returns a 500 for an interaction recorded as 200
- **THEN** `verifyProviderAgainstPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `pact:verify` fails the build

View File

@@ -0,0 +1,74 @@
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Package skeleton — `@suaveplan/testing-contract`
- [ ] 1.1 Create `packages/testing/testing-contract/` with `package.json` (version `0.1.0`, `type: "module"`, `sideEffects: false`, only `@suaveplan/types` as a `workspace:^` internal dep, catalog deps on `zod` only).
- [ ] 1.2 Add `tsconfig.lib.json` extending `@suaveplan/typescript-config/library.json`; add `vite.config.ts` (`preserveModules: true`); add `vitest.config.ts` via `createTestConfig()`.
- [ ] 1.3 Stub `README.md` (one-paragraph description, install, quick start, **Modules** index linking each co-located `.md`).
## 2. Pact data model
- [ ] 2.1 Implement `src/pact-file/pact-file.types.ts` — types matching Pact Specification v3 (`PactFile`, `PactInteraction`, `PactRequest`, `PactResponse`, `PactProviderState`).
- [ ] 2.2 Implement `src/pact-file/pact-file.schemas.ts` — Zod schemas for every type above; round-trip parse/serialise covered by tests.
- [ ] 2.3 Implement `src/pact-file/read-write.ts` — `readPactFile(path)` and `writePactFile(path, pact)` with deterministic key order (consumer / provider / interactions / metadata) so diffs are reviewable.
## 3. Consumer-side recording
- [ ] 3.1 Implement `src/define/define-consumer-interaction.ts` — `defineConsumerInteraction({ provider, request, response, state? })`; registers the expectation against a per-process registry; returns a handle for cleanup.
- [ ] 3.2 Implement `src/record/record-pact-interaction.ts` — `recordPactInteraction(httpClient)` wraps a `FetchLike` and matches each request/response pair against registered interactions; on `flushPact(path)` writes the pact file.
- [ ] 3.3 Implement `src/record/match-interaction.ts` — match algorithm (method + path + headers subset + body shape); reports unmatched interactions distinctly from interactions with no traffic.
- [ ] 3.4 Co-locate tests, schemas, and `.md` per file (≥200 words with simple + advanced examples per `openspec/AGENTS.md` §4).
## 4. Provider-side verification
- [ ] 4.1 Implement `src/verify/verify-provider-against-pact.ts` — `verifyProviderAgainstPact(pactFile, providerUrl, options)`; replays each interaction via injectable `fetchFn`; asserts response matches recorded shape modulo `options.ignoreFields`.
- [ ] 4.2 Implement `src/verify/state-handler.ts` — provider-state handler registry; `options.stateHandlers: Record<string, () => Promise<void>>` runs before each interaction whose `state` is registered.
- [ ] 4.3 Implement `src/verify/diff-report.ts` — produce a structured diff when a verification fails (path → expected vs actual); included in the thrown error's `context`.
## 5. Pact Broker integration
- [ ] 5.1 Implement `src/broker/publish-pact.ts` — `publishPact(brokerUrl, version)` HTTP POST to `/pacts/provider/<P>/consumer/<C>/version/<v>`; auth via `PACT_BROKER_TOKEN`.
- [ ] 5.2 Implement `src/broker/pull-pacts.ts` — `pullPactsForVerification(brokerUrl, providerName)` returns `PactFile[]`; uses the broker's `provider-pacts-for-verification` endpoint.
- [ ] 5.3 Implement `src/broker/auth.ts` — read `PACT_BROKER_TOKEN`; throw `TestingContractError("TESTING_CONTRACT.BROKER.MISSING_TOKEN")` when broker calls are made without auth.
## 6. Errors
- [ ] 6.1 Implement `src/errors/testing-contract-error.ts` — `TestingContractError extends Error` with `{ code, cause?, context? }`; subclasses for the recording, verification, and broker domains.
- [ ] 6.2 Implement `src/errors/codes.ts` — string-literal-union of every `TESTING_CONTRACT.<DOMAIN>.<REASON>` code.
## 7. Spec deltas
- [ ] 7.1 Land `specs/testing-contract/spec.md` with `## ADDED Requirements` (data model, consumer recording, provider verification, broker integration, error code prefix, coverage gate).
- [ ] 7.2 Land `specs/oauth-client/spec.md` with `## ADDED Requirements` for consumer-side pact recording.
- [ ] 7.3 Land `specs/webhook-client/spec.md` with `## ADDED Requirements` for consumer-side and provider-side pact requirements.
- [ ] 7.4 Land `specs/schema-registry/spec.md` with `## ADDED Requirements` for consumer-side and provider-side pact requirements.
- [ ] 7.5 Land `specs/api-schemas/spec.md` with `## ADDED Requirements` for consumer-side pact recording over envelope shapes.
## 8. Per-consumer wiring
- [ ] 8.1 Add `oauth-client.contract.test.ts` to `@suaveplan/oauth-client`; record token endpoint + discovery + introspection; commit `pacts/oauth-client__oidc-issuer.json`.
- [ ] 8.2 Add `webhook-client.contract.test.ts` to `@suaveplan/webhook-client` (consumer side: signed delivery POST); add `webhook-client.provider.test.ts` (verifies receiver against the same pact); commit `pacts/webhook-client__webhook-receiver.json`.
- [ ] 8.3 Add `schema-registry.contract.test.ts` to `@suaveplan/schema-registry` (consumer side: schema lookup); add `schema-registry.provider.test.ts` (verifies the registry server); commit `pacts/schema-registry__upstream-registry.json`.
- [ ] 8.4 Add `api-schemas.contract.test.ts` to `@suaveplan/api-schemas`; record envelope-shape interactions for `common`/`auth`/`admin`/`events`; commit `pacts/api-schemas__enterprise-api.json`.
## 9. CI
- [ ] 9.1 Add a `pact:verify` Turborepo task that runs every provider-shaped package's `verifyProviderAgainstPact` against the committed pact files.
- [ ] 9.2 Wire `pact:verify` into the pre-publish gate alongside `lint typecheck test`.
## 10. Verification
- [ ] 10.1 `bunx turbo lint typecheck test --filter=@suaveplan/testing-contract` green.
- [ ] 10.2 `bunx turbo test:coverage --filter=@suaveplan/testing-contract` at 100% statement / branch / function / line.
- [ ] 10.3 `bunx turbo pact:verify --filter=@suaveplan/{oauth-client,webhook-client,schema-registry,api-schemas}` green.
- [ ] 10.4 Banned-pattern scan clean.
## 11. Archive
- [ ] 11.1 Move `openspec/changes/enterprise-testing-contract/` → `openspec/archive/<yyyy-mm-dd>-enterprise-testing-contract/` after all gates pass.