Restructure refimpl into go-lang and python subdirectories

Move Go reference implementation to refimpl/go-lang/ and add new
Python reference implementation in refimpl/python/. Update build.sh
with renamed draft and simplified tool paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 23:11:55 +01:00
parent ff795c72e6
commit bbf557e54b
52 changed files with 3972 additions and 341 deletions

View File

@@ -0,0 +1 @@
# Tests package

View File

@@ -0,0 +1,49 @@
"""Tests for config module."""
import os
import pytest
from ect import default_config, load_config_from_env
from ect.config import ENV_IAT_MAX_AGE_MINUTES, ENV_JTI_REPLAY_CACHE_SIZE
def test_default_config():
c = default_config()
assert c.iat_max_age_sec == 900
assert c.jti_replay_size == 0
def test_load_config_from_env():
os.environ[ENV_IAT_MAX_AGE_MINUTES] = "20"
os.environ[ENV_JTI_REPLAY_CACHE_SIZE] = "500"
try:
c = load_config_from_env()
assert c.iat_max_age_sec == 20 * 60
assert c.jti_replay_size == 500
finally:
os.environ.pop(ENV_IAT_MAX_AGE_MINUTES, None)
os.environ.pop(ENV_JTI_REPLAY_CACHE_SIZE, None)
def test_config_create_options():
c = default_config()
opts = c.create_options("my-kid")
assert opts.key_id == "my-kid"
assert opts.default_expiry_sec == c.default_expiry_sec
def test_config_verify_options():
c = default_config()
opts = c.verify_options()
assert opts.iat_max_age_sec == c.iat_max_age_sec
assert opts.dag is not None
def test_load_config_invalid_int():
os.environ[ENV_IAT_MAX_AGE_MINUTES] = "bad"
try:
c = load_config_from_env()
assert c.iat_max_age_sec == 900
finally:
os.environ.pop(ENV_IAT_MAX_AGE_MINUTES, None)

View File

@@ -0,0 +1,77 @@
"""Tests for ECT creation and roundtrip."""
import json
import os
import time
import pytest
from ect import (
Payload,
create,
generate_key,
CreateOptions,
verify,
VerifyOptions,
POL_DECISION_APPROVED,
)
def test_create_roundtrip():
key = generate_key()
now = int(time.time())
payload = Payload(
iss="spiffe://example.com/agent/a",
aud=["spiffe://example.com/agent/b"],
iat=now,
exp=now + 600,
jti="e4f5a6b7-c8d9-0123-ef01-234567890abc",
exec_act="review_spec",
par=[],
pol="spec_review_policy_v2",
pol_decision=POL_DECISION_APPROVED,
)
compact = create(payload, key, CreateOptions(key_id="agent-a-key-1"))
assert compact
def resolver(kid):
if kid == "agent-a-key-1":
return key.public_key()
return None
opts = VerifyOptions(
verifier_id="spiffe://example.com/agent/b",
resolve_key=resolver,
now=now,
)
parsed = verify(compact, opts)
assert parsed.payload.jti == payload.jti
assert parsed.payload.exec_act == payload.exec_act
def test_create_with_test_vector():
path = os.path.join(os.path.dirname(__file__), "..", "testdata", "valid_root_ect_payload.json")
if not os.path.exists(path):
pytest.skip(f"test vector not found: {path}")
with open(path) as f:
data = json.load(f)
payload = Payload.from_claims(data)
key = generate_key()
now = int(time.time())
payload.iat = now
payload.exp = now + 600
compact = create(payload, key, CreateOptions(key_id="test-kid"))
assert compact
def resolver(kid):
if kid == "test-kid":
return key.public_key()
return None
opts = VerifyOptions(
verifier_id=payload.aud[0],
resolve_key=resolver,
now=now,
)
verify(compact, opts)

View File

