# Epic 005 — Implementation tasks

Ordered plan for `docs/specs/005-mocking-bird-checkout/README.md`. Task IDs (`MBC-TASK-NNN`) and
scenario IDs (`MBC-SNN`) are stable once committed; never renumber. Every implementation task
includes its focused tests, keeps `pnpm quality` green before completion, and updates
`docs/current-state.md` / `docs/ai-handover.md` when it changes the implementation state.

Conventions used below:

- **Files** lists the likely touched paths; it is guidance, not a restriction.
- **Depends on** lists tasks that must be complete first; tasks without dependencies may proceed in
  parallel.
- Evidence for a task is a passing focused suite plus the bracketed scenario IDs in test titles.

## Phase A — Generalize Mocking Bird (mechanical, no behavior change)

- [x] **MBC-TASK-001** — Extract the email interceptor into its own module and introduce the
      `MockingBird` facade.
  - Move the current class body of `backend/src/integrations/mocking-bird.ts` verbatim into
    `backend/src/integrations/mocking-bird-email.ts` as `MockingBirdEmail` (transport, scenarios,
    latency, captures, previews, capacity, `clear` unchanged).
  - Reimplement `backend/src/integrations/mocking-bird.ts` as `class MockingBird` exposing
    `readonly email: MockingBirdEmail` (constructor keeps `syntheticRecipients` and `capacity`);
    document the intended second capability (`checkout`) in the class TSDoc without adding it yet.
  - Update call sites: `backend/src/server.ts` (`mockingBird.email.transport`), the console block in
    `backend/src/auth-http.ts` (`bird.email.*`), and tests:
    `backend/src/integrations/mocking-bird.test.ts` (rename if the module name changes),
    `backend/src/application/password-recovery-service.test.ts`, `backend/src/auth-http.test.ts`,
    `backend/src/openapi-contract.test.ts`.
  - Done when: full existing suites pass with assertions unchanged (pure refactor; no wiring change
    yet). Depends on: none.

- [x] **MBC-TASK-002** — Decouple Mocking Bird availability from `MAIL_MODE`.
  - `backend/src/server.ts`: construct the facade from `config.MOCKING_BIRD_ENABLED` only (no
    `MAIL_MODE` reference); pass it to `buildApp` as its own option
    `mockingBird: { bird, mailMode: config.MAIL_MODE }`, outside the `developerEmail` conditional;
    attach the interceptor with the explicit guard
    `config.MAIL_MODE === "mock" ? mockingBird?.email.transport : undefined`; construct
    `mockingBird` before `createCheckoutGateway` (forward-compatibility with MBC-TASK-007).
  - `backend/src/auth-http.ts`: `AuthHttpOptions.mockingBird` becomes
    `{ bird: MockingBird; mailMode: "disabled" | "mock" | "real" }`; `consoleState()` reports
    `mailMode` instead of the hardcoded `"mock"`; route gate and all protections unchanged.
  - `backend/src/config.ts`: treat empty `MOCKING_BIRD_ENABLED` as absent; derive `true` for
    `DEPLOYMENT_ENV` `local`/`test` and `false` for hosted; reword the hosted rejection message from
    "developer email features" to developer tooling if touched; keep `MAIL_MODE=mock` requirements
    unchanged.
  - Tests: `backend/src/auth-http.test.ts` (console routes registered with `MAIL_MODE` effectively
    disabled/real — MBC-S01; state reports the configured mode; existing auth/CSRF matrix
    unchanged), `backend/src/config.test.ts` (derivation, explicit opt-out, hosted rejection; cases
    relying on the old implicit `false` now state it explicitly).
  - Done when: MBC-S01 backend evidence passes and no mail behavior changed. Depends on:
    MBC-TASK-001.

