## 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/

/consumer//version/` 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