How @orchestkit/hook-contract (npm) + orchestkit-hook-contract (PyPI) ship as ONE contract. Approach E from the brainstorm.
spec/hook-events.spec.yml โ single source of truth (19 events)
โ codegen
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โผ โผ
packages/hook-contract packages/hook-contract-py
(npm, esm+cjs) (PyPI, pydantic)
โ โ
โ contract-parity.yml gate: npm-side โ pypi-side โ spec (no drift)
โผ โผ
tag hook-contract-npm/ tag hook-contract-py/
vX.Y.Z vX.Y.Z โ SAME X.Y.Z (lockstep, enforced)
โ OIDC โ OIDC
โผ โผ
npm registry PyPI
โโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ dependabot
consumers: orchestkit hooks (JS) ยท yonatan-hq/platform #1809 (Py) ยท core
A contract's value is that consumers reason about ONE number: @orchestkit/hook-contract@1.2.0 โ orchestkit-hook-contract==1.2.0 โ spec v1.2.0. The parity preflight (in BOTH publish workflows) refuses to release unless npm == PyPI == HOOK_CONTRACT_VERSION == tag โ so the registries can never drift apart.
Auth: OIDC trusted publishing on both sides โ no long-lived NPM_TOKEN (which can shadow gh credentials). One-time trusted-publisher setup per registry.