Faults and Acknowledgments
The formal Vultron protocol defines per-state-machine error and acknowledgment
shorthands (RE/EE/CE/GE for faults; RK/EK/CK/GK for
acknowledgments). In the AS2 wire vocabulary, both concerns are served by
mechanisms partitioned on different axes from the formal set. Neither is
unimplemented; both are shaped differently.
This page is the primary reference for Create(ProcessingFault) and the refusal notice.
It is the second home of two activities documented elsewhere: the ledger negative acknowledgment, Reject case ledger entry, and Close report as an ordinary as:Reject on the fault axis.
The normative mapping is the Fault and Acknowledgment Mechanism Evolution requirements group; the design rationale is in ADR-0083 and Activity Vocabulary Design.
Occasions
| When… | Send | Send to | Sent by | Distinguished by | Details |
|---|---|---|---|---|---|
| a Participant has received a message it cannot process | Create(ProcessingFault) |
the case manager | not yet checked | — | Create processing fault |
| the Case Manager has refused a participant's assertion because a precondition did not hold | Reject(Activity) |
a participant | the case manager | — | Refusal notice |
Fault reporting
The formal set partitions faults by which state machine the unexpected
message belonged to (RE, EE, CE, GE). The AS2 vocabulary partitions
faults by why the message failed — an axis that is actionable to a receiver
in a way that state-machine origin is not.
| Failure mode | Wire activity | Formal counterpart |
|---|---|---|
| Received and not understood | Create(ProcessingFault) |
RE / EE / CE / GE |
| Received, understood, processing failed | Create(ProcessingFault), internal-error class |
RE / EE / CE / GE |
| Received, understood, declined | as:Reject |
RE / EE / CE / GE |
| Received, understood, needs explanation | Add(Note → Case) |
RE / EE / CE / GE |
Create processing fault
Tells the sender that its message was received but not understood, or understood but its processing failed on the receiver's internal error. It names the failed activity and a failure class, with no implementation diagnostics. This fault serves the not-understood purpose of the formal RE, EE, CE and GE messages.
- When… a Participant has received a message it cannot process
- Send:
Create(ProcessingFault) - Send to: the case manager
- Sent by: not yet checked
- Seen in: How to Report a Protocol Fault
- Mapping status: evolved
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"id": "urn:uuid:34196e85-2d05-47d3-9a60-33df11eda687",
"name": "https://vultron.example/organizations/vendorco Create urn:uuid:d1eaeefe-c467-49e5-9fa9-6aa124b55b8e",
"to": [
"https://vultron.example/case-actors/vendorco-case-actor"
],
"actor": "https://vultron.example/organizations/vendorco",
"object": {
"@context": "https://certcc.github.io/Vultron/ns/context.jsonld",
"type": "ProcessingFault",
"id": "urn:uuid:d1eaeefe-c467-49e5-9fa9-6aa124b55b8e",
"inReplyTo": "https://vultron.example/case-actors/vendorco-case-actor/announce/9",
"published": "2026-10-23T20:17:38+00:00",
"updated": "2026-10-23T20:17:38+00:00",
"failureClass": "https://certcc.github.io/Vultron/ns/errors/StatusAssertionRefused/ImpossibleState"
}
}
In the example, a Vendor cannot apply a ledger entry from the CASE_MANAGER whose participant status names an impossible state, and reports the fault to the CASE_MANAGER (RSH-09-006).
A fault also answers a message that the receiver understood but failed to process.
When the use case that processes a received activity fails on the receiver's own internal error, the receiver keeps nothing of the act and its inbox sends the sender one Create(ProcessingFault) (ASK-07-010).
Its failure class is https://certcc.github.io/Vultron/ns/errors/InternalProcessingError, and it names the failed activity in inReplyTo and nothing else, with no cause and no diagnostics (ASK-07-006).
The receiver sends it after the act is rolled back, so the rollback does not remove it.
The inbox retries an activity it holds and replays later, such as one deferred until its case arrived, a bounded number of times (IE-06-004).
For such an activity, the fault goes out only when the inbox stops retrying: when it moves the activity to its dead-letter store, or when it drops the activity at once because processing raised a protocol violation.
The fault is a notice, not a recovery: the sender decides whether to send the activity again, and a later delivery runs the act again with the body the receiver kept from the first delivery (IE-10-001).
Its id is derived from the failed activity and the failure class, so a second failure of the same activity builds no second fault.
The receiver may send the same fault again, with the same id and body, and the sender discards the repeat.
This internal-error fault answers only a use case that failed: a body the inbox could not parse into an activity that names its sender never reaches one, and a processing fault that fails is not answered with another, so two receivers never answer each other's faults.
The prototype inbox does not yet authenticate a sender, so it answers the actor the activity names; a fault to an unauthenticated sender is not allowed (ASK-07-002).
An activity the receiver will try again later is answered only when it stops trying, so a later success never follows a fault.
This implementation does not ledger the fault, as it ledgers no processing fault; whether a case-attributable fault belongs in the ledger is not yet decided (ASK-07-008).
The formal fault shorthands have no dedicated wire counterpart (MSM-05-001).
Refusal notice
Tells the participant that its assertion was received and understood but refused, so it had no effect. It carries the refused activity inline and names the refusing guard's reason. This notice serves the declined purpose of the formal RE, EE, CE and GE messages.
- When… the Case Manager has refused a participant's assertion because a precondition did not hold
- Send:
Reject(Activity) - Send to: a participant
- Sent by: the case manager
- Mapping status: evolved
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Reject",
"id": "urn:uuid:0eef8ba6-b44b-4dd5-8aa9-e7851e76da5c",
"name": "https://vultron.example/case-actors/vendorco-case-actor Reject https://vultron.example/organizations/vendor Add ACCEPTED VF NONE pxa to https://vultron.example/cases/1/participants/vendor",
"context": "https://vultron.example/cases/1",
"inReplyTo": "urn:uuid:d4ad19d6-8d2b-4cae-b606-ed46f9930c2d",
"summary": "fix readiness (VF) asserted before vendor awareness (Vf) was recorded for the Vendor",
"to": [
"https://vultron.example/organizations/vendor"
],
"actor": "https://vultron.example/case-actors/vendorco-case-actor",
"object": {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Add",
"id": "urn:uuid:d4ad19d6-8d2b-4cae-b606-ed46f9930c2d",
"name": "https://vultron.example/organizations/vendor Add ACCEPTED VF NONE pxa to https://vultron.example/cases/1/participants/vendor",
"published": "2026-10-23T20:17:38+00:00",
"updated": "2026-10-23T20:17:38+00:00",
"actor": "https://vultron.example/organizations/vendor",
"target": "https://vultron.example/cases/1/participants/vendor",
"object": {
"id": "https://vultron.example/cases/1/participants/vendor/status/3",
"type": "ParticipantStatus",
"name": "ACCEPTED VF NONE pxa",
"context": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0",
"published": "2026-06-01T19:12:00+00:00",
"updated": "2026-06-01T19:12:00+00:00",
"attributedTo": "https://vultron.example/organizations/vendor",
"rmState": "ACCEPTED",
"vfState": "VF",
"caseEngagement": true,
"cvdRole": [
"VENDOR"
],
"caseStatus": {
"id": "https://vultron.example/cases/1/status/1",
"type": "CaseStatus",
"name": "NONE pxa",
"context": "https://vultron.example/cases/1",
"published": "2026-06-01T19:12:00+00:00",
"updated": "2026-06-01T19:12:00+00:00",
"emState": "NONE",
"pxaState": "pxa",
"@context": "https://certcc.github.io/Vultron/ns/context.jsonld"
},
"@context": "https://certcc.github.io/Vultron/ns/context.jsonld"
}
}
}
In the example, the CASE_MANAGER refuses a Vendor's status assertion because a precondition did not hold.
The refusal notice is the one answer a precondition guard's refusal owes the asserting participant (CLP-05-001).
Its object is the refused activity, inline, and inReplyTo names that activity's id.
It goes to the asserting participant alone, and its summary gives the refusing guard's reason.
Its id is derived from the CASE_MANAGER and the refused activity, so a refused activity is answered once.
The CASE_MANAGER sends it only when a precondition guard refuses the assertion.
It sends none when the sender guard refuses the sender, when the activity repeats one whose effect is already in place, when the activity is a redelivery, or when the case is fully closed (CLP-02-005).
It sends none for a delivery that an answer of its own replaces, such as the ER to an embargo Invite or Accept once the case is public, an exploit is public, or attacks are observed, because that answer already tells the sender (CLP-10-026).
It sends none for a failure inside the CASE_MANAGER, which is not a refusal: the internal-error fault above answers that.
A refusal is not a case event, so the notice is not ledgered (CLP-05-002).
The participant closes the pending assertion it recorded for the refused activity (CLP-06-006). It writes no case state and does not send the assertion again.
A Reject from the CASE_MANAGER of an Invite or an Offer the participant sent is both a decline and a refusal.
The two have the same shape, and either way the request had no effect.
The participant reads it with the use case that reads the decline, which closes the pending entry.
as:Reject is overloaded
as:Reject carries both error semantics and legitimate protocol refusals
(close_report, reject_invite_to_embargo_on_case,
reject_invite_actor_to_case, reject_case_proposal,
reject_case_ownership_transfer). A receiver cannot infer "error" from the
verb alone. The reject_case_ledger_entry semantic is also an as:Reject but
it is the ledger NAK described below — not an ordinary refusal (MSM-05-003).
Acknowledgment
The formal set defines per-message positive acknowledgments keyed by state
machine: RK (RM), EK (EM), CK (CS), GK (General). The AS2
vocabulary splits them into two mechanisms.
RK — a real wire activity
RK (Report Acknowledgment) is realized as Read(Offer(VulnerabilityReport))
(MessageSemantics.ACK_REPORT). Report submission is not ledger-replicated, so
an explicit per-message acknowledgment is the correct mechanism here.
Status: direct mapping — see MSM-01-008.
EK / CK / GK — cumulative and implicit via hash-chain
EK, CK, and GK have no per-message wire equivalents. Ledger-replicated
state is acknowledged cumulatively and implicitly via hash-chain continuity:
- A participant receiving
Announce(CaseLedgerEntry)whoseprev_log_hashmatches its local ledger tail says nothing — the match is the acknowledgment. - On a mismatch the participant emits
Reject(CaseLedgerEntry), whereupon the CASE_MANAGER replays all entries after the last accepted hash.
This is negative acknowledgment with gap-fill replay — structurally closer to TCP cumulative ACK/SACK than to per-message positive acknowledgment. A matching hash proves receipt of the entire log prefix, not only one message.
Status: evolved — see MSM-05-002.
Liveness
Between Announce(CaseLedgerEntry) deliveries, silence and unreachability are
observationally indistinguishable. A participant that has received all entries
and a participant that is unreachable look identical until a new entry arrives.
Implementations MAY rely solely on Announce deliveries for liveness inference
without emitting a dedicated periodic heartbeat (MSM-05-006).
NAK path bounding
The replay triggered by Reject(CaseLedgerEntry) is rate-limited per peer to prevent amplification; see SYNC-15-003.
The outbox delivery budget (ADR-0066) provides a finite total-attempt bound independent of the replay rate limit.
See also
- Activity Vocabulary Design — why faults are partitioned by failure mode and acknowledgment moved to the hash chain.
- How to Report a Protocol Fault — how-to guide for fault activities.
- How to Acknowledge a Report —
how-to guide for
RK. - Ledger Replication — the wire format for
Announce(CaseLedgerEntry)andReject(CaseLedgerEntry).