@@ -0,0 +1,98 @@
"""Additional tests for create module."""
import time
import pytest
from ect import Payload, create, generate_key, CreateOptions, default_create_options, POL_DECISION_APPROVED
def test_default_create_options():
opts = default_create_options()
assert opts.key_id == ""
def test_create_errors():
key = generate_key()
p = Payload(iss="i", aud=["a"], iat=1, exp=2, jti="j", exec_act="e", par=[], pol="p", pol_decision=POL_DECISION_APPROVED)
with pytest.raises(ValueError, match="KeyID|required"):
create(p, key, CreateOptions(key_id=""))
with pytest.raises((ValueError, TypeError, AttributeError)):
create(None, key, CreateOptions(key_id="k"))
def test_create_optional_pol():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["a"], iat=now, exp=now + 3600,
jti="jti-nopol", exec_act="act", par=[],
)
compact = create(p, key, CreateOptions(key_id="kid"))
assert compact
def test_create_validation_errors():
key = generate_key()
base = dict(iss="i", aud=["a"], iat=1, exp=2, jti="j", exec_act="e", par=[])
with pytest.raises(ValueError, match="iss"):
create(Payload(**{**base, "iss": ""}), key, CreateOptions(key_id="k"))
with pytest.raises(ValueError, match="aud"):
create(Payload(**{**base, "aud": []}), key, CreateOptions(key_id="k"))
with pytest.raises(ValueError, match="jti"):
create(Payload(**{**base, "jti": ""}), key, CreateOptions(key_id="k"))
with pytest.raises(ValueError, match="exec_act"):
create(Payload(**{**base, "exec_act": ""}), key, CreateOptions(key_id="k"))
with pytest.raises(ValueError, match="pol and pol_decision"):
create(Payload(**{**base, "pol": "p", "pol_decision": ""}), key, CreateOptions(key_id="k"))
with pytest.raises(ValueError, match="pol_decision"):
create(Payload(**{**base, "pol": "p", "pol_decision": "bad"}), key, CreateOptions(key_id="k"))
def test_create_ext_compensation_reason_requires_required():
key = generate_key()
p = Payload(
iss="i", aud=["a"], iat=1, exp=2, jti="j", exec_act="e", par=[],
ext={"compensation_reason": "rollback", "compensation_required": False},
)
with pytest.raises(ValueError, match="compensation_required"):
create(p, key, CreateOptions(key_id="k"))
def test_create_zero_expiry_uses_default():
key = generate_key()
p = Payload(iss="i", aud=["a"], iat=0, exp=0, jti="j", exec_act="e", par=[])
compact = create(p, key, CreateOptions(key_id="k", default_expiry_sec=300))
assert compact
# create() works on a copy; decode the token to verify defaults were applied
import jwt
claims = jwt.decode(compact, options={"verify_signature": False})
assert claims["exp"] > claims["iat"]
def test_create_validate_uuids_rejects_non_uuid_jti():
key = generate_key()
now = int(time.time())
p = Payload(iss="i", aud=["a"], iat=now, exp=now + 3600, jti="not-a-uuid", exec_act="e", par=[])
with pytest.raises(ValueError, match="jti must be UUID"):
create(p, key, CreateOptions(key_id="k", validate_uuids=True))
def test_create_max_par_length():
key = generate_key()
now = int(time.time())
p = Payload(iss="i", aud=["a"], iat=now, exp=now + 3600, jti="550e8400-e29b-41d4-a716-446655440000", exec_act="e", par=["p1", "p2"])
with pytest.raises(ValueError, match="par exceeds max length"):
create(p, key, CreateOptions(key_id="k", max_par_length=1))
def test_create_ext_size_rejected():
from ect.validate import EXT_MAX_SIZE
key = generate_key()
now = int(time.time())
p = Payload(
iss="i", aud=["a"], iat=now, exp=now + 3600, jti="550e8400-e29b-41d4-a716-446655440000", exec_act="e", par=[],
ext={"x": "y" * (EXT_MAX_SIZE - 5)},
)
with pytest.raises(ValueError, match="ext exceeds max size"):
create(p, key, CreateOptions(key_id="k"))

