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
97 lines
3.1 KiB
Python
97 lines
3.1 KiB
Python
"""ACT Phase 1 to Phase 2 transition logic.
|
|
|
|
Handles the transition from Authorization Mandate to Execution Record,
|
|
including re-signing by the executing agent (sub).
|
|
|
|
Reference: ACT §3.2, §3.3 (Lifecycle State Machine).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
from .crypto import PrivateKey, sign as crypto_sign
|
|
from .errors import ACTCapabilityError, ACTPhaseError, ACTValidationError
|
|
from .token import (
|
|
ACTMandate,
|
|
ACTRecord,
|
|
ErrorClaim,
|
|
encode_jws,
|
|
)
|
|
|
|
|
|
def transition_to_record(
|
|
mandate: ACTMandate,
|
|
*,
|
|
sub_kid: str,
|
|
sub_private_key: PrivateKey,
|
|
exec_act: str,
|
|
pred: list[str] | None = None,
|
|
exec_ts: int | None = None,
|
|
status: str = "completed",
|
|
inp_hash: str | None = None,
|
|
out_hash: str | None = None,
|
|
err: ErrorClaim | None = None,
|
|
) -> tuple[ACTRecord, str]:
|
|
"""Transition a Phase 1 mandate to a Phase 2 execution record.
|
|
|
|
The executing agent (sub) adds execution claims and re-signs the
|
|
complete token with its own private key. The kid in the Phase 2
|
|
JOSE header MUST reference sub's key, not iss's key.
|
|
|
|
All Phase 1 claims are preserved unchanged in the Phase 2 token.
|
|
|
|
Reference: ACT §3.2, §8.2 step 17.
|
|
|
|
Args:
|
|
mandate: The Phase 1 ACTMandate to transition.
|
|
sub_kid: The kid for the sub agent's signing key.
|
|
sub_private_key: The sub agent's private key for re-signing.
|
|
exec_act: The action actually performed (must match a cap[].action).
|
|
pred: Predecessor task jti values (DAG dependencies). Empty list for root tasks.
|
|
exec_ts: Execution timestamp (defaults to current time).
|
|
status: Execution status: "completed", "failed", or "partial".
|
|
inp_hash: Base64url SHA-256 hash of input data (optional).
|
|
out_hash: Base64url SHA-256 hash of output data (optional).
|
|
err: Error details when status is "failed" or "partial".
|
|
|
|
Returns:
|
|
Tuple of (ACTRecord, JWS compact serialization string).
|
|
|
|
Raises:
|
|
ACTPhaseError: If the mandate is already a Phase 2 token.
|
|
ACTCapabilityError: If exec_act does not match any cap[].action.
|
|
ACTValidationError: If the resulting record fails validation.
|
|
"""
|
|
if mandate.is_phase2():
|
|
raise ACTPhaseError("Cannot transition: token is already Phase 2")
|
|
|
|
# Verify exec_act matches a capability
|
|
cap_actions = {c.action for c in mandate.cap}
|
|
if exec_act not in cap_actions:
|
|
raise ACTCapabilityError(
|
|
f"exec_act {exec_act!r} does not match any cap[].action: "
|
|
f"{sorted(cap_actions)}"
|
|
)
|
|
|
|
record = ACTRecord.from_mandate(
|
|
mandate,
|
|
kid=sub_kid,
|
|
exec_act=exec_act,
|
|
pred=pred if pred is not None else [],
|
|
exec_ts=exec_ts if exec_ts is not None else int(time.time()),
|
|
status=status,
|
|
inp_hash=inp_hash,
|
|
out_hash=out_hash,
|
|
err=err,
|
|
)
|
|
|
|
record.validate()
|
|
|
|
# Re-sign with sub's private key
|
|
signature = crypto_sign(sub_private_key, record.signing_input())
|
|
compact = encode_jws(record, signature)
|
|
|
|
return record, compact
|