# Wrapper Contract Amendments (P1 contract rev — one rev, apply together)

Audience: AI coding agents first. Amends `spec/WRAPPER-CONTRACT.md` + `spec/events.schema.json` for V2 (usage metering), X5 (quota), W7 (transcripts). Apply as ONE schema/contract version bump; update every wrapper in `wrappers/` in the same change. Until applied, the base contract stands.

## A. `usage` event (V2 metering)

- New event kind `usage` added to `events.schema.json` `kind` enum. Payload:
  ```json
  {"kind": "usage", "usage": {"inputTokens": 0, "outputTokens": 0, "cachedTokens": 0, "costUsd": 0.0, "model": "<engine model id>"}}
  ```
  `inputTokens`, `outputTokens`, `model` REQUIRED; `cachedTokens`, `costUsd` optional. Emitted ONCE, as the final event before wrapper exit (any exit code — a failed attempt's cost still counts).
- Wrapper that cannot obtain usage from its engine: emit `{"kind": "usage", "usage": {"unknown": true, "model": "..."}}` and its adapter gets `metered: false` in `presets/adapters.json`. Preflight WARNs when `--unattended` uses unmetered adapters.
- Runner journals it as `task.usage {attemptKind: implement|fix|review, adapter, ...usage}` per attempt.

## B. Quota channel (X5)

- Base contract exit `75` (rate-limited, "retry SAME binding") is SUPERSEDED for engine-level quota exhaustion: retry policy moves OUT of wrappers into the resolver (`spec/RESOLVER.md` rows `wrapper-rate-limited` / `provider-quota`). Wrappers still exit `75` and still SHOULD emit `resume_at`; they MUST NOT self-retry beyond the base contract's single in-process retry.
- `error` event payload gains optional `errorKind`: `{"enum": ["quota", "transport", "semantic"]}` (field on the `error` object — `additionalProperties: false` there means the schema MUST be amended, not just tolerated). `quota` = subscription/429/token-budget exhausted at the provider. Wrappers MUST map their engine's quota errors to `errorKind: "quota"`; unmapped engines omit the field (treated generically).
- Classifier (`src/fail-taxonomy.js`): exit 75 or `errorKind: "quota"` → `provider-quota` (new class). `wrapper-rate-limited` remains for transient 429s WITH a near-term `resume_at`; `provider-quota` is for exhausted windows/subscriptions. Disambiguation rule: `resume_at` ≤ 15 min away → `wrapper-rate-limited`, else `provider-quota`.

## C. Transcript tee (W7)

- Wrappers MUST tee their full event stream to `$HARNESS_TRANSCRIPT_PATH` when that env var is set (engine sets it per attempt: `runstate/transcripts/<slug>/<taskId>-<attempt>.jsonl`). Append-only, same event format as stdout. Unset var → no tee (backward compatible).

## D. `dialect` enum gap

- `events.schema.json` `dialect` enum is `["claude", "cursor", "codex"]` but `presets/adapters.json` ships adapter `north`. Add `"north"` (and every future adapter id) to the enum — schema validation of wrapper output (I7) is impossible for north today. Rule going forward: adding an adapter to `presets/adapters.json` REQUIRES its dialect in the schema enum; `presets/_validate.mjs` enforces the cross-check.

## Tests

1. Schema: fixture `usage`, `usage.unknown`, `errorKind: "quota"`, north-dialect events validate; pre-amendment fixtures still validate (additive change).
2. Each wrapper in `wrappers/`: emits `usage` on success AND failure paths; tees when `$HARNESS_TRANSCRIPT_PATH` set.
3. Classifier: exit-75 + near `resume_at` → `wrapper-rate-limited`; exit-75 + far/absent `resume_at` → `provider-quota`; `errorKind: "quota"` → `provider-quota`.
4. `presets/_validate.mjs` fails on adapter id missing from dialect enum.
