M141 โ€” dual-registry lockstep publish ๐Ÿ“œ

How @orchestkit/hook-contract (npm) + orchestkit-hook-contract (PyPI) ship as ONE contract. Approach E from the brainstorm.

One spec, two registries, one version

          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

Try the lockstep gate (the core of E)

npm package.json

version

HOOK_CONTRACT_VERSION

src/index.ts

pyproject.toml

PyPI

git tag

hook-contract-npm/v
โ€”

Why lockstep (not independent versions)

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.