View File

@@ -0,0 +1,123 @@
"""Tests for DAG validation."""
import time
import pytest
from ect import Payload, MemoryLedger, validate_dag, default_dag_config, POL_DECISION_APPROVED
def test_validate_dag_root():
store = MemoryLedger()
payload = Payload(
iss="",
aud=[],
iat=0,
exp=0,
jti="jti-001",
exec_act="",
par=[],
pol="",
pol_decision=POL_DECISION_APPROVED,
wid="wf-1",
)
validate_dag(payload, store, default_dag_config())
def test_validate_dag_duplicate_jti():
store = MemoryLedger()
p = Payload(
iss="x",
aud=["y"],
iat=0,
exp=0,
jti="jti-001",
exec_act="a",
par=[],
pol="p",
pol_decision=POL_DECISION_APPROVED,
wid="wf-1",
)
store.append("dummy-jws", p)
payload = Payload(
iss="",
aud=[],
iat=0,
exp=0,
jti="jti-001",
exec_act="",
par=[],
pol="",
pol_decision=POL_DECISION_APPROVED,
wid="wf-1",
)
with pytest.raises(ValueError, match="task ID.*already exists"):
validate_dag(payload, store, default_dag_config())
def test_validate_dag_parent_exists():
store = MemoryLedger()
now = int(time.time())
p = Payload(
iss="x",
aud=["y"],
iat=now - 60,
exp=now + 600,
jti="jti-001",
exec_act="a",
par=[],
pol="p",
pol_decision=POL_DECISION_APPROVED,
wid="wf-1",
)
store.append("jws1", p)
payload = Payload(
iss="",
aud=[],
iat=now,
exp=now + 600,
jti="jti-002",
exec_act="b",
par=["jti-001"],
pol="p",
pol_decision=POL_DECISION_APPROVED,
wid="wf-1",
)
validate_dag(payload, store, default_dag_config())
def test_validate_dag_parent_not_found():
store = MemoryLedger()
now = int(time.time())
payload = Payload(
iss="",
aud=[],
iat=now,
exp=now + 600,
jti="jti-002",
exec_act="",
par=["jti-missing"],
pol="",
pol_decision=POL_DECISION_APPROVED,
)
with pytest.raises(ValueError, match="parent task not found"):
validate_dag(payload, store, default_dag_config())
def test_validate_dag_parent_policy_rejected_requires_compensation():
from ect import POL_DECISION_REJECTED
store = MemoryLedger()
now = int(time.time())
p = Payload(
iss="x", aud=["y"], iat=now - 60, exp=now + 600,
jti="jti-rej", exec_act="a", par=[], pol="p", pol_decision=POL_DECISION_REJECTED, wid="wf-1",
)
store.append("jws1", p)
payload = Payload(
iss="", aud=[], iat=now, exp=now + 600,
jti="jti-child", exec_act="b", par=["jti-rej"], pol="p", pol_decision=POL_DECISION_APPROVED, wid="wf-1",
)
with pytest.raises(ValueError, match="compensation"):
validate_dag(payload, store, default_dag_config())
payload.ext = {"compensation_required": True}
validate_dag(payload, store, default_dag_config())

View File

