Skip to content

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.

The suite auth contract had two reference-quality implementations that had drifted into three copies:

  • session decode (VF’s Auth.js JWE → Python claims) and the suite-service token verify lived as byte-identical backend/app/core/vf_session.py and backend/app/core/suite_service_token.py in both CHAD and WARDEN.
  • The suite-service token mint existed twice more: VF’s TypeScript mintSuiteServiceToken / mintSuiteExternalToken (the real minters) and WARDEN’s Python mint_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_token and mint_service_token (generalized from WARDEN’s minter to explicit secret/svc params, no settings coupling) + ServiceTokenClaims / ServiceToken* errors + the SUITE_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-contract job — installs the package and runs its pytest suite, including tests/conformance/test_fresh_session_roundtrip.py, which shells scripts/mint-test-session.mjs to 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 via astral-sh/setup-uv — the self-hosted Debian runner has no actions/setup-python build.)
  • contract-vectors.test.ts — pins the committed service-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’s test_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).

  • 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-auth from 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_role still requires updating the _SUITE_ROLES tuple — 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.