Skip to content
How-to guide

How to Report a Protocol Fault

Use this guide when a message you received cannot be processed as sent. Vultron partitions fault reporting by failure mode, so your first job is to decide which of three modes you are in, and your second is to send the activity that mode calls for. You finish with the sender informed of why the message failed, in a form it can act on.


Prerequisites

  • A running Vultron instance you can drive. The demo containers are the quickest route — see Tutorial: Run the Receive-Report Demo for the two steps that get you a container shell.
  • An actor identity on that instance, holding the role each step names.
  • Working knowledge of the ActivityStreams verb, object, and target shape, summarized in Vultron and ActivityPub.
  • Working knowledge of the case model: the objects a case is made of, in The Case Model, and who is allowed to change them, in The CASE_MANAGER and the Case Ledger. Where a guide's messages are case-scoped, they route through the CASE_MANAGER and their results appear on the case ledger.

  • The message that failed, and the sender's actor Uniform Resource Identifier (URI).

  • Your own determination of why it failed. The modes below are mutually exclusive.

Choose the failure mode

Failure mode Send
Received but not understood — format error, unknown type, parse failure Create(ProcessingFault)
Received and understood, but your processing failed — an internal error, nothing kept Create(ProcessingFault), internal-error class
Received and understood but declined — invalid state, rule violation, unauthorized as:Reject
Received and understood, but the condition needs narrative explanation A case note: one Add(Note) carrying the note inline

Three mechanisms cover every case (MSM-05-001).

  • If you could not parse the message, send Create(ProcessingFault). Do not reject it — rejection claims you understood it.

    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
    • Reference: Create processing fault
    Example activity
    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Create",
      "id": "urn:uuid:086de57f-b28b-4d80-8b8c-1df1a2ed8ff8",
      "name": "https://vultron.example/organizations/vendorco Create urn:uuid:975f2884-434d-4c0d-91b2-83bea6bb3e67",
      "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:975f2884-434d-4c0d-91b2-83bea6bb3e67",
        "inReplyTo": "https://vultron.example/case-actors/vendorco-case-actor/announce/9",
        "published": "2026-10-23T20:17:06+00:00",
        "updated": "2026-10-23T20:17:06+00:00",
        "failureClass": "https://certcc.github.io/Vultron/ns/errors/StatusAssertionRefused/ImpossibleState"
      }
    }
    
  • If you understood the message but your own processing of it failed, send Create(ProcessingFault) with the internal-error class, after your act has rolled back, and say nothing about the cause (Faults and Acknowledgments). The Vultron inbox sends this one for you.

  • If you understood the message and are refusing it, send as:Reject. Say which rule or state made it inadmissible.
  • If the condition needs a human to read it, add a note to the case. See How to Post a Status Update or a Case Note.

The failure mode is the actionable axis. Knowing a message was not understood tells the sender to check its serialization; knowing it was declined tells the sender to check the protocol state it assumed.


Interpret an incoming as:Reject

as:Reject carries ordinary protocol refusals as well as faults — close_report, reject_invite_to_embargo_on_case, reject_invite_actor_to_case, reject_case_proposal, and reject_case_ownership_transfer among them.

Read the object and the context before treating a Reject as an error.

Never infer a fault from the verb alone

A receiver MUST NOT infer error semantics from as:Reject by itself (MSM-05-003). Treating every Reject as a fault turns each legitimate refusal into a false alarm.

A Reject from the CASE_MANAGER whose object is an activity you sent, carried inline, is a refusal notice. It tells you a precondition did not hold, so your assertion had no effect, and its summary gives the reason. Close the pending assertion you recorded for that activity, and do not send it again because of the notice (CLP-05-001). A refusal notice of an Invite or an Offer you sent reads as the decline of that request.

A Reject(CaseLedgerEntry) is a third thing again: it is the ledger negative acknowledgment, and it asks the CASE_MANAGER to replay a missing prefix rather than reporting a fault. See Faults and Acknowledgments.


Verify

The sender holds your fault report, and the exchange that failed has not advanced any state on your side. A fault report is not a state transition.


Further reading

  • Faults and Acknowledgments — the wire format for each mechanism, and why the formal RE, EE, CE, and GE shorthands have no dedicated wire activity
  • Activity Vocabulary Design — why the implementation partitions faults by failure mode rather than by state machine
  • Message Types — the formal error shorthands these mechanisms realize

Name collision

VultronError in vultron/errors.py is a Python exception base class. It is unrelated to the protocol wire-level fault activities above.