@@ -0,0 +1,40 @@
"""Tests for JTI replay cache."""
import time
import pytest
from ect import new_jti_cache
def test_jti_cache_seen_and_add():
cache = new_jti_cache(10, 60)
assert cache.seen("jti-1") is False
cache.add("jti-1")
assert cache.seen("jti-1") is True
assert cache.seen("jti-2") is False
cache.add("jti-2")
assert cache.seen("jti-2") is True
def test_jti_cache_expiry():
cache = new_jti_cache(10, 1) # 1 second TTL
cache.add("jti-1")
assert cache.seen("jti-1") is True
time.sleep(1.1)
assert cache.seen("jti-1") is False
def test_jti_cache_max_size_eviction():
cache = new_jti_cache(2, 60)
cache.add("jti-1")
cache.add("jti-2")
cache.add("jti-3")
assert cache.seen("jti-3") is True
def test_jti_cache_add_when_already_present():
cache = new_jti_cache(2, 60)
cache.add("jti-1")
cache.add("jti-1")
assert cache.seen("jti-1") is True

View File

@@ -0,0 +1,38 @@
"""Additional tests for ledger module."""
import time
import pytest
from ect import Payload, MemoryLedger, ErrTaskIDExists, POL_DECISION_APPROVED
def test_ledger_append_and_get():
m = MemoryLedger()
p = Payload(iss="i", aud=["a"], iat=1, exp=2, jti="j1", exec_act="act", par=[])
seq = m.append("jws1", p)
assert seq == 1
assert m.get_by_tid("j1").jti == "j1"
def test_ledger_err_task_id_exists():
m = MemoryLedger()
p = Payload(iss="i", aud=["a"], iat=1, exp=2, jti="j-dup", exec_act="e", par=[])
m.append("jws1", p)
with pytest.raises(ErrTaskIDExists):
m.append("jws2", p)
def test_ledger_contains_wid():
m = MemoryLedger()
p = Payload(iss="i", aud=["a"], iat=1, exp=2, jti="j1", exec_act="e", par=[], wid="wf1")
m.append("jws", p)
assert m.contains("j1", "") is True
assert m.contains("j1", "wf1") is True
assert m.contains("j1", "wf2") is False
def test_ledger_append_none():
m = MemoryLedger()
seq = m.append("jws", None)
assert seq == 0

View File

@@ -0,0 +1,63 @@
"""Additional tests for types module."""
import pytest
from ect import Payload, POL_DECISION_APPROVED
from ect.types import valid_pol_decision
def test_valid_pol_decision():
assert valid_pol_decision("approved") is True
assert valid_pol_decision("rejected") is True
assert valid_pol_decision("pending_human_review") is True
assert valid_pol_decision("invalid") is False
def test_payload_contains_audience():
p = Payload(iss="", aud=["a", "b"], iat=0, exp=0, jti="", exec_act="", par=[])
assert p.contains_audience("a") is True
assert p.contains_audience("c") is False
def test_payload_compensation_required():
p = Payload(iss="", aud=[], iat=0, exp=0, jti="", exec_act="", par=[])
assert p.compensation_required() is False
p.ext = {"compensation_required": True}
assert p.compensation_required() is True
def test_payload_has_policy_claims():
p = Payload(iss="", aud=[], iat=0, exp=0, jti="", exec_act="", par=[], pol="p", pol_decision=POL_DECISION_APPROVED)
assert p.has_policy_claims() is True
p.pol = ""
assert p.has_policy_claims() is False
def test_payload_to_claims_optional():
p = Payload(iss="i", aud=["a"], iat=1, exp=2, jti="j", exec_act="e", par=[], wid="wf")
claims = p.to_claims()
assert claims["wid"] == "wf"
assert "ext" not in claims or not claims.get("ext")
def test_payload_from_claims_aud_string():
claims = {"iss": "i", "aud": "single", "iat": 1, "exp": 2, "jti": "j", "exec_act": "e", "par": []}
p = Payload.from_claims(claims)
assert p.aud == ["single"]
def test_payload_to_claims_all_optional():
p = Payload(
iss="i", aud=["a"], iat=1, exp=2, jti="j", exec_act="e", par=[],
sub="s", wid="w", pol="p", pol_decision="approved", pol_enforcer="e",
pol_timestamp=1, inp_hash="h", out_hash="o", inp_classification="c",
)
claims = p.to_claims()
assert claims["sub"] == "s"
assert claims["wid"] == "w"
assert claims["pol"] == "p"
assert claims["pol_enforcer"] == "e"
assert claims["pol_timestamp"] == 1
assert claims["inp_hash"] == "h"
assert claims["out_hash"] == "o"
assert claims["inp_classification"] == "c"

