Files
ietf-draft-analyzer/workspace/packages/interop/README.md
Christian Nennemann 37859beef6 feat: interop test package + session handoff doc
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).
2026-04-12 07:39:41 +02:00

88 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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?