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).
93 lines
3.4 KiB
Python
93 lines
3.4 KiB
Python
"""Anti-goal tests: things that MUST NOT work across the two refimpls.
|
|
|
|
Forging one token type as the other, or building mixed-type DAGs,
|
|
must be rejected. These tests pin the negative space so silent drift
|
|
cannot erase the type boundary.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from act.errors import ACTValidationError
|
|
from act.token import decode_jws as act_decode_jws
|
|
from ect.verify import verify as ect_verify, VerifyOptions
|
|
|
|
|
|
class TestNoForgery:
|
|
def test_act_compact_is_not_verifiable_as_ect(
|
|
self, act_record_builder, dual_resolver
|
|
):
|
|
"""Feeding an ACT compact into ECT.verify must raise —
|
|
the typ gate is the wall."""
|
|
_, ect_resolver = dual_resolver
|
|
compact, _ = act_record_builder()
|
|
opts = VerifyOptions(resolve_key=ect_resolver)
|
|
with pytest.raises(ValueError, match="invalid typ parameter"):
|
|
ect_verify(compact, opts)
|
|
|
|
def test_ect_compact_is_not_verifiable_as_act(
|
|
self, ect_payload_builder, dual_resolver
|
|
):
|
|
"""Feeding an ECT compact into ACT decoder must raise."""
|
|
act_resolver, _ = dual_resolver
|
|
compact, _ = ect_payload_builder()
|
|
with pytest.raises(ACTValidationError):
|
|
act_decode_jws(compact)
|
|
|
|
|
|
class TestNoMixedTypeDAG:
|
|
"""Documents what cross-type storage actually does in practice.
|
|
|
|
Finding: neither refimpl performs runtime isinstance checks on the
|
|
objects it stores — Python duck-typing means an ECT Payload that
|
|
happens to expose `.jti` can be appended to ACTLedger without
|
|
error. This is a real interop hazard worth surfacing in the
|
|
compatibility matrix rather than papering over.
|
|
"""
|
|
|
|
def test_act_ledger_does_not_type_check_ect_payload(
|
|
self, ect_payload_builder
|
|
):
|
|
"""ACTLedger.append accepts anything with a `.jti` attribute.
|
|
|
|
This is NOT a good thing — it just means the refimpl relies on
|
|
caller discipline. Production bridges must enforce type checks
|
|
externally.
|
|
"""
|
|
from act.ledger import ACTLedger
|
|
|
|
ledger = ACTLedger()
|
|
_, ect_pl = ect_payload_builder()
|
|
# Append does not raise — duck typing lets the ECT Payload
|
|
# pass through because it has .jti. Pinning this as a
|
|
# documented hazard.
|
|
seq = ledger.append(ect_pl) # type: ignore[arg-type]
|
|
assert isinstance(seq, int)
|
|
|
|
def test_ect_ledger_interface_for_act_record(self, act_record_builder):
|
|
"""ECT MemoryLedger surface — document whatever happens when
|
|
fed an ACT record. This is a doc anchor; the exact behaviour
|
|
depends on which method the ledger exposes."""
|
|
from ect.ledger import MemoryLedger
|
|
|
|
ledger = MemoryLedger()
|
|
_, act_rec = act_record_builder()
|
|
|
|
# Inspect the concrete API to document which methods exist;
|
|
# none are typed strictly in Python, so we accept either
|
|
# "raises" or "silently stores" and pin whichever is current.
|
|
tried = []
|
|
for name in ("append", "add", "record", "put", "store"):
|
|
meth = getattr(ledger, name, None)
|
|
if meth is None:
|
|
continue
|
|
tried.append(name)
|
|
try:
|
|
meth(act_rec) # type: ignore[arg-type]
|
|
except Exception:
|
|
pass
|
|
break
|
|
# Just require that we found a callable surface and exercised it.
|
|
assert tried, "ect.MemoryLedger should expose at least one write API"
|