ADR-005: The suite auth contract lives in one package (veltro-suite-auth)
Source of truth:
docs/adr/005-suite-auth-contract-package.md(synced at build time — edit the original, not this copy). Status: Accepted (2026-07-09). Extracts the two cross-service security primitives — the Auth.js session-cookie decoder (ADR-001 Decision 2, as amended by ADR-003/004) and the suite-service token mint/verify (ADR-004 Decision 2) — into a single published Python package, and pins cross-language conformance per-PR. No runtime behavior changes; ADR-001/003/004 mechanisms stand verbatim.
Context
Section titled “Context”The suite auth contract had two reference-quality implementations that had drifted into three copies:
sessiondecode (VF’s Auth.js JWE → Python claims) and the suite-service tokenverifylived as byte-identicalbackend/app/core/vf_session.pyandbackend/app/core/suite_service_token.pyin both CHAD and WARDEN.- The suite-service token mint existed twice more: VF’s TypeScript
mintSuiteServiceToken/mintSuiteExternalToken(the real minters) and WARDEN’s Pythonmint_suite_service_token.
Cross-language conformance (does the Python verifier accept exactly what the TS
minter produces? does the Python decoder accept a real VF cookie?) was pinned by
one static fixture + contract test in CHAD’s CI. WARDEN had none, and
nothing caught VF-minter drift per-PR. A copy could be “fixed” in one repo and
silently diverge; the seam between Node-minted and Python-verified tokens was
only exercised by the operator-gated full-suite e2e (SUITE_E2E_ENABLED), which
is dark on most PRs.
Decision 1 — One package, consumed everywhere
Section titled “Decision 1 — One package, consumed everywhere”The canonical Python implementation of the contract lives in
veltro-suite-auth, published to PyPI, source in
vectorflow/packages/suite-auth-py (src layout, hatchling):
veltro_suite_auth.session—decode_session+SessionClaims/Session*errors +SESSION_COOKIE_NAMES/derive_encryption_key.veltro_suite_auth.service_token—verify_service_tokenandmint_service_token(generalized from WARDEN’s minter to explicitsecret/svcparams, no settings coupling) +ServiceTokenClaims/ServiceToken*errors + theSUITE_SERVICE_*constants.veltro_suite_auth.testing— loaders for the packaged golden vectors.
CHAD and WARDEN add veltro-suite-auth==0.1.0 to backend/requirements.txt,
delete their local vf_session.py / suite_service_token.py copies (WARDEN
also collapses app/services/suite_token.py to a thin settings wrapper over
mint_service_token), and rewrite imports to the package under a fixed rename
map (decode_vf_session→decode_session, VfSession*→Session*,
verify_suite_service_token→verify_service_token, SuiteService*→
ServiceToken*). App-local names that are genuinely app-local (CHAD’s
resolve_vf_user, WARDEN’s funnel) keep their vf-flavored names — only the
shared contract module was renamed.
Publishing a public package from private repos is intentional: the AGPL products
depend on it, and the sdist ships the source. It is published manually by
the maintainer (matching the design-system’s manual-npm-publish precedent); no
publish workflow. Fixes ship as a new 0.1.x — an existing version is never
republished.
Decision 2 — Per-PR cross-language conformance gates (in the VF repo)
Section titled “Decision 2 — Per-PR cross-language conformance gates (in the VF repo)”Two gates run on every VectorFlow PR (added to .github/workflows/ci.yml;
packages/** and scripts/** added to the PR path filters):
suite-auth-contractjob — installs the package and runs its pytest suite, includingtests/conformance/test_fresh_session_roundtrip.py, which shellsscripts/mint-test-session.mjsto mint a fresh Auth.js cookie in Node and decodes it in Python. Catches VF-minter ⟂ Python-decoder drift on the session cookie. (Runner Python is provisioned viaastral-sh/setup-uv— the self-hosted Debian runner has noactions/setup-pythonbuild.)contract-vectors.test.ts— pins the committedservice-token-vectors.json(four vectors:settings:{read,write}with actor,external:{read,write}without) against the live TS minters. Catches TS-minter drift on the service token; the Python half of the same vectors is asserted by the package’stest_vectors.py.
Both drift directions were verified to actually gate (corrupt a committed vector
→ the TS gate fails; change SUITE_SERVICE_ISSUER → the Python gate fails).
Consequences
Section titled “Consequences”- One source of truth. The contract can no longer be edited in one product and silently diverge in another; CHAD and WARDEN carry zero local copies.
- The dark seam is now lit per-PR. The Node↔Python conformance that only the
operator-gated full-suite e2e (
SUITE_E2E_ENABLED) exercised is now covered on every VF PR, so that e2e staying gated no longer leaves the contract untested. - A shared dependency. CHAD/WARDEN backend images install
veltro-suite-authfrom PyPI; a contract change is a package release + a pin bump in each consumer, not an edit in three places. - New-role coupling unchanged. A new VF
suite_rolestill requires updating the_SUITE_ROLEStuple — now in one place (veltro_suite_auth) instead of four. - Amends ADR-001 Decision 2 (the committed-fixture contract test moved out of CHAD’s CI into the package + VF CI) and ADR-004 Decision 2 (the token spec now has a single reference implementation). The mechanisms themselves are unchanged.