Merge tid into jti and make policy claims optional

- Eliminate the "tid" claim; "jti" now serves as both token ID (for
  replay detection) and task ID (for DAG parent references in "par")
- Make "pol" and "pol_decision" OPTIONAL (must be paired when present)
- Regulated deployments SHOULD still include policy claims
- Reduces required ECT-specific claims to just "exec_act" and "par"
- Remove "tid" from IANA JWT Claims registration
- Update all examples, pseudocode, and DAG validation rules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 22:39:46 +01:00
parent f9357fdf88
commit 102a120d65
4 changed files with 1072 additions and 1211 deletions

View File

@@ -2180,11 +2180,15 @@ upon issuance.<a href="#section-4.2.1-3" class="pilcrow">¶</a></p>
<span class="break"></span><dl class="dlParallel" id="section-4.2.1-4"> <span class="break"></span><dl class="dlParallel" id="section-4.2.1-4">
<dt id="section-4.2.1-4.1">jti:</dt> <dt id="section-4.2.1-4.1">jti:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.1-4.2"> <dd style="margin-left: 1.5em" id="section-4.2.1-4.2">
<p id="section-4.2.1-4.2.1"><span class="bcp14">REQUIRED</span>. String. A unique identifier for the ECT in UUID <p id="section-4.2.1-4.2.1"><span class="bcp14">REQUIRED</span>. String. A globally unique identifier for both the
format <span>[<a href="#RFC9562" class="cite xref">RFC9562</a>]</span>. Used for replay detection: receivers <span class="bcp14">MUST</span> ECT and the task it records, in UUID format <span>[<a href="#RFC9562" class="cite xref">RFC9562</a>]</span>. Since
reject ECTs whose "jti" has already been seen within the each ECT represents exactly one task, "jti" serves as both the
expiration window. The "jti" value <span class="bcp14">MUST</span> be unique across all token identifier (for replay detection) and the task identifier
ECTs issued by the same agent.<a href="#section-4.2.1-4.2.1" class="pilcrow"></a></p> (for DAG parent references in "par"). Receivers <span class="bcp14">MUST</span> reject
ECTs whose "jti" has already been seen within the expiration
window. When "wid" is present, uniqueness is scoped to the
workflow; when "wid" is absent, uniqueness <span class="bcp14">MUST</span> be enforced
globally across the ECT store.<a href="#section-4.2.1-4.2.1" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
</dl> </dl>
@@ -2201,36 +2205,26 @@ ECTs issued by the same agent.<a href="#section-4.2.1-4.2.1" class="pilcrow">¶<
<dd style="margin-left: 1.5em" id="section-4.2.2-2.2"> <dd style="margin-left: 1.5em" id="section-4.2.2-2.2">
<p id="section-4.2.2-2.2.1"><span class="bcp14">OPTIONAL</span>. String. A workflow identifier that groups related <p id="section-4.2.2-2.2.1"><span class="bcp14">OPTIONAL</span>. String. A workflow identifier that groups related
ECTs into a single workflow. When present, <span class="bcp14">MUST</span> be a UUID ECTs into a single workflow. When present, <span class="bcp14">MUST</span> be a UUID
<span>[<a href="#RFC9562" class="cite xref">RFC9562</a>]</span>. When absent, the "tid" uniqueness requirement <span>[<a href="#RFC9562" class="cite xref">RFC9562</a>]</span>.<a href="#section-4.2.2-2.2.1" class="pilcrow"></a></p>
applies globally across the entire ledger.<a href="#section-4.2.2-2.2.1" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
<dt id="section-4.2.2-2.3">tid:</dt> <dt id="section-4.2.2-2.3">exec_act:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.2-2.4"> <dd style="margin-left: 1.5em" id="section-4.2.2-2.4">
<p id="section-4.2.2-2.4.1"><span class="bcp14">REQUIRED</span>. String. A globally unique task identifier in UUID <p id="section-4.2.2-2.4.1"><span class="bcp14">REQUIRED</span>. String. The action or task type identifier describing
format <span>[<a href="#RFC9562" class="cite xref">RFC9562</a>]</span>. Each task <span class="bcp14">MUST</span> have a unique "tid" value.
When "wid" is present, uniqueness is scoped to the workflow;
when "wid" is absent, uniqueness <span class="bcp14">MUST</span> be enforced globally
across the ledger.<a href="#section-4.2.2-2.4.1" class="pilcrow"></a></p>
</dd>
<dd class="break"></dd>
<dt id="section-4.2.2-2.5">exec_act:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.2-2.6">
<p id="section-4.2.2-2.6.1"><span class="bcp14">REQUIRED</span>. String. The action or task type identifier describing
what the agent performed (e.g., "process_payment", what the agent performed (e.g., "process_payment",
"validate_safety", "calculate_dosage"). Note: this claim is "validate_safety", "calculate_dosage"). Note: this claim is
intentionally named "exec_act" rather than "act" to avoid intentionally named "exec_act" rather than "act" to avoid
collision with the "act" (Actor) claim registered by collision with the "act" (Actor) claim registered by
<span>[<a href="#RFC8693" class="cite xref">RFC8693</a>]</span>.<a href="#section-4.2.2-2.6.1" class="pilcrow"></a></p> <span>[<a href="#RFC8693" class="cite xref">RFC8693</a>]</span>.<a href="#section-4.2.2-2.4.1" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
<dt id="section-4.2.2-2.7">par:</dt> <dt id="section-4.2.2-2.5">par:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.2-2.8"> <dd style="margin-left: 1.5em" id="section-4.2.2-2.6">
<p id="section-4.2.2-2.8.1"><span class="bcp14">REQUIRED</span>. Array of strings. Parent task identifiers <p id="section-4.2.2-2.6.1"><span class="bcp14">REQUIRED</span>. Array of strings. Parent task identifiers
representing DAG dependencies. Each element <span class="bcp14">MUST</span> be a valid representing DAG dependencies. Each element <span class="bcp14">MUST</span> be the "jti"
"tid" from a previously executed task. An empty array indicates value of a previously verified ECT. An empty array indicates
a root task with no dependencies. A workflow <span class="bcp14">MAY</span> contain a root task with no dependencies. A workflow <span class="bcp14">MAY</span> contain
multiple root tasks.<a href="#section-4.2.2-2.8.1" class="pilcrow"></a></p> multiple root tasks.<a href="#section-4.2.2-2.6.1" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
</dl> </dl>
@@ -2245,17 +2239,18 @@ multiple root tasks.<a href="#section-4.2.2-2.8.1" class="pilcrow">¶</a></p>
<span class="break"></span><dl class="dlParallel" id="section-4.2.3-2"> <span class="break"></span><dl class="dlParallel" id="section-4.2.3-2">
<dt id="section-4.2.3-2.1">pol:</dt> <dt id="section-4.2.3-2.1">pol:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.3-2.2"> <dd style="margin-left: 1.5em" id="section-4.2.3-2.2">
<p id="section-4.2.3-2.2.1"><span class="bcp14">REQUIRED</span>. String. The identifier of the policy rule that was <p id="section-4.2.3-2.2.1"><span class="bcp14">OPTIONAL</span>. String. The identifier of the policy rule that was
evaluated for this task (e.g., evaluated for this task (e.g.,
"clinical_data_access_policy_v1").<a href="#section-4.2.3-2.2.1" class="pilcrow"></a></p> "clinical_data_access_policy_v1"). <span class="bcp14">MUST</span> be present when
"pol_decision" is present.<a href="#section-4.2.3-2.2.1" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
<dt id="section-4.2.3-2.3">pol_decision:</dt> <dt id="section-4.2.3-2.3">pol_decision:</dt>
<dd style="margin-left: 1.5em" id="section-4.2.3-2.4"> <dd style="margin-left: 1.5em" id="section-4.2.3-2.4">
<p id="section-4.2.3-2.4.1"><span class="bcp14">REQUIRED</span>. String. The result of the policy evaluation. <span class="bcp14">MUST</span> <p id="section-4.2.3-2.4.1"><span class="bcp14">OPTIONAL</span>. String. The result of the policy evaluation. When
be one of the values registered in the ECT Policy Decision present, <span class="bcp14">MUST</span> be one of the values registered in the ECT Policy
Values registry (<a href="#pol-decision-registry" class="auto internal xref">Section 13.4</a>). Initial values Decision Values registry (<a href="#pol-decision-registry" class="auto internal xref">Section 13.4</a>). <span class="bcp14">MUST</span> be
are:<a href="#section-4.2.3-2.4.1" class="pilcrow"></a></p> present when "pol" is present. Initial values are:<a href="#section-4.2.3-2.4.1" class="pilcrow"></a></p>
<ul class="normal"> <ul class="normal">
<li class="normal" id="section-4.2.3-2.4.2.1"> <li class="normal" id="section-4.2.3-2.4.2.1">
<p id="section-4.2.3-2.4.2.1.1">"approved": The policy evaluation succeeded and the task <p id="section-4.2.3-2.4.2.1.1">"approved": The policy evaluation succeeded and the task
@@ -2263,11 +2258,12 @@ was authorized to proceed.<a href="#section-4.2.3-2.4.2.1.1" class="pilcrow">¶<
</li> </li>
<li class="normal" id="section-4.2.3-2.4.2.2"> <li class="normal" id="section-4.2.3-2.4.2.2">
<p id="section-4.2.3-2.4.2.2.1">"rejected": The policy evaluation failed. A "rejected" ECT <p id="section-4.2.3-2.4.2.2.1">"rejected": The policy evaluation failed. A "rejected" ECT
<span class="bcp14">MUST</span> still be appended to the audit ledger for accountability. <span class="bcp14">MUST</span> still be recorded for accountability. An ECT with
An ECT with "pol_decision" of "rejected" <span class="bcp14">MAY</span> appear as a "pol_decision" of "rejected" <span class="bcp14">MAY</span> appear as a parent in the
parent in the "par" array of a subsequent ECT, but only for "par" array of a subsequent ECT, but only for compensation,
compensation, rollback, or remediation tasks. Agents <span class="bcp14">MUST NOT</span> proceed with normal workflow execution based on a parent rollback, or remediation tasks. Agents <span class="bcp14">MUST NOT</span> proceed
ECT whose "pol_decision" is "rejected".<a href="#section-4.2.3-2.4.2.2.1" class="pilcrow"></a></p> with normal workflow execution based on a parent ECT whose
"pol_decision" is "rejected".<a href="#section-4.2.3-2.4.2.2.1" class="pilcrow"></a></p>
</li> </li>
<li class="normal" id="section-4.2.3-2.4.2.3"> <li class="normal" id="section-4.2.3-2.4.2.3">
<p id="section-4.2.3-2.4.2.3.1">"pending_human_review": The policy evaluation requires human <p id="section-4.2.3-2.4.2.3.1">"pending_human_review": The policy evaluation requires human
@@ -2277,6 +2273,10 @@ records an "approved" decision referencing this task as a
parent.<a href="#section-4.2.3-2.4.2.3.1" class="pilcrow"></a></p> parent.<a href="#section-4.2.3-2.4.2.3.1" class="pilcrow"></a></p>
</li> </li>
</ul> </ul>
<p id="section-4.2.3-2.4.3">When "pol" and "pol_decision" are absent, the ECT records task
execution without a policy checkpoint. Regulated deployments
<span class="bcp14">SHOULD</span> include policy claims on all ECTs to maintain complete
audit trails.<a href="#section-4.2.3-2.4.3" class="pilcrow"></a></p>
</dd> </dd>
<dd class="break"></dd> <dd class="break"></dd>
<dt id="section-4.2.3-2.5">pol_enforcer:</dt> <dt id="section-4.2.3-2.5">pol_enforcer:</dt>
@@ -2463,10 +2463,9 @@ ECTs whose "ext" claim exceeds these limits.<a href="#section-4.2.7-2" class="pi
"aud": "spiffe://example.com/agent/safety", "aud": "spiffe://example.com/agent/safety",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "7f3a8b2c-d1e4-4f56-9a0b-c3d4e5f6a7b8", "jti": "550e8400-e29b-41d4-a716-446655440001",
"wid": "a0b1c2d3-e4f5-6789-abcd-ef0123456789", "wid": "a0b1c2d3-e4f5-6789-abcd-ef0123456789",
"tid": "550e8400-e29b-41d4-a716-446655440001",
"exec_act": "recommend_treatment", "exec_act": "recommend_treatment",
"par": [], "par": [],
@@ -2576,10 +2575,10 @@ parent ECTs available to the verifier.<a href="#section-6.1-2" class="pilcrow">
the following DAG validation steps:<a href="#section-6.2-1" class="pilcrow"></a></p> the following DAG validation steps:<a href="#section-6.2-1" class="pilcrow"></a></p>
<ol start="1" type="1" class="normal type-1" id="section-6.2-2"> <ol start="1" type="1" class="normal type-1" id="section-6.2-2">
<li id="section-6.2-2.1"> <li id="section-6.2-2.1">
<p id="section-6.2-2.1.1">Task ID Uniqueness: The "tid" claim <span class="bcp14">MUST</span> be unique within the <p id="section-6.2-2.1.1">Task ID Uniqueness: The "jti" claim <span class="bcp14">MUST</span> be unique within the
applicable scope (the workflow identified by "wid", or the applicable scope (the workflow identified by "wid", or the
entire ECT store if "wid" is absent). If a task with the same entire ECT store if "wid" is absent). If an ECT with the same
"tid" already exists, the ECT <span class="bcp14">MUST</span> be rejected.<a href="#section-6.2-2.1.1" class="pilcrow"></a></p> "jti" already exists, the ECT <span class="bcp14">MUST</span> be rejected.<a href="#section-6.2-2.1.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-6.2-2.2"> <li id="section-6.2-2.2">
<p id="section-6.2-2.2.1">Parent Existence: Every task identifier listed in the "par" <p id="section-6.2-2.2.1">Parent Existence: Every task identifier listed in the "par"
@@ -2601,17 +2600,19 @@ ECT <span class="bcp14">MUST</span> be rejected.<a href="#section-6.2-2.3.1" cla
</li> </li>
<li id="section-6.2-2.4"> <li id="section-6.2-2.4">
<p id="section-6.2-2.4.1">Acyclicity: Following the chain of parent references <span class="bcp14">MUST NOT</span> <p id="section-6.2-2.4.1">Acyclicity: Following the chain of parent references <span class="bcp14">MUST NOT</span>
lead back to the current task's "tid". If a cycle is detected, lead back to the current ECT's "jti". If a cycle is detected,
the ECT <span class="bcp14">MUST</span> be rejected.<a href="#section-6.2-2.4.1" class="pilcrow"></a></p> the ECT <span class="bcp14">MUST</span> be rejected.<a href="#section-6.2-2.4.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-6.2-2.5"> <li id="section-6.2-2.5">
<p id="section-6.2-2.5.1">Parent Policy Decision: If any parent task has a "pol_decision" <p id="section-6.2-2.5.1">Parent Policy Decision: If any parent ECT contains a
of "rejected" or "pending_human_review", the current task's "pol_decision" of "rejected" or "pending_human_review", the
"exec_act" <span class="bcp14">MUST</span> indicate a compensation, rollback, remediation, current ECT's "exec_act" <span class="bcp14">MUST</span> indicate a compensation,
or human review action. Implementations <span class="bcp14">MUST NOT</span> accept an ECT rollback, remediation, or human review action.
representing normal workflow continuation when a parent's Implementations <span class="bcp14">MUST NOT</span> accept an ECT representing normal
"pol_decision" is not "approved", unless the current ECT has workflow continuation when a parent's "pol_decision" is not
"compensation_required" set to true.<a href="#section-6.2-2.5.1" class="pilcrow"></a></p> "approved", unless the current ECT has "compensation_required"
set to true. This rule only applies when the parent ECT
contains policy claims.<a href="#section-6.2-2.5.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-6.2-2.6"> <li id="section-6.2-2.6">
<p id="section-6.2-2.6.1">Trust Domain Consistency: Parent tasks <span class="bcp14">SHOULD</span> belong to the <p id="section-6.2-2.6.1">Trust Domain Consistency: Parent tasks <span class="bcp14">SHOULD</span> belong to the
@@ -2634,8 +2635,8 @@ relationship has been established.<a href="#section-6.2-2.6.1" class="pilcrow">
function validate_dag(ect, ect_store, clock_skew_tolerance): function validate_dag(ect, ect_store, clock_skew_tolerance):
// ect_store: ledger or local cache of verified ECTs // ect_store: ledger or local cache of verified ECTs
// Step 1: Uniqueness check // Step 1: Uniqueness check
if ect_store.contains(ect.tid, ect.wid): if ect_store.contains(ect.jti, ect.wid):
return error("Task ID already exists") return error("ECT ID already exists")
// Step 2: Parent existence and temporal ordering // Step 2: Parent existence and temporal ordering
for parent_id in ect.par: for parent_id in ect.par:
@@ -2647,26 +2648,26 @@ function validate_dag(ect, ect_store, clock_skew_tolerance):
// Step 3: Cycle detection (with traversal limit) // Step 3: Cycle detection (with traversal limit)
visited = set() visited = set()
result = has_cycle(ect.tid, ect.par, ect_store, visited, result = has_cycle(ect.jti, ect.par, ect_store, visited,
max_ancestor_limit) max_ancestor_limit)
if result is error or result is true: if result is error or result is true:
return error("Circular dependency or depth limit exceeded") return error("Circular dependency or depth limit exceeded")
return success return success
function has_cycle(target_tid, parent_ids, ect_store, function has_cycle(target_jti, parent_ids, ect_store,
visited, max_depth): visited, max_depth):
if visited.size() &gt;= max_depth: if visited.size() &gt;= max_depth:
return error("Maximum ancestor traversal limit exceeded") return error("Maximum ancestor traversal limit exceeded")
for parent_id in parent_ids: for parent_id in parent_ids:
if parent_id == target_tid: if parent_id == target_jti:
return true return true
if parent_id in visited: if parent_id in visited:
continue continue
visited.add(parent_id) visited.add(parent_id)
parent = ect_store.get(parent_id) parent = ect_store.get(parent_id)
if parent is not null: if parent is not null:
result = has_cycle(target_tid, parent.par, ect_store, result = has_cycle(target_jti, parent.par, ect_store,
visited, max_depth) visited, max_depth)
if result is error or result is true: if result is error or result is true:
return result return result
@@ -2757,12 +2758,13 @@ identity <span class="bcp14">MUST</span> appear in "aud".<a href="#section-7.1-2
verifier's current time, to account for clock skew).<a href="#section-7.1-2.11.1" class="pilcrow"></a></p> verifier's current time, to account for clock skew).<a href="#section-7.1-2.11.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-7.1-2.12"> <li id="section-7.1-2.12">
<p id="section-7.1-2.12.1">Verify all required claims ("jti", "tid", "exec_act", "par", <p id="section-7.1-2.12.1">Verify all required claims ("jti", "exec_act", "par") are
"pol", "pol_decision") are present and well-formed.<a href="#section-7.1-2.12.1" class="pilcrow"></a></p> present and well-formed.<a href="#section-7.1-2.12.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-7.1-2.13"> <li id="section-7.1-2.13">
<p id="section-7.1-2.13.1">Verify "pol_decision" is one of "approved", "rejected", or <p id="section-7.1-2.13.1">If "pol" or "pol_decision" is present, verify that both are
"pending_human_review".<a href="#section-7.1-2.13.1" class="pilcrow"></a></p> present and that "pol_decision" is one of "approved",
"rejected", or "pending_human_review".<a href="#section-7.1-2.13.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-7.1-2.14"> <li id="section-7.1-2.14">
<p id="section-7.1-2.14.1">Perform DAG validation per <a href="#dag-validation" class="auto internal xref">Section 6</a>.<a href="#section-7.1-2.14.1" class="pilcrow"></a></p> <p id="section-7.1-2.14.1">Perform DAG validation per <a href="#dag-validation" class="auto internal xref">Section 6</a>.<a href="#section-7.1-2.14.1" class="pilcrow"></a></p>
@@ -2847,12 +2849,14 @@ function verify_ect(ect_jws, verifier_id,
return reject("ECT issued in the future") return reject("ECT issued in the future")
// Verify required claims // Verify required claims
for claim in ["jti", "tid", "exec_act", "par", for claim in ["jti", "exec_act", "par"]:
"pol", "pol_decision"]:
if claim not in payload: if claim not in payload:
return reject("Missing required claim: " + claim) return reject("Missing required claim: " + claim)
// Validate pol_decision value // Validate policy claims (optional, but must be paired)
if "pol" in payload or "pol_decision" in payload:
if "pol" not in payload or "pol_decision" not in payload:
return reject("pol and pol_decision must both be present")
if payload.pol_decision not in if payload.pol_decision not in
["approved", "rejected", "pending_human_review"]: ["approved", "rejected", "pending_human_review"]:
return reject("Invalid pol_decision value") return reject("Invalid pol_decision value")
@@ -2921,7 +2925,7 @@ ECTs<a href="#section-8.1-5.1.1" class="pilcrow">¶</a></p>
</li> </li>
<li class="normal" id="section-8.1-5.2"> <li class="normal" id="section-8.1-5.2">
<p id="section-8.1-5.2.1">Global replay detection relies solely on "jti" caches at each <p id="section-8.1-5.2.1">Global replay detection relies solely on "jti" caches at each
agent; there is no centralized "tid" uniqueness check<a href="#section-8.1-5.2.1" class="pilcrow"></a></p> agent; there is no centralized "jti" uniqueness check<a href="#section-8.1-5.2.1" class="pilcrow"></a></p>
</li> </li>
<li class="normal" id="section-8.1-5.3"> <li class="normal" id="section-8.1-5.3">
<p id="section-8.1-5.3.1">The parent ECT chain grows with each hop, increasing HTTP <p id="section-8.1-5.3.1">The parent ECT chain grows with each hop, increasing HTTP
@@ -3008,7 +3012,7 @@ entries via a monotonically increasing sequence number.<a href="#section-9.2-2.2
</li> </li>
<li id="section-9.2-2.3"> <li id="section-9.2-2.3">
<p id="section-9.2-2.3.1">Lookup by task ID: The ledger <span class="bcp14">MUST</span> support efficient retrieval <p id="section-9.2-2.3.1">Lookup by task ID: The ledger <span class="bcp14">MUST</span> support efficient retrieval
of ECT entries by "tid" value.<a href="#section-9.2-2.3.1" class="pilcrow"></a></p> of ECT entries by "jti" value.<a href="#section-9.2-2.3.1" class="pilcrow"></a></p>
</li> </li>
<li id="section-9.2-2.4"> <li id="section-9.2-2.4">
<p id="section-9.2-2.4.1">Integrity verification: The ledger <span class="bcp14">SHOULD</span> provide a mechanism <p id="section-9.2-2.4.1">Integrity verification: The ledger <span class="bcp14">SHOULD</span> provide a mechanism
@@ -3032,7 +3036,7 @@ workflow agents to reduce the risk of collusion.<a href="#section-9.2-3" class="
<pre> <pre>
{ {
"ledger_sequence": 42, "ledger_sequence": 42,
"task_id": "550e8400-e29b-41d4-a716-446655440001", "ect_jti": "550e8400-e29b-41d4-a716-446655440001",
"agent_id": "spiffe://example.com/agent/clinical", "agent_id": "spiffe://example.com/agent/clinical",
"action": "recommend_treatment", "action": "recommend_treatment",
"parents": [], "parents": [],
@@ -3070,7 +3074,7 @@ examples demonstrate ECT mechanics; production deployments would
include additional domain-specific requirements beyond the scope include additional domain-specific requirements beyond the scope
of this specification.<a href="#section-10-1" class="pilcrow"></a></p> of this specification.<a href="#section-10-1" class="pilcrow"></a></p>
<p id="section-10-2">Note: task identifiers in this section are abbreviated for <p id="section-10-2">Note: task identifiers in this section are abbreviated for
readability. In production, all "tid" values are required to be readability. In production, all "jti" values are required to be
UUIDs per <a href="#exec-claims" class="auto internal xref">Section 4.2.2</a>.<a href="#section-10-2" class="pilcrow"></a></p> UUIDs per <a href="#exec-claims" class="auto internal xref">Section 4.2.2</a>.<a href="#section-10-2" class="pilcrow"></a></p>
<div id="medical-device-sdlc-workflow"> <div id="medical-device-sdlc-workflow">
<section id="section-10.1"> <section id="section-10.1">
@@ -3088,27 +3092,27 @@ software used in medical devices.<a href="#section-10.1-1" class="pilcrow">¶</a
<div class="alignLeft art-text artwork" id="section-10.1-2.1"> <div class="alignLeft art-text artwork" id="section-10.1-2.1">
<pre> <pre>
Agent A (Spec Reviewer): Agent A (Spec Reviewer):
tid: task-001 par: [] jti: task-001 par: []
exec_act: review_requirements_spec exec_act: review_requirements_spec
pol: spec_review_policy_v2 pol_decision: approved pol: spec_review_policy_v2 pol_decision: approved
Agent B (Code Generator): Agent B (Code Generator):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: implement_module exec_act: implement_module
pol: coding_standards_v3 pol_decision: approved pol: coding_standards_v3 pol_decision: approved
Agent C (Test Agent): Agent C (Test Agent):
tid: task-003 par: [task-002] jti: task-003 par: [task-002]
exec_act: execute_test_suite exec_act: execute_test_suite
pol: test_coverage_policy_v1 pol_decision: approved pol: test_coverage_policy_v1 pol_decision: approved
Agent D (Build Agent): Agent D (Build Agent):
tid: task-004 par: [task-003] jti: task-004 par: [task-003]
exec_act: build_release_artifact exec_act: build_release_artifact
pol: build_validation_v2 pol_decision: approved pol: build_validation_v2 pol_decision: approved
Human Release Manager: Human Release Manager:
tid: task-005 par: [task-004] jti: task-005 par: [task-004]
exec_act: approve_release exec_act: approve_release
pol: release_approval_policy pol_decision: approved pol: release_approval_policy pol_decision: approved
pol_enforcer: spiffe://meddev.example/human/release-mgr-42 pol_enforcer: spiffe://meddev.example/human/release-mgr-42
@@ -3213,17 +3217,17 @@ execution.<a href="#section-10.2-1" class="pilcrow">¶</a></p>
<div class="alignLeft art-text artwork" id="section-10.2-2.1"> <div class="alignLeft art-text artwork" id="section-10.2-2.1">
<pre> <pre>
Agent A (Risk Assessment): Agent A (Risk Assessment):
tid: task-001 par: [] jti: task-001 par: []
exec_act: calculate_risk_exposure exec_act: calculate_risk_exposure
pol: risk_limits_policy_v2 pol_decision: approved pol: risk_limits_policy_v2 pol_decision: approved
Agent B (Compliance): Agent B (Compliance):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: verify_compliance exec_act: verify_compliance
pol: compliance_check_v1 pol_decision: approved pol: compliance_check_v1 pol_decision: approved
Agent C (Execution): Agent C (Execution):
tid: task-003 par: [task-002] jti: task-003 par: [task-002]
exec_act: execute_trade exec_act: execute_trade
pol: execution_policy_v3 pol_decision: approved pol: execution_policy_v3 pol_decision: approved
</pre> </pre>
@@ -3266,9 +3270,8 @@ a cryptographic link to the original task:<a href="#section-10.3-1" class="pilcr
"aud": "spiffe://bank.example/system/ledger", "aud": "spiffe://bank.example/system/ledger",
"iat": 1772150550, "iat": 1772150550,
"exp": 1772151150, "exp": 1772151150,
"jti": "e4f5a6b7-c8d9-0123-ef01-234567890abc", "jti": "550e8400-e29b-41d4-a716-446655440099",
"wid": "d3e4f5a6-b7c8-9012-def0-123456789012", "wid": "d3e4f5a6-b7c8-9012-def0-123456789012",
"tid": "550e8400-e29b-41d4-a716-446655440099",
"exec_act": "initiate_trade_rollback", "exec_act": "initiate_trade_rollback",
"par": ["550e8400-e29b-41d4-a716-446655440003"], "par": ["550e8400-e29b-41d4-a716-446655440003"],
"pol": "compensation_policy_v1", "pol": "compensation_policy_v1",
@@ -3301,27 +3304,27 @@ required checks were completed:<a href="#section-10.4-1" class="pilcrow">¶</a><
<div class="alignLeft art-text artwork" id="section-10.4-2.1"> <div class="alignLeft art-text artwork" id="section-10.4-2.1">
<pre> <pre>
Agent A (Route Planning): Agent A (Route Planning):
tid: task-001 par: [] jti: task-001 par: []
exec_act: plan_route exec_act: plan_route
pol: route_policy_v1 pol_decision: approved pol: route_policy_v1 pol_decision: approved
Agent B (Customs): Agent B (Customs):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: validate_customs exec_act: validate_customs
pol: customs_policy_v2 pol_decision: approved pol: customs_policy_v2 pol_decision: approved
Agent C (Safety): Agent C (Safety):
tid: task-003 par: [task-001] jti: task-003 par: [task-001]
exec_act: verify_cargo_safety exec_act: verify_cargo_safety
pol: safety_policy_v1 pol_decision: approved pol: safety_policy_v1 pol_decision: approved
Agent D (Payment): Agent D (Payment):
tid: task-004 par: [task-002, task-003] jti: task-004 par: [task-002, task-003]
exec_act: authorize_payment exec_act: authorize_payment
pol: payment_policy_v3 pol_decision: approved pol: payment_policy_v3 pol_decision: approved
System (Commitment): System (Commitment):
tid: task-005 par: [task-004] jti: task-005 par: [task-004]
exec_act: commit_shipment exec_act: commit_shipment
pol: commitment_policy_v1 pol_decision: approved pol: commitment_policy_v1 pol_decision: approved
</pre> </pre>
@@ -3422,7 +3425,7 @@ attestation ECT:<a href="#section-11.2.1-1" class="pilcrow">¶</a></p>
specific equivalent).<a href="#section-11.2.1-2.2.1" class="pilcrow"></a></p> specific equivalent).<a href="#section-11.2.1-2.2.1" class="pilcrow"></a></p>
</li> </li>
<li class="normal" id="section-11.2.1-2.3"> <li class="normal" id="section-11.2.1-2.3">
<p id="section-11.2.1-2.3.1"><span class="bcp14">MUST</span> include the observed task's "tid" in the "par" array, <p id="section-11.2.1-2.3.1"><span class="bcp14">MUST</span> include the observed task's "jti" in the "par" array,
linking the attestation to the original task.<a href="#section-11.2.1-2.3.1" class="pilcrow"></a></p> linking the attestation to the original task.<a href="#section-11.2.1-2.3.1" class="pilcrow"></a></p>
</li> </li>
<li class="normal" id="section-11.2.1-2.4"> <li class="normal" id="section-11.2.1-2.4">
@@ -3892,14 +3895,6 @@ the "JSON Web Token Claims" registry maintained by IANA:<a href="#section-13.3-1
<td class="text-center" rowspan="1" colspan="1">IETF</td> <td class="text-center" rowspan="1" colspan="1">IETF</td>
<td class="text-center" rowspan="1" colspan="1"> <td class="text-center" rowspan="1" colspan="1">
<a href="#exec-claims" class="auto internal xref">Section 4.2.2</a> <a href="#exec-claims" class="auto internal xref">Section 4.2.2</a>
</td>
</tr>
<tr>
<td class="text-center" rowspan="1" colspan="1">tid</td>
<td class="text-left" rowspan="1" colspan="1">Task Identifier</td>
<td class="text-center" rowspan="1" colspan="1">IETF</td>
<td class="text-center" rowspan="1" colspan="1">
<a href="#exec-claims" class="auto internal xref">Section 4.2.2</a>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -4438,8 +4433,8 @@ use cases are distinct.<a href="#appendix-A.7-1" class="pilcrow">¶</a></p>
<ol start="1" type="1" class="normal type-1" id="appendix-B.1-2"> <ol start="1" type="1" class="normal type-1" id="appendix-B.1-2">
<li id="appendix-B.1-2.1"> <li id="appendix-B.1-2.1">
<p id="appendix-B.1-2.1.1">Create JWTs with all required claims ("iss", "aud", "iat", <p id="appendix-B.1-2.1.1">Create JWTs with all required claims ("iss", "aud", "iat",
"exp", "jti", "tid", "exec_act", "par", "pol", "exp", "jti", "exec_act", "par") and policy claims ("pol",
"pol_decision").<a href="#appendix-B.1-2.1.1" class="pilcrow"></a></p> "pol_decision") when policy evaluation was performed.<a href="#appendix-B.1-2.1.1" class="pilcrow"></a></p>
</li> </li>
<li id="appendix-B.1-2.2"> <li id="appendix-B.1-2.2">
<p id="appendix-B.1-2.2.1">Sign ECTs with the agent's private key using an algorithm <p id="appendix-B.1-2.2.1">Sign ECTs with the agent's private key using an algorithm
@@ -4612,9 +4607,8 @@ Agent B:<a href="#appendix-D.1-1" class="pilcrow">¶</a></p>
"aud": "spiffe://example.com/agent/validator", "aud": "spiffe://example.com/agent/validator",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "1a2b3c4d-e5f6-7890-abcd-ef0123456701", "jti": "550e8400-e29b-41d4-a716-446655440001",
"wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890", "wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890",
"tid": "550e8400-e29b-41d4-a716-446655440001",
"exec_act": "fetch_patient_data", "exec_act": "fetch_patient_data",
"par": [], "par": [],
"pol": "clinical_data_access_policy_v1", "pol": "clinical_data_access_policy_v1",
@@ -4636,9 +4630,8 @@ task, and creates its own ECT:<a href="#appendix-D.1-6" class="pilcrow">¶</a></
"aud": "spiffe://example.com/system/ledger", "aud": "spiffe://example.com/system/ledger",
"iat": 1772064160, "iat": 1772064160,
"exp": 1772064760, "exp": 1772064760,
"jti": "2b3c4d5e-f6a7-8901-bcde-f01234567802", "jti": "550e8400-e29b-41d4-a716-446655440002",
"wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890", "wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890",
"tid": "550e8400-e29b-41d4-a716-446655440002",
"exec_act": "validate_safety", "exec_act": "validate_safety",
"par": ["550e8400-e29b-41d4-a716-446655440001"], "par": ["550e8400-e29b-41d4-a716-446655440001"],
"pol": "safety_validation_policy_v2", "pol": "safety_validation_policy_v2",
@@ -4675,9 +4668,8 @@ autonomous agents and human release approval:<a href="#appendix-D.2-1" class="pi
"aud": "spiffe://meddev.example/agent/code-gen", "aud": "spiffe://meddev.example/agent/code-gen",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "3c4d5e6f-a7b8-9012-cdef-012345678903", "jti": "a1b2c3d4-0001-0000-0000-000000000001",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000001",
"exec_act": "review_requirements_spec", "exec_act": "review_requirements_spec",
"par": [], "par": [],
"pol": "spec_review_policy_v2", "pol": "spec_review_policy_v2",
@@ -4698,9 +4690,8 @@ autonomous agents and human release approval:<a href="#appendix-D.2-1" class="pi
"aud": "spiffe://meddev.example/agent/test-runner", "aud": "spiffe://meddev.example/agent/test-runner",
"iat": 1772064200, "iat": 1772064200,
"exp": 1772064800, "exp": 1772064800,
"jti": "4d5e6f7a-b8c9-0123-def0-123456789004", "jti": "a1b2c3d4-0001-0000-0000-000000000002",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000002",
"exec_act": "implement_module", "exec_act": "implement_module",
"par": ["a1b2c3d4-0001-0000-0000-000000000001"], "par": ["a1b2c3d4-0001-0000-0000-000000000001"],
"pol": "coding_standards_v3", "pol": "coding_standards_v3",
@@ -4719,9 +4710,8 @@ autonomous agents and human release approval:<a href="#appendix-D.2-1" class="pi
"aud": "spiffe://meddev.example/agent/build", "aud": "spiffe://meddev.example/agent/build",
"iat": 1772064260, "iat": 1772064260,
"exp": 1772064860, "exp": 1772064860,
"jti": "5e6f7a8b-c9d0-1234-ef01-234567890005", "jti": "a1b2c3d4-0001-0000-0000-000000000003",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000003",
"exec_act": "execute_test_suite", "exec_act": "execute_test_suite",
"par": ["a1b2c3d4-0001-0000-0000-000000000002"], "par": ["a1b2c3d4-0001-0000-0000-000000000002"],
"pol": "test_coverage_policy_v1", "pol": "test_coverage_policy_v1",
@@ -4740,9 +4730,8 @@ autonomous agents and human release approval:<a href="#appendix-D.2-1" class="pi
"aud": "spiffe://meddev.example/human/release-mgr-42", "aud": "spiffe://meddev.example/human/release-mgr-42",
"iat": 1772064310, "iat": 1772064310,
"exp": 1772064910, "exp": 1772064910,
"jti": "6f7a8b9c-d0e1-2345-f012-345678900006", "jti": "a1b2c3d4-0001-0000-0000-000000000004",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000004",
"exec_act": "build_release_artifact", "exec_act": "build_release_artifact",
"par": ["a1b2c3d4-0001-0000-0000-000000000003"], "par": ["a1b2c3d4-0001-0000-0000-000000000003"],
"pol": "build_validation_v2", "pol": "build_validation_v2",
@@ -4761,9 +4750,8 @@ autonomous agents and human release approval:<a href="#appendix-D.2-1" class="pi
"aud": "spiffe://meddev.example/system/ledger", "aud": "spiffe://meddev.example/system/ledger",
"iat": 1772064510, "iat": 1772064510,
"exp": 1772065110, "exp": 1772065110,
"jti": "7a8b9c0d-e1f2-3456-0123-456789000007", "jti": "a1b2c3d4-0001-0000-0000-000000000005",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000005",
"exec_act": "approve_release", "exec_act": "approve_release",
"par": ["a1b2c3d4-0001-0000-0000-000000000004"], "par": ["a1b2c3d4-0001-0000-0000-000000000004"],
"pol": "release_approval_policy", "pol": "release_approval_policy",
@@ -4833,9 +4821,8 @@ task-...-0004 (execute_trade)
"aud": "spiffe://bank.example/system/ledger", "aud": "spiffe://bank.example/system/ledger",
"iat": 1772064250, "iat": 1772064250,
"exp": 1772064850, "exp": 1772064850,
"jti": "8b9c0d1e-f2a3-4567-1234-567890000008", "jti": "f1e2d3c4-0004-0000-0000-000000000004",
"wid": "d3e4f5a6-b7c8-9012-def0-123456789012", "wid": "d3e4f5a6-b7c8-9012-def0-123456789012",
"tid": "f1e2d3c4-0004-0000-0000-000000000004",
"exec_act": "execute_trade", "exec_act": "execute_trade",
"par": [ "par": [
"f1e2d3c4-0002-0000-0000-000000000002", "f1e2d3c4-0002-0000-0000-000000000002",

View File

@@ -462,11 +462,15 @@ because ECTs record completed actions and are valid immediately
upon issuance. upon issuance.
jti: jti:
: REQUIRED. String. A unique identifier for the ECT in UUID : REQUIRED. String. A globally unique identifier for both the
format {{RFC9562}}. Used for replay detection: receivers MUST ECT and the task it records, in UUID format {{RFC9562}}. Since
reject ECTs whose "jti" has already been seen within the each ECT represents exactly one task, "jti" serves as both the
expiration window. The "jti" value MUST be unique across all token identifier (for replay detection) and the task identifier
ECTs issued by the same agent. (for DAG parent references in "par"). Receivers MUST reject
ECTs whose "jti" has already been seen within the expiration
window. When "wid" is present, uniqueness is scoped to the
workflow; when "wid" is absent, uniqueness MUST be enforced
globally across the ECT store.
### Execution Context {#exec-claims} ### Execution Context {#exec-claims}
@@ -475,15 +479,7 @@ The following claims are defined by this specification:
wid: wid:
: OPTIONAL. String. A workflow identifier that groups related : OPTIONAL. String. A workflow identifier that groups related
ECTs into a single workflow. When present, MUST be a UUID ECTs into a single workflow. When present, MUST be a UUID
{{RFC9562}}. When absent, the "tid" uniqueness requirement {{RFC9562}}.
applies globally across the entire ledger.
tid:
: REQUIRED. String. A globally unique task identifier in UUID
format {{RFC9562}}. Each task MUST have a unique "tid" value.
When "wid" is present, uniqueness is scoped to the workflow;
when "wid" is absent, uniqueness MUST be enforced globally
across the ledger.
exec_act: exec_act:
: REQUIRED. String. The action or task type identifier describing : REQUIRED. String. The action or task type identifier describing
@@ -495,8 +491,8 @@ exec_act:
par: par:
: REQUIRED. Array of strings. Parent task identifiers : REQUIRED. Array of strings. Parent task identifiers
representing DAG dependencies. Each element MUST be a valid representing DAG dependencies. Each element MUST be the "jti"
"tid" from a previously executed task. An empty array indicates value of a previously verified ECT. An empty array indicates
a root task with no dependencies. A workflow MAY contain a root task with no dependencies. A workflow MAY contain
multiple root tasks. multiple root tasks.
@@ -505,26 +501,27 @@ par:
The following claims record policy evaluation outcomes: The following claims record policy evaluation outcomes:
pol: pol:
: REQUIRED. String. The identifier of the policy rule that was : OPTIONAL. String. The identifier of the policy rule that was
evaluated for this task (e.g., evaluated for this task (e.g.,
"clinical_data_access_policy_v1"). "clinical_data_access_policy_v1"). MUST be present when
"pol_decision" is present.
pol_decision: pol_decision:
: REQUIRED. String. The result of the policy evaluation. MUST : OPTIONAL. String. The result of the policy evaluation. When
be one of the values registered in the ECT Policy Decision present, MUST be one of the values registered in the ECT Policy
Values registry ({{pol-decision-registry}}). Initial values Decision Values registry ({{pol-decision-registry}}). MUST be
are: present when "pol" is present. Initial values are:
* "approved": The policy evaluation succeeded and the task * "approved": The policy evaluation succeeded and the task
was authorized to proceed. was authorized to proceed.
* "rejected": The policy evaluation failed. A "rejected" ECT * "rejected": The policy evaluation failed. A "rejected" ECT
MUST still be appended to the audit ledger for accountability. MUST still be recorded for accountability. An ECT with
An ECT with "pol_decision" of "rejected" MAY appear as a "pol_decision" of "rejected" MAY appear as a parent in the
parent in the "par" array of a subsequent ECT, but only for "par" array of a subsequent ECT, but only for compensation,
compensation, rollback, or remediation tasks. Agents MUST rollback, or remediation tasks. Agents MUST NOT proceed
NOT proceed with normal workflow execution based on a parent with normal workflow execution based on a parent ECT whose
ECT whose "pol_decision" is "rejected". "pol_decision" is "rejected".
* "pending_human_review": The policy evaluation requires human * "pending_human_review": The policy evaluation requires human
judgment before proceeding. Agents MUST NOT proceed with judgment before proceeding. Agents MUST NOT proceed with
@@ -532,6 +529,11 @@ pol_decision:
records an "approved" decision referencing this task as a records an "approved" decision referencing this task as a
parent. parent.
When "pol" and "pol_decision" are absent, the ECT records task
execution without a policy checkpoint. Regulated deployments
SHOULD include policy claims on all ECTs to maintain complete
audit trails.
pol_enforcer: pol_enforcer:
: OPTIONAL. StringOrURI. The identity of the entity (system or : OPTIONAL. StringOrURI. The identity of the entity (system or
person) that evaluated the policy decision. When present, person) that evaluated the policy decision. When present,
@@ -659,10 +661,9 @@ The following is a complete ECT payload example:
"aud": "spiffe://example.com/agent/safety", "aud": "spiffe://example.com/agent/safety",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "7f3a8b2c-d1e4-4f56-9a0b-c3d4e5f6a7b8", "jti": "550e8400-e29b-41d4-a716-446655440001",
"wid": "a0b1c2d3-e4f5-6789-abcd-ef0123456789", "wid": "a0b1c2d3-e4f5-6789-abcd-ef0123456789",
"tid": "550e8400-e29b-41d4-a716-446655440001",
"exec_act": "recommend_treatment", "exec_act": "recommend_treatment",
"par": [], "par": [],
@@ -744,10 +745,10 @@ parent ECTs available to the verifier.
When receiving and verifying an ECT, implementations MUST perform When receiving and verifying an ECT, implementations MUST perform
the following DAG validation steps: the following DAG validation steps:
1. Task ID Uniqueness: The "tid" claim MUST be unique within the 1. Task ID Uniqueness: The "jti" claim MUST be unique within the
applicable scope (the workflow identified by "wid", or the applicable scope (the workflow identified by "wid", or the
entire ECT store if "wid" is absent). If a task with the same entire ECT store if "wid" is absent). If an ECT with the same
"tid" already exists, the ECT MUST be rejected. "jti" already exists, the ECT MUST be rejected.
2. Parent Existence: Every task identifier listed in the "par" 2. Parent Existence: Every task identifier listed in the "par"
array MUST correspond to a task that is available in the ECT array MUST correspond to a task that is available in the ECT
@@ -767,16 +768,18 @@ the following DAG validation steps:
ECT MUST be rejected. ECT MUST be rejected.
4. Acyclicity: Following the chain of parent references MUST NOT 4. Acyclicity: Following the chain of parent references MUST NOT
lead back to the current task's "tid". If a cycle is detected, lead back to the current ECT's "jti". If a cycle is detected,
the ECT MUST be rejected. the ECT MUST be rejected.
5. Parent Policy Decision: If any parent task has a "pol_decision" 5. Parent Policy Decision: If any parent ECT contains a
of "rejected" or "pending_human_review", the current task's "pol_decision" of "rejected" or "pending_human_review", the
"exec_act" MUST indicate a compensation, rollback, remediation, current ECT's "exec_act" MUST indicate a compensation,
or human review action. Implementations MUST NOT accept an ECT rollback, remediation, or human review action.
representing normal workflow continuation when a parent's Implementations MUST NOT accept an ECT representing normal
"pol_decision" is not "approved", unless the current ECT has workflow continuation when a parent's "pol_decision" is not
"compensation_required" set to true. "approved", unless the current ECT has "compensation_required"
set to true. This rule only applies when the parent ECT
contains policy claims.
6. Trust Domain Consistency: Parent tasks SHOULD belong to the 6. Trust Domain Consistency: Parent tasks SHOULD belong to the
same trust domain or to a trust domain with which a federation same trust domain or to a trust domain with which a federation
@@ -790,8 +793,8 @@ The following pseudocode describes the DAG validation procedure:
function validate_dag(ect, ect_store, clock_skew_tolerance): function validate_dag(ect, ect_store, clock_skew_tolerance):
// ect_store: ledger or local cache of verified ECTs // ect_store: ledger or local cache of verified ECTs
// Step 1: Uniqueness check // Step 1: Uniqueness check
if ect_store.contains(ect.tid, ect.wid): if ect_store.contains(ect.jti, ect.wid):
return error("Task ID already exists") return error("ECT ID already exists")
// Step 2: Parent existence and temporal ordering // Step 2: Parent existence and temporal ordering
for parent_id in ect.par: for parent_id in ect.par:
@@ -803,26 +806,26 @@ function validate_dag(ect, ect_store, clock_skew_tolerance):
// Step 3: Cycle detection (with traversal limit) // Step 3: Cycle detection (with traversal limit)
visited = set() visited = set()
result = has_cycle(ect.tid, ect.par, ect_store, visited, result = has_cycle(ect.jti, ect.par, ect_store, visited,
max_ancestor_limit) max_ancestor_limit)
if result is error or result is true: if result is error or result is true:
return error("Circular dependency or depth limit exceeded") return error("Circular dependency or depth limit exceeded")
return success return success
function has_cycle(target_tid, parent_ids, ect_store, function has_cycle(target_jti, parent_ids, ect_store,
visited, max_depth): visited, max_depth):
if visited.size() >= max_depth: if visited.size() >= max_depth:
return error("Maximum ancestor traversal limit exceeded") return error("Maximum ancestor traversal limit exceeded")
for parent_id in parent_ids: for parent_id in parent_ids:
if parent_id == target_tid: if parent_id == target_jti:
return true return true
if parent_id in visited: if parent_id in visited:
continue continue
visited.add(parent_id) visited.add(parent_id)
parent = ect_store.get(parent_id) parent = ect_store.get(parent_id)
if parent is not null: if parent is not null:
result = has_cycle(target_tid, parent.par, ect_store, result = has_cycle(target_jti, parent.par, ect_store,
visited, max_depth) visited, max_depth)
if result is error or result is true: if result is error or result is true:
return result return result
@@ -889,11 +892,12 @@ verification steps in order:
(RECOMMENDED: no more than 30 seconds ahead of the (RECOMMENDED: no more than 30 seconds ahead of the
verifier's current time, to account for clock skew). verifier's current time, to account for clock skew).
12. Verify all required claims ("jti", "tid", "exec_act", "par", 12. Verify all required claims ("jti", "exec_act", "par") are
"pol", "pol_decision") are present and well-formed. present and well-formed.
13. Verify "pol_decision" is one of "approved", "rejected", or 13. If "pol" or "pol_decision" is present, verify that both are
"pending_human_review". present and that "pol_decision" is one of "approved",
"rejected", or "pending_human_review".
14. Perform DAG validation per {{dag-validation}}. 14. Perform DAG validation per {{dag-validation}}.
@@ -969,12 +973,14 @@ function verify_ect(ect_jws, verifier_id,
return reject("ECT issued in the future") return reject("ECT issued in the future")
// Verify required claims // Verify required claims
for claim in ["jti", "tid", "exec_act", "par", for claim in ["jti", "exec_act", "par"]:
"pol", "pol_decision"]:
if claim not in payload: if claim not in payload:
return reject("Missing required claim: " + claim) return reject("Missing required claim: " + claim)
// Validate pol_decision value // Validate policy claims (optional, but must be paired)
if "pol" in payload or "pol_decision" in payload:
if "pol" not in payload or "pol_decision" not in payload:
return reject("pol and pol_decision must both be present")
if payload.pol_decision not in if payload.pol_decision not in
["approved", "rejected", "pending_human_review"]: ["approved", "rejected", "pending_human_review"]:
return reject("Invalid pol_decision value") return reject("Invalid pol_decision value")
@@ -1023,7 +1029,7 @@ Limitations of point-to-point mode:
- No persistent audit trail unless agents independently retain - No persistent audit trail unless agents independently retain
ECTs ECTs
- Global replay detection relies solely on "jti" caches at each - Global replay detection relies solely on "jti" caches at each
agent; there is no centralized "tid" uniqueness check agent; there is no centralized "jti" uniqueness check
- The parent ECT chain grows with each hop, increasing HTTP - The parent ECT chain grows with each hop, increasing HTTP
header size header size
- Post-hoc audit reconstruction requires collecting ECTs from - Post-hoc audit reconstruction requires collecting ECTs from
@@ -1083,7 +1089,7 @@ An audit ledger implementation MUST provide:
entries via a monotonically increasing sequence number. entries via a monotonically increasing sequence number.
3. Lookup by task ID: The ledger MUST support efficient retrieval 3. Lookup by task ID: The ledger MUST support efficient retrieval
of ECT entries by "tid" value. of ECT entries by "jti" value.
4. Integrity verification: The ledger SHOULD provide a mechanism 4. Integrity verification: The ledger SHOULD provide a mechanism
to verify that no entries have been tampered with (e.g., to verify that no entries have been tampered with (e.g.,
@@ -1099,7 +1105,7 @@ Each ledger entry is a logical record containing:
~~~json ~~~json
{ {
"ledger_sequence": 42, "ledger_sequence": 42,
"task_id": "550e8400-e29b-41d4-a716-446655440001", "ect_jti": "550e8400-e29b-41d4-a716-446655440001",
"agent_id": "spiffe://example.com/agent/clinical", "agent_id": "spiffe://example.com/agent/clinical",
"action": "recommend_treatment", "action": "recommend_treatment",
"parents": [], "parents": [],
@@ -1129,7 +1135,7 @@ include additional domain-specific requirements beyond the scope
of this specification. of this specification.
Note: task identifiers in this section are abbreviated for Note: task identifiers in this section are abbreviated for
readability. In production, all "tid" values are required to be readability. In production, all "jti" values are required to be
UUIDs per {{exec-claims}}. UUIDs per {{exec-claims}}.
## Medical Device SDLC Workflow ## Medical Device SDLC Workflow
@@ -1143,27 +1149,27 @@ software used in medical devices.
~~~ ~~~
Agent A (Spec Reviewer): Agent A (Spec Reviewer):
tid: task-001 par: [] jti: task-001 par: []
exec_act: review_requirements_spec exec_act: review_requirements_spec
pol: spec_review_policy_v2 pol_decision: approved pol: spec_review_policy_v2 pol_decision: approved
Agent B (Code Generator): Agent B (Code Generator):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: implement_module exec_act: implement_module
pol: coding_standards_v3 pol_decision: approved pol: coding_standards_v3 pol_decision: approved
Agent C (Test Agent): Agent C (Test Agent):
tid: task-003 par: [task-002] jti: task-003 par: [task-002]
exec_act: execute_test_suite exec_act: execute_test_suite
pol: test_coverage_policy_v1 pol_decision: approved pol: test_coverage_policy_v1 pol_decision: approved
Agent D (Build Agent): Agent D (Build Agent):
tid: task-004 par: [task-003] jti: task-004 par: [task-003]
exec_act: build_release_artifact exec_act: build_release_artifact
pol: build_validation_v2 pol_decision: approved pol: build_validation_v2 pol_decision: approved
Human Release Manager: Human Release Manager:
tid: task-005 par: [task-004] jti: task-005 par: [task-004]
exec_act: approve_release exec_act: approve_release
pol: release_approval_policy pol_decision: approved pol: release_approval_policy pol_decision: approved
pol_enforcer: spiffe://meddev.example/human/release-mgr-42 pol_enforcer: spiffe://meddev.example/human/release-mgr-42
@@ -1231,17 +1237,17 @@ execution.
~~~ ~~~
Agent A (Risk Assessment): Agent A (Risk Assessment):
tid: task-001 par: [] jti: task-001 par: []
exec_act: calculate_risk_exposure exec_act: calculate_risk_exposure
pol: risk_limits_policy_v2 pol_decision: approved pol: risk_limits_policy_v2 pol_decision: approved
Agent B (Compliance): Agent B (Compliance):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: verify_compliance exec_act: verify_compliance
pol: compliance_check_v1 pol_decision: approved pol: compliance_check_v1 pol_decision: approved
Agent C (Execution): Agent C (Execution):
tid: task-003 par: [task-002] jti: task-003 par: [task-002]
exec_act: execute_trade exec_act: execute_trade
pol: execution_policy_v3 pol_decision: approved pol: execution_policy_v3 pol_decision: approved
~~~ ~~~
@@ -1268,9 +1274,8 @@ a cryptographic link to the original task:
"aud": "spiffe://bank.example/system/ledger", "aud": "spiffe://bank.example/system/ledger",
"iat": 1772150550, "iat": 1772150550,
"exp": 1772151150, "exp": 1772151150,
"jti": "e4f5a6b7-c8d9-0123-ef01-234567890abc", "jti": "550e8400-e29b-41d4-a716-446655440099",
"wid": "d3e4f5a6-b7c8-9012-def0-123456789012", "wid": "d3e4f5a6-b7c8-9012-def0-123456789012",
"tid": "550e8400-e29b-41d4-a716-446655440099",
"exec_act": "initiate_trade_rollback", "exec_act": "initiate_trade_rollback",
"par": ["550e8400-e29b-41d4-a716-446655440003"], "par": ["550e8400-e29b-41d4-a716-446655440003"],
"pol": "compensation_policy_v1", "pol": "compensation_policy_v1",
@@ -1294,27 +1299,27 @@ required checks were completed:
~~~ ~~~
Agent A (Route Planning): Agent A (Route Planning):
tid: task-001 par: [] jti: task-001 par: []
exec_act: plan_route exec_act: plan_route
pol: route_policy_v1 pol_decision: approved pol: route_policy_v1 pol_decision: approved
Agent B (Customs): Agent B (Customs):
tid: task-002 par: [task-001] jti: task-002 par: [task-001]
exec_act: validate_customs exec_act: validate_customs
pol: customs_policy_v2 pol_decision: approved pol: customs_policy_v2 pol_decision: approved
Agent C (Safety): Agent C (Safety):
tid: task-003 par: [task-001] jti: task-003 par: [task-001]
exec_act: verify_cargo_safety exec_act: verify_cargo_safety
pol: safety_policy_v1 pol_decision: approved pol: safety_policy_v1 pol_decision: approved
Agent D (Payment): Agent D (Payment):
tid: task-004 par: [task-002, task-003] jti: task-004 par: [task-002, task-003]
exec_act: authorize_payment exec_act: authorize_payment
pol: payment_policy_v3 pol_decision: approved pol: payment_policy_v3 pol_decision: approved
System (Commitment): System (Commitment):
tid: task-005 par: [task-004] jti: task-005 par: [task-004]
exec_act: commit_shipment exec_act: commit_shipment
pol: commitment_policy_v1 pol_decision: approved pol: commitment_policy_v1 pol_decision: approved
~~~ ~~~
@@ -1377,7 +1382,7 @@ attestation ECT:
- MUST set "iss" to the witness's own workload identity. - MUST set "iss" to the witness's own workload identity.
- MUST set "exec_act" to "witness_attestation" (or a domain- - MUST set "exec_act" to "witness_attestation" (or a domain-
specific equivalent). specific equivalent).
- MUST include the observed task's "tid" in the "par" array, - MUST include the observed task's "jti" in the "par" array,
linking the attestation to the original task. linking the attestation to the original task.
- MUST set "pol_decision" to "approved" to indicate the witness - MUST set "pol_decision" to "approved" to indicate the witness
confirms the observation. confirms the observation.
@@ -1673,7 +1678,6 @@ the "JSON Web Token Claims" registry maintained by IANA:
| Claim Name | Claim Description | Change Controller | Reference | | Claim Name | Claim Description | Change Controller | Reference |
|:---:|:---|:---:|:---:| |:---:|:---|:---:|:---:|
| wid | Workflow Identifier | IETF | {{exec-claims}} | | wid | Workflow Identifier | IETF | {{exec-claims}} |
| tid | Task Identifier | IETF | {{exec-claims}} |
| exec_act | Action/Task Type | IETF | {{exec-claims}} | | exec_act | Action/Task Type | IETF | {{exec-claims}} |
| par | Parent Task Identifiers | IETF | {{exec-claims}} | | par | Parent Task Identifiers | IETF | {{exec-claims}} |
| pol | Policy Rule Identifier | IETF | {{policy-claims}} | | pol | Policy Rule Identifier | IETF | {{policy-claims}} |
@@ -1861,8 +1865,8 @@ use cases are distinct.
A minimal conforming implementation needs to: A minimal conforming implementation needs to:
1. Create JWTs with all required claims ("iss", "aud", "iat", 1. Create JWTs with all required claims ("iss", "aud", "iat",
"exp", "jti", "tid", "exec_act", "par", "pol", "exp", "jti", "exec_act", "par") and policy claims ("pol",
"pol_decision"). "pol_decision") when policy evaluation was performed.
2. Sign ECTs with the agent's private key using an algorithm 2. Sign ECTs with the agent's private key using an algorithm
matching the WIT (ES256 recommended). matching the WIT (ES256 recommended).
3. Verify ECT signatures against WIT public keys. 3. Verify ECT signatures against WIT public keys.
@@ -1950,9 +1954,8 @@ ECT Payload:
"aud": "spiffe://example.com/agent/validator", "aud": "spiffe://example.com/agent/validator",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "1a2b3c4d-e5f6-7890-abcd-ef0123456701", "jti": "550e8400-e29b-41d4-a716-446655440001",
"wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890", "wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890",
"tid": "550e8400-e29b-41d4-a716-446655440001",
"exec_act": "fetch_patient_data", "exec_act": "fetch_patient_data",
"par": [], "par": [],
"pol": "clinical_data_access_policy_v1", "pol": "clinical_data_access_policy_v1",
@@ -1974,9 +1977,8 @@ task, and creates its own ECT:
"aud": "spiffe://example.com/system/ledger", "aud": "spiffe://example.com/system/ledger",
"iat": 1772064160, "iat": 1772064160,
"exp": 1772064760, "exp": 1772064760,
"jti": "2b3c4d5e-f6a7-8901-bcde-f01234567802", "jti": "550e8400-e29b-41d4-a716-446655440002",
"wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890", "wid": "b1c2d3e4-f5a6-7890-bcde-f01234567890",
"tid": "550e8400-e29b-41d4-a716-446655440002",
"exec_act": "validate_safety", "exec_act": "validate_safety",
"par": ["550e8400-e29b-41d4-a716-446655440001"], "par": ["550e8400-e29b-41d4-a716-446655440001"],
"pol": "safety_validation_policy_v2", "pol": "safety_validation_policy_v2",
@@ -2010,9 +2012,8 @@ Task 1 (Spec Review Agent):
"aud": "spiffe://meddev.example/agent/code-gen", "aud": "spiffe://meddev.example/agent/code-gen",
"iat": 1772064150, "iat": 1772064150,
"exp": 1772064750, "exp": 1772064750,
"jti": "3c4d5e6f-a7b8-9012-cdef-012345678903", "jti": "a1b2c3d4-0001-0000-0000-000000000001",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000001",
"exec_act": "review_requirements_spec", "exec_act": "review_requirements_spec",
"par": [], "par": [],
"pol": "spec_review_policy_v2", "pol": "spec_review_policy_v2",
@@ -2033,9 +2034,8 @@ Task 2 (Code Generation Agent):
"aud": "spiffe://meddev.example/agent/test-runner", "aud": "spiffe://meddev.example/agent/test-runner",
"iat": 1772064200, "iat": 1772064200,
"exp": 1772064800, "exp": 1772064800,
"jti": "4d5e6f7a-b8c9-0123-def0-123456789004", "jti": "a1b2c3d4-0001-0000-0000-000000000002",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000002",
"exec_act": "implement_module", "exec_act": "implement_module",
"par": ["a1b2c3d4-0001-0000-0000-000000000001"], "par": ["a1b2c3d4-0001-0000-0000-000000000001"],
"pol": "coding_standards_v3", "pol": "coding_standards_v3",
@@ -2054,9 +2054,8 @@ Task 3 (Autonomous Test Agent):
"aud": "spiffe://meddev.example/agent/build", "aud": "spiffe://meddev.example/agent/build",
"iat": 1772064260, "iat": 1772064260,
"exp": 1772064860, "exp": 1772064860,
"jti": "5e6f7a8b-c9d0-1234-ef01-234567890005", "jti": "a1b2c3d4-0001-0000-0000-000000000003",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000003",
"exec_act": "execute_test_suite", "exec_act": "execute_test_suite",
"par": ["a1b2c3d4-0001-0000-0000-000000000002"], "par": ["a1b2c3d4-0001-0000-0000-000000000002"],
"pol": "test_coverage_policy_v1", "pol": "test_coverage_policy_v1",
@@ -2075,9 +2074,8 @@ Task 4 (Build Agent):
"aud": "spiffe://meddev.example/human/release-mgr-42", "aud": "spiffe://meddev.example/human/release-mgr-42",
"iat": 1772064310, "iat": 1772064310,
"exp": 1772064910, "exp": 1772064910,
"jti": "6f7a8b9c-d0e1-2345-f012-345678900006", "jti": "a1b2c3d4-0001-0000-0000-000000000004",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000004",
"exec_act": "build_release_artifact", "exec_act": "build_release_artifact",
"par": ["a1b2c3d4-0001-0000-0000-000000000003"], "par": ["a1b2c3d4-0001-0000-0000-000000000003"],
"pol": "build_validation_v2", "pol": "build_validation_v2",
@@ -2096,9 +2094,8 @@ Task 5 (Human Release Manager Approval):
"aud": "spiffe://meddev.example/system/ledger", "aud": "spiffe://meddev.example/system/ledger",
"iat": 1772064510, "iat": 1772064510,
"exp": 1772065110, "exp": 1772065110,
"jti": "7a8b9c0d-e1f2-3456-0123-456789000007", "jti": "a1b2c3d4-0001-0000-0000-000000000005",
"wid": "c2d3e4f5-a6b7-8901-cdef-012345678901", "wid": "c2d3e4f5-a6b7-8901-cdef-012345678901",
"tid": "a1b2c3d4-0001-0000-0000-000000000005",
"exec_act": "approve_release", "exec_act": "approve_release",
"par": ["a1b2c3d4-0001-0000-0000-000000000004"], "par": ["a1b2c3d4-0001-0000-0000-000000000004"],
"pol": "release_approval_policy", "pol": "release_approval_policy",
@@ -2165,9 +2162,8 @@ Task 004 ECT payload:
"aud": "spiffe://bank.example/system/ledger", "aud": "spiffe://bank.example/system/ledger",
"iat": 1772064250, "iat": 1772064250,
"exp": 1772064850, "exp": 1772064850,
"jti": "8b9c0d1e-f2a3-4567-1234-567890000008", "jti": "f1e2d3c4-0004-0000-0000-000000000004",
"wid": "d3e4f5a6-b7c8-9012-def0-123456789012", "wid": "d3e4f5a6-b7c8-9012-def0-123456789012",
"tid": "f1e2d3c4-0004-0000-0000-000000000004",
"exec_act": "execute_trade", "exec_act": "execute_trade",
"par": [ "par": [
"f1e2d3c4-0002-0000-0000-000000000002", "f1e2d3c4-0002-0000-0000-000000000002",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff