EBMP-2: Evidence Bundle and Manifest Protocol
Version: 2.0 (wire schema_version 2.0)
Status: normative companion of ECL-1 Version 1.0
Schemas: schemas/ebmp-2.schema.json; signed evidence bundles
schemas/ecl-evidence-bundle-v1.schema.json; native adapter bindings
schemas/ecl-adapter-binding-v1.schema.json
1. Purpose
EBMP-2 is an event-and-manifest protocol for reconstructing why an action was or was not released, what was observed, what effects remain, how evidence was verified, and what happened to that evidence over time.
An EBMP-2 record is an authenticated statement by its producer. It does not make the statement true merely because the signature verifies. Conformance requires both integrity and evidence-fidelity analysis.
The BCP 14 interpretation stated in DCMP-2 applies to this document.
Requirement identifiers are allocated in the order requirements were added and never reused, so a later identifier appears among earlier ones where its subject belongs: EBMP2-065 and EBMP2-066 follow EBMP2-011, EBMP2-070 and EBMP2-071 follow EBMP2-015, EBMP2-069 follows EBMP2-045, EBMP2-072 follows EBMP2-050, EBMP2-075 follows EBMP2-051, EBMP2-073 follows EBMP2-052, EBMP2-074 follows EBMP2-055, EBMP2-077 follows EBMP2-029, EBMP2-078 follows EBMP2-058, and EBMP2-067, EBMP2-068 and EBMP2-076 are in section 11.
2. Evidence primitives
EBMP-2 defines:
- event records;
- immutable resource references;
- causal predecessor references;
- bundle manifests;
- closure reports;
- verification events;
- lifecycle events.
EBMP2-001§ — Every record MUST satisfy schema, semantic, canonicalization, extension, and integrity requirements applicable to its profile.
EBMP2-002§ — Every record MUST identify its logical object, exact content through a canonical digest when referenced, workflow, root trace, attempt, sequence, producer, observation time, event type, extensions, and integrity policy.
EBMP2-003§ — A record producer MUST be identified separately from the actor, subject, decision service, authority issuer, enforcement point, verifier, and evidence custodian whenever those roles differ.
EBMP2-004§ — A verifier MUST treat a producer's event as a claim with a declared observation boundary, not as omniscient ground truth.
3. Event types
The core event types are:
request_receiveddecision_recordedauthority_issuedauthority_consumedrelease_preparedexecution_releasedexecution_observedexecution_terminalhuman_interventionverificationsafe_state_transitionfleet_transitionlifecycle_transition
EBMP2-005§ — A profile MAY require additional registered critical events. An unregistered event cannot silently redefine a core state.
Section 9 (EBMP2-046) fixes the event kind, and for authority and execution events the states, that each core event type carries.
3.1 Decision and authority events
EBMP2-006§ — A decision event MUST reference the exact DCMP-2 decision, record its outcome, and bind the evaluated policy.
EBMP2-007§ — An authority event MUST identify the authority and its state:
issued, active, consumed, revoked, expired, or superseded.
EBMP2-008§ — Consumption MUST be separately recorded for a single-use authority. A release without provable consumption is nonconformant where the profile requires replay prevention.
EBMP2-009§ — Revocation, expiry, and supersession MUST NOT delete an authority record and MUST NOT be treated as proof that a prior release did not occur.
3.2 Execution events
The execution progression is:
not_started -> prepared -> released -> running -> terminal
Terminal execution outcomes are:
succeededfailedpartially_succeededcancelledtimed_outrolled_backcompensatedunknown
Non-execution terminal dispositions are:
deniedheldchallengedexpired_before_release
EBMP2-010§ — An execution attempt MUST have a unique attempt identifier.
EBMP2-011§ — A terminal attempt MUST NOT return to a non-terminal state.
Further work requires a new attempt identifier and causal link. A consumer MUST
reject an execution_terminal event whose event.state is neither a terminal
execution outcome nor a non-execution terminal disposition, and an event of any
other type whose event.state is one of them, with
ecl.rule.terminal-execution-state-consistent.v1.
The states of one attempt move only forward along the progression above. This table gives, for each state, the states that may follow it in the same attempt; an attempt's first state may be any state, and nothing follows a terminal outcome or a non-execution terminal disposition.
| State | States that may follow it |
|---|---|
not_started |
prepared, denied, held, challenged, expired_before_release |
prepared |
released, cancelled, timed_out, denied, held, expired_before_release |
released |
running, succeeded, failed, partially_succeeded, cancelled, timed_out, unknown |
running |
succeeded, failed, partially_succeeded, cancelled, timed_out, rolled_back, compensated, unknown |
EBMP2-065§ — The execution events of one attempt, those with the same
event.action_id and event.attempt_id, taken in ascending order of
workflow.sequence and then of object_id, MUST carry states in which every
state after the first is one that the table lists for the state before it. A
consumer MUST reject a bundle with an attempt that does not with
ecl.rule.execution-state-transition-valid.v1.
EBMP2-066§ — Every execution event with the same event.action_id,
workflow.workflow_id and workflow.attempt MUST carry the same
event.attempt_id, and one event.attempt_id MUST NOT appear with two
different such triples. A bundle verifier MUST report a bundle that breaks
this as invalid with ecl.error.decision-binding-invalid.v1.
EBMP2-012§ — denied, held, challenged, and
expired_before_release MUST NOT be reported as execution failures because the
governed action was not released.
EBMP2-013§ — A terminal event in partially_succeeded, timed_out,
cancelled or unknown MUST carry side_effects, listing every known side
effect (an empty array when none is known), and a non-empty residual_state
that states the residual uncertainty or references evidence that does. A
consumer MUST reject such an event that lacks either member with
ecl.rule.terminal-execution-state-consistent.v1.
EBMP2-014§ — Rollback and compensation are new effects. They MUST have their own action, attempt, authority, outcome, and causal evidence; they do not rewrite the original outcome.
EBMP2-015§ — A successful tool or API return MUST NOT be equated with successful external effect unless the declared observation boundary supports that conclusion.
The registered extension ecl.ext.obligation-discharge.v1 records, on an
execution_released event, how Enforcement proved before the release the
obligations that DCMP2-017 leaves to it. Its value schema is
https://externalcontrollayer.ai/ns/schema/extension/obligation-discharge/1.
EBMP2-070§ — When the decision response that an execution_released event
records carries an obligation whose satisfaction is before_release, or
carries in ecl.ext.consequence-coupling.v1 a witness_result whose outcome
is required_before_release, the event MUST carry
ecl.ext.obligation-discharge.v1 with an entry for each such obligation: for an
obligation, an entry whose obligation_id and type are the obligation's id
and type; for the witness result, one whose obligation_id is the uri of
the result's reference and whose type is ecl.obligation.verify-witness.v1.
Each entry MUST record the result satisfied, the proof that Enforcement
checked by reference and by the ecl.digest.sha-256.v1 digest of the proof's
RFC 8785 canonical form, the proof's issuer, when the proof was obtained
(obtained_at), when Enforcement checked it (checked_at) and, whenever the
proof's type declares a freshness bound, fresh_until, the instant after which
the proof no longer shows the obligation met, which MUST NOT be later than the
bound that the proof declares. The obligations that need an entry are those of
the signed response that the bundle holds and that EBMP2-058 pairs with the
event, never a list that the producer states elsewhere. When the proof is an ECL
object, a signed envelope of a registered type, the entry MUST carry it: the
object that reference names by its message_id or object_id is in the
bundle as its own object, a verification event among the observations and
any other object among the envelopes, and digest is that object's digest; a
proof that is not an ECL object is recorded by kind, reference and digest
only. The table after this rule gives, for each obligation type, the object
admitted as its proof, the member that binds the proof to the exchange and the
issuer that the decision names for it. A verifier MUST report with
ecl.rule.prerelease-obligations-discharged.v1 an execution_released event
that binds its request and response (section 10) and that lacks the extension or
an entry for one of these obligations, or that carries an entry that:
- names an obligation that the response does not carry, or one that another entry also names;
- records a
resultother thansatisfied; - has a
checked_atlater than the event'sobserved_at, or earlier than the response'sissued_atby more than the response clock-skew allowance of DCMP2-047; - has a proof whose
obtained_atis later than the entry'schecked_at; - carries a proof whose signers, as the trust policy binds their keys to
principals, are not exactly the issuer that the decision names for the
obligation, compared as exact strings with no decoding and no
normalization, or any of whose signing keys the trust policy binds to the
requesting actor, or, for an approval obligation, carries an approval
record whose signing keys do not all hold
ecl.purpose.approval.v1in the trust-policy version that the record names; the entry'sissueris a label and is not the test; - names as its proof the
message_idorobject_idof an object in the bundle and gives adigestthat is not that object's digest; - has a
fresh_untilthat is not later than the event'sobserved_atplus itsrelease_guard_ms, or carries a proof that declares a bound of its own, an approval record'sexpires_at, that is not later than the event'sobserved_atplus itsrelease_guard_ms, whether or not the entry recordsfresh_until, or has afresh_untillater than that bound; or - carries a proof that is not a valid envelope under the trust policy, whose object type is not one that the obligation's type admits by the table, or that does not bind this exchange by the member that the table gives.
| Obligation type | Object admitted as its proof | Member that binds the proof to the exchange | Issuer that the decision names |
|---|---|---|---|
ecl.obligation.approval-record.v1 |
A human_intervention event whose operation is approve and whose actor is the approver of the obligation's value |
target_id equals the decision request's message_id, and one of its causal_predecessors names the request by message_id and digest |
None: the approver is the record's actor, not its signer; in its place, every key that signed the record holds ecl.purpose.approval.v1, the purpose of an approval service |
ecl.obligation.verify-witness.v1, a deferred witness result included |
A verification event whose decision is pass and whose subject.uri is the witness |
One of its causal_predecessors names the decision request by message_id and digest |
The source of the witness result, when the response gives one; otherwise the decision names none |
| Any other type | Any EBMP-2 event record; a decision request or a decision response is never a proof | One of its causal_predecessors names the decision request by message_id and digest |
None |
The rule belongs to stage 8 of EBMP2-068, and each condition is checked on the
members that are present with the types that the value schema gives them
(DCMP2-043). Conditions 5, 6 and 8 apply only to a proof that the bundle
carries; the first test of condition 7 applies to every entry that has
fresh_until, and its other two only to a carried proof. A verifier does not
check the digest, the signer or the type of a proof held elsewhere, and does not
tell from an uncarried proof's kind whether it is an ECL object. Where the
decision names no issuer for an obligation, a witness result without source or
an obligation of a type for which the table names none, the issuer test of
condition 5 is not made, its other tests stand, and the scope item
prerelease-obligations-proofs-verified is not established, with the reason
issuer-not-named (EBMP2-076); for an approval obligation the approval purpose
stands for the issuer. A discharge is the enforcement point's statement. A verifier that finds the record complete has checked that it names
every obligation that the response leaves to Enforcement and that it agrees with
the rest of the bundle, and, for a proof that the bundle carries, the proof's
signatures, issuer, type, binding and freshness; it has not checked that a proof
establishes what the obligation requires, or that the obligation was met, and
its report says so (EBMP2-076).
EBMP2-071§ — The observed_at of an execution_released event MUST be the
enforcement point's observation of the release boundary that its profile
declares, taken after every operation that can block or wait between the
decision and the release, such as writing the evidence of the consumed
authority, waiting for a lock or obtaining the proof of an obligation, other
than the recording of the execution_released event itself and the steps that
the enforcement point's profile bounds by a declared release guard; the event
MUST carry that guard as release_guard_ms, an integer number of milliseconds
from 1 to 60000, the declared bound on the delay from the observation to the
release, within which the release begins. It MUST NOT be an earlier time, such
as the time at which the event was built, the response was received or the
authority was consumed, when such an operation comes between that time and the
release. An enforcement point that observes, by its own clock immediately after
the release, that the authorization's valid_until had passed before the
release completed MUST record that in the terminal event of the attempt, in an
extension that it marks critical (XR1-005), and MUST NOT take that observation
from the time at which the point of effect answered. EBMP2-050 and EBMP2-072
compare these times with the authorization's validity window. A recorded time is
the producer's statement (EBMP2-004): no verifier establishes from the evidence
alone that it is the true time of the event.
An MCP gateway takes its final observation under the lock of MG1-014, after
reading its monotonic clock, then records the execution_released event with
its release guard, which it makes durable before the write (MG1-016), and opens
the connection to the server; MG1-014 bounds that interval by the declared
release guard and checks it again by a monotonic clock immediately before the
write. Immediately after the write returns it sets released_after_valid_until
in the ecl.ext.mcp-binding.v1 of the terminal event when its own observation
shows that the write may have come after valid_until, and it records the time
at which the server answered as forward_completed_at, as MCP-GATEWAY-1
describes (MG1-015).
3.3 Human intervention
EBMP2-016§ — A human intervention event MUST identify the human or approved
organizational principal (actor), operation, target (target_id), authority
(authority), issue time (issued_at), expiry (expires_at), and reason
(reason).
EBMP2-017§ — An approval MUST expire, be supersedable, and be transaction- bound. A generic chat statement or interface click without this binding is not release authority.
An approval that discharges an obligation before a release shows the obligation
met only while it has not expired: the discharge records the approval's
expires_at as fresh_until, the enforcement point tests it at its final
observation before the release (MG1-013, MG1-014), and a verifier compares it
with the release time and the release guard (EBMP2-070, condition 7).
EBMP2-018§ — Dual-control profiles MUST preserve each independent approval and prove that the required threshold was met before release.
The registered extension ecl.ext.approval-record.v1 records what an approver
was shown (presented, a digest-bound reference), when it was shown
(presented_at), and the minimum review time that applied
(minimum_review_seconds). The registered extension ecl.ext.control-change.v1
records a change to an in-scope control: the control (control), the kind of
change (change: disable, bypass, auto_approve, modify or restore),
the digest of the change (change_digest), its requester (requested_by), its
approvers (approved_by), and when it takes effect and expires (effective_at,
expires_at). Their value schemas are named in the extension registry.
EBMP2-063§ — A human intervention event that approves or rejects and carries
ecl.ext.approval-record.v1 MUST have an issued_at no earlier than
presented_at plus minimum_review_seconds. A consumer MUST reject one that does
not with ecl.rule.approval-review-time-respected.v1.
EBMP2-064§ — An event that carries ecl.ext.control-change.v1 MUST name at
least one approver, none of whom is the requester, and a change of kind
disable, bypass or auto_approve MUST carry an expires_at later than its
effective_at. A consumer MUST reject an event that does not with
ecl.rule.control-change-approved-and-bounded.v1.
3.4 Verification and fleet events
EBMP2-019§ — A verification event MUST identify the verifier, subject,
rules applied, decision, findings (findings, empty when there are none),
registry versions (registry_snapshot, a digest-bound reference), and evidence
boundary (evidence_boundary, what the verifier observed and could not
observe).
EBMP2-020§ — indeterminate MUST remain distinct from pass and fail.
Missing evidence MUST NOT default to pass.
EBMP2-021§ — Fleet events MUST record relevant policy, trust-root, registry, verifier, revocation, and evidence-export frontiers.
4. Causal closure
EBMP2-022§ — A causal predecessor reference MUST include the predecessor's logical identifier and canonical digest.
EBMP2-023§ — A complete bundle MUST contain every required predecessor or declare it as an external immutable dependency available to the verifier.
EBMP2-024§ — A causal graph MUST be acyclic.
EBMP2-025§ — Within one workflow sequence domain, causal predecessors MUST
have lower sequence values than successors. The sequence domain of a record is
the workflow that its workflow.workflow_id names; the sequence of a
predecessor in another workflow is not compared with its successor's.
Note (informative), sequence order across the whole graph: the rule applies to every edge of the causal graph within one workflow, DCMP-2 messages and observations included (EBMP2-059). A producer gives a record a sequence greater than the sequence of every predecessor that it names in the same workflow. A producer that shares no counter with the others, such as an independent observer or an approval service, uses one more than the greatest such predecessor, which it can always do because it holds those predecessors.
EBMP2-026§ — Timestamps MUST NOT be used to fabricate missing causal edges or override contradictory sequence and digest evidence.
EBMP2-027§ — A bundle manifest MUST declare complete, incomplete, or
indeterminate, and enumerate terminal actions, open actions, and unresolved
predecessors.
EBMP2-028§ — A manifest claiming complete MUST be rejected if closure
recalculation yields any open action or unresolved required predecessor.
EBMP2-029§ — Bundle order is not semantic order. Consumers MUST derive causal order from authenticated references and profile rules.
EBMP2-077§ — A causal predecessor MUST have its successor's
workflow.root_trace_id, and MUST be in its successor's workflow or in the
workflow that its successor's workflow.parent_workflow_id names, as a
child workflow names its parent. A predecessor in its successor's workflow
MUST NOT have a greater workflow.attempt than its successor: a later attempt
can follow an earlier one, never the reverse. A verifier MUST report a
predecessor that the bundle holds and that fails this rule with
ecl.rule.bundle-causal-context-consistent.v1.
5. Identity and integrity
EBMP2-030§ — A storage path, database primary key, display label, or tenant- local sequence MUST NOT substitute for canonical content identity.
EBMP2-031§ — The exact immutable version of a record MUST be identified by the pair of its logical identifier and canonical digest.
EBMP2-032§ — A conflicting digest for the same logical identifier MUST be reported as a collision, equivocation, or explicit superseding version according to authenticated lifecycle evidence.
EBMP2-033§ — Evidence needed for offline verification MUST include or immutably reference the trust policy, verification material, registry snapshots, revocation state, and time evidence required by the invoked profile.
6. Privacy and fidelity
EBMP2-034§ — Evidence collection MUST be limited to declared purpose and data classes. Expanded evidence does not automatically mean better evidence.
EBMP2-035§ — Secrets, raw credentials, unnecessary personal data, and unbounded reasoning transcripts MUST NOT be included merely to make a bundle look complete.
EBMP2-036§ — A redacted artifact is a new derivative with a new identity. It MUST bind the sealed source digest, redaction policy, transformation, removed data classes, retained verifier-critical fields, producer, and time.
EBMP2-037§ — Redaction MUST NOT preserve a claim whose required verifier- critical evidence was removed. The claim becomes bounded, indeterminate, or failed as defined by the profile.
7. Lifecycle
EBMP2-038§ — Seal, verify, retain, export, redact, supersede, revoke, legal hold, release hold, and destroy MUST be represented as append-only lifecycle events.
EBMP2-039§ — A lifecycle event MUST reference the prior digest and active lifecycle policy.
EBMP2-040§ — Redact and supersede MUST reference the derived artifact
(derived_artifact), and redact MUST also reference its redaction manifest
(redaction_manifest, EL1-014). A consumer MUST reject a
lifecycle event that lacks one of these references with
ecl.rule.lifecycle-derived-artifact-required.v1. Destroy MUST reference a
destruction proof where the profile requires one.
EBMP2-041§ — Legal hold MUST take precedence over scheduled destruction until a separately authorized release-hold event.
EBMP2-042§ — Lifecycle metadata MUST NOT falsely claim physical deletion beyond the observation and control boundary of its producer.
8. Error and version behavior
EBMP2-043§ — Each invalid vector MUST map to one stable primary error and MAY map to registered subcodes.
EBMP2-044§ — An EBMP-1 adapter MUST identify every lossless, derived, defaulted, omitted, and impossible field. It MUST NOT label a lossy conversion as native EBMP-2.
EBMP2-045§ — A richer implementation-native receipt MAY coexist with EBMP-2 only when a digest-bound causal relation and precedence rule identify which artifact supports which claim.
EBMP2-069§ — A native adapter binding (ecl_adapter_binding_v1) binds one
retained implementation-native object to the ECL objects derived from it. A
consumer MUST reject a binding with ecl.rule.adapter-source-binding-valid.v1
unless each of the following conditions holds:
binding_digestis theecl.digest.sha-256.v1digest of the restricted canonical JSON of the binding without itsbinding_digestmember.binding_idishttps://externalcontrollayer.ai/ns/adapter-binding/sha256/followed by the lowercase hexadecimal SHA-256 digest of the restricted canonical JSON of an object with exactly five members:system,contractandobject_id, each equal to that member ofsource;object_digest, equal tosource.object_digest.value; andecl_object_ids, theobject_idof eachecl_objectsentry, in order.- No two
ecl_objectsentries have the sameobject_id. - Each
ecl_objectsentry has theprotocolDCMP-2and themedia_typeapplication/vnd.ecl.dcmp+json, or theprotocolEBMP-2and themedia_typeapplication/vnd.ecl.ebmp+json. - No two
field_accountingentries have the samesource_path. loss_classisrejectedwhen thedispositionof somefield_accountingentry isrejected, otherwiseloss_accountedwhen thedispositionof some entry isretained_source_only, and otherwiselossless.- Each item of the
target_pathsof afield_accountingentry, and thetarget_pathof eachderived_fieldsentry, is a JSON Pointer (RFC 6901) whose first reference token is an array index in the sense of RFC 6901, section 4, less than the number ofecl_objectsentries. - For each of the six responsibilities, its member of
responsibility_coveragehas thestatusrepresentedexactly when someecl_objectsentry lists the responsibility in itsresponsibilities, and itsobject_idsare theobject_idof each such entry, in the order ofecl_objects. authority_boundaryequals the one value that the schema allows for it.
On a binding that fails its schema, each condition is checked only when every
member it names is present with the JSON type that the schema requires
(DCMP-2 DCMP2-043), so that a member that one condition cannot read does not hide
the findings of the others. The conditions need only the binding. A consumer that
also holds the retained source object or a derived ECL object can compare that
object's digest with source.object_digest or with the digest of its entry.
The dispositions and loss classes are values of the binding; the categories of
EBMP2-044 name what an adapter identifies, and are not values of the binding.
9. Event kinds and states
EBMP2-046§ — An event record's event.kind MUST be the kind its
event_type requires, and an authority or execution event's event.state MUST
be one its event_type permits, as listed below. A consumer MUST reject a
mismatch with ecl.rule.event-type-kind-consistent.v1. The state of an
execution_terminal event is checked under EBMP2-011 only.
event_type |
event.kind |
Permitted event.state |
|---|---|---|
request_received |
request |
not applicable |
decision_recorded |
decision |
not applicable |
authority_issued |
authority |
issued, active |
authority_consumed |
authority |
consumed, revoked, expired, superseded |
release_prepared |
execution |
prepared |
execution_released |
execution |
released |
execution_observed |
execution |
running |
execution_terminal |
execution |
checked under EBMP2-011 |
human_intervention |
intervention |
not applicable |
verification |
verification |
not applicable |
safe_state_transition, fleet_transition |
state |
not applicable |
lifecycle_transition |
lifecycle |
not applicable |
10. Event binding
These rules state what binds an event to the request and response it records.
An execution event binds its request and response when it meets EBMP2-047 to
EBMP2-049 and, for an execution_released event, belongs to an admit
response (EBMP2-050), or, for an execution_terminal event, meets EBMP2-051;
the validity window of EBMP2-050 is checked apart from the binding.
A verifier applies them in the order listed and reports the first failure with
the error code named for it, or with ecl.error.decision-binding-invalid.v1
where none is named. The digest of an object is its ecl.digest.sha-256.v1
digest over restricted canonical JSON. A member a rule reads that is missing or
of the wrong type fails that rule.
EBMP2-047§ — An execution_released or execution_terminal event MUST bind
its request: event.action_id MUST equal the request's action.action_id; when
the event carries them, event.operation, the algorithm and value of
event.parameters_digest, and event.target.uri MUST equal those of the
request's action; and the event's workflow_id, root_trace_id and attempt
MUST equal the request's.
EBMP2-048§ — When a response to the request exists, an execution event MUST
name it: event.decision.uri MUST equal the response's message_id,
event.decision.digest MUST be the response's digest (otherwise
ecl.error.digest-mismatch.v1), and the event's workflow_id, root_trace_id
and attempt MUST equal the response's. When no response exists, the event
MUST NOT carry event.decision.
EBMP2-049§ — An execution event that carries event.authority MUST belong to
an admit response; event.authority.uri MUST equal the authorization's
authorization_id, and event.authority.digest MUST be the digest of the
response's authorization object (otherwise ecl.error.digest-mismatch.v1).
EBMP2-050§ — An execution_released event MUST belong to an admit
response, its observed_at MUST NOT be earlier than the authorization's
valid_from, and its observed_at plus its release_guard_ms MUST be earlier
than the authorization's valid_until (otherwise ecl.error.expired.v1).
EBMP2-072§ — A verifier MUST report with ecl.error.expired.v1, as a finding
of stage 7 of EBMP2-068, an execution_terminal event that binds its request
and response and carries ecl.ext.mcp-binding.v1 with
released_after_valid_until set to true, whatever the extension's critical
member says: the enforcement point states that its release may have come after
valid_until. A forward_completed_at at or after valid_until is not a
finding, since the answer to a request released inside its window can arrive
after the authority has expired; a verifier MUST report such a completion beside
the scope of its verdict as completion_after_window set to true (EBMP2-076).
The terminal event's own observed_at is not compared with the window, since an
effect can end after its authority has expired.
EBMP2-051§ — The state of an execution_terminal event MUST agree with the
outcome of its response under the state-outcome table of the verification
profile. In the reference verification profile
(ecl.verification-profile.reference.v1), whose dependency failures resolve to a
fail-closed deny, held requires hold; challenged requires challenge;
denied requires deny, an admit later denied at enforcement, or no response;
and every other terminal state requires admit.
EBMP2-075§ — When a request carries ecl.ext.mcp-binding.v1, an
execution_released or execution_terminal event of its action that carries
the extension MUST carry, in each occurrence, every member of the request's
value with the same value, so that the evidence names the call that was decided;
and an execution_terminal event in state succeeded or failed MUST carry
the extension with response_digest, the digest of the server's response. A
verifier MUST report an event that binds its request and response and breaks
this with ecl.error.decision-binding-invalid.v1, as a finding of stage 7 of
EBMP2-068. The request's value is that of its first occurrence of the extension.
MCP-GATEWAY-1 forwards only the call that was decided (MG1-005) and records the server's response by its digest (MG1-010). With EBMP2-075 a verifier checks that the gateway's evidence names that call and binds a response; whether the server returned that response is outside what the bundle shows.
EBMP2-052§ — An authority_issued or authority_consumed event MUST belong
to an admit response whose authorization.authorization_id equals the event's
authorization_id; exactly one of its causal predecessors MUST name that
response with the response's digest (otherwise ecl.error.digest-mismatch.v1);
and its workflow_id, root_trace_id and attempt MUST equal the response's.
EBMP2-073§ — An authority_consumed event in state consumed that binds its
response under EBMP2-052 and carries use_count MUST give a value from 1 to the
number of uses that the authorization allows: 1 when its use_policy is
single_use, and otherwise its max_uses when it has one. A bundle MUST NOT
hold, for one authorization, more such events than the authorization allows, or
two such events with the same use_count. A verifier MUST report a bundle that
breaks this with ecl.error.decision-binding-invalid.v1, as a finding of stage
7 of EBMP2-068.
EBMP2-053§ — For one action attempt, a non-execution terminal state
(denied, held, challenged, expired_before_release) MUST NOT coexist with
consumed authority, an execution_released event, an event.authority
reference or side effects.
EBMP2-054§ — A request or decision reference in an event MUST resolve to a
message in the bundle with the same workflow_id, root_trace_id and attempt,
and a digest the reference carries MUST be that message's digest. A
decision_recorded event's outcome and policy MUST equal those of the
response it references.
EBMP2-055§ — A succeeded attempt MUST have an authority_consumed event in
state consumed and an execution_released event for the same attempt. A
verifier MUST apply this rule to every attempt of the bundle, whatever the
causal closure of the rest of the bundle, except an attempt one of whose objects
in the bundle (its request, its response or one of its events) names a causal
predecessor that the bundle does not hold, since that predecessor could be the
event that the rule asks for (EBMP2-059).
EBMP2-074§ — An action attempt with an execution_released event that binds
its request and response, and whose authorization's use_policy is
single_use, MUST also have an authority_consumed event in state consumed
(EBMP2-008), whatever its terminal state. A verifier MUST apply this rule to
every such attempt, whatever the causal closure of the rest of the bundle,
except an attempt one of whose objects in the bundle names a causal predecessor
that the bundle does not hold, since that predecessor could be the consumption
(EBMP2-059). A verifier MUST report an attempt that does not with
ecl.error.decision-binding-invalid.v1, as a finding of stage 8 of EBMP2-068.
EBMP2-056§ — A terminal event MUST carry at most one
ecl.ext.outcome-reason.v1 extension, whose value is exactly {"code": C} with
C an active registered error code. A succeeded terminal MUST NOT carry one.
An expired_before_release terminal, and a denied terminal whose response is
an admit or absent, MUST carry one. A verifier MUST report a terminal event
that fails this rule with ecl.error.semantic-invalid.v1.
11. Signed evidence bundle verification
A signed evidence bundle (ecl_evidence_bundle_v1) carries the ECL-IE-1
envelopes of the DCMP-2 messages and EBMP-2 events of one or more actions in
envelopes, and separately keyed observations in observations. Its
verification is network-free; the caller supplies the trust policy and earlier
versions of it (TRUST-POLICY-1), the evaluation time and the verification
profile (EBMP2-067). A bundle carries decision requests, decision responses and
EBMP-2 events; the bundle verifier does not verify the other DCMP-2 message
types. The evaluation time is the instant at which the verifier evaluates
current status. It is not a cutoff: Version 1.0 does not define an as-of
assessment, and a verifier does not report what a bundle established at an
earlier instant.
EBMP2-057§ — An observation MUST be an EBMP-2 verification event; a
verifier MUST report one that is not with ecl.error.semantic-invalid.v1. A
verifier MUST evaluate every envelope under TRUST-POLICY-1: historical validity
at the object's own time under the policy version the object names, and current
status at the evaluation time under the caller's current policy. The object's
purpose is the one that INTEGRITY-ENVELOPE-1 IE1-039 gives its role: the
decision-request purpose for a decision request, the decision-response purpose
for a decision response, the evidence-event purpose for an EBMP-2 event in
envelopes and the observation purpose for one in observations. The object's
own time is its issued_at for DCMP-2 and its observed_at for EBMP-2, and its
signed verification-time policy MUST be ecl.time.issued-at.v1 and
ecl.time.observed-at.v1 respectively, which IE1-040 step 4 checks; when every
trusted-time countersignature of the envelope is valid (IE1-032 to IE1-036), each
signature that a countersignature covers is evaluated at the earliest attested
time among those that cover it, in place of the object's own time, and each
other signature at the object's own time (IE1-034).
An envelope whose object has no such purpose, no own time or no supplied policy
version (TP1-001) is not historically valid, with
ecl.error.signature-policy-invalid.v1. Otherwise every signer of the envelope
MUST pass IE1-040 step 6 (a) to (c) under the object's purpose and principal
(TP1-004, TP1-005), and the envelope's status is then the code of IE1-040.
An object of the bundle, in envelopes or observations, whose own time, or
the attested time of one of its trusted-time countersignatures, is later than
the evaluation time is not historically valid, with ecl.error.clock-regressed.v1: evidence cannot be
later than the instant at which it is verified.
EBMP2-058§ — A verifier MUST pair objects by identity and never by an
event's claimed decision URI: a request by its message_id and by its
(action.action_id, workflow_id, attempt); a response by its request_id;
an authority event by its authorization_id; an execution event by its
(action_id, workflow_id, attempt). Two requests with one identity or one
action attempt, two responses to one request, an authority event without a
unique response, or an event without a request MUST make the bundle invalid,
and a verifier MUST report each with ecl.error.decision-binding-invalid.v1.
EBMP2-078§ — A bundle MAY hold a continued request and its continuation
(DCMP-2 DCMP2-056), each with its own response and attempt, and then verifies as
one history. For a request that carries continues, a verifier MUST check that
the bundle's objects that continues names are the decision request that
request_id names; the decision response that response_id names, whose
request_id is that request's message_id and whose outcome is challenge;
and the human_intervention event that approval_id names, whose operation
is approve and whose target_id is the continued request's message_id; that
the continuation's workflow, attempt, action, actor, subject and
authority_request relate to the continued request's as DCMP2-056 requires;
that the continuation lists the response and the approval that continues
names among its workflow.causal_predecessors, each by identifier and digest;
and that no other request of the bundle continues the same response. When the
continuation's response is an admit, the verifier MUST also check that every
key that signed the approval holds ecl.purpose.approval.v1 in the
trust-policy version that the approval names, and that no key that signed it is
bound to the request's actor, as EBMP2-070 condition 5 asks of an approval
record; and that the approval's expires_at, when present, is later than the
evaluated_at of that response. A continuation answered with a deny or a new
challenge, as DCMP2-057 requires for an approval that is late, expired or not
acceptable, is correct evidence and is not refused for its approval. A verifier MUST report a
continuation that fails a check with ecl.error.decision-binding-invalid.v1, as
a finding of stage 5 of EBMP2-068. A named object that the bundle does not hold
leaves the causal closure incomplete (EBMP2-059) and keeps bindings out of
established (EBMP2-076); it exempts no other check of this rule.
EBMP2-059§ — A verifier MUST apply DCMP2-044 to DCMP2-051 to every request
and response pair, EBMP2-046 to EBMP2-056, EBMP2-070 and EBMP2-072 to
EBMP2-075 to every event, and the causal rules of section 4, EBMP2-077 among
them, to every object that the bundle holds: each DCMP-2 message and EBMP-2
event in envelopes and each observation. Each object's causal predecessors
MUST be objects of the bundle, with digests equal to the predecessor
references; a signed evidence bundle declares no external dependency
(EBMP2-023), so a predecessor that it does not hold, a DCMP-2 message of a type
that the bundle does not carry included, is unresolved. Identifiers are unique
across envelopes and observations: a DCMP-2 message is identified by its
message_id and an EBMP-2 record by its object_id. An object that a causal
predecessor reference or continues names and that the bundle does not hold
exempts no other check: a verifier runs every check whose objects the bundle
holds, and a failure of such a check gives its finding and verdict as if nothing
were absent; the absent object makes the causal closure incomplete and keeps out
of established every item whose object set names it (EBMP2-076). A proof that
EBMP2-070 lets a discharge entry reference without carrying it is not such an
object: it keeps prerelease-obligations-proofs-verified out of established,
with the reason that the table gives, and changes no verdict. Registry snapshots
and policy references are not objects of the causal graph.
EBMP2-060§ — A verifier MUST report exactly one verdict: invalid when an
object of the bundle, in envelopes or observations, is not historically
valid, a trust dependency of it among the reasons
(TRUST-POLICY-1 TP1-006), an object fails its schema or semantic rules, a
pairing, binding, outcome-reason or causal rule fails, or causal analysis is
indeterminate; incomplete when causal closure is incomplete or a request has
no terminal event; current-trust-failed when the bundle would otherwise be
verified but some trust dependency, a time authority's key included, is
revoked, expired or otherwise not acceptable under the current policy at the
evaluation time; and verified otherwise. A verifier MUST NOT report verified
for a bundle with a current trust failure unless its caller has explicitly
accepted current trust failures for that run, and then MUST report the
acceptance with the verdict. The acceptance applies to the
current-trust-failed verdict only: it never changes an invalid or
incomplete verdict, so it cannot restore evidence that is not historically
valid. The primary finding of an invalid bundle is the one EBMP2-068 gives,
that of an incomplete bundle is ecl.error.causal-closure-incomplete.v1, and
that of a current-trust-failed bundle is the current status of its trust
dependencies that comes first in the order of stage 4 of EBMP2-068.
EBMP2-061§ — A verifier MUST report the evidence level of each terminal
effect: none when the terminal is not historically valid; issuer-asserted
when it is; and corroborated only when the report establishes
envelope-signatures and causal-completeness (EBMP2-076) and, for every side
effect of a succeeded terminal, some observation
is a historically valid verification event with decision pass whose
subject has the side effect's uri and digest, that names the terminal event
among its causal predecessors by object_id and digest, that has the
terminal's workflow.workflow_id, workflow.root_trace_id and
workflow.attempt, observed no earlier than the terminal, produced by a
principal other than the terminal's producer, and signed only with key material
disjoint from the terminal's. Corroboration therefore binds one observation to
one terminal event of one attempt.
A bundle can fail several checks. Its checks are taken in stages, and within a stage the findings have a fixed order, so that every conforming verifier names the same primary finding for an invalid bundle whatever the order of its envelopes (EBMP2-029).
EBMP2-068§ — A verifier MAY stop at the first of the stages below that has a finding, and need not report a bundle's other findings as secondary findings, but MUST report as the primary finding of an invalid bundle the first finding of the earliest stage that has one and, within that stage, the finding that comes first in the order the stage gives, whatever the positions of the objects in the bundle. The stages are:
- the input, where a failure stops verification: the earlier policy versions that the caller supplies (TP1-001),
ecl.error.signature-policy-invalid.v1; the bundle's shape,ecl.error.schema-invalid.v1; its limits on envelopes and observations (EBMP2-062),ecl.error.resource-limit-exceeded.v1; then its schema,ecl.error.schema-invalid.v1; - the payloads: each envelope's payload under INTEGRITY-ENVELOPE-1 IE1-040 step 3, whose strict parse takes the nodes that remain of the payload node limit of EBMP2-062 as its value limit (IE1-037, check 3), so that a payload that exceeds them stops verification before its later checks;
ecl.error.resource-limit-exceeded.v1beforeecl.error.integrity-invalid.v1; - the objects: each object's schema and semantic rules, with the findings that DCMP2-043 gives the object, an object that fails its schema making its envelope not historically valid with
ecl.error.schema-invalid.v1and one that satisfies its schema but fails a semantic rule withecl.error.semantic-invalid.v1; unique object identifiers; the decision-basis rules of DCMP2-053 (ecl.rule.decision-basis-required.v1,ecl.rule.decision-basis-matches-register.v1, andecl.rule.decision-basis-one-way.v1for an input recorded asverifiablewhose source is the request's actor); and the kind of each observation (EBMP2-057);ecl.error.schema-invalid.v1, then the registered rules in ascending order of precedence, thenecl.error.semantic-invalid.v1, so that a bundle whose only defect is one object has the primary finding that DCMP2-043 gives that object; - the signers: each other envelope's historical validity (EBMP2-057, IE1-032 to IE1-036, IE1-040, TRUST-POLICY-1 TP1-006);
ecl.error.clock-regressed.v1,ecl.error.signature-policy-invalid.v1,ecl.error.trusted-time-invalid.v1,ecl.error.trusted-time-required.v1,ecl.error.revoked.v1,ecl.error.expired.v1,ecl.error.resource-limit-exceeded.v1,ecl.error.integrity-invalid.v1, thenecl.error.threshold-not-met.v1; - pairing (EBMP2-058, EBMP2-066, EBMP2-078);
ecl.error.schema-invalid.v1, thenecl.error.decision-binding-invalid.v1; - the binding of each response to its request (DCMP2-044 to DCMP2-051), each pair stopping at its first failure;
ecl.error.request-binding-invalid.v1,ecl.error.digest-mismatch.v1,ecl.error.audience-mismatch.v1, thenecl.error.scope-mismatch.v1; - the binding of each event (EBMP2-047 to EBMP2-054, EBMP2-072, EBMP2-073 and EBMP2-075);
ecl.error.decision-binding-invalid.v1,ecl.error.digest-mismatch.v1, thenecl.error.expired.v1; - causal analysis (section 4 over every object of the bundle, EBMP2-059, EBMP2-077, EBMP2-055 and EBMP2-074);
ecl.error.resource-limit-exceeded.v1, then the registered rules in ascending order of precedence, thenecl.error.decision-binding-invalid.v1; - outcome reasons (EBMP2-056);
ecl.error.semantic-invalid.v1.
A bundle rule that the registry adds names its stage in its stage attribute
(3 or 8) and takes its place in that stage by its precedence, with no change to
this list.
EBMP2-067§ — A verifier MUST apply the verification profile that its caller
names, and the reference verification profile
ecl.verification-profile.reference.v1 when the caller names none; a bundle
does not select its profile. The verifier MUST report the profile it applied
with the verdict.
EBMP2-076§ — With each verdict a verifier MUST report its scope, as the two
lists of the table below: established, the checks that it made for the bundle
and that held, and not_established, the other checks that apply to the bundle
and the statements that no verification establishes, each list in the order of
the table. Where the table names a reason for which an item is not established,
the report MUST give that reason with the item. A report whose verdict is
invalid, or in which no envelope was evaluated, MUST have an empty
established. A verifier MUST NOT list in established a check that it did not
make or that failed, and wherever it shows a verdict to a person it MUST show
the not_established list with it. The registry registries/scope-checks.json
(schema https://externalcontrollayer.ai/ns/schema/scope-checks/1) gives, for
each item of the table and for the corroborated evidence level of EBMP2-061,
its object set, the checks that must hold over it, the trust knowledge they use
and the waivers that it permits. A waiver changes the verdict only (EBMP2-060):
an item whose failure the waiver accepts stays in not_established, and no item
is ever established over less than its whole object set. A verifier MUST NOT list an
item in established unless it ran every check that the registry requires for
the item over the item's whole object set and each held, a check with nothing
to check in that set holding; MUST NOT list an item in established when its
object set names an object that the bundle does not hold, whatever the verdict;
MUST NOT report the corroborated level unless every check and item that the
registry requires for it held; and MUST report, for each item in established,
the checks that it ran for the item and the objects that they covered. Every
report MUST state, in the member evaluation_time, the evaluation time that it
used, and every report MUST carry the member unresolved_predecessor_ids, the
identifiers of the predecessors that it could not resolve, which is non-empty
only when the verdict is incomplete. A report MUST carry, beside the scope, the
informational member completion_after_window set to true when a terminal
event records a forward_completed_at at or after the authorization's
valid_until (EBMP2-072); the member is not a finding and changes no verdict.
An offline package has the scope of the report that IE1-022 gives it.
| Item | What it says | When it is established |
|---|---|---|
envelope-signatures |
Each envelope's and observation's payload digest and signatures verify under the trust-policy version that its object names, at the object's own time or its trusted time, and the caller's current policy revokes none of its trust dependencies at or before its time (EBMP2-057, TRUST-POLICY-1 TP1-006). | The verdict is verified, current-trust-failed or incomplete. |
current-trust |
Each trust dependency, each signing key of each envelope and observation and of each time attestation, is acceptable under the caller's current trust policy at the evaluation time (EBMP2-060, TRUST-POLICY-1 TP1-006). | The verdict is verified or incomplete and every trust dependency's current status is valid; an acceptance of current trust failures never establishes it. |
trusted-time |
A separate time authority attests a time for each signature of each envelope and observation, binding that signature (IE1-033), and each signature was valid at that time (IE1-032 to IE1-035); each time authority's key is valid at its attested time under the policy version its attestation names and is not revoked by the caller's current policy at or before that time (TRUST-POLICY-1 TP1-006). | As envelope-signatures, and every signature of every envelope and observation is covered by a valid trusted-time countersignature. |
object-rules |
Each signed object satisfies its schema and its semantic rules, the one-way rule for a recorded decision basis among them (EBMP2-068, stage 3). | As envelope-signatures. |
decision-basis-recorded |
Each decision response records its decision basis (DCMP2-053). | As envelope-signatures. |
decision-basis-register |
Each decision basis agrees with the Consequential Action Register that the caller supplied (DCMP2-053). | As envelope-signatures. It applies only when the caller supplies a register. |
bindings |
Each response binds its request, each event binds its request, response and authority, with the digests and use counts that they name, each continuation binds the request, challenge and approval that it continues, and each terminal event carries the outcome reason that its state and response require (DCMP2-044 to DCMP2-051, EBMP2-047 to EBMP2-054, EBMP2-056, EBMP2-073, EBMP2-075, EBMP2-078). | As envelope-signatures. |
release-within-authority |
Each recorded release time lies within the authorization's validity window with the release guard added, and no terminal event records that a release may have come after valid_until (EBMP2-050, EBMP2-072). |
As envelope-signatures, when the bundle records a release; when it records none, it is not established, with the reason no-release-recorded. |
causal-completeness |
Over every object that the bundle holds, the DCMP-2 messages and EBMP-2 events in envelopes and the observations: every causal predecessor is in the bundle with the digest that names it, in the workflow, trace and attempt that EBMP2-077 allows, the graph is acyclic and identifiers are unique, and every request has a terminal event (section 4, EBMP2-055, EBMP2-059, EBMP2-074, EBMP2-077). |
The verdict is verified or current-trust-failed. |
prerelease-obligations-discharge-recorded |
Each release records a discharge of every obligation that its decision left to Enforcement before the release, checked no later than the release was observed and fresh at the release (EBMP2-070). | As envelope-signatures. It applies only when the verifier applied EBMP2-070 to an execution_released event whose response carries such an obligation. |
prerelease-obligations-proofs-verified |
Each pre-release discharge carries its proof, and each proof is a valid ECL object under the trust policy, signed by the issuer the obligation names, of a type the obligation admits, bound to this exchange and fresh at the release (EBMP2-070). | As prerelease-obligations-discharge-recorded, and only when every entry's proof is an ECL object that the bundle carries and that passes conditions 5 to 8 of EBMP2-070; when an entry's proof is not an ECL object that the bundle carries, it is not established, with the reason proof-not-ecl-object, and when the decision names no issuer for an obligation, with the reason issuer-not-named; when both apply, the report gives proof-not-ecl-object. |
prerelease-obligations-met |
That each pre-release obligation was met: the verifier checks a proof's signatures, issuer, type, binding and freshness, not what the proof shows. | Never; it applies as prerelease-obligations-discharge-recorded does. |
post-release-obligations-met |
That the obligations to be met during or after execution were met, which the verifier does not check. | Never; it applies only when the response that an execution_released event records carries an obligation whose satisfaction is during_execution or after_execution. |
recorded-times-true |
That each recorded time is the true time of its event: each producer states its own times (EBMP2-004, EBMP2-071). | Never. |
effects-occurred |
That the effects happened: a terminal event is its producer's statement, and corroboration is another signed statement, an observation bound to that terminal event and its attempt (EBMP2-015, EBMP2-061). | Never. |
deployment-assessed |
That the deployment that produced the evidence has been assessed or meets ECL-1. | Never. |
statements-true |
That the statements in the bundle are true (section 1), among them the enforcement point's statements of what it checked and released. | Never. |
EBMP2-062§ — A verifier MUST declare finite limits on the number of
envelopes and observations and on the decoded payload nodes of a bundle, MUST
count the nodes of each payload while it parses it, against those that remain
of the bundle's limit, so that it builds no payload beyond that limit (IE1-029),
and MUST refuse a bundle that exceeds them before verifying signatures, with
ecl.error.resource-limit-exceeded.v1. For example, a verifier can declare
limits of 256 envelopes, 256 observations and 100 000 nodes.