- [x] **MBC-TASK-003** — Prove existing email interception is preserved (regression gate).
  - Run and, where needed, extend: `backend/src/integrations/mocking-bird.test.ts`,
    `backend/src/application/password-recovery-service.test.ts`, `backend/src/auth-http.test.ts`
    (console + email-test describes), `backend/src/openapi-contract.test.ts`,
    `frontend/src/pages/MockingBirdConsolePage.test.tsx`,
    `frontend/src/app/api/mocking-bird-api.test.ts`, and the local E2E mail journey (`tests/e2e/`).
  - Add the explicit transport-guard test required by MBC-S02: with the facade present and
    `MAIL_MODE` `real`/`disabled`, `ResendEmailSender` receives no interceptor (no capture can
    occur); with `MAIL_MODE=mock`, interception works exactly as before.
  - Done when: MBC-S02 evidence passes with `[MBC-S02]` in test titles. Depends on: MBC-TASK-002.

## Phase B — Mocking Bird payment mode and adapter

- [x] **MBC-TASK-004** — Add `CHECKOUT_MODE=mocking-bird` to configuration.
  - `backend/src/config.ts`: extend the enum; extend the hosted rejection (own message for
    `mocking-bird`); add the cross-field guard requiring Mocking Bird to be enabled; keep the schema
    default `disabled`; update inline comments.
  - Tests in `backend/src/config.test.ts`: local accepts `mocking-bird`; both hosted values reject
    it; `MOCKING_BIRD_ENABLED=false` + `mocking-bird` rejects; `fake`/`surfboard`/ `disabled` rules
    untouched (MBC-S03 config half).
  - Done when: config tests pass. Depends on: none (independent of Phase A).

- [x] **MBC-TASK-005** — Implement `MockingBirdCheckoutStore` (pending/settled state) with unit
      tests.
  - New `backend/src/integrations/mocking-bird-checkout.ts`: entry type (`MockingBirdCheckoutEntry`
    per the README data model), `MockingBirdCheckoutTrigger`,
    `MOCKING_BIRD_CHECKOUT_REFERENCE_PREFIX = "mocking-bird-"`; `register(request, context)`,
    `settle(id, trigger)` (first-wins; settled / already-settled / not-found), `list()` newest-first
    defensive copies, `counts() {total, pending, capacity}`, `clear()` (pending → `unknown` with
    `settleNote: "cleared while pending"`, then empty), capacity bounding settled entries only,
    parked-promise registry resolving at most once.
  - Wire `readonly checkout: MockingBirdCheckoutStore` onto the `MockingBird` facade
    (`backend/src/integrations/mocking-bird.ts`); email `clear()` stays email-only.
  - New `backend/src/integrations/mocking-bird-checkout.test.ts`: register/park, each trigger's
    `CheckoutResult` mapping (reference present for all settled outcomes), single-resolution,
    not-found/already-settled, ordering, capacity never evicting pending, clear semantics,
    email/checkout state independence (MBC-S07 store half, MBC-S11).
  - Done when: store unit tests pass. Depends on: MBC-TASK-001.

- [x] **MBC-TASK-006** — Implement `MockingBirdCheckoutGateway implements CheckoutGateway`.
  - New `backend/src/integrations/mocking-bird-checkout-gateway.ts`: `charge(request, context)`
    registers the entry and returns its parked promise; settles map triggers to `CheckoutResult`
    (`timeout`/`provider-error` → `unknown`); emits the redacted `payment.initiate` diagnostic with
    `provider.label: "mocking-bird"` on settle; contains no timers, deadlines, network calls, or
    outcome heuristics (the developer is the only clock — asynchronous provider semantics are
    expressed solely by the parked charge, no callbacks or webhooks are simulated).
  - New `backend/src/integrations/mocking-bird-checkout-gateway.test.ts`: each of the five triggers
    produces the exact `CheckoutResult`; `waitMs` is informational only; diagnostics payload
    contains no request content; a thrown internal fault is not converted to success.
  - Done when: gateway unit tests pass. Depends on: MBC-TASK-005.

