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).
This commit is contained in:
94
workspace/packages/interop/tests/test_dag_structure.py
Normal file
94
workspace/packages/interop/tests/test_dag_structure.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""Cross-spec DAG structural compatibility.
|
||||
|
||||
The refimpls keep separate stores (ACTStore for ACT records, ECTStore
|
||||
for ECT payloads). These tests verify that the DAG topology expressed
|
||||
via `pred` arrays has the same interpretation in both — a 3-node
|
||||
graph looks the same whether it is made of ACT records or ECT payloads.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from ect.dag import ECTStore
|
||||
|
||||
|
||||
class TestPredArraySemantics:
|
||||
def test_identical_pred_values_in_both_refimpls(
|
||||
self, act_record_builder, ect_payload_builder
|
||||
):
|
||||
"""Same pred array produces same on-wire interpretation."""
|
||||
parent1 = str(uuid.uuid4())
|
||||
parent2 = str(uuid.uuid4())
|
||||
|
||||
_, act_rec = act_record_builder(pred=[parent1, parent2])
|
||||
_, ect_pl = ect_payload_builder(pred=[parent1, parent2])
|
||||
|
||||
assert act_rec.pred == ect_pl.pred == [parent1, parent2]
|
||||
|
||||
def test_empty_pred_is_root_node_on_both(
|
||||
self, act_record_builder, ect_payload_builder
|
||||
):
|
||||
_, act_rec = act_record_builder(pred=[])
|
||||
_, ect_pl = ect_payload_builder(pred=[])
|
||||
assert act_rec.pred == [] == ect_pl.pred
|
||||
|
||||
|
||||
class TestThreeNodeDAG:
|
||||
"""A diamond: root -> {child1, child2} -> join.
|
||||
|
||||
Built twice: once in ACT's ledger and once in ECT's store. The
|
||||
shape must be recognised identically by each refimpl's DAG
|
||||
machinery.
|
||||
"""
|
||||
|
||||
def test_diamond_topology_consistent(self, act_record_builder, ect_payload_builder):
|
||||
# Shared jti identifiers so the topologies are isomorphic.
|
||||
root_jti = str(uuid.uuid4())
|
||||
c1_jti = str(uuid.uuid4())
|
||||
c2_jti = str(uuid.uuid4())
|
||||
join_jti = str(uuid.uuid4())
|
||||
|
||||
# --- Build ACT records ---
|
||||
_, root_act = act_record_builder(jti=root_jti, pred=[])
|
||||
_, c1_act = act_record_builder(jti=c1_jti, pred=[root_jti])
|
||||
_, c2_act = act_record_builder(jti=c2_jti, pred=[root_jti])
|
||||
_, join_act = act_record_builder(jti=join_jti, pred=[c1_jti, c2_jti])
|
||||
|
||||
# --- Build ECT payloads ---
|
||||
_, root_ect = ect_payload_builder(jti=root_jti, pred=[])
|
||||
_, c1_ect = ect_payload_builder(jti=c1_jti, pred=[root_jti])
|
||||
_, c2_ect = ect_payload_builder(jti=c2_jti, pred=[root_jti])
|
||||
_, join_ect = ect_payload_builder(jti=join_jti, pred=[c1_jti, c2_jti])
|
||||
|
||||
# Topological equivalence check: same predecessor sets by jti.
|
||||
act_pred_map = {
|
||||
r.jti: set(r.pred) for r in (root_act, c1_act, c2_act, join_act)
|
||||
}
|
||||
ect_pred_map = {
|
||||
p.jti: set(p.pred) for p in (root_ect, c1_ect, c2_ect, join_ect)
|
||||
}
|
||||
assert act_pred_map == ect_pred_map
|
||||
|
||||
|
||||
class TestStoresAreSeparate:
|
||||
"""Document that refimpls do not cross-resolve predecessor jtis."""
|
||||
|
||||
def test_ect_store_is_abstract_and_payload_typed(self):
|
||||
"""ECTStore is an ABC whose API only accepts ECT Payload objects.
|
||||
|
||||
No method on ECTStore accepts ACTRecord — cross-type
|
||||
predecessor resolution would have to be implemented outside
|
||||
both refimpls. Documented here so no one copy-pastes a
|
||||
bridging store into either codebase.
|
||||
"""
|
||||
# ECTStore is abstract, so we can only inspect its interface.
|
||||
from ect.dag import ECTStore as _ECTStore
|
||||
|
||||
abstract_methods = getattr(_ECTStore, "__abstractmethods__", frozenset())
|
||||
assert "get_by_tid" in abstract_methods
|
||||
assert "contains" in abstract_methods
|
||||
# No cross-type API surface.
|
||||
assert not hasattr(_ECTStore, "add_act_record")
|
||||
Reference in New Issue
Block a user