Agent Transaction Policy
Agent transaction policy is the policy engine's answer to "who is actually holding this grant?" when the requester is an agent acting on behalf of a user rather than the user's own key. The engine separates two principals — the eligible subject (the DID the Policy's when is written about, typically the user) and the holder (the agent DID that will receive and exercise the grant) — and admits a request only when a signed HolderEnrollment proves the subject authorized that holder. This is the policy engine generalized from "grant to a key" to "grant to an enrolled agent transacting for a subject."
Role
A capability is bearer authority: whoever holds the chain wields it. For autonomous agents that is too blunt — the owner's Policy may say "anyone with credential Y," but the agent presenting the credential is not the subject the credential is about. Agent transaction policy closes that gap inside Layer 1: the when rule still ranges over the subject, but the grant is bound to the holder, and a revocable enrollment links the two. Revoke the enrollment and the agent stops qualifying — without touching the subject's identity or the Policy.
Mechanics
The binding is one variant of HolderBindingProof (src/types.rs:348), EnrolledAgent { enrollment, status? }, carried inside every GrantPresentation. During resolve the runtime calls validate_enrolled_agent_binding (src/enrollment.rs:108) after presentation validation and before evidence verification. It enforces, in order:
- Identity match (
validate_enrollment_identity) — the enrollment'seligible_subject_didandholder_didmust equal the presentation's; otherwiseenrollment-binding-mismatch. - Time validity (
validate_enrollment_time) —now ≥ not_beforeand, if set,now ≤ expires_at; elseenrollment-not-yet-valid/enrollment-expired. - Scope (
check_enrollment_scope) — if the enrollment carries ascope, the request'spolicy_idand the Policy'sresource_idmust be in the allowed lists; elseenrollment-out-of-scope. - Status / revocation (
validate_enrollment_statusagainst anEnrollmentStatusTracker) — enrollment statuses are monotonic and revocation is irreversible: aHolderEnrollmentStatuswith a non-increasingsequenceisenrollment-status-rollback; once the tracker has observed aRevokedstatus, a laterActivestatus is rejectedenrollment-revoked-irreversible; and a presentation that omits the status after the engine has seen a revocation is rejectedenrollment-revoked(omission must not silently re-admit).
Only after this binding holds does the engine verify evidence, re-evaluate when over the subject, and issue the portable-delegation to the holder.
Shape
HolderEnrollment (xyz.tinycloud.policy/holder-enrollment/v0):
{ enrollment_id, eligible_subject_did, holder_did, scope?, not_before, expires_at?, signing_key_did, signature }
HolderEnrollmentScope: { policy_ids?: [String], resource_ids?: [String] }
HolderEnrollmentStatus: { status_id, enrollment_id, sequence, disposition: active|revoked, effective_at, … }
HolderBindingProof: EnrolledAgent { enrollment, status? } // tagged "type": "enrolled-agent"
The enrollment is itself a signed object under the Signed Object Profile; the subject signs it to delegate "this agent may act for me," and a separate signed HolderEnrollmentStatus chain (anti-rollback by sequence) revokes it.
Relationships
Generalizes the policy engine to agent holders; the subject side feeds when's subject{} condition; runs alongside credential evidence verification; the grant it produces is a holder-bound portable-delegation; depends on the agent's DID and the subject's DID; sits in Layer 1.
Example
A user (did:key:z6Mk…subject) enrolls their assistant agent (did:key:z6Mk…holder) with a HolderEnrollment scoped to pol_email_domain. The agent later presents that enrollment plus an email-domain credential about the subject. The engine verifies the enrollment binds subject→holder, confirms the credential, and issues a one-hour transcript-read grant to the agent. If the user revokes the enrollment (a Revoked status), the next presentation is rejected enrollment-revoked even if the credential is still valid — the agent's authority to transact for the subject is gone. (This binding flow is exercised in the runtime's challenge_resolve_native_read_then_active_cutoff_denies test.)
Status & drift
in-progress. The enrollment model — HolderEnrollment, HolderEnrollmentStatus, scope and anti-rollback rules — is frozen v0 (spec/holder-enrollment.md) and implemented + tested in policy-core (enrollment.rs) and exercised by policy-runtime. The single HolderBindingProof variant today is EnrolledAgent; the broader "agent transaction policy" framing (richer binding kinds, full operational-key role enforcement) is design-intent — the OperationalKeyRole types exist but full role-chain enforcement is not complete in core verification. See contradictions.
Sources
policy-engine:src/enrollment.rs:108(validate_enrolled_agent_binding+ sub-checks, anti-rollback),src/types.rs:271,348(HolderEnrollment,HolderBindingProof),crates/policy-runtime/src/lib.rs(binding called inresolve)