# 🛡 M141-6 — cross-language parity gate

Mechanical guarantee that `@orchestkit/hook-contract` (npm) and `orchestkit-hook-contract` (PyPI) emit semantically identical contracts. Closes the loop on M141-2 + M141-3.

## What the gate checks

Check | What fails it
--- | ---
Event name set equality | One side has an event the other doesn't
Per-event `payload.required` field names | Set difference between sides
Per-event `payload.properties` field names | Set difference between sides
Per-event field JSON Schema type category | e.g. npm says `string` while PyPI says `object`

## Why both per-side drift gates AND a cross-language gate

                 ┌───────────────────────────────────┐
                 │  spec/hook-events.spec.yml        │
                 └────────┬────────────────┬─────────┘
                          │                │
                (codegen.mjs) ✓     (codegen-py.py) ✓
                          │                │
                          ▼                ▼
             ┌────────────────┐  ┌────────────────┐
             │ npm emit       │  │ PyPI emit      │
             └────┬───────────┘  └──┬─────────────┘
                  │                 │
                  │  per-side       │  per-side
                  │  drift gate     │  drift gate
                  │  (--check) ✓    │  (--check) ✓
                  │                 │
                  └──────┬──────────┘
                         ▼
                  ┌──────────────────────────┐
                  │ M141-6 PARITY GATE       │
                  │ (this PR)                │
                  │                          │
                  │ "are npm and PyPI emits  │
                  │  semantically equal?"    │
                  └──────────────────────────┘

A subtle bug in either codegen could let both `--check` gates pass while npm and PyPI diverge (e.g. one emits a field as `type: string` and the other as `type: object`). The cross-language gate catches that case mechanically.

## What ships in this PR

- `scripts/parity-check.py` — diff script. Extracts `HOOK_EVENT_SCHEMAS` from both packages, runs set/type comparison, exits non-zero with a human-readable diff on divergence.
- `.github/workflows/contract-parity.yml` — installs both packages, runs each per-side drift gate, then the cross-language gate. Path-filtered on `spec/`, both package dirs, the script, the workflow itself.
- `package.json` — adds `npm run test:parity` shortcut for local invocation.

## Verified locally

    $ npm run --workspace=@orchestkit/hook-contract build
    $ cd packages/hook-contract-py && .venv/bin/pip install -e . pyyaml
    $ python3 scripts/parity-check.py
    [parity-check] OK: npm + PyPI emit identical contract (19 events, 13 with typed payloads).
    exit 0 ✓

## What a real failure would look like

    [parity-check] FAILED: npm and PyPI hook-contract emit diverge.

    PreToolUse:
      required only in npm: ['session_id']
      field 'tool_input' type mismatch: npm=object vs PyPI=string

    Fix: identify which codegen drifted from the other, regenerate, and
    commit. Both should emit identical schemas from spec/hook-events.spec.yml.