- [x] **MBC-TASK-007** — Select the adapter in composition and wire it in the server.
  - `backend/src/integrations/checkout-composition.ts`: add the optional `mockingBirdCheckoutStore`
    parameter; `CHECKOUT_MODE=mocking-bird` → `MockingBirdCheckoutGateway`; store missing →
    `UnavailableCheckoutGateway` (defense-in-depth); rewrite the function doc comment to the new
    mode map.
  - `backend/src/server.ts`: pass `mockingBird?.checkout` (requires the Phase A reordering).
  - Tests: `backend/src/integrations/checkout-composition.test.ts` (selection, fallback, unchanged
    fake/surfboard/disabled paths — MBC-S03 composition half); extend
    `tests/ci/checkout-boundary.test.mjs` with network-free assertions for the new Mocking Bird
    checkout modules.
  - Done when: composition tests and the static boundary suite pass. Depends on: MBC-TASK-004,
    MBC-TASK-006 (and MBC-TASK-002 for server wiring).

- [x] **MBC-TASK-008** — Point local configuration at Mocking Bird and verify harness pins.
  - `.env.example`: `CHECKOUT_MODE=mocking-bird` with the updated explanatory comment; show
    `MOCKING_BIRD_ENABLED=true` explicitly; leave every `SURFBOARD_*` value empty (nothing set
    toward Surfboard).
  - `docker-compose.yml`: backend service defaults `CHECKOUT_MODE: ${CHECKOUT_MODE:-mocking-bird}`
    and `MOCKING_BIRD_ENABLED: ${MOCKING_BIRD_ENABLED:-true}` with updated comments (Compose pins
    `DEPLOYMENT_ENV: local`; hosted guards unaffected).
  - Verify unchanged pins: `tests/e2e/playwright.config.ts` and `scripts/docker-validation.mjs`
    still set `CHECKOUT_MODE: "fake"`; `.github/workflows/ci.yml` mail fixtures unchanged;
    `backend/src/config.test.ts` still asserts schema default `disabled`; re-run
    `node --test tests/ci/artifacts.test.mjs`.
  - Done when: `pnpm quality` passes with the config edits. Depends on: MBC-TASK-004.

## Phase C — Developer/admin endpoints

- [x] **MBC-TASK-009** — Expose the pending/completed checkout list.
  - `backend/src/auth-http.ts`: inside `if (options.mockingBird)`, add
    `GET /api/developer/mocking-bird/checkout` returning `{requests}` newest-first via
    `requireCurrentUser`, rejecting query input with `400`; extend `consoleState()` with
    `checkoutCount`, `checkoutPendingCount`, `checkoutCapacity`.
  - Tests in `backend/src/auth-http.test.ts`: 401 without session, 400 with query, shape and
    ordering of the response, state counters, `no-store` inherited from the `/api/developer/` hook
    (MBC-S04 read half, MBC-S11 read half).
  - Done when: read-path API tests pass. Depends on: MBC-TASK-005 (and MBC-TASK-002 for the option
    shape).

- [x] **MBC-TASK-010** — Expose the manual outcome endpoint.
  - `backend/src/auth-http.ts`: add `POST /api/developer/mocking-bird/checkout/:id/outcome` with
    strict Zod body
    `{outcome: "succeeded" | "rejected" | "cancelled" | "timeout" | "provider-error"}`,
    `consoleMutation` protections (origin/CSRF/rate limit), `requireCurrentUser`, unexpected query
    rejected `400`; responses: `200 {request}`, `404` malformed/unknown id, `409` already settled,
    `400` invalid body.
  - Tests in `backend/src/auth-http.test.ts`: each trigger settles and returns the entry with the
    mapped `outcome` and `mocking-bird-` reference; 404/409/400/403/401/429 matrix; a parked promise
    created by the store resolves to the expected `CheckoutResult` (MBC-S05–S09 API half, MBC-S10
    settle half).
  - Done when: outcome API tests pass. Depends on: MBC-TASK-009.

