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
This commit is contained in:
191
workspace/packages/act/tests/test_verify.py
Normal file
191
workspace/packages/act/tests/test_verify.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""Tests for act.verify module."""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from act.crypto import (
|
||||
ACTKeyResolver,
|
||||
KeyRegistry,
|
||||
generate_ed25519_keypair,
|
||||
sign,
|
||||
)
|
||||
from act.errors import (
|
||||
ACTAudienceMismatchError,
|
||||
ACTCapabilityError,
|
||||
ACTExpiredError,
|
||||
ACTPhaseError,
|
||||
ACTSignatureError,
|
||||
ACTValidationError,
|
||||
)
|
||||
from act.ledger import ACTLedger
|
||||
from act.lifecycle import transition_to_record
|
||||
from act.token import (
|
||||
ACTMandate,
|
||||
ACTRecord,
|
||||
Capability,
|
||||
Delegation,
|
||||
TaskClaim,
|
||||
encode_jws,
|
||||
)
|
||||
from act.verify import ACTVerifier
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup():
|
||||
iss_priv, iss_pub = generate_ed25519_keypair()
|
||||
sub_priv, sub_pub = generate_ed25519_keypair()
|
||||
registry = KeyRegistry()
|
||||
registry.register("iss-key", iss_pub)
|
||||
registry.register("sub-key", sub_pub)
|
||||
resolver = ACTKeyResolver(registry=registry)
|
||||
base_time = 1772064000
|
||||
return {
|
||||
"iss_priv": iss_priv, "iss_pub": iss_pub,
|
||||
"sub_priv": sub_priv, "sub_pub": sub_pub,
|
||||
"registry": registry, "resolver": resolver,
|
||||
"base_time": base_time,
|
||||
}
|
||||
|
||||
|
||||
def make_mandate(setup, **overrides):
|
||||
bt = setup["base_time"]
|
||||
defaults = dict(
|
||||
alg="EdDSA", kid="iss-key",
|
||||
iss="agent-issuer", sub="agent-subject",
|
||||
aud="agent-subject",
|
||||
iat=bt, exp=bt + 900,
|
||||
jti=str(uuid.uuid4()),
|
||||
task=TaskClaim(purpose="test"),
|
||||
cap=[Capability(action="read.data")],
|
||||
)
|
||||
defaults.update(overrides)
|
||||
return ACTMandate(**defaults)
|
||||
|
||||
|
||||
def sign_mandate(mandate, priv_key):
|
||||
sig = sign(priv_key, mandate.signing_input())
|
||||
return encode_jws(mandate, sig)
|
||||
|
||||
|
||||
class TestVerifyMandate:
|
||||
def test_valid_mandate(self, setup):
|
||||
verifier = ACTVerifier(
|
||||
setup["resolver"],
|
||||
verifier_id="agent-subject",
|
||||
trusted_issuers={"agent-issuer"},
|
||||
)
|
||||
mandate = make_mandate(setup)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
result = verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
||||
assert result.iss == "agent-issuer"
|
||||
|
||||
def test_expired(self, setup):
|
||||
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
||||
mandate = make_mandate(setup)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
with pytest.raises(ACTExpiredError):
|
||||
verifier.verify_mandate(compact, now=setup["base_time"] + 2000)
|
||||
|
||||
def test_wrong_audience(self, setup):
|
||||
verifier = ACTVerifier(
|
||||
setup["resolver"], verifier_id="other-agent",
|
||||
trusted_issuers={"agent-issuer"},
|
||||
)
|
||||
mandate = make_mandate(setup)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
with pytest.raises(ACTAudienceMismatchError):
|
||||
verifier.verify_mandate(
|
||||
compact, now=setup["base_time"] + 100, check_sub=False,
|
||||
)
|
||||
|
||||
def test_untrusted_issuer(self, setup):
|
||||
verifier = ACTVerifier(
|
||||
setup["resolver"], verifier_id="agent-subject",
|
||||
trusted_issuers={"trusted-only"},
|
||||
)
|
||||
mandate = make_mandate(setup)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
with pytest.raises(ACTValidationError, match="not trusted"):
|
||||
verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
||||
|
||||
def test_signature_failure(self, setup):
|
||||
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
||||
mandate = make_mandate(setup)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
# Tamper with signature
|
||||
parts = compact.split(".")
|
||||
parts[2] = parts[2][:-4] + "XXXX"
|
||||
tampered = ".".join(parts)
|
||||
with pytest.raises(ACTSignatureError):
|
||||
verifier.verify_mandate(tampered, now=setup["base_time"] + 100)
|
||||
|
||||
def test_phase2_as_mandate(self, setup):
|
||||
verifier = ACTVerifier(setup["resolver"])
|
||||
mandate = make_mandate(setup)
|
||||
record, compact = transition_to_record(
|
||||
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
||||
exec_act="read.data", pred=[], status="completed",
|
||||
exec_ts=setup["base_time"] + 100,
|
||||
)
|
||||
with pytest.raises(ACTPhaseError):
|
||||
verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
||||
|
||||
def test_future_iat(self, setup):
|
||||
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
||||
bt = setup["base_time"]
|
||||
mandate = make_mandate(setup, iat=bt + 1000, exp=bt + 2000)
|
||||
compact = sign_mandate(mandate, setup["iss_priv"])
|
||||
with pytest.raises(ACTValidationError, match="future"):
|
||||
verifier.verify_mandate(compact, now=bt)
|
||||
|
||||
|
||||
class TestVerifyRecord:
|
||||
def test_valid_record(self, setup):
|
||||
verifier = ACTVerifier(
|
||||
setup["resolver"],
|
||||
verifier_id="agent-subject",
|
||||
trusted_issuers={"agent-issuer"},
|
||||
)
|
||||
mandate = make_mandate(setup)
|
||||
record, compact = transition_to_record(
|
||||
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
||||
exec_act="read.data", pred=[],
|
||||
exec_ts=setup["base_time"] + 100, status="completed",
|
||||
)
|
||||
result = verifier.verify_record(
|
||||
compact, now=setup["base_time"] + 200, check_aud=False,
|
||||
)
|
||||
assert result.exec_act == "read.data"
|
||||
|
||||
def test_wrong_signer(self, setup):
|
||||
verifier = ACTVerifier(setup["resolver"])
|
||||
mandate = make_mandate(setup)
|
||||
record = ACTRecord.from_mandate(
|
||||
mandate, kid="sub-key", exec_act="read.data",
|
||||
pred=[], exec_ts=setup["base_time"] + 100, status="completed",
|
||||
)
|
||||
# Sign with iss key instead of sub key
|
||||
sig = sign(setup["iss_priv"], record.signing_input())
|
||||
compact = encode_jws(record, sig)
|
||||
with pytest.raises(ACTSignatureError):
|
||||
verifier.verify_record(compact, now=setup["base_time"] + 200)
|
||||
|
||||
def test_with_dag_validation(self, setup):
|
||||
verifier = ACTVerifier(
|
||||
setup["resolver"], verifier_id="agent-subject",
|
||||
trusted_issuers={"agent-issuer"},
|
||||
)
|
||||
ledger = ACTLedger()
|
||||
mandate = make_mandate(setup)
|
||||
record, compact = transition_to_record(
|
||||
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
||||
exec_act="read.data", pred=[],
|
||||
exec_ts=setup["base_time"] + 100, status="completed",
|
||||
)
|
||||
result = verifier.verify_record(
|
||||
compact, store=ledger,
|
||||
now=setup["base_time"] + 200, check_aud=False,
|
||||
)
|
||||
assert result.status == "completed"
|
||||
Reference in New Issue
Block a user