Strategic work for IETF submission of draft-nennemann-act-01 and
draft-nennemann-wimse-ect-02:
Package restructure:
- move ACT and ECT refimpls to workspace/packages/{act,ect}/
- ietf-act and ietf-ect distribution names (sibling packages)
- cross-spec interop test plan (INTEROP-TEST-PLAN.md)
ACT draft -01 revisions:
- rename 'par' claim to 'pred' (align with ECT)
- rename 'Agent Compact Token' to 'Agent Context Token' (semantic
alignment with ECT family)
- add Applicability section (MCP, OpenAI, LangGraph, A2A, CrewAI)
- add DAG vs Linear Delegation Chains section (differentiator vs
txn-tokens-for-agents actchain, Agentic JWT, AIP/IBCTs)
- add Related Work: AIP, SentinelAgent, Agentic JWT, txn-tokens-for-agents,
HDP, SCITT-AI-agent-execution
- pin SCITT arch to -22, note AUTH48 status
Outreach drafts:
- Emirdag liaison email (SCITT-AI coordination)
- OAuth ML response on txn-tokens-for-agents-06
Strategy document:
- STRATEGY.md with phased action plan, risk register, timeline
Submodule:
- update workspace/drafts/ietf-wimse-ect pointer to -02 commit
9.0 KiB
9.0 KiB
ACT / ECT Cross-Spec Interop Test Plan
Status: Draft (Task C4 preparation — planning only, not yet implemented)
Scope: Python refimpls ietf-act (Phase 1/2, 103 tests) and ietf-ect (single-phase, 56 tests)
Deliverable: packages/interop/tests/test_interop.py + compatibility matrix docs
1. Goals and Non-Goals
Goals
- Empirically document which shared claims round-trip cleanly between refimpls.
- Surface real format-level incompatibilities (hash encoding, typ header, algorithm support) rather than assume the spec-level claim overlap implies wire interop.
- Produce a user-facing compatibility matrix that implementers can rely on when building bridges between Phase 2 ACT Records and ECT payloads.
- Provide executable regression tests so future changes to either refimpl cannot silently break the documented interop level without CI noticing.
Non-Goals
- Propose spec unification or new shared claim registries.
- Build a lossy translator/bridge between ACT Records and ECT payloads.
- Test
typcross-acceptance —act+jwtvsexec+jwtMUST remain distinct token types. - Forge one token type as the other.
- Add new crypto backends (e.g., Ed25519 support) to ECT as part of this work.
2. Known Shape of the Problem
Shared claims (by name): jti, wid, iat, exp, aud, exec_act, pred, inp_hash, out_hash.
Confirmed divergences discovered while reading the code:
- Hash encoding mismatch: ACT
b64url_sha256()emits plain base64url (e.g.n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg). ECTvalidate_hash_format()requiresalg:base64urlform (e.g.sha-256:...) and raises on plain b64url. The briefing says this was "recently fixed to match ACT's plain base64url format" but the ECT validator still requires the prefix — plan must include a reproducer. - Algorithm: ACT supports
EdDSA+ES256; ECT hard-codesES256(seeect/verify.py, line 59,"ect: expected ES256"). - Typ header: ACT requires
act+jwt; ECT requiresexec+jwt(with legacywimse-exec+jwt). Neither accepts the other — and per anti-goals, neither should. - aud shape: ACT stores
audasstr | list[str]; ECT normalises tolist[str]via_audience_deserialize. - Claims unique to ACT:
sub,iss(required string),task,cap,del,oversight,exec_ts,status,err. - Claims unique to ECT:
ect_ext,inp_classification, and policy claims insideect_ext(pol,pol_decision,compensation_required).
3. Test Categories
3.1 Shared claim consistency (TestSharedClaims)
test_jti_format_roundtrips: UUID-v4 jti accepted by both refimpls; non-UUID jti accepted by ACT (no UUID check) but only by ECT whenvalidate_uuids=False(document the asymmetry).test_wid_shared_semantics: same wid value on an ACT Record and an ECT payload — both accept.test_iat_exp_numericdate: identical integer NumericDate accepted by both (ACT uses strict> 0, ECT usesint(claims["iat"])).test_aud_string_vs_list: stringaudpreserved by ACT, coerced to list by ECT; list form is lossless on both.test_exec_act_string_both_sides: sameexec_actvalue (e.g.read.data) serialises identically; ACT additionally validates ABNF grammar — test that ECT accepts an ACT-grammar-legal value unchanged.test_pred_array_shape:pred=[],pred=[jti1],pred=[jti1, jti2]— both refimpls serialise/deserialise identically.test_inp_hash_format_divergence(expected xfail/documented): feed ACT's plain b64url output into ECT validator — expectValueError("ect: inp_hash/out_hash must be algorithm:base64url..."). This pins the incompatibility so a future fix flips the test green.test_inp_hash_prefixed_form:sha-256:<b64url>value accepted by ECT; ACT treats it as opaque string (no validation), roundtrips without error.test_out_hash_same_as_inp: mirror the above forout_hash.
3.2 Algorithm compatibility (TestAlgorithmMatrix)
test_es256_act_record_signature_verifies_with_ect_key_resolver: build a Phase 2 ACTRecord, sign with ES256 P-256 key. Feed the compact JWS bytes and an ECT-shaped resolver throughect.verify. ExpectValueError("ect: invalid typ parameter")because typ isact+jwt. Document: JWS/ES256 signature layer is compatible, but typ gate prevents verifier reuse as-is.test_eddsa_act_record_rejected_by_ect: Phase 2 ACTRecord signed EdDSA. ECT must reject at alg gate ("ect: expected ES256"). Documents the ES256-only limitation.test_ect_payload_signature_verifies_with_act_crypto: sign an ECT payload (ES256), strip to raw JWS, feed signature bytes throughact.crypto.verifywith the ECT public key. Expect success — proves the ES256 primitive is wire-compatible at the raw-sig level.
3.3 DAG cross-reference (TestDagInterop)
test_pred_array_referenceable_both_ways: construct ACT Record withpred=[ect_jti]and an ECT payload withpred=[act_jti]. Both refimpls accept the arrays structurally (they're opaque strings).test_mixed_dag_is_out_of_scope: document and assert thatACTStoreonly stores ACT records andECTStoreonly stores ECT payloads; neither is designed to resolve apredjti from the other type. A bridging verifier would have to walk both stores — out of scope for refimpls.test_jti_collision_across_types: the same UUID used asjtiin an ACT Record and an unrelated ECT payload — both refimpls accept independently; document that jti uniqueness is scoped per-token-type in the refimpls.
3.4 Semantic divergence (TestClaimDivergence)
test_ect_ignores_act_only_claims: ECTPayload.from_claimsis called on a dict that includessub,task,cap,oversight,exec_ts,status. Expect: silently ignored (no error, no retention). Document as "ECT is lenient on unknown top-level claims".test_act_ignores_ect_only_claims: feedACTRecord.from_claimsa claim dict withect_ext,inp_classification. Expect: silently ignored and not retained.test_exec_act_not_validated_against_cap_in_ect: ACT Record withexec_act="read.data"andcap=[{"action":"write.result"}]→ ACT verifier raisesACTCapabilityError. Sameexec_actin an ECT payload with nocap→ ECT accepts. Documents the cap-validation asymmetry; guards against anyone accidentally copy-pasting cap logic into ECT.test_act_requires_status_ect_does_not: ACTRecord withoutstatus→ACTValidationError. ECT withoutstatus→ accepted.
3.5 Anti-goals (encoded as negative tests)
test_act_jwt_typ_rejected_by_ect: ACT compact withtyp=act+jwtfed toect.verify→ MUST raise "invalid typ parameter".test_exec_jwt_typ_rejected_by_act: ECT compact withtyp=exec+jwtfed toact.decode_jws→ MUST raiseACTValidationErroron typ check.test_no_forgery_as_other_type: explicit comment-only placeholder asserting we do not re-encode one type as the other; kept as a doc anchor.
4. Expected Compatibility Matrix (user-facing)
| Layer | Direction | Status | Notes |
|---|---|---|---|
| ES256 raw signature | ACT ↔ ECT | Compatible | Same JWS/ES256 primitive |
| EdDSA signature | ACT → ECT | Incompatible | ECT is ES256-only |
typ header |
ACT ↔ ECT | Strictly separated | By design |
jti, wid, iat, exp, aud, exec_act, pred |
Shared | Compatible | Identical wire shapes |
inp_hash/out_hash |
ACT → ECT | Incompatible today | ACT emits plain b64url, ECT requires sha-256:<b64url> |
inp_hash/out_hash |
ECT → ACT | Compatible | ACT treats as opaque string |
cap / exec_act coupling |
ACT-only | N/A | ECT does not enforce |
DAG pred traversal |
Separate stores | Manual bridging required | Refimpls do not cross-resolve |
5. Dependencies and Structure
Both packages must be importable in a single venv:
pip install -e packages/act packages/ect packages/interop[dev]
Proposed layout:
packages/
act/ …
ect/ …
interop/
pyproject.toml # declares ietf-act, ietf-ect as deps
tests/
__init__.py
conftest.py # shared ES256 keypair + resolver fixtures
test_interop.py # classes Test{SharedClaims,AlgorithmMatrix,DagInterop,ClaimDivergence,AntiGoals}
README.md # published compatibility matrix
conftest.py exposes fixtures: es256_keypair, act_record_builder, ect_payload_builder, dual_resolver (one kid → same ES256 pubkey for both refimpls).
6. What the Compatibility Matrix Docs Should Tell Users
- Do reuse ES256 key material across ACT and ECT deployments — the signing primitive is identical.
- Do not feed ACT compact tokens to an ECT verifier or vice versa;
typgates are deliberate. - Do treat
jti,wid,pred,exec_actas semantically aligned when building cross-type audit logs. - Do not rely on
inp_hash/out_hashbeing portable today — raise a spec issue if portability matters for your deployment. - Do not expect ECT to enforce ACT's
cap/exec_actcoupling — authorization remains an ACT concern. - Open question for spec editors: align hash encoding (plain b64url vs prefixed), and decide whether Ed25519 should be optional-to-support for ECT.