Files
ietf-draft-analyzer/workspace/packages/INTEROP-TEST-PLAN.md
Christian Nennemann 3a139dfc7e feat: ACT/ECT strategy, package restructure, draft -01/-02 prep
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
2026-04-12 07:33:08 +02:00

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 typ cross-acceptance — act+jwt vs exec+jwt MUST 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). ECT validate_hash_format() requires alg:base64url form (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-codes ES256 (see ect/verify.py, line 59, "ect: expected ES256").
  • Typ header: ACT requires act+jwt; ECT requires exec+jwt (with legacy wimse-exec+jwt). Neither accepts the other — and per anti-goals, neither should.
  • aud shape: ACT stores aud as str | list[str]; ECT normalises to list[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 inside ect_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 when validate_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 uses int(claims["iat"])).
  • test_aud_string_vs_list: string aud preserved by ACT, coerced to list by ECT; list form is lossless on both.
  • test_exec_act_string_both_sides: same exec_act value (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 — expect ValueError("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 for out_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 through ect.verify. Expect ValueError("ect: invalid typ parameter") because typ is act+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 through act.crypto.verify with 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 with pred=[ect_jti] and an ECT payload with pred=[act_jti]. Both refimpls accept the arrays structurally (they're opaque strings).
  • test_mixed_dag_is_out_of_scope: document and assert that ACTStore only stores ACT records and ECTStore only stores ECT payloads; neither is designed to resolve a pred jti 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 as jti in 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: ECT Payload.from_claims is called on a dict that includes sub, 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: feed ACTRecord.from_claims a claim dict with ect_ext, inp_classification. Expect: silently ignored and not retained.
  • test_exec_act_not_validated_against_cap_in_ect: ACT Record with exec_act="read.data" and cap=[{"action":"write.result"}] → ACT verifier raises ACTCapabilityError. Same exec_act in an ECT payload with no cap → 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 without statusACTValidationError. ECT without status → accepted.

3.5 Anti-goals (encoded as negative tests)

  • test_act_jwt_typ_rejected_by_ect: ACT compact with typ=act+jwt fed to ect.verify → MUST raise "invalid typ parameter".
  • test_exec_jwt_typ_rejected_by_act: ECT compact with typ=exec+jwt fed to act.decode_jws → MUST raise ACTValidationError on 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; typ gates are deliberate.
  • Do treat jti, wid, pred, exec_act as semantically aligned when building cross-type audit logs.
  • Do not rely on inp_hash/out_hash being portable today — raise a spec issue if portability matters for your deployment.
  • Do not expect ECT to enforce ACT's cap/exec_act coupling — 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.