View File

@@ -0,0 +1,63 @@
"""Tests for validate module."""
import json
import pytest
from ect.validate import (
EXT_MAX_DEPTH,
EXT_MAX_SIZE,
validate_ext,
validate_hash_format,
valid_uuid,
)
def test_valid_uuid():
assert valid_uuid("550e8400-e29b-41d4-a716-446655440000") is True
assert valid_uuid("00000000-0000-0000-0000-000000000000") is True
assert valid_uuid("") is False
assert valid_uuid("not-a-uuid") is False
assert valid_uuid("550e8400e29b41d4a716446655440000") is False # no dashes
def test_validate_ext_none():
validate_ext(None)
validate_ext({})
def test_validate_ext_size():
# Serialized JSON must exceed EXT_MAX_SIZE (4096) bytes
big = {"x": "y" * (EXT_MAX_SIZE - 2)} # "{\"x\":\"...\"}" + payload
raw = json.dumps(big)
assert len(raw.encode("utf-8")) > EXT_MAX_SIZE
with pytest.raises(ValueError, match="max size"):
validate_ext(big)
def test_validate_ext_depth():
deep = {"a": 1}
for _ in range(EXT_MAX_DEPTH):
deep = {"n": deep}
with pytest.raises(ValueError, match="depth"):
validate_ext(deep)
def test_validate_hash_format_empty():
validate_hash_format("")
def test_validate_hash_format_ok():
# sha-256:base64url (minimal valid)
validate_hash_format("sha-256:YQ")
validate_hash_format("sha-384:YQ")
validate_hash_format("sha-512:YQ")
def test_validate_hash_format_bad():
with pytest.raises(ValueError, match="algorithm:base64url|inp_hash"):
validate_hash_format("md5:abc")
with pytest.raises(ValueError, match="algorithm:base64url|inp_hash"):
validate_hash_format("no-colon")
# Invalid base64 that triggers decode error (e.g. binary)
with pytest.raises(ValueError, match="algorithm:base64url|inp_hash"):
validate_hash_format("sha-256:YQ\x00") # null in payload

View File

