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).
88 lines
4.5 KiB
Markdown
88 lines
4.5 KiB
Markdown
# 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?
|