Cross-spec interop validation between ietf-act and ietf-ect: - new packages/interop/ sibling package (ietf-act-ect-interop) - 32 tests pass: shared claims, algorithm matrix, DAG structure, divergence handling, anti-goals - documents ES256 raw signature wire-compatibility - documents airtight typ separation (act+jwt vs exec+jwt) Hazards surfaced: - ACTLedger.append() silently accepts ECT Payload via duck-typing (both have .jti) — documented in interop README as a production hazard requiring external isinstance checks Session handoff: - SESSION-2026-04-12.md — snapshot of decisions, artifacts, open actions, and next-session starting points Also: session-end commit of hash-format fix propagation to packages/ect/ (the fix was applied to the old refimpl location but did not propagate through the parallel package-move agent).
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_actas semantically aligned when building cross-type audit views. - Rely on
inp_hash/out_hashbeing byte-portable between refimpls today.
Do not
- Feed an ACT compact token to an ECT verifier or vice versa. The
typgates 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_actcoupling. Authorization stays in ACT. - Use EdDSA-signed ACT tokens in an ECT-only deployment. ECT is ES256-only.
- Cross-insert objects between
ACTLedgerandECTStore/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:
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?