@@ -0,0 +1,197 @@
"""Tests for verify module."""
import time
import pytest
from ect import (
Payload,
create,
generate_key,
CreateOptions,
parse,
verify,
VerifyOptions,
default_verify_options,
POL_DECISION_APPROVED,
)
def test_parse():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["a"], iat=now, exp=now + 3600,
jti="jti-parse", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
parsed = parse(compact)
assert parsed.payload.jti == "jti-parse"
assert parsed.raw == compact
def test_default_verify_options():
opts = default_verify_options()
assert opts.dag is not None
assert opts.iat_max_age_sec == 900
def test_verify_expired():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["v"], iat=now - 3600, exp=now - 60,
jti="jti-exp", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
with pytest.raises(ValueError, match="expired"):
verify(compact, VerifyOptions(verifier_id="v", resolve_key=resolver, now=now))
def test_verify_replay():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["v"], iat=now, exp=now + 3600,
jti="jti-replay", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
with pytest.raises(ValueError, match="replay"):
verify(compact, VerifyOptions(
verifier_id="v", resolve_key=resolver, now=now,
jti_seen=lambda j: j == "jti-replay",
))
def test_verify_invalid_typ():
import jwt as jwt_lib
with pytest.raises((ValueError, jwt_lib.exceptions.DecodeError)):
verify("not-a-jws", VerifyOptions())
def test_verify_audience_mismatch():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["other"], iat=now, exp=now + 3600,
jti="jti-a", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
with pytest.raises(ValueError, match="audience"):
verify(compact, VerifyOptions(verifier_id="verifier", resolve_key=resolver, now=now))
def test_verify_wit_subject_mismatch():
key = generate_key()
now = int(time.time())
p = Payload(
iss="wrong-iss", aud=["v"], iat=now, exp=now + 3600,
jti="jti-w", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
with pytest.raises(ValueError, match="WIT subject"):
verify(compact, VerifyOptions(
verifier_id="v", resolve_key=resolver, now=now, wit_subject="correct-iss",
))
def test_verify_iat_too_old():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["v"], iat=now - 2000, exp=now + 3600,
jti="jti-old", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
with pytest.raises(ValueError, match="iat"):
verify(compact, VerifyOptions(
verifier_id="v", resolve_key=resolver, now=now, iat_max_age_sec=900,
))
def test_verify_unknown_key():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["v"], iat=now, exp=now + 3600,
jti="jti-k", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda kid: None # unknown key
with pytest.raises(ValueError, match="unknown key"):
verify(compact, VerifyOptions(verifier_id="v", resolve_key=resolver, now=now))
def test_verify_resolve_key_required():
key = generate_key()
now = int(time.time())
p = Payload(
iss="iss", aud=["v"], iat=now, exp=now + 3600,
jti="jti-r", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact = create(p, key, CreateOptions(key_id="kid"))
with pytest.raises(ValueError, match="ResolveKey"):
verify(compact, VerifyOptions(verifier_id="v", resolve_key=None))
def test_verify_with_dag():
from ect import MemoryLedger
key = generate_key()
ledger = MemoryLedger()
now = int(time.time())
root = Payload(
iss="iss", aud=["v"], iat=now, exp=now + 3600,
jti="jti-root", exec_act="act", par=[], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact_root = create(root, key, CreateOptions(key_id="kid"))
resolver = lambda kid: key.public_key() if kid == "kid" else None
opts = VerifyOptions(verifier_id="v", resolve_key=resolver, store=ledger, now=now)
parsed = verify(compact_root, opts)
ledger.append(compact_root, parsed.payload)
child = Payload(
iss="iss", aud=["v"], iat=now + 1, exp=now + 3600,
jti="jti-child", exec_act="act2", par=["jti-root"], pol="p", pol_decision=POL_DECISION_APPROVED,
)
compact_child = create(child, key, CreateOptions(key_id="kid"))
parsed2 = verify(compact_child, opts)
assert parsed2.payload.jti == "jti-child"
def test_on_verify_attempt_callback():
"""Observability: on_verify_attempt is called with jti and error (or None)."""
key = generate_key()
now = int(time.time())
p = Payload(iss="i", aud=["v"], iat=now, exp=now + 3600, jti="jti-obs", exec_act="a", par=[])
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda k: key.public_key() if k == "kid" else None
seen = []
def hook(jti, err):
seen.append((jti, err))
opts = VerifyOptions(verifier_id="v", resolve_key=resolver, on_verify_attempt=hook)
result = verify(compact, opts)
assert result.payload.jti == "jti-obs"
assert len(seen) == 1
assert seen[0][0] == "jti-obs"
assert seen[0][1] is None
def test_on_verify_attempt_called_on_failure():
key = generate_key()
now = int(time.time())
p = Payload(iss="i", aud=["v"], iat=now, exp=now - 1, jti="jti-fail", exec_act="a", par=[])
compact = create(p, key, CreateOptions(key_id="kid"))
resolver = lambda k: key.public_key() if k == "kid" else None
seen = []
opts = VerifyOptions(verifier_id="v", resolve_key=resolver, now=now, on_verify_attempt=lambda jti, err: seen.append((jti, err)))
with pytest.raises(ValueError, match="expired"):
verify(compact, opts)
assert len(seen) == 1
assert seen[0][0] == "jti-fail"
assert seen[0][1] is not None