- [x] **MBC-TASK-011** — Expose the checkout clear endpoint with safe pending resolution.
  - `backend/src/auth-http.ts`: add `DELETE /api/developer/mocking-bird/checkout` using
    `consoleMutation`; rejects body/query with `400`; settles all pending as `unknown` ("cleared
    while pending") before clearing; `204` on success.
  - Tests: pending entries resolve (their parked promises settle as `unknown`), checkout and email
    state clear independently of `DELETE .../history`, protection matrix (MBC-S11 clear half).
  - Done when: clear API tests pass. Depends on: MBC-TASK-009.

- [x] **MBC-TASK-012** — Document the developer HTTP contract. (complete on
      `mbc-012-document-the-developer-http-contract`)
  - `docs/api/openapi.yaml`: add the three paths (including the `:id` path parameter schema), the
    entry and trigger schemas, the extended console-state schema, and
    `x-deployment-environments: [local, test]` on every new developer path; update the state schema
    for the real mail `mode` and the checkout counters.
  - `backend/src/openapi-contract.test.ts`: add the new paths to the developer-path list; fix the
    `mockingBird` fixture for the new option shape; implemented-vs-documented routes must match.
  - Run `pnpm docs:openapi:check` and `pnpm docs:build` (Swagger UI is regenerated from the same
    spec — no separate API description is maintained).
  - Done when: the OpenAPI contract test passes and the site is rebuilt. Depends on: MBC-TASK-009,
    MBC-TASK-010, MBC-TASK-011.

## Phase D — Developer UI

- [x] **MBC-TASK-013** — Add checkout operations to the frontend API module.
  - `frontend/src/app/api/mocking-bird-api.ts`: add `MockingBirdCheckoutTrigger`,
    `MockingBirdCheckoutEntry`, extended `MockingBirdState` counters, `mockingBirdCheckoutQueryKey`,
    and operations `getMockingBirdCheckoutRequests`, `settleMockingBirdCheckoutOutcome`,
    `clearMockingBirdCheckoutHistory` (CSRF via `acquireCsrfToken`, existing request helpers, strict
    runtime parsers matching the OpenAPI schemas).
  - Tests in `frontend/src/app/api/mocking-bird-api.test.ts`: query key below the shared
    authenticated root, parser acceptance/rejection per field, method/URL/CSRF assertions in the
    style of the existing cases.
  - Done when: API module tests pass. Depends on: MBC-TASK-012.

- [x] **MBC-TASK-014** — Build the checkout inspection UI (summary, structured, raw).
  - `frontend/src/pages/MockingBirdConsolePage.tsx`: new "Checkout requests" card — pending group
    above settled group, newest-first; per-entry `<details>` disclosure with the summary line
    (status, short `operationId`, `formatGrossPrice` total, line count, relative time), the
    structured definition list (all fields listed in the README, including the per-line table with
    derived totals and VAT percent), and an escaped raw `<pre>` of the full `CheckoutRequest`;
    section header with pending/total counters and the checkout-specific clear button; `useQuery` on
    `mockingBirdCheckoutQueryKey` with `refetchInterval: POLL_INTERVAL_MS`, loading/empty/error
    states via the existing components.
  - `frontend/src/app/i18n/resources.ts`: `developer.checkout.*` keys for `en`, `sv`, `hu`
    (`pnpm i18n:check`).
  - Tests in `frontend/src/pages/MockingBirdConsolePage.test.tsx`: rendering from fixtures,
    grouping/order, disclosure content (lines, amounts, ids, timestamps, raw JSON), counters, clear
    flow, error/loading/empty states (MBC-S04 UI half, MBC-S11 UI half).
  - Done when: component tests pass at the existing mobile/desktop test sizes. Depends on:
    MBC-TASK-013.

- [x] **MBC-TASK-015** — Wire the five outcome controls.
  - `frontend/src/pages/MockingBirdConsolePage.tsx`: outcome buttons on pending entries bound to
    `settleMockingBirdCheckoutOutcome` (`useMutation`, `retry: false`), per-entry busy state,
    success notification, invalidation of the checkout query and console state; settled entries show
    trigger/outcome instead of buttons; mutation errors rendered without implying delivery.
  - Tests: each button posts its trigger to the right URL; buttons disable while the mutation is in
    flight; the entry rerenders settled after invalidation; the error path keeps controls available
    (MBC-S06/S07 frontend half; supports MBC-S05, MBC-S08, MBC-S09).
  - Done when: component tests pass. Depends on: MBC-TASK-014.

## Phase E — Flow evidence (outcome → normal payment handling)

- [x] **MBC-TASK-016** — Application-level integration through the parked charge, one test per
      outcome.
  - New focused suite (colocated, e.g.
    `backend/src/integrations/mocking-bird-checkout.integration.test.ts` or an application-level
    test wiring `CheckoutService` + `MockingBirdCheckoutGateway` + a stubbed/order repository, in
    the style of `order-service.test.ts`): drive `finalizeCommercial` (or `CheckoutService` +
    `OrderService`) with `CHECKOUT_MODE=mocking-bird` semantics — assert the charge parks, the entry
    appears with the full request, then per trigger assert the downstream result:
    - `succeeded` → `201`-equivalent success, order created `Queued`, sale contribution, attempt row
      with `mocking-bird-` reference `[MBC-S05]` (dual-tag `[MVP-PAY-001-S01]`);
    - `rejected` and `cancelled` → `OrderCheckoutDeclinedError` / `409 CHECKOUT_NOT_PAID`, no order,
      no sale, no unresolved row `[MBC-S06]`, `[MBC-S07]` (dual-tag `[MVP-PAY-001-S02]`);
    - `timeout` and `provider-error` → unresolved attempt recorded, `409 CHECKOUT_UNRESOLVED`,
      `GET /api/checkout/unresolved` visibility, replay of the same `operationId` does not charge
      `[MBC-S08]`, `[MBC-S09]` (dual-tag `[MVP-PAY-001-S03]`).
  - Done when: the suite passes and each bracketed ID appears in a test title. Depends on:
    MBC-TASK-007.

- [x] **MBC-TASK-017** — Multiple pending requests and duplicate suppression.
  - Same suite level: two concurrent `finalizeCommercial` calls with distinct `operationId`s park
    two entries; settling them in reverse order resolves each with its own result; a repeated
    finalize for a pending operation answers `OrderCheckoutInProgressError` /
    `409 CHECKOUT_IN_PROGRESS` without a second entry `[MBC-S10]` (dual-tag `[MVP-PAY-001-S04]`);
    store-level assertion that entries key by UUID, not `operationId`.
  - Done when: concurrency tests pass deterministically (no sleeps beyond awaiting promises).
    Depends on: MBC-TASK-016.

- [x] **MBC-TASK-018** — Lifecycle and retention evidence.
  - Store/API tests: settled entries retained with timestamps/trigger/outcome/reference until clear;
    capacity evicts settled only (pending never); clear resolves pending as `unknown` before
    emptying; email history/scenario/latency untouched by checkout clear and vice versa `[MBC-S11]`.
  - Done when: lifecycle tests pass. Depends on: MBC-TASK-005, MBC-TASK-011.

- [x] **MBC-TASK-019** — Environment and authorization safeguards.
  - `backend/src/config.test.ts`: hosted + `CHECKOUT_MODE=mocking-bird` startup failure; hosted +
    `MOCKING_BIRD_ENABLED=true` startup failure; mode-without-instance failure `[MBC-S12]`.
  - `backend/src/auth-http.test.ts`: with the instance absent, every new path answers `404` by
    direct HTTP; with it present, the full 401/403/CSRF/429 matrix for the three new routes
    `[MBC-S12]`.
  - `frontend/src/app/pages.test.tsx`: registry behavior unchanged with/without
    `VITE_ENABLE_DEVELOPER_TOOLS` `[MBC-S12]`.
  - `tests/ci/checkout-boundary.test.mjs`: network-free Mocking Bird checkout modules (from
    MBC-TASK-007) plus the unchanged neutral-port assertions.
  - Done when: all static and API protection tests pass. Depends on: MBC-TASK-010, MBC-TASK-011,
    MBC-TASK-015.

- [x] **MBC-TASK-020** (optional, not gating) — Browser evidence decision.
  - Decision: no new Playwright journey under `CHECKOUT_MODE=mocking-bird` is added. MBC scenarios
    are already evidenced at application/API/component level by MBC-TASK-016 (application-level
    integration, one test per outcome) and MBC-TASK-017 (multiple pending requests and duplicate
    suppression), bracketed with their MBC-Snn scenario IDs. `tests/e2e/playwright.config.ts` and
    `scripts/docker-validation.mjs` keep their `fake` pins; the Mocking Bird mode does not disturb
    the existing acceptance projects.
  - Depends on: MBC-TASK-015, MBC-TASK-016.

## Phase F — Documentation and completion

- [x] **MBC-TASK-021** — Documentation cleanup for configuration and behavior.
  - `docs/development.md`: rework the developer-console enablement matrix (the mail-transport row is
    no longer the console's master switch; deployment classification + `MOCKING_BIRD_ENABLED` +
    bundle flag are), document `CHECKOUT_MODE=mocking-bird` alongside `disabled`/`fake`/
    `surfboard`, describe the checkout section, its five triggers, in-memory retention, and the
    clear semantics; update the "Mocking Bird email" subsection wording where it implies the console
    is mock-mail-only.
  - `docs/docker.md`: Compose defaults (`CHECKOUT_MODE`, `MOCKING_BIRD_ENABLED`) and the note that
    Compose still sets no provider credential.
  - `docs/test-strategy.md`: only if the new suites change the described evidence layout.
  - `.env.example` / `docker-compose.yml` comments already changed in MBC-TASK-008 — reconcile
    wording so code, comments, and guides agree.
  - Done when: `pnpm docs:links:check` and `pnpm quality` pass. Depends on: MBC-TASK-008,
    MBC-TASK-015.

- [x] **MBC-TASK-022** — Register the epic specification in the documentation site.
  - Add a `projectPages` entry for `docs/specs/005-mocking-bird-checkout/README.md` in
    `scripts/docs-site-manifest.mjs` (group `Specifications`, title mirroring the H1); add the file
    to the required-rendered list in `tests/ci/documentation-gates.test.mjs` alongside the Epic
    001–004 entries; `tasks.md` stays a raw-linked companion file, matching Epic 004.
  - Run `pnpm docs:build` and `pnpm docs:site:check`.
  - Done when: the site renders the Epic 005 page and the documentation gates pass. Depends on: this
    spec's README existing; safe to do at any point.

- [x] **MBC-TASK-023** — Record the architectural decision.
  - If implementation confirms the parked-charge design, add
    `docs/decisions/005-mocking-bird-checkout-adapter.md` following the existing ADR format
    (context: the only seam is `CheckoutGateway.charge`, unresolved attempts are append-only, the
    Surfboard webhook defers processing; decision: interactive parked charge plus manual triggers;
    consequences: finalize duration is developer-controlled, no clocks, single-process state).
  - Done when: the ADR is written and `pnpm quality` passes. Depends on: MBC-TASK-016.

- [x] **MBC-TASK-024** — Final validation, evidence, and handover.
  - Run the full gate: `pnpm quality`; the focused suites from Phases A–E; `pnpm test` / coverage as
    required by `docs/definition-of-done.md`; `pnpm validate:docker` (Compose defaults changed).
  - Tick the acceptance checkboxes in `docs/specs/005-mocking-bird-checkout/README.md` and record
    the evidence references (test paths/titles) next to each `MBC-SNN`.
  - Update `docs/current-state.md` (implementation state, next action) and `docs/ai-handover.md`
    (latest task, changed files, continuation notes); run `pnpm docs:build` if rendered pages
    changed.
  - Done when: every check executed in this task passed and the results are recorded honestly,
    including any check that could not be run. Depends on: all previous tasks.
