# ietf-act-ect-interop Cross-spec interop tests between the `ietf-act` (Agent Context Token, draft-nennemann-act-01) and `ietf-ect` (Execution Context Tokens, draft-nennemann-wimse-ect-01) Python reference implementations. The purpose of this package is not to ship runtime code — it is to **document empirically** which shared claims, algorithms, and structures round-trip cleanly between the two refimpls, so implementers building bridges or shared tooling know what they can rely on. ## Compatibility matrix Observed as of the commit that produced these 32 passing tests: | Layer | Direction | Status | Evidence | |---|---|---|---| | ES256 raw JWS signature | ACT ↔ ECT | Compatible | `test_es256_primitive_is_wire_compatible_at_raw_sig_level` — ACT's `crypto.verify` accepts an ECT-signed compact's r\|\|s bytes | | EdDSA signature | ACT → ECT | Incompatible | ECT verify refuses EdDSA at the alg gate (ES256-only) | | `typ` header | ACT ↔ ECT | Strictly separated (by design) | `act+jwt` vs `exec+jwt`; each verifier rejects the other | | `jti` format | Shared | Compatible | Same UUID string accepted by both | | `wid` | Shared | Compatible | Preserved on both sides | | `iat` / `exp` (NumericDate) | Shared | Compatible | Integer seconds on both | | `aud` (string form) | ACT → ECT | Compatible (lossy round-trip) | ACT stores `str \| list[str]`; ECT coerces to `list[str]` via `_audience_deserialize` | | `exec_act` | Shared | Compatible | ACT ABNF-legal values pass through ECT unchanged | | `pred` array | Shared | Compatible | Same topology, same wire shape | | `inp_hash` / `out_hash` | Shared | Compatible **now** | Both specs use plain base64url (ECT was aligned — the prefixed `sha-256:` form is now rejected by ECT) | | `cap` ↔ `exec_act` coupling | ACT-only | Divergent | ACT verifier raises `ACTCapabilityError`; ECT does not enforce | | `status` claim | ACT-only | Divergent | Required in ACT Phase 2; absent in ECT | | `sub`, `task`, `iss` required | ACT-only | Divergent | ECT `Payload.from_claims` silently drops them | | `ect_ext`, `inp_classification` | ECT-only | Divergent | ACT `ACTRecord.from_claims` silently drops them | | DAG cross-resolution | Separate stores | **Not supported** | `ECTStore` is keyed on `Payload`; `ACTLedger` on `ACTRecord`; no refimpl bridges the two | ### Hazard flag (found while writing these tests) `ACTLedger.append()` does **not** perform a runtime `isinstance` check — it relies on duck typing. An ECT `Payload` object has a `.jti` attribute, and will therefore be **silently accepted** by the ACT ledger. This is an implementation hazard, not a spec-level guarantee: production bridges must enforce explicit type checks outside both refimpls. Pinned in `test_act_ledger_does_not_type_check_ect_payload`. ## Do / Do not **Do** - Reuse ES256 (P-256) key material across ACT and ECT deployments — the signing primitive is byte-identical. - Treat `jti`, `wid`, `pred`, `exec_act` as semantically aligned when building cross-type audit views. - Rely on `inp_hash` / `out_hash` being byte-portable between refimpls today. **Do not** - Feed an ACT compact token to an ECT verifier or vice versa. The `typ` gates are deliberate and permanent. - Forge one token type as the other. This is a first-class anti-goal. - Expect ECT to enforce ACT's `cap` / `exec_act` coupling. Authorization stays in ACT. - Use EdDSA-signed ACT tokens in an ECT-only deployment. ECT is ES256-only. - Cross-insert objects between `ACTLedger` and `ECTStore` / `MemoryLedger`. Python's duck typing will let some of these through; that's a bug waiting to happen. ## Install and run From the workspace root: ```bash pip install -e packages/act pip install -e packages/ect pip install -e packages/interop cd packages/interop python -m pytest tests/ -v ``` Expected: **32 passed**. ## Test file map | File | Focus | |---|---| | `tests/test_shared_claims.py` | `pred`, `jti`/`wid`, hash format, `exec_act` string shape | | `tests/test_algorithm_matrix.py` | ES256 ↔ EdDSA × verifier compatibility | | `tests/test_dag_structure.py` | `pred`-array topology equivalence; store separation | | `tests/test_divergence.py` | claims each parser ignores; `typ` separation; `cap` coupling; `status` requirement | | `tests/test_anti_goals.py` | cross-type forgery rejection; cross-type store hazard | ## Open questions for spec editors - Should ECT optionally accept Ed25519? Today it is ES256-only. - Should the refimpls enforce type-level rejection of cross-type objects passed into their stores/ledgers, or is that outside scope?