Problem statement
#36 has two MUST-level cases that can't be written yet: a delegation credential that's
expired, and one that isn't valid yet.
Neither can be represented today. DelegationCredential signs credential_id, issuer,
subject, scope, depth and parent_id. Nothing says when the grant starts or ends, so
chain verification can check structure and scope narrowing but can't decide whether the grant
covers an evaluation time.
I looked at agent-manifest first, since the delegation-chain doc says these semantics mirror
it. It's further along, and it stops one step short:
- every hop signs
delegated_at
scope_grant can carry ttl_seconds
- the verifier rejects a child TTL longer than its parent's
- nothing evaluates
delegated_at + ttl_seconds against a clock
So the signed data an interval needs is already present there and unused. cA2A has its own
credential shape and its own verify_chain, with no temporal field at all.
Proposed solution
Give the credential an issuer-signed validity period:
- a child's period is contained in its parent's
- a child that starts before its parent or outlives it is a structural chain failure, next to
SCOPE_ESCALATION
- a structurally valid credential outside its own period is an authorization failure at
evaluation time, with separate expired and not-yet-valid codes
- verification takes the evaluation time as an argument, so vectors don't depend on the
machine clock
- structural offline verification can stay time-unchecked, runtime authorization has to
evaluate the period
Two shapes are reasonable:
delegated_at plus ttl_seconds, matching the agent-manifest hop
- an absolute window,
not_before and expires_at
I lean toward one hop-level shape shared by both implementations. I don't have a strong view
on which field names win, but the two repos agreeing on one meaning matters more than the
choice does.
One detail affects the #36 boundary. In the conformance suite, _verify_action_evidence maps
every CA2AError out of structural verification to provenance_invalid. If the active-at
check runs inside that structural path, an expired credential classifies as provenance rather
than authorization, so the check probably belongs in the authorization step or behind its own
call.
Alternatives considered
A detached issuer-signed validity record bound to the credential digest keeps existing
credential bytes unchanged. The cost is a second signed object, plus rules for carrying it,
for it being absent, and for more than one turning up.
The holder challenge already expires, but that proves recent control of the holder key. It
doesn't show the issuer's grant is still live.
Revocation ends a grant early. It can't express a future start, and an offline verifier still
needs current revocation state.
Compatibility
DelegationCredential.from_dict rejects unknown and missing fields against a fixed field set,
so this breaks in both directions: today's verifier rejects a credential carrying a new
member, and a verifier expecting the new member rejects every existing credential.
That makes it a signed version or profile rather than a field addition. v1 canonical bytes and
fixtures stay frozen, unsupported versions fail closed, and mixed-version chains need an
explicit rule.
Security/TEE impact
No TEE change.
The validity check should run immediately before authorization or dispatch. Checking only when
the chain is loaded leaves a window where the credential expires before the action runs.
Spec alignment
No trace-spec change required. This touches the delegation credential spec, the error-code
list, the runtime authorization path, and the DELEG and ACTION conformance groups.
Once the shape is settled I can write the vectors: active, not yet valid, expired, inverted
and zero-length periods, child starting before its parent, child ending after its parent, both
exact boundaries, mixed versions, and the time-encoding cases that split implementations.
Problem statement
#36 has two MUST-level cases that can't be written yet: a delegation credential that's
expired, and one that isn't valid yet.
Neither can be represented today.
DelegationCredentialsignscredential_id,issuer,subject,scope,depthandparent_id. Nothing says when the grant starts or ends, sochain verification can check structure and scope narrowing but can't decide whether the grant
covers an evaluation time.
I looked at
agent-manifestfirst, since the delegation-chain doc says these semantics mirrorit. It's further along, and it stops one step short:
delegated_atscope_grantcan carryttl_secondsdelegated_at + ttl_secondsagainst a clockSo the signed data an interval needs is already present there and unused. cA2A has its own
credential shape and its own
verify_chain, with no temporal field at all.Proposed solution
Give the credential an issuer-signed validity period:
SCOPE_ESCALATIONevaluation time, with separate expired and not-yet-valid codes
machine clock
evaluate the period
Two shapes are reasonable:
delegated_atplusttl_seconds, matching the agent-manifest hopnot_beforeandexpires_atI lean toward one hop-level shape shared by both implementations. I don't have a strong view
on which field names win, but the two repos agreeing on one meaning matters more than the
choice does.
One detail affects the #36 boundary. In the conformance suite,
_verify_action_evidencemapsevery
CA2AErrorout of structural verification toprovenance_invalid. If the active-atcheck runs inside that structural path, an expired credential classifies as provenance rather
than authorization, so the check probably belongs in the authorization step or behind its own
call.
Alternatives considered
A detached issuer-signed validity record bound to the credential digest keeps existing
credential bytes unchanged. The cost is a second signed object, plus rules for carrying it,
for it being absent, and for more than one turning up.
The holder challenge already expires, but that proves recent control of the holder key. It
doesn't show the issuer's grant is still live.
Revocation ends a grant early. It can't express a future start, and an offline verifier still
needs current revocation state.
Compatibility
DelegationCredential.from_dictrejects unknown and missing fields against a fixed field set,so this breaks in both directions: today's verifier rejects a credential carrying a new
member, and a verifier expecting the new member rejects every existing credential.
That makes it a signed version or profile rather than a field addition. v1 canonical bytes and
fixtures stay frozen, unsupported versions fail closed, and mixed-version chains need an
explicit rule.
Security/TEE impact
No TEE change.
The validity check should run immediately before authorization or dispatch. Checking only when
the chain is loaded leaves a window where the credential expires before the action runs.
Spec alignment
No trace-spec change required. This touches the delegation credential spec, the error-code
list, the runtime authorization path, and the DELEG and ACTION conformance groups.
Once the shape is settled I can write the vectors: active, not yet valid, expired, inverted
and zero-length periods, child starting before its parent, child ending after its parent, both
exact boundaries, mixed versions, and the time-encoding cases that split implementations.