Skip to content
Reference

Messages and Vocabulary

This page is normative

This page is considered a core part of the Vultron Protocol. This is a normative section of the documentation.

The whole specification on one page

The specification is also published as a single page that holds every section and annex in order, for printing or for searching the whole text at once.

This page holds section 14.2 of the Vultron Protocol Specification, part of its Requirements Catalog. Each row is one requirement, and the rest of the documentation links a requirement to its row here.

14.2 Messages and Vocabulary

The requirements on the messages participants exchange: how each protocol message maps onto an ActivityStreams 2.0 (AS2) activity, the vocabulary objects, message validation, semantic extraction, what an actor knows, and the wire forms of field values.

Vultron Semantic–AS2 Mapping

Defines the authoritative mapping from each Vultron semantic message type (MessageSemantics enum value) to its ActivityStreams 2.0 wire representation. This is the single source of truth for how Vultron domain semantics are encoded as AS2 activity structures, isolating transport and wire-encoding concerns from the core domain logic and case-state machines.

General Mapping Conventions

ID Priority Requirement Related
VAM-01-008 MUST Inbound activities represent state-change notifications, not commands; the AS2 activity type used for each semantic MUST reflect the completed state transition, not a requested action

Each activity reports an act that the sender has already performed, such as an Offer it made or an Invite it sent, and not an act it asks the receiver to perform. If an activity stated a requested action, the receiver could not know whether the sender's state had changed, and two participants could record different states for the same case. A completed-action type makes each message match the change that it reports.
VAM-01-011 MUST Each of these case records MUST enter a case only by its one attaching message, which carries it inline or by reference. A CaseStatus enters by the Add(CaseStatus) that attaches it (VAM-08-002). A ParticipantStatus enters by the Add(ParticipantStatus) that attaches it (VAM-08-004). A proposed EmbargoEvent enters by the Invite that proposes it (VAM-05-005). A Note enters by the Add(Note) that attaches it (VAM-07-002).

These records mean nothing apart from their case, so a separate Create would add a "created but not attached" state needing its own ledger entry and replica step (CLP-07-013, RSH-08-004) for no gain (ADR-0137). A Create(Note), with or without a case context, is a deferred form recorded as an open question (MS-18-001). These records get one attaching message each. The Create a CASE_MANAGER ledger entry carries as its payload snapshot records a change; it is not a received message.
Constrains: VAM-08-002 — the Add(CaseStatus) is the only way a case status enters a case
Constrains: VAM-08-004 — the Add(ParticipantStatus) is the only way a participant status enters a case
Constrains: VAM-05-005 — the Invite is the only way a proposed embargo event enters a case
Constrains: VAM-07-002 — the Add(Note) is the only way a note enters a case
VAM-01-019 MUST NOT A received Create whose object is a CaseStatus, a ParticipantStatus, an EmbargoEvent, a CaseParticipant or a Note is not a protocol message. Its receiver MUST NOT change the case file because of it.

Each of these records enters a case only by its attaching message (VAM-01-011) or as the CASE_MANAGER's consequence of an act it records (VAM-01-020). A separate Create would add a "created but not attached" state needing its own ledger entry and replica step (CLP-07-013, RSH-08-004) for no gain (ADR-0137).
Derives from: VAM-01-011
VAM-01-020 MUST A CaseParticipant MUST come into existence only as the CASE_MANAGER's consequence of an act that it records.

A participant record means nothing apart from its case, and the CASE_MANAGER records each act that changes the case. A participant made any other way would be a case-file change outside the ledger (CLP-07-013, ADR-0137).

Report Management Messages

ID Priority Requirement Related
VAM-02-001 MUST CREATE_REPORT MUST be represented as Create(VulnerabilityReport)

A report is a new object, and AS2 records a new object with Create. The sender and every receiver must use the same wire form, or a receiver classifies the activity as unknown.
VAM-02-002 MUST SUBMIT_REPORT MUST be represented as Offer(VulnerabilityReport)

To submit a report is to give it to a receiver for a decision, which is what an AS2 Offer means. The receiver answers the same Offer with Read, Accept, TentativeReject or Reject (VAM-02-003 to VAM-02-006), so each answer wraps the submission that it answers.
VAM-02-003 MUST ACK_REPORT MUST be represented as Read(Offer(VulnerabilityReport))

An acknowledgement says only that the receiver has the report, not that it has judged the report. AS2 Read states that an actor has seen an object, so the Reporter gets a receipt that it cannot confuse with a validation.
VAM-02-004 MUST VALIDATE_REPORT MUST be represented as Accept(Offer(VulnerabilityReport)). This is the direct report recipient's RV; a participant that joined through an Invite sends RV as the full-case Invite reply of VAM-04-012.

Validation is the receiver's answer to the submitted Offer, so it wraps that Offer. A participant that joined through an Invite never received the Offer, so it answers the full-case Invite instead (ADR-0121). The two forms differ in their inner object, so a receiver tells them apart by shape.
VAM-02-005 MUST INVALIDATE_REPORT MUST be represented as TentativeReject(Offer(VulnerabilityReport)). This is the direct report recipient's RI; a participant that joined through an Invite sends RI as the full-case Invite reply of VAM-04-013.

TentativeReject says that the receiver does not accept the report now but keeps it open. That matches RM Invalid, from which a report can still become Valid. A Reject would read as final and is the form of the close in VAM-02-006.
VAM-02-006 MUST CLOSE_REPORT MUST be represented as Reject(Offer(VulnerabilityReport)). This is the direct report recipient's RC; a participant that joined through an Invite sends RC as the full-case Invite reply of VAM-04-014.

A close ends the receiver's work on the report, so the answer is a final Reject of the submitted Offer. Its activity type, Reject and not TentativeReject, tells it apart from an invalidation (VAM-02-005).

Case Management Messages

ID Priority Requirement Related
VAM-03-001 MUST CREATE_CASE MUST be represented as Create(VulnerabilityCase)

A case is a new object, and AS2 records a new object with Create. One wire form lets every receiver classify the creation of a case by shape.
VAM-03-003 MUST ENGAGE_CASE MUST be represented as Join(VulnerabilityCase)

Engage and defer are the two answers that a participant gives about its own work on a case. AS2 Join and Ignore (VAM-03-004) state those two answers, and both have the case as their object, so a receiver tells them apart by activity type alone.
VAM-03-004 MUST DEFER_CASE MUST be represented as Ignore(VulnerabilityCase)

A defer says that the participant sets the case aside but stays in it. AS2 Ignore states that. It differs from Leave (VAM-03-006), which ends the participant's part in the case.
VAM-03-005 MUST ADD_REPORT_TO_CASE MUST be represented as Add(VulnerabilityReport)[target=VulnerabilityCase]

A report is attached to a case that already exists, so the message is an Add with the case as its target. The target tells a receiver which case the report goes into.
VAM-03-006 MUST CLOSE_CASE MUST be represented as Leave(VulnerabilityCase)

A close ends the participant's part in the case, which is what AS2 Leave means. Its object, the case and not an EmbargoEvent, tells it apart from a withdrawal from an embargo, which is also a Leave (VAM-05-011).
VAM-03-007 MUST UPDATE_CASE_OWNER MUST be represented as Update(VulnerabilityCase), carrying the case with its new owner in attributedTo. Only the CASE_MANAGER sends it, as its own act on an accepted ownership transfer (CM-21-002), and it is not handled on receipt (SE-05-002, ADR-0140).

An entry's semantic type is the registry's label for its payload (ADR-0140), so the CASE_MANAGER's own case-field change needs a registry entry of its own.
Satisfies: CM-21-002

Actor Suggestion and Invitation Messages

ID Priority Requirement Related
VAM-04-001 MUST OFFER_ACTOR_TO_CASE MUST be represented as Offer(Actor)[target=VulnerabilityCase]

This is the first message of the actor-suggestion exchange: a participant suggests an Actor to the CASE_MANAGER. A suggestion names only an actor (CM-16-001, CM-16-003), so the object is the Actor itself. The pattern names cross: SuggestActorToCasePattern is bound to OFFER_ACTOR_TO_CASE, and OfferActorToCasePattern is bound to OFFER_CASE_PARTICIPANT (VAM-04-010).
VAM-04-002 MUST ACCEPT_OFFER_CASE_PARTICIPANT MUST be represented as Accept(Offer(CaseParticipant)[target=VulnerabilityCase])

The Case Owner decides on the CASE_MANAGER's Offer(CaseParticipant) (VAM-04-010), which carries the proposed roles, so its Accept wraps that offer and not the first Offer(Actor). The inner object type tells this Accept apart from the CASE_MANAGER's answer to the recommender (VAM-04-016). See CM-16-003, CM-16-004 and ADR-0026.
VAM-04-003 MUST REJECT_OFFER_CASE_PARTICIPANT MUST be represented as Reject(Offer(CaseParticipant)[target=VulnerabilityCase])

As in VAM-04-002, the Case Owner's Reject wraps the CASE_MANAGER's Offer(CaseParticipant) and not the first Offer(Actor). The inner object type tells it apart from the CASE_MANAGER's answer to the recommender (VAM-04-017).
VAM-04-004 MUST INVITE_ACTOR_TO_CASE (the stub Invite) MUST be represented as Invite(Actor)[target=VulnerabilityCaseStub], with the stub shaped as CM-11-013 requires.

An invitee has not yet joined the case, so the Invite carries a stub that stands for the case (ADR-0114). The stub has its own type (CM-11-013), so a receiver tells the stub Invite from the full-case Invite (VAM-04-011) by the type of the target, with no lookup.
VAM-04-005 MUST ACCEPT_INVITE_ACTOR_TO_CASE MUST be represented as Accept(Invite(Actor)[target=VulnerabilityCaseStub])

The invitee answers the Invite that it received, so its Accept wraps that Invite. The stub target tells this Accept apart from the RV reply to the full-case Invite (VAM-04-012).
VAM-04-006 MUST REJECT_INVITE_ACTOR_TO_CASE MUST be represented as Reject(Invite(Actor)[target=VulnerabilityCaseStub])

A Reject of the stub Invite is the invitee's refusal to join (CM-11-007). The stub target tells it apart from the RC reply to the full-case Invite (VAM-04-014), which a participant that has already joined sends to close the report.
VAM-04-007 MUST OFFER_CASE_OWNERSHIP_TRANSFER MUST be represented as Offer(VulnerabilityCase): the one Offer of a transfer, which the CASE_MANAGER makes from the Case Owner's proposal (CM-21-005).

The Offer asks the transferee to become the Case Owner, and an AS2 Offer gives an object to a receiver for a decision. The CASE_MANAGER makes the Offer, so the transfer goes through the actor that records it in the case ledger (CM-21-005). An Offer of the case differs by shape from the Case Owner's proposal, an Offer of an Offer (VAM-04-018).
VAM-04-008 MUST ACCEPT_CASE_OWNERSHIP_TRANSFER MUST be represented as Accept(Offer(VulnerabilityCase))

The transferee answers the Offer that it received, so its Accept wraps that Offer. The CASE_MANAGER then moves the CASE_OWNER role (CM-21-002 to CM-21-004).
VAM-04-009 MUST REJECT_CASE_OWNERSHIP_TRANSFER MUST be represented as Reject(Offer(VulnerabilityCase))

A Reject of the Offer leaves the current Case Owner in place. It wraps the same Offer as the Accept of VAM-04-008, so a receiver tells the two answers apart by activity type.
VAM-04-010 MUST OFFER_CASE_PARTICIPANT MUST be represented as Offer(CaseParticipant)[target=VulnerabilityCase]

This is the second message of the actor-suggestion exchange. When it has the Offer(Actor) (VAM-04-001), the CASE_MANAGER offers case participation to the Case Owner as a CaseParticipant that carries the proposed roles (CM-16-003). The Case Owner answers this offer (VAM-04-002, VAM-04-003), so the roles that it decides on are in the object that it answers. See CM-16-004 and ADR-0026.
VAM-04-011 MUST The full-case Invite (CM-11-010) MUST be represented as Invite(Actor)[target=VulnerabilityCase], with the case referenced by its plain URI in target (AKM-02-003) and the CASE_MANAGER's ledger position carried as the Invite's ledgerPosition, the object with logIndex and entryHash every case activity carries (SYNC-18-001), for example "ledgerPosition": {"logIndex": 3, "entryHash": "<hash>"}. The position of an empty ledger is logIndex -1 with the case's genesisHash as entryHash (CLP-08-004). A receiver MUST refuse an Invite whose ledgerPosition is missing or does not parse as a ledger position, naming the activity and writing nothing (SYNC-18-003).

The participant already holds the case from the replica, so the Invite references it by ID. Because the stub is its own type (VAM-04-004, CM-11-013), this shape and the stub Invite differ in their target's type and are classified without a lookup. The position is the sender's ledger tail, which every case activity states in the same property (ADR-0156), so the Invite's floor needs no slot of its own.
VAM-04-012 MUST The RV reply to the full-case Invite MUST be represented as Accept(Invite(Actor)[target=VulnerabilityCase]), carrying the replier's ledger position as its ledgerPosition, in the form VAM-04-011 defines (CM-11-011, SYNC-18-001).

The RV reply answers the full-case Invite, so it wraps that Invite. Its ledger position states the replier's ledger tail when it judged the case (ADR-0156). The full-case target tells this reply apart from the Accept of a stub Invite (VAM-04-005).
VAM-04-013 MUST The RI reply to the full-case Invite MUST be represented as TentativeReject(Invite(Actor)[target=VulnerabilityCase]), carrying the replier's ledger position as its ledgerPosition, in the form VAM-04-011 defines (CM-11-011, SYNC-18-001).

TentativeReject records RM Invalid, from which the participant's RM state can still become Valid, so it is the same tentative answer as for a direct report (VAM-02-005). A stub Invite has no tentative reply (CM-11-028), so this form exists only for the full-case Invite.
VAM-04-014 MUST The RC reply to the full-case Invite MUST be represented as Reject(Invite(Actor)[target=VulnerabilityCase]), carrying the replier's ledger position as its ledgerPosition, in the form VAM-04-011 defines (CM-11-011, SYNC-18-001).

Reject records RM Closed for a participant that has already joined. The full-case target tells it apart from a refusal to join, which is the Reject of a stub Invite (VAM-04-006).
VAM-04-015 MUST UNDO_CASE_OWNERSHIP_TRANSFER MUST be represented as Undo(Offer(Offer(VulnerabilityCase))): the Case Owner's withdrawal of its own proposal, and with it of the open Offer the CASE_MANAGER made from it (CM-21-015).

An actor undoes only an activity that it performed, so the Case Owner withdraws its own proposal with an Undo of that proposal. The withdrawal also ends the Offer that the CASE_MANAGER made from the proposal (CM-21-015), so no transferee can accept an Offer that the Case Owner withdrew.
Refines: CM-21-015
VAM-04-016 MUST ACCEPT_ACTOR_RECOMMENDATION MUST be represented as Accept(Offer(Actor)[target=VulnerabilityCase]): the CASE_MANAGER's answer to the recommender, wrapping the recommender's own Offer(Actor) (VAM-04-001), with inReplyTo naming that Offer (CM-16-006, CM-16-009).

This is the third hop of the exchange, addressed back to the recommender. It wraps the first offer, where the Case Owner's decision (VAM-04-002) wraps the second, so the inner object type tells the two Accepts apart.
Refines: CM-16-006
VAM-04-017 MUST REJECT_ACTOR_RECOMMENDATION MUST be represented as Reject(Offer(Actor)[target=VulnerabilityCase]): the CASE_MANAGER's answer to the recommender after the Case Owner declines, with inReplyTo naming the recommender's Offer(Actor) (CM-16-007).

Symmetric with VAM-04-016; the Case Owner's Reject (VAM-04-003) wraps Offer(CaseParticipant) instead.
Refines: CM-16-007
VAM-04-018 MUST PROPOSE_CASE_OWNERSHIP_TRANSFER MUST be represented as Offer(Offer(VulnerabilityCase)) with the case as the proposal's context: the Case Owner's proposal to the CASE_MANAGER, whose inner Offer is the template of the Offer the CASE_MANAGER makes (CM-21-005).

The proposal and the CASE_MANAGER's Offer(VulnerabilityCase) (VAM-04-007) are told apart by shape, an Offer of an Offer against an Offer of a case, so neither pattern needs a sender check and no new vocabulary type is needed (ADR-0148).
Refines: CM-21-005

Embargo Management Messages

ID Priority Requirement Related
VAM-05-003 MUST REMOVE_EMBARGO_EVENT_FROM_CASE MUST be represented as Remove(EmbargoEvent)

A removal takes the embargo out of the case, which is what AS2 Remove means. The sender and every receiver must use the same wire form, or a receiver classifies the activity as unknown.
VAM-05-004 MUST ANNOUNCE_EMBARGO_EVENT_TO_CASE MUST be represented as Announce(EmbargoEvent)[context=VulnerabilityCase]

An announcement tells participants about an embargo event and asks for no answer, which is what AS2 Announce means. The case in context names the case that the embargo belongs to.
VAM-05-005 MUST INVITE_TO_EMBARGO_ON_CASE MUST be represented as Invite(EmbargoEvent)[context=VulnerabilityCase]

A proposed embargo needs the answer of each invitee, so it is an Invite with the EmbargoEvent as its object. The EmbargoEvent object and the case in context keep this form separate from the Invite of an actor to a case, whose case is in target (VAM-04-004).
VAM-05-006 MUST ACCEPT_INVITE_TO_EMBARGO_ON_CASE MUST be represented as Accept(Invite(EmbargoEvent)[context=VulnerabilityCase])

An invitee consents to the proposed embargo when it accepts the Invite that it received, so the Accept wraps that Invite. Its inner object tells it apart from the Case Owner's activation, which is an Accept of the EmbargoEvent itself (VAM-05-008).
VAM-05-007 MUST REJECT_INVITE_TO_EMBARGO_ON_CASE MUST be represented as Reject(Invite(EmbargoEvent)[context=VulnerabilityCase])

An invitee declines the proposed embargo when it rejects the Invite that it received. Its inner object tells it apart from the Case Owner's rejection of the proposal, which is a Reject of the EmbargoEvent itself (VAM-05-009).
VAM-05-008 MUST ACTIVATE_EMBARGO_ON_CASE MUST be represented as Accept(EmbargoEvent)[target=VulnerabilityCase], whose object is the proposed EmbargoEvent itself, carried inline or by reference, and not the Invite that proposed it (ADR-0122).

The Case Owner's activation is a decision about the embargo for the case, so it accepts the embargo; an Accept of the Invite is each sender's own consent (VAM-05-006). Giving the two different objects keeps them apart on the wire.
VAM-05-009 MUST REJECT_EMBARGO_PROPOSAL_ON_CASE MUST be represented as Reject(EmbargoEvent)[target=VulnerabilityCase], whose object is the proposed EmbargoEvent itself, carried inline or by reference, and not the Invite that proposed it (ADR-0122).

The Case Owner's rejection is a decision about the embargo for the case; a Reject of the Invite is each sender's own consent (VAM-05-007).
VAM-05-011 MUST WITHDRAW_FROM_EMBARGO_ON_CASE MUST be represented as Leave(EmbargoEvent)[context=VulnerabilityCase], whose object is the embargo in force, carried inline or by reference, and not an Invite (ADR-0122).

Withdrawal answers no Invite, so it names the embargo itself; it is told apart from Leave(VulnerabilityCase), which leaves the case, by its object (MSM-07-010).
VAM-05-013 MUST UNDO_INVITE_TO_EMBARGO_ON_CASE MUST be represented as Undo(Invite(EmbargoEvent)[context=VulnerabilityCase]), whose object is an Invite(EmbargoEvent) the sender itself sent. Only the CASE_MANAGER sends it, when it abandons an open proposal (EMB-16-001, ADR-0152).

An actor undoes only an activity it performed, so withdrawing an invitation one sent is an Undo and declining one received is a Reject (ADR-0152). The shape is told apart from the ownership-transfer Undo (VAM-04-015) by its object, an embargo Invite.
Satisfies: EMB-16-001
VAM-05-014 MUST CREATE_EMBARGO_CONSENT MUST be represented as Create(EmbargoConsent), carrying the new consent row in full. Only the CASE_MANAGER sends it, as its own act when it brings a consent row into existence (ADR-0122, CLP-07-013), and it is not handled on receipt (SE-05-002, ADR-0140).

An entry's semantic type is the registry's label for its payload (ADR-0140). The record is a case-file change, not a PEC transition (MSM-07-001).
Refines: MSM-07-001
VAM-05-015 MUST UPDATE_EMBARGO_CONSENT MUST be represented as Update(EmbargoConsent), carrying the changed consent row in full. Only the CASE_MANAGER sends it, as its own act when it changes a consent row, its own consent included (MSM-07-017, CLP-07-013). It is not handled on receipt (SE-05-002, ADR-0140).

An entry's semantic type is the registry's label for its payload (ADR-0140). An expired invitation is this Update carrying the invitee's TIMED_OUT row (ADR-0140 item 4).
Refines: MSM-07-017
VAM-05-016 MUST CREATE_EMBARGO_EVENT MUST be represented as Create(EmbargoEvent), carrying the embargo in full, its start and end times included. Only the CASE_MANAGER commits it, as a ledger entry and never as a sent message, once for each embargo a case's creation writes, such as the default embargo (EP-04-002), right after the case's genesis entry and before the case's other creation entries (CLP-07-013). It is not handled on receipt (SE-05-002, ADR-0140): a replica stores the embargo from the entry.

The genesis entry carries only the case's active embargo inline, and a replica copies each record from its own entry (ADR-0124). No other entry carries a creation-time embargo until the embargo is removed. Without this entry, a replica built from the ledger alone could not read the end time that its embargo decisions need. An entry's semantic type is the registry's label for its payload (ADR-0140).
Refines: CLP-07-013

Case Participant Management Messages

ID Priority Requirement Related
VAM-06-002 MUST ADD_CASE_PARTICIPANT_TO_CASE MUST be represented as Add(CaseParticipant)[target=VulnerabilityCase]

The message is the Case Owner's request to reinstate a removed participant into the case's activeParticipants (CM-31-011, ADR-0116); it does not seat a new member, which joins by accepting a stub Invite.
VAM-06-003 MUST REMOVE_CASE_PARTICIPANT_FROM_CASE MUST be represented as Remove(CaseParticipant)[target=VulnerabilityCase]

The message removes a participant from the case's activeParticipants, not from caseParticipants: the record stays and the participant stops receiving case content (CM-31-001, CM-31-004, ADR-0116).
VAM-06-004 MUST CREATE_CASE_PARTICIPANT MUST be represented as Create(CaseParticipant), carrying the record in full. Only the CASE_MANAGER sends it, as its own act when it brings a participant record into existence (ADR-0114, CLP-07-013), and it is not handled on receipt (SE-05-002, ADR-0140).

An entry's semantic type is the registry's label for its payload (ADR-0140). A received Create(CaseParticipant) is still not a protocol message (VAM-01-011): it is recognized only so that it is reported, not acted on.
Refines: VAM-01-011
VAM-06-005 MUST UPDATE_CASE_PARTICIPANT MUST be represented as Update(CaseParticipant), carrying the changed record in full. Only the CASE_MANAGER sends it, as its own act when it changes a participant record, such as moving the CASE_OWNER role (CM-21-002, CLP-07-013), and it is not handled on receipt (SE-05-002, ADR-0140).

An entry's semantic type is the registry's label for its payload (ADR-0140), so each participant-record change the CASE_MANAGER commits needs a registry entry.
Satisfies: CM-21-002

Note Management Messages

ID Priority Requirement Related
VAM-07-002 MUST ADD_NOTE_TO_CASE MUST be represented as Add(Note)[target=VulnerabilityCase]

A note means nothing apart from its case, so it enters the case only by the Add that attaches it, with the case as target (VAM-01-011, ADR-0137). One attaching message means that a note has no state in which it exists but is not in a case.
VAM-07-003 MUST REMOVE_NOTE_FROM_CASE MUST be represented as Remove(Note)[target=VulnerabilityCase]

A removal takes the note out of the case named in target, which is what AS2 Remove means. The Note object tells it apart from the other Remove forms, such as the removal of a participant (VAM-06-003).

Status Tracking Messages

ID Priority Requirement Related
VAM-08-002 MUST ADD_CASE_STATUS_TO_CASE MUST be represented as Add(CaseStatus)[target=VulnerabilityCase]

A case status means nothing apart from its case, so it enters the case only by the Add that attaches it (VAM-01-011, ADR-0137). The case in target names the case that the status belongs to.
VAM-08-004 MUST ADD_PARTICIPANT_STATUS_TO_PARTICIPANT MUST be represented as Add(ParticipantStatus)[target=CaseParticipant]

A participant status belongs to one participant in one case, so its target is that CaseParticipant and not the case. The Add is the only way the status enters the case (VAM-01-011, ADR-0137).

Protocol Shorthand to MessageSemantics Mapping

Authoritative normative bridge from protocol message shorthand labels (RS, EP, CV, etc.) used in behavioral conformance specs to their MessageSemantics enum values and corresponding AS2 wire-format specifications. Each entry provides one row in the three-hop chain: shorthand to MessageSemantics enum value to AS2 wire form. The correspondence is many-to-many in both directions: several shorthands may collapse onto one wire activity (CV/CF onto vfState, CP/CX/CA onto pxaState, EV onto EP), and one shorthand may expand across several wire activities (GI, ER). Shorthands with no dedicated dispatch value are the protocol-layer error and acknowledgment signals; these are not unimplemented — the purposes they name are served by mechanisms partitioned on a different axis, specified normatively in MSM-05.

RM Protocol Message Shorthands

Normative shorthand-to-semantic mappings for Report Management (RM) protocol messages, covering RS, RI, RV, RD, RA, RC, RE, and RK.

ID Priority Requirement Related
MSM-01-001 MUST Protocol shorthand RS (Report Submission) MUST have the semantic type SUBMIT_REPORT; its AS2 wire form is Offer(VulnerabilityReport) per VAM-02-002.

Report submission opens a CVD exchange, so a recipient on any implementation must recognize it as a report submission and not some other activity. Offer fits because the reporter puts the report before the recipient for a decision, which the recipient's RV, RI or RC then answers by naming that Offer.
Derives from: VAM-02-002
MSM-01-002 MUST Protocol shorthand RI (Report Invalid) MUST have the semantic type INVALIDATE_REPORT; its AS2 wire form is TentativeReject(Offer(VulnerabilityReport)) per VAM-02-005. This is the direct report recipient's RI; a participant that joined through an Invite sends RI as the full-case Invite reply of VAM-04-013.

Invalidating a report answers the reporter's Offer, so the reply names it. TentativeReject marks the decision as reversible, since an invalid report can become valid when new information arrives, and keeps RI distinct from the final Reject of RC. A participant that joined through an Invite has no Offer to answer, so its RI rides the Invite reply instead.
Derives from: VAM-02-005
MSM-01-003 MUST Protocol shorthand RV (Report Valid) MUST have the semantic type VALIDATE_REPORT; its AS2 wire form is Accept(Offer(VulnerabilityReport)) per VAM-02-004. This is the direct report recipient's RV; a participant that joined through an Invite sends RV as the full-case Invite reply of VAM-04-012.

Validating a report answers the reporter's Offer, so the reply names it, and Accept says the recipient takes the report as a real vul to handle. Report validity is a separate decision from engaging in the case (RA), so RV needs its own form. A participant that joined through an Invite has no Offer to answer, so its RV rides the Invite reply instead.
Derives from: VAM-02-004
MSM-01-004 MUST Protocol shorthand RD (Report/Case Deferred) MUST have the semantic type DEFER_CASE; its AS2 wire form is Ignore(VulnerabilityCase) per VAM-03-004.

A deferred report indicates the receiving actor will not engage with the case at this time. The wire representation uses Ignore(VulnerabilityCase) rather than a report-level verb because deferral is a case-participation decision, not a report-validity judgment.
Derives from: VAM-03-004
MSM-01-005 MUST Protocol shorthand RA (Report/Case Accepted) MUST have the semantic type ENGAGE_CASE; its AS2 wire form is Join(VulnerabilityCase) per VAM-03-003.

Accepting a report means the receiving actor commits to engaging with the vulnerability case. The wire verb Join captures the case-participation decision, symmetric with Ignore for RD.
Derives from: VAM-03-003
MSM-01-006 MUST Protocol shorthand RC (Report Closed) MUST have the semantic type CLOSE_REPORT; its AS2 wire form is Reject(Offer(VulnerabilityReport)) per VAM-02-006. This is the direct report recipient's RC; a participant that joined through an Invite sends RC as the full-case Invite reply of VAM-04-014.

Closing a report answers the reporter's Offer, so the reply names it. Reject is final, which tells the reporter the report will not be reconsidered and keeps RC distinct from the reversible TentativeReject of RI. A participant that joined through an Invite has no Offer to answer, so its RC rides the Invite reply instead.
Derives from: VAM-02-006
MSM-01-008 MUST Protocol shorthand RK (Report Management Acknowledgment) MUST have the semantic type ACK_REPORT; its AS2 wire form is Read(Offer(VulnerabilityReport)) per VAM-02-003.

A reporter needs to know its report arrived before any validity decision is made. Read acknowledges receipt of the Offer without judging it. Report submission happens before any case ledger exists, so no hash-chain acknowledgement covers it (MSM-05-002) and it needs this explicit one.
Derives from: VAM-02-003

EM Protocol Message Shorthands

Normative shorthand-to-semantic mappings for Embargo Management (EM) protocol messages, covering EP, EA, EV, EJ, EC, ER, ET, EE, and EK. The formal message set models the case's view of the embargo, so an acceptance or rejection shorthand names the Case Owner's decision that makes its EM transition, not each Participant's answer to the Invite. Embargo revision shorthands (EV, EJ, EC) share wire formats with their initial-proposal counterparts (EP, ER, EA); the distinction is contextual rather than structural.

ID Priority Requirement Related
MSM-02-001 MUST Protocol shorthand EP (Embargo Proposed) MUST have the semantic type INVITE_TO_EMBARGO_ON_CASE; its AS2 wire form is Invite(EmbargoEvent)[context=VulnerabilityCase] per VAM-05-005.

Proposing an embargo asks each participant to consent to the proposed terms, which is what Invite expresses; the Event is the proposed embargo. The context names the case, so a receiver can tie the proposal to that case's EM state. One wire form lets every implementation recognize a proposal.
Derives from: VAM-05-005
MSM-02-002 MUST Protocol shorthand EA (Embargo Accepted) MUST have the semantic type ACTIVATE_EMBARGO_ON_CASE; its AS2 wire form is Accept(EmbargoEvent)[target=VulnerabilityCase] per VAM-05-008, the Case Owner's decision that moves EM from PROPOSED to ACTIVE (MSM-07-008).

EA names the EM transition PROPOSED to ACTIVE, and only the Case Owner's decision makes it. A Participant's Accept(Invite(EmbargoEvent)) records that Participant's consent (MSM-07-003) and moves no register entry, so mapping EA to it would name a message that never makes EA's transition. The consent answers are what the Case Owner weighs before it decides (MSM-02-010).
Derives from: VAM-05-008
MSM-02-004 MUST Protocol shorthand EJ (Embargo Revision Rejected) MUST have the semantic type REJECT_EMBARGO_PROPOSAL_ON_CASE; its AS2 wire form is Reject(EmbargoEvent)[target=VulnerabilityCase] per VAM-05-009, the Case Owner's decision that returns EM from REVISE to ACTIVE with the prior terms in force (MSM-07-009).

EJ names the EM transition REVISE to ACTIVE, and only the Case Owner's rejection of the revision makes it; a Participant's Reject(Invite(EmbargoEvent)) of the revision declines that Participant's consent row and nothing else (MSM-07-004). EJ shares its wire form with the Case Owner's ER, and the receiver's EM state tells the two apart, as MSM-02-003 does for EV.
Derives from: VAM-05-009
MSM-02-005 MUST Protocol shorthand EC (Embargo Revision Accepted) MUST have the semantic type ACTIVATE_EMBARGO_ON_CASE; its AS2 wire form is Accept(EmbargoEvent)[target=VulnerabilityCase] per VAM-05-008, the Case Owner's decision that replaces the active embargo with the revision and moves EM from REVISE to ACTIVE (MSM-07-008).

EC names the EM transition REVISE to ACTIVE, and only the Case Owner's activation of the revision makes it; a Participant's Accept(Invite(EmbargoEvent)) of the revision records that Participant's consent and binds it to nothing new (MSM-07-003). EC shares its wire form with EA, and the receiver's EM state tells the two apart, as MSM-02-003 does for EV.
Derives from: VAM-05-008
MSM-02-006 MUST Protocol shorthand ER (Embargo Rejected) MUST have the semantic type REJECT_EMBARGO_PROPOSAL_ON_CASE, with AS2 wire form Reject(EmbargoEvent)[target=VulnerabilityCase] per VAM-05-009, for the Case Owner's decision that rejects a proposal, and the semantic type REJECT_INVITE_TO_EMBARGO_ON_CASE, with AS2 wire form Reject(Invite(EmbargoEvent)[context=VulnerabilityCase]) per VAM-05-007, for the CASE_MANAGER's abandonment of a proposal (EMB-16-001); either moves EM from PROPOSED to NONE once no other proposal is open.

ER names the EM transition PROPOSED to NONE, and two activities make it: the Case Owner's rejection of the proposal (MSM-07-009) and the CASE_MANAGER's abandonment, which cancels the proposal once the case is public, exploited or attacked. A Participant's Reject(Invite(EmbargoEvent)) declines only that Participant's consent row (MSM-07-004), and a replica tells it from the abandonment by the payload's actor (ADR-0152).
Derives from: VAM-05-009
Derives from: VAM-05-007
MSM-02-007 MUST Protocol shorthand ET (Embargo Terminated) MUST have the semantic type REMOVE_EMBARGO_EVENT_FROM_CASE; its AS2 wire form is Remove(EmbargoEvent) per VAM-05-003.

Ending an embargo takes the embargo event off the case, which is what Remove expresses. A verb distinct from Reject keeps the end of an active embargo apart from a refusal of a proposal, so every participant knows the embargo has ended and binds no one.
Derives from: VAM-05-003
MSM-02-010 MUST NOT An implementation MUST NOT apply the EM transition of EA, EC, ER or EJ on receipt of a Participant's Accept(Invite(EmbargoEvent)[context=VulnerabilityCase]) or Reject(Invite(EmbargoEvent)[context=VulnerabilityCase]), because that activity is the sender's consent to one embargo (MSM-07-003, MSM-07-004), and the transition is made only by the activity MSM-02-002, MSM-02-004, MSM-02-005 or MSM-02-006 maps the shorthand to. The CASE_MANAGER's own Reject(Invite(EmbargoEvent)) is not its consent but its abandonment of the proposal, which MSM-02-006 maps to ER (EMB-16-001).

The formal message set models the case's view of the embargo, not each Participant's answer to each proposal: Participant Embargo Consent (PEC) is a finer layer over EM that it does not describe (MSM-07-001). One formal message can take several activities in practice: Participants answer the Invite, and the Case Owner decides for the case in light of those answers. Reading an answer as the case's transition would let one Participant decide for everyone.
Refines: MSM-07-001

CS Protocol Message Shorthands

Normative shorthand-to-semantic mappings for CVD Case State (CS) protocol messages, covering CV, CF, CD, CP, CX, CA, CE, and CK. CS status shorthands are split across two semantics by dimension scope, because the CS hypercube mixes participant-scoped and case-scoped dimensions: the participant-scoped V/F/D dimensions (CV, CF, CD) dispatch as ADD_PARTICIPANT_STATUS_TO_PARTICIPANT and are encoded in the ParticipantStatus payload, while the case-scoped P/X/A dimensions (CP, CX, CA) dispatch as ADD_CASE_STATUS_TO_CASE and are encoded in the CaseStatus payload. There are no case-level V/F/D states (ADR-0075). In both groups the specific state transition is encoded in the object payload rather than in the activity type.

ID Priority Requirement Related
MSM-03-001 MUST Protocol shorthand CV (Vendor Aware) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT; its AS2 wire form is Add(ParticipantStatus)[target=CaseParticipant] per VAM-08-004. The ParticipantStatus payload encodes vendor awareness in the vfState field, advancing the vendor-path ladder from vf to Vf.

The V dimension is participant-scoped, not case-scoped: it records whether a specific vendor is aware. Per ADR-0075 there is no case-level V/F/D state, and a case-level Add(CaseStatus) cannot express which vendor became aware, which is the purpose of the V dimension in MPCVD.
Derives from: VAM-08-004
MSM-03-002 MUST Protocol shorthand CF (Fix Ready) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT; its AS2 wire form is Add(ParticipantStatus)[target=CaseParticipant] per VAM-08-004. The ParticipantStatus payload encodes fix readiness in the vfState field, advancing the vendor-path ladder from Vf to VF.

The F dimension shares the participant-scoped vendor-path sub-machine with V (CS_vf: vf → Vf → VF), so fix readiness is recorded against the specific vendor that produced the fix. Same scope reasoning as MSM-03-001.
Derives from: VAM-08-004
MSM-03-003 MUST Protocol shorthand CD (Fix Deployed) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT; its AS2 wire form is Add(ParticipantStatus)[target=CaseParticipant] per VAM-08-004. The ParticipantStatus payload encodes deployment in the dState field, advancing the deployer-path ladder from d to D.

The D dimension is participant-scoped on the deployer, and ADR-0075 split it into its own sub-machine (CS_d) precisely because its scope differs from the vendor path. as_ParticipantStatus.d is None for non-DEPLOYER participants (structurally enforced).
Derives from: VAM-08-004
MSM-03-004 MUST Protocol shorthand CP (Public Aware) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT when a participant reports it, which it does only in the CaseStatus that its Add(ParticipantStatus)[target=CaseParticipant] embeds (RSH-04-001), and the semantic type ADD_CASE_STATUS_TO_CASE when it is carried in Add(CaseStatus)[target=VulnerabilityCase] (VAM-08-002), the CASE_MANAGER's record of the case status it adopted (RSH-04-001, RSH-04-005). In both forms the CaseStatus payload encodes public awareness (P) in its pxaState.

A participant embeds its suggested CaseStatus in Add(ParticipantStatus) (RSH-04-001), so the formal message has one wire form per sender. The participant's is its view, not a change: the CASE_MANAGER adopts it, a non-owner's by default only with the Case Owner's authorization (RSH-07-001, RSH-07-006), as its own Add(CaseStatus) ledger entry (RSH-01-003), and a receiver refuses an Add(CaseStatus) from anyone else (RSH-04-005). Each form dispatches to its own entry (MSM-08-001).
Derives from: VAM-08-002
Depends on: RSH-04-001
MSM-03-005 MUST Protocol shorthand CX (Exploit Published) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT when a participant reports it, which it does only in the CaseStatus that its Add(ParticipantStatus)[target=CaseParticipant] embeds (RSH-04-001), and the semantic type ADD_CASE_STATUS_TO_CASE when it is carried in Add(CaseStatus)[target=VulnerabilityCase] (VAM-08-002), the CASE_MANAGER's record of the case status it adopted (RSH-04-001, RSH-04-005). In both forms the CaseStatus payload encodes a published exploit (X) in its pxaState.

A participant embeds its suggested CaseStatus in Add(ParticipantStatus) (RSH-04-001), so the formal message has one wire form per sender. The participant's is its view, not a change: the CASE_MANAGER adopts it, a non-owner's by default only with the Case Owner's authorization (RSH-07-001, RSH-07-006), as its own Add(CaseStatus) ledger entry (RSH-01-003), and a receiver refuses an Add(CaseStatus) from anyone else (RSH-04-005). Each form dispatches to its own entry (MSM-08-001).
Derives from: VAM-08-002
Depends on: RSH-04-001
MSM-03-006 MUST Protocol shorthand CA (Attacks Observed) MUST have the semantic type ADD_PARTICIPANT_STATUS_TO_PARTICIPANT when a participant reports it, which it does only in the CaseStatus that its Add(ParticipantStatus)[target=CaseParticipant] embeds (RSH-04-001), and the semantic type ADD_CASE_STATUS_TO_CASE when it is carried in Add(CaseStatus)[target=VulnerabilityCase] (VAM-08-002), the CASE_MANAGER's record of the case status it adopted (RSH-04-001, RSH-04-005). In both forms the CaseStatus payload encodes observed attacks (A) in its pxaState.

A participant embeds its suggested CaseStatus in Add(ParticipantStatus) (RSH-04-001), so the formal message has one wire form per sender. The participant's is its view, not a change: the CASE_MANAGER adopts it, a non-owner's by default only with the Case Owner's authorization (RSH-07-001, RSH-07-006), as its own Add(CaseStatus) ledger entry (RSH-01-003), and a receiver refuses an Add(CaseStatus) from anyone else (RSH-04-005). Each form dispatches to its own entry (MSM-08-001).
Derives from: VAM-08-002
Depends on: RSH-04-001

Fault and Acknowledgement Mechanism Evolution

Normative record of how the formal protocol's per-model error and acknowledgement shorthands (RE, EE, CE, GE and RK, EK, CK, GK) are realized in the AS2 wire vocabulary. The formal set partitions both concerns by which state machine the message belonged to; the implementation partitions faults by failure mode and acknowledges replicated state cumulatively. These are orthogonal axes, so the shorthands have no one-to-one wire counterparts even though the behaviours they describe are implemented.

ID Priority Requirement Related
MSM-05-001 MUST Fault reporting MUST be partitioned by failure mode rather than by state machine, using exactly three mechanisms: Create(ProcessingFault) when a message was received but not understood; as:Reject when a message was received and understood but declined; and Add(Note)[target=VulnerabilityCase] when the condition requires narrative explanation.

Failure mode is actionable to a receiver in a way that originating state machine is not. Not-understood calls for a format or version fix, declined calls for a protocol-level decision, and needs-explanation calls for a human. A partition by state machine (RE, EE, CE and GE) carries no such signal, so it would tell the sender only where the fault arose, not what to do about it.
MSM-05-002 MUST Acknowledgement of replicated case-ledger state MUST be cumulative and implicit, through hash-chain continuity.

A matching prev_log_hash already proves receipt of every prior entry, so per-message positive acknowledgement would be redundant. This is negative acknowledgement with gap-fill replay, structurally closer to a cumulative ACK or SACK scheme than to the formal per-message EK, CK and GK design. RK remains a real wire activity (MSM-01-008) because report submission is not ledger-replicated.
Refines: SYNC-15-003
MSM-05-007 MUST NOT A receiver whose prev_log_hash matches its local tail MUST NOT emit an acknowledgement.

The match already acknowledges every prior entry (MSM-05-002), so a positive acknowledgement would only add traffic.
Refines: MSM-05-002
MSM-05-008 MUST A receiver MUST emit Reject(CaseLedgerEntry) when an entry's prev_log_hash does not match its local tail. The CASE_MANAGER then replays all entries after the last accepted hash.

The mismatch is the one event the sender cannot see for itself. The Reject names where the receiver's chain ends, so the replay fills the gap.
Extends: MSM-05-002
MSM-05-005 MUST All protocol-significant activities MUST be committed to the canonical case ledger by the CASE_MANAGER before fan-out to participant actors. No protocol-significant activity bypasses the ledger replication path.

The hash-chain acknowledgement model (MSM-05-002) is only complete if every activity a participant needs to acknowledge reaches them via an Announce(CaseLedgerEntry). Activities that bypassed the ledger would have no acknowledgement mechanism. Report submission (RS) is the only activity not carried by the ledger; it uses RK (MSM-01-008) instead, which is the intended exception rather than a gap.
MSM-05-006 MAY A conformant implementation MAY rely solely on Announce(CaseLedgerEntry) deliveries for liveness inference, without emitting a dedicated periodic heartbeat. Silence between Announce deliveries is therefore an accepted protocol property: a participant that is online but has received all entries is indistinguishable from one that is unreachable. This does not preclude a future revision adding a normative heartbeat requirement.

The hash-chain NAK model (MSM-05-002) provides implicit acknowledgement only when new entries arrive. Between deliveries, silence and unreachability are observationally equivalent. Stating this as a deliberate MAY-level property tells implementers that the silence is expected behavior, not a defect they must detect or repair, while leaving room for a heartbeat mechanism should a deployment, such as a multi-node CASE_MANAGER cluster, need one.
MSM-05-003 MUST NOT Implementations MUST NOT infer error semantics from the as:Reject verb alone, because as:Reject also carries legitimate protocol rejections, including closing a report and declining an embargo invitation, a case invitation, a case proposal or a case-ownership transfer.

as:Reject is overloaded across error reporting and ordinary protocol refusal. A receiver that treats every as:Reject as a fault will misclassify routine protocol traffic as errors.

Participant Embargo Consent (PEC) State Machine Coupling to EM Wire Activities

Normative record of how each EM wire activity drives the per-participant, per-embargo Participant Embargo Consent (PEC) rows as a side-effect, and which consent positions are derived rather than written. PEC is participant-local state tracking each actor's relationship to each embargo in the case's embargo register; its transitions are not independent protocol messages and have no formal shorthands in the 28-shorthand set. A participant's consent to an embargo is one row holding UNINVITED, INVITED, AGREED, DECLINED or TIMED_OUT; whether the participant is bound by the current active embargo is a lookup of the active embargo's row, and a lapse is read from the rows and the register when a longer embargo replaces the one the participant agreed to. Unlike RM state (self-reported by the participant) and VF/D state (self-reported by vendor/deployer), consent rows are set by the CASE_MANAGER (CM-28-003) based on observed participant behavior — incoming Accept/Reject wire activities and timer-enforced invitation expiry — and replayed on each replica. Normative authority: specs/case-management.yaml CM-18. Decisions: ADR-0048, ADR-0091, ADR-0118, ADR-0122.

ID Priority Requirement Related
MSM-07-001 MUST The Participant Embargo Consent (PEC) state machine MUST NOT be assigned any formal shorthand in the 28-shorthand protocol message set; all PEC transitions are either side-effects of EM wire activities (MSM-07-002 through MSM-07-005, MSM-07-008) or timer-based internal events (MSM-07-007) with no dedicated wire message or semantic type of their own. The CASE_MANAGER's Create(EmbargoConsent) and Update(EmbargoConsent), which record a consent row it brings into existence or changes as its own ledger entry (CLP-07-013), are case-file record changes, not PEC transitions: they carry no shorthand, their ledger semantic types are create_embargo_consent and update_embargo_consent, and neither is handled on receipt (ADR-0140).

PEC is participant-local state tracking each actor's relationship to the shared EM state, not a parallel message-driven machine. Extending the formal set with PEC-specific shorthands would imply dedicated wire messages where none exist and none are needed — the EM messages already carry the PEC signal as a side-effect. Documenting PEC as a side-effect is the faithful description of the design. A ledger entry's semantic type is the registry's label for its payload (ADR-0140), so the CASE_MANAGER's consent-row records need registry entries; they name the record, not a PEC trigger, and no receiver dispatches on them.
Derives from: CM-18-003
MSM-07-002 MUST The wire activity INVITE_TO_EMBARGO_ON_CASE (EP wire form: Invite(EmbargoEvent)[context=VulnerabilityCase]) MUST apply the PEC INVITE trigger to the invited participant's row for the invited embargo, advancing it from UNINVITED, DECLINED or TIMED_OUT to INVITED.

An embargo proposal is an invitation to consent; the EP wire activity conveys the invitation and the PEC rows record each participant's position on each embargo. A participant whose row is AGREED is unaffected by EP; INVITE is not legal from AGREED (CM-18-003), and a signatory asked about a revision has its row for the revision invited while its row for the active embargo stays AGREED (EP-09-004). The PEC trigger is applied via EmbargoLifecycle.record_embargo_invite() on the CASE_MANAGER as it relays the Invite, and on each replica as it replays the relay's ledger entry (EP-09-007).
Derives from: CM-18-003
Refines: MSM-02-001
MSM-07-003 MUST The wire activity ACCEPT_INVITE_TO_EMBARGO_ON_CASE (wire form: Accept(Invite(EmbargoEvent)[context=VulnerabilityCase])) is always its sender's own consent, the Case Owner's included. An actor applying it MUST apply the PEC AGREE trigger to the sender's row for the accepted embargo. The row advances from UNINVITED, INVITED, DECLINED or TIMED_OUT to AGREED. The accepted embargo may be the active embargo, a first proposal (EM PROPOSED) or a proposed revision of an embargo still in force. Only the accepted embargo's row is marked. A participant is a signatory exactly when its row for the active embargo is AGREED. So accepting a proposed revision binds the participant to nothing new until the owner activates it (MSM-07-008). An Accept from a DECLINED row is the participant changing its answer, so the row moves directly to AGREED with no re-invite first (CM-18-003). An Accept of an embargo that is neither the active embargo nor an open proposal records nothing (CM-18-003), and MSM-07-012 refuses it.

The Case Owner's decision for the case has its own activity (MSM-07-008, ADR-0122), so an Accept of an Invite means one thing whoever sends it and no handler branches on the sender to learn what it means. The PEC AGREE trigger is valid directly from UNINVITED because consent can be self-determined without an invitation (ADR-0048, ADR-0091, CM-18-003). Consent is per embargo (CM-10-001, ADR-0093, ADR-0122): an Accept is always a statement about the embargo it names, so it always marks that embargo's row and there is no first-proposal versus revision asymmetry to special-case.
Derives from: CM-18-003
MSM-07-004 MUST The wire activity REJECT_INVITE_TO_EMBARGO_ON_CASE (wire form: Reject(Invite(EmbargoEvent)[context=VulnerabilityCase])) is its sender's own consent whenever a participant sends it, the Case Owner included. Unless MSM-07-014 refuses it, an actor applying it MUST apply the PEC DECLINE trigger to the sender's row for the rejected embargo. DECLINE is valid from UNINVITED, INVITED, AGREED or TIMED_OUT. A Reject of the active embargo from any row but AGREED refuses the terms in force and declines that row only. The CASE_MANAGER's own Reject of a proposal is not its consent but its abandonment (MSM-07-016).

Symmetric with MSM-07-003: the Case Owner's rejection of a proposal for the case has its own activity (MSM-07-009, ADR-0122), so a Reject of an Invite is the sender's consent and nothing else. The PEC DECLINE trigger is valid directly from UNINVITED for symmetric reasons to AGREE (ADR-0048, ADR-0091, CM-18-003).
Derives from: CM-18-003
MSM-07-005 MUST This rule covers the Case Owner's Accept(EmbargoEvent)[target=VulnerabilityCase] (MSM-07-008) that replaces active embargo A with revision B. The register activates B and supersedes A, so the derived EM goes REVISE → ACTIVE, or stays REVISE while another proposal is open (EMB-18-005). Implementations MUST settle every participant's consent rows against B.

Only the owner's acceptance changes the embargo on the case; other participants' accepts and rejects are recorded per embargo and inform that decision. When it replaces the embargo in force, each participant's standing has to be read again against the new terms (ADR-0093).
Derives from: CM-18-003
Refines: MSM-02-003
MSM-07-019 MUST This rule covers the owner's activation of revision B in place of active embargo A, where B ends no later than A. The CASE_MANAGER MUST carry over every participant whose row for A is AGREED. It applies CARRY_OVER to that participant's row for B, a DECLINED row for B included (CM-18-003).

Agreeing to N days is agreeing to every shorter period — the containment that makes shortest-wins safe (EP-04-003) — so a shorter revision asks nothing new of an existing signatory and carries them over, even one that declined B: its decline objected to losing time, not to keeping the embargo.
Refines: MSM-07-005
MSM-07-020 MUST NOT This rule covers the owner's activation of revision B in place of active embargo A, where B ends later than A. Implementations MUST NOT write any consent row to lapse a participant. A participant whose row for B is not AGREED is not a signatory to B, and has lapsed if it had agreed to A (CM-18-001).

Longer terms ask for more than the signatories of A promised, so only those who agreed to B are bound by B, and the rows say so without a write.
Refines: MSM-07-005
MSM-07-021 MUST NOT A revision proposal (EV, ACTIVE → REVISE, or a counter-revision REVISE → REVISE) MUST NOT change any participant's consent to the embargo in force.

While a revision is merely proposed the prior embargo is still in force and every signatory to it is still bound by it, so nothing about their consent has changed (ADR-0093).
MSM-07-022 MUST When a participant proposes a revision, implementations MUST mark the proposer's row for the proposed embargo AGREED.

Proposing terms is agreeing to them, so the proposer needs no Invite of its own proposal.
Extends: MSM-07-021
MSM-07-023 MUST The CASE_MANAGER MUST commit each carried-over row as its own CASE_MANAGER Update(EmbargoConsent) ledger entry, after the Accept's entry. The owner's own row is what its Accept states, applied from the Accept's entry.

The CASE_MANAGER decides the carry-over. Committing each carried-over row as its own entry keeps every replica's consent rows in step with the CASE_MANAGER's.
Implements: MSM-07-019
MSM-07-024 MUST Every replica MUST copy the CASE_MANAGER's carried-over consent entries rather than run the carry-over itself (ADR-0124, CLP-07-013, CM-23-016).

A replica that ran the containment comparison on its own state could reach a different answer from the CASE_MANAGER's.
Depends on: MSM-07-023
MSM-07-006 MUST When an ET / REMOVE_EMBARGO_EVENT_FROM_CASE wire activity terminates the embargo register's ACTIVE entry, implementations MUST leave the case with no active embargo. The same step cancels every open proposal, so the derived EM becomes EXITED. No separate outbound wire activity is emitted; the participant position is derived from the case.

A register with a TERMINATED entry accepts no further change (ADR-0122), so a terminated embargo can never be invited to again. That is a fact about the case, which every replica holds once it replays the teardown.
Derives from: CM-18-003
Refines: MSM-02-007
MSM-07-025 MUST NOT When an ET terminates the embargo register's ACTIVE entry, implementations MUST NOT write any participant's consent rows. With no active embargo no participant is a signatory and no row binds anyone. An Accept or Reject that arrives afterwards records nothing (EMB-17-004).

The termination is a fact about the case, so it does not need copying onto each participant record as a terminal consent state. Leaving the rows as they were also keeps the history of who agreed to what, and every row of a final entry is frozen (CM-18-003).
Extends: MSM-07-006
MSM-07-007 MUST The timer-based pocket-veto transition MUST be enforced lazily, with no outbound wire message for the consent change itself. That transition moves the row of the invitation whose deadline passed from INVITED to TIMED_OUT. A lapse has no timer path (CM-18-002): a lapsed participant is re-invited, and the deadline attaches to that invitation.

Inaction cannot produce an outbound message because there is no actor-observable event to report. The expiry is a derived conclusion from (the row's rsvp_deadline, now). Requiring a wire message would demand the system speak on behalf of a participant who has said nothing, which contradicts the Actor Knowledge Model (AKM).
Derives from: CM-18-002
MSM-07-026 MUST The CASE_MANAGER MUST author a case-ledger entry that records a pocket-veto expiry (CM-28-005), so the consent change is visible in the canonical ledger. No other protocol wire activity is required.

The ledger entry is the record of the derived expiry, not a message sent by the silent participant. The entry already reaches every owner entitled to case content, so a second message would only duplicate it.
Derives from: CM-28-005
MSM-07-008 MUST The wire activity ACTIVATE_EMBARGO_ON_CASE (wire form: Accept(EmbargoEvent)[target=VulnerabilityCase], the EmbargoEvent itself as the object) is the Case Owner's decision for the case. It MUST activate the named proposal's embargo register entry and supersede any entry in force in the same step. The derived EM becomes ACTIVE (EA, or EC for a revision).

Giving the owner's decision its own activity retires the two-audience rule: an Accept of an Invite is always the sender's consent (MSM-07-003), and this activity is always the owner's decision for the case, so neither meaning depends on who sent the message (ADR-0122). When it replaces an embargo in force, the MSM-07-005 consent rule settles the rows.
Depends on: VAM-05-008
Derives from: EP-09-005
Depends on: MSM-07-005
Refines: MSM-02-002
Refines: MSM-02-005
MSM-07-027 MUST The CASE_MANAGER MUST accept ACTIVATE_EMBARGO_ON_CASE only from the Case Owner. It refuses any other sender with REFUSED before any write (HP-01-006).

Activating an embargo is the Case Owner's decision for the case (CM-02-005), so no other participant's activity may make it.
Extends: MSM-07-008
MSM-07-028 MUST ACTIVATE_EMBARGO_ON_CASE MUST mark the Case Owner's row for the activated embargo AGREED unless it already is. This includes a DECLINED row, since activating is the owner changing its answer (CM-18-003).

The owner that activates terms is bound by them, so its own row has to say so, whatever it answered before.
Extends: MSM-07-008
MSM-07-029 MUST The CASE_MANAGER MUST refuse ACTIVATE_EMBARGO_ON_CASE before committing it in two cases. One is a named embargo that is not an open proposal of the case. The other is CS that is public, exploited or attacked (EMB-02-002).

Refusing a doomed activation before the commit keeps the ledger free of an entry that replicas, copying its step, would apply without the CASE_MANAGER's P/X/A check (CM-23-016).
Extends: MSM-07-008
MSM-07-009 MUST The wire activity REJECT_EMBARGO_PROPOSAL_ON_CASE (wire form: Reject(EmbargoEvent)[target=VulnerabilityCase], the EmbargoEvent itself as the object) is the Case Owner's decision for the case. It MUST reject the named proposal's embargo register entry. Once no other proposal is open, the derived EM becomes NONE (ER) or stays ACTIVE with the prior terms in force (EJ). It stays PROPOSED or REVISE while another proposal is open (EP-08-001).

The owner's rejection decides the proposal for the case; whether the owner, as a participant, also refuses those terms is a separate Reject of the Invite (MSM-07-004). Keeping the two apart lets the owner keep the prior terms without declining them (ADR-0122).
Depends on: VAM-05-009
Derives from: EP-09-005
Refines: MSM-02-004
Refines: MSM-02-006
MSM-07-030 MUST The CASE_MANAGER MUST accept REJECT_EMBARGO_PROPOSAL_ON_CASE only from the Case Owner. It refuses any other sender with REFUSED before any write (HP-01-006).

Deciding a proposal for the case is the Case Owner's authority, so no other participant's activity may reject it for the case.
Extends: MSM-07-009
MSM-07-031 MUST NOT REJECT_EMBARGO_PROPOSAL_ON_CASE MUST NOT change any participant's consent rows, the Case Owner's included.

The owner's decision for the case is not its consent as a participant; that is a Reject of the Invite (MSM-07-004).
Extends: MSM-07-009
MSM-07-032 MUST This rule covers REJECT_EMBARGO_PROPOSAL_ON_CASE of the last open revision while CS is public, exploited or attacked. The CASE_MANAGER MUST also terminate the embargo in the same act (EMB-04-002). It trial-applies both steps before committing either. It commits the Reject's entry carrying the REJECT step, then its own ET Remove(EmbargoEvent) entry carrying the TERMINATE step. A refused termination commits neither.

Reverting to the prior terms is not viable once disclosure has happened, so the rejection ends the embargo. Deciding both steps together means a replica copying both entries ends with the revision REJECTED and EM EXITED, as the CASE_MANAGER does.
Extends: MSM-07-009
Depends on: EMB-04-002
MSM-07-033 MUST The CASE_MANAGER MUST refuse REJECT_EMBARGO_PROPOSAL_ON_CASE before committing it when the named embargo is not an open proposal of the case.

There is nothing to decide, and a committed entry naming no open proposal would be applied by every replica that copies it.
Extends: MSM-07-009
MSM-07-010 MUST The wire activity Leave(EmbargoEvent)[context=VulnerabilityCase] is a participant's withdrawal of its consent to the case's active embargo. For a sender whose row for that embargo is AGREED, the receiver MUST apply the PEC DECLINE trigger to that row. A Leave changes no register entry.

Accept and Reject answer an Invite; leaving an embargo one is bound by is a different act, as Leave(VulnerabilityCase) is for leaving the case. A dedicated activity covers every signatory, including those made signatories with no Invite (CM-14-005, CM-11-001), and keeps a Reject from meaning refusal or withdrawal by the state of the case. The rejected alternatives are recorded in ADR-0122.
Depends on: VAM-05-011
Derives from: CM-18-003
Refines: VP-13-017
MSM-07-034 MUST On a withdrawal (MSM-07-010), the receiver MUST also decline every open proposal's row that the sender had AGREED to. Every open proposal is a revision of the embargo withdrawn from (EP-08-004).

An agreed revision left in place would make the withdrawer a signatory again as soon as the owner activated it (MSM-07-035).
Implements: MSM-07-035
Extends: MSM-07-010
MSM-07-035 MUST NOT A DECLINED participant MUST NOT hold an AGREED row for an embargo that can become active without re-invitation.

A participant that has withdrawn from an embargo has to be asked again before terms bind it, or its withdrawal would be undone by an activation it took no part in.
MSM-07-036 MUST The receiver MUST refuse a Leave(EmbargoEvent), with a reported reason, in three cases. Its embargo is not the active one, the sender's row for it is not AGREED, or the sender is the Case Owner. The Case Owner ends an embargo by terminating it (CM-02-005).

Only a signatory of the embargo in force can withdraw from it, and the Case Owner holds the termination instead.
Extends: MSM-07-010
MSM-07-011 MUST NOT An actor that receives ACCEPT_INVITE_TO_EMBARGO_ON_CASE MUST NOT change any embargo register entry for it, whoever sent it, the Case Owner included.

The Accept is the sender's consent (MSM-07-003), and the Case Owner's decision for the case is a separate activity (MSM-07-008, ADR-0122). If an Accept moved a register entry, one Participant's answer would decide the embargo for everyone (MSM-02-010).
Extends: MSM-07-003
MSM-07-012 MUST An Accept may name an embargo the register does not hold, or holds in a final status. The CASE_MANAGER MUST refuse that Accept before committing it, with the refusal notice that reports the reason (CLP-05-001).

An Accept of an embargo that is neither the active embargo nor an open proposal records nothing (CM-18-003): no row can move toward consent to terms that can no longer bind. Refusing it before the commit keeps it off the ledger, and the refusal notice tells the sender why its answer did not count.
Supersedes: MSM-07-003 — In the case it names, the refusal replaces the AGREE
Depends on: CLP-05-001
MSM-07-013 MUST NOT An actor that receives REJECT_INVITE_TO_EMBARGO_ON_CASE from any sender but the CASE_MANAGER MUST NOT change any embargo register entry for it, the Case Owner's Reject included.

Symmetric with MSM-07-011: the Reject is the sender's consent (MSM-07-004), and the Case Owner's rejection of a proposal for the case is a separate activity (MSM-07-009, ADR-0122). If a Reject moved a register entry, one Participant's answer would decide the embargo for everyone (MSM-02-010).
Extends: MSM-07-004
MSM-07-014 MUST An actor applying a Reject of the active embargo from the sender's AGREED row MUST refuse it, with a reported reason. That Reject is not consent withdrawal, which is Leave(EmbargoEvent) (MSM-07-010).

Leaving an embargo one is bound by is a different act with its own activity (MSM-07-010, ADR-0122), so a Reject never has to be read against the case's state to tell a refusal from a withdrawal. The reported reason tells the signatory to send Leave(EmbargoEvent) if it means to withdraw (CLP-05-001).
Supersedes: MSM-07-004 — In the case it names, the refusal replaces the DECLINE
MSM-07-015 MUST An actor that applies a Reject of a proposed embargo that is not active MUST leave the sender's row for the active embargo unchanged. A signatory's rejection of a proposed revision refuses those terms; it is not withdrawal from the embargo in force. When no embargo is in force (EM PROPOSED), a Reject of a proposal declines that proposal's row only. It withdraws from nothing, and the participant's AGREED rows for other open proposals stay AGREED.

Consent is per embargo, so a signatory who refuses proposed terms is still bound by, and still consents to, the embargo that remains active; only the Case Owner decides whether the proposal replaces it (MSM-07-009, ADR-0122).
Extends: MSM-07-004
MSM-07-016 MUST The CASE_MANAGER's own Reject of a proposal is its abandonment of the proposal (EMB-16-001). A replica MUST tell it from a participant's Reject by the payload's actor (ADR-0152).

A CASE_MANAGER act with a participant's verb and object carries the participant's semantic type (ADR-0152), so the CASE_MANAGER's abandonment and a participant's refusal share one. The abandonment cancels the proposal, and is ER (MSM-02-006); the refusal declines only the sender's row (MSM-07-004). Only the actor tells them apart.
Extends: MSM-07-004
Depends on: MSM-07-017
Refines: MSM-02-006
MSM-07-017 MUST A CASE_MANAGER can hold an embargo stake of its own (EP-09-002). It MUST record a change of its own consent as its Update(EmbargoConsent) of the row. MSM-07-018 bans recording it as a Reject(Invite(EmbargoEvent)).

The CASE_MANAGER's own consent is the row it changes (CLP-07-013). Recording it as an Update keeps every CASE_MANAGER-sent Reject an abandonment (MSM-07-016), so a replica never has to guess which one the CASE_MANAGER meant.
MSM-07-018 MUST NOT A CASE_MANAGER MUST NOT send or commit a Reject(Invite(EmbargoEvent)) as a change of its own consent.

Every CASE_MANAGER-sent Reject is its abandonment of the proposal (MSM-07-016, ADR-0152). A Reject sent as its consent would cancel the proposal for everyone.
Extends: MSM-07-017

Vocabulary Model

Requirements governing how ActivityStreams 2.0 vocabulary types are defined, registered, configured, and deserialized in the Vultron wire layer. The vocabulary model is the foundation of the inbound pipeline: every activity must be deserialized into a known vocabulary type before semantic extraction can assign it a domain meaning.

Static Object Integrity

ID Priority Requirement Related
VM-08-003 MUST The copy of an activity recorded in the case ledger (the entry's payloadSnapshot) MUST be identical to the activity delivered to participants.

Ledger integrity requires that what is recorded and what is delivered are identical. A participant that holds both the delivered activity and the ledger entry can check this through the hash chain, so a ledger that records one form while recipients receive another is visible to them.
Refines: VM-08-001

Unknown Message Handling

ID Priority Requirement Related
VM-09-001 MAY Messages that cannot be parsed or whose semantics are unknown MAY still be forwarded to the case event log to create an entry for human or agent review

A receiver can get a message that it cannot parse or interpret, for example from a peer that runs a newer version of the protocol. If the receiver drops it, information that may matter to the case is lost and nobody can see that it arrived. Keeping it for human or agent review lets a person decide what it means. This is a MAY because the protocol does not define a meaning for such a message.

Vocabulary Namespace and JSON-LD Context

ID Priority Requirement Related
VM-10-001 MUST Outbound Vultron wire messages MUST declare the Vultron JSON-LD context by setting @context to "https://certcc.github.io/Vultron/ns/context.jsonld". Using only the ActivityStreams namespace (https://www.w3.org/ns/activitystreams) is not sufficient: Vultron-specific types (VulnerabilityCase, EmbargoEvent, etc.) are not defined in that namespace and cannot be resolved by receivers without the Vultron context declaration.

Derived from ADR-0069 (docs/adr/0069-vultron-namespace-uri.md). The Vultron vocabulary extends ActivityStreams with CVD-specific types; those types must be declared in a dereferenceable JSON-LD context so that external implementations can resolve them. The context document at the Vultron namespace URI imports the AS2 namespace, so implementations need only cite the Vultron context URI.
Refines: VM-02-002

Message Validation

Requirements for validating ActivityStreams 2.0 activities at the inbox handler boundary, covering ActivityStreams structure and semantic validation. Invalid activities are rejected with appropriate error responses before dispatch.

Required Field Validation

ID Priority Requirement Related
MV-03-001 MUST The system MUST validate required fields based on activity type

Each activity type needs some fields to have a meaning. For example, an Accept with no object accepts nothing. A check for each type refuses such an activity at the edge. Thus handlers do not need to check for a missing field that their activity type requires.
MV-03-002 MUST A receiver MUST report a present but blank required field as an omitted field, and a non-blank unparseable value as a schema fault. A blank value is an empty string or one containing only whitespace.

A blank value has no data, so it is the same omission as a missing key (CS-08-001: if present, then non-empty). One omission must cause one error and one status code, whatever its form. If a guard tests only the key, a blank published goes on to schema validation, which gives a Pydantic error dump in place of the explanation in CLP-15-006. A value that is not blank but does not parse is a different fault, so it stays on the schema path.
Refines: MV-03-001

Object Type Validation

ID Priority Requirement Related
MV-04-001 MUST The system MUST validate that object types are recognized Vultron types

The activity type and the object type together decide the semantic type of an activity, and thus its handler. The receiver cannot route or validate an object whose type is not in the Vultron vocabulary. If the receiver accepts the object, the failure only moves to a later stage that cannot act on it.
MV-04-002 SHOULD For Create-style activities that create sub-objects (e.g., Create(CaseParticipant)), the activity name field SHOULD be a descriptive human-readable string identifying the actor, created object ID, and context (case ID)

A person who reads a log or a timeline cannot learn much from ids alone. The ids in a Create of a sub-object do not show who created which object in which case. A descriptive name lets an operator read the activity without a lookup of its ids.

URI Validation

ID Priority Requirement Related
MV-05-002 MUST The system MUST treat ActivityStreams object IDs as opaque URIs

An id identifies an object and carries no other meaning, so a receiver that reads structure into it depends on how one sender mints its identifiers. ActivityStreams 2.0 and ActivityPub require an id to be an IRI (MV-05-001) but say nothing about opacity, so the rule is not inherited: it is Vultron's own, binding every implementation that receives another's identifiers. A case stub's id is the case's ID with /stub appended, and still no receiver derives the case from it (CM-11-003).

Outbound Activity Object Integrity

ID Priority Requirement Related
MV-09-001 MUST The object of an outbound Vultron activity MUST let its recipient determine the activity's semantic type: it is carried inline as a typed object, or by a reference the recipient can dereference to a typed object (AKM-05-003, AKM-05-004).

A recipient routes an activity by its activity type and the type of its object (SE-01-001). If the recipient cannot read the object inline or get it by its URI, it cannot classify the activity. It then cannot act on the message.

Stub Objects

ID Priority Requirement Related
MV-10-001 MUST A stub object MUST carry at minimum an id field (full URI), a type field and a non-empty summary field that gives human-readable context.

The stub form has one narrow purpose. It is not a replacement for a full object. An invitee must decide whether to join a case before it can receive the case (MV-10-005). The stub represents the case until the participant is active (CM-10-004). The summary tells the invitee which case it is asked to join. The sender decides what the summary says (CM-17-020).
Constrains: MV-04-003
MV-10-007 MUST A receiver MUST refuse a stub whose summary is absent or blank.

A stub with no summary gives the invitee nothing to decide on, so it cannot do the one job the stub form exists for (MV-10-001).
Implements: MV-10-001
MV-10-008 MUST A receiver MUST classify a stub by its type. The stub form is permitted only for a case, as the distinct VulnerabilityCaseStub type of CM-11-013.

No other type needs the stub form, and no other stub type is implemented. A classification by the fields present would read a sparse case as a stub, or a stub as a case.
Refines: MV-10-001
MV-10-009 MUST This rule covers an object type other than a case, when the sender does not inline the full object. The sender MUST reference the object by URI or by an ActivityStreams Link, not by a partial inline object.

Only a case has a stub form (MV-10-008). A partial inline object of another type claims to be the object while it lacks fields, so the receiver cannot tell what the sender left out.
Depends on: MV-10-008
MV-10-010 MAY A sender MAY send an actor that it knows only by URI as an inline object that carries exactly its id and type (AKM-05-005).

The actor reference claims no field beyond the two that the sender knows, so it is not a partial object under MV-10-009. It keeps the type that a receiver routes Offer(Actor) and Invite(Actor) by (ADR-0159).
Refines: MV-10-009
MV-10-002 MUST A VulnerabilityCaseStub object is ONLY permitted in the following field positions of an ActivityStreams activity: actor, object, target, and origin.

Scopes the position rule to the one permitted stub type (MV-10-001). A reference in any of these positions may also be a bare URI or a Link; those forms are always acceptable and are unaffected by MV-04-003's refusal of malformed inline objects.
MV-10-005 MUST A participant MUST be active (CM-10-004) before the CASE_MANAGER delivers full case details to it.

Full case details can include embargoed vul information. An active participant has joined the case and has not been removed. When an embargo is active, it has also agreed to that embargo (CM-10-004). Thus no actor gets the details before it joins, or before it agrees to the active embargo.
Depends on: MV-10-006
MV-10-006 MUST When a participant accepts the stub Invite (Accept(Invite(Actor)[target=VulnerabilityCaseStub])) and the case has an active embargo, the acceptance MUST imply consent to the active embargo, marking that participant's consent row for it AGREED (CM-11-001).

An invitee uses the stub to decide whether to join. To join a case under an active embargo is to take part under that embargo. When the acceptance counts as consent, no participant becomes active without a recorded answer to the embargo. The delivery gate of MV-10-005 relies on this.
Depends on: CM-03-008

Semantic Extraction

The inbox handler extracts semantic meaning from ActivityStreams activities by matching activity type and object type patterns. This semantic type determines which handler function processes the activity.

Target-Field Discriminators

ID Priority Requirement Related
SE-08-003 SHOULD When two semantics are discriminated only by a target-field value, a dedicated wire object type (e.g. CaseParticipantRole) SHOULD be preferred over target-field discrimination so that the wire shape is unambiguously self-describing without depending on the order in which a receiver tries its patterns.

When patterns use only the target field to tell activities apart, a sender that omits the target causes a silent routing error. This sender can be minimally conformant or have a defect. The pattern order of the receiver is then its only protection. A distinct object type removes this risk, and the wire shape then tells its own meaning (ADR-0039). Offer(CaseParticipantRole)[context=VulnerabilityCase] is the canonical example. See notes/activitystreams-state-update.md § "Patterns Sharing a Verb and Object Need a Strict Discriminator".
Refines: SE-08-001

Actor Knowledge Model

Defines the Actor Knowledge Model: a Vultron Actor's knowledge of the world is strictly bounded by the ActivityStreams 2.0 activities it has received. Actors must not assume recipients hold objects that were not explicitly included in prior activities; DataLayer access is private and non-shareable, and all inter-Actor communication occurs exclusively through the AS2 wire protocol.

DataLayer Isolation

ID Priority Requirement Related
AKM-01-002 MUST NOT Co-located actors MUST NOT communicate by reading or writing each other's local state, or by calling into each other directly. Co-located actors share a server or a process.

A direct shortcut between co-located actors bypasses the inbox pipeline, so its exchanges skip validation, the addressee and entitlement gates, and the case ledger, and leave nothing a remote participant would receive.
AKM-01-003 MUST Co-located actors MUST still exchange protocol messages through each other's inboxes. Co-located actors share a server or a process.

Delivery through inboxes makes co-location invisible to the protocol: the same actors behave the same whether or not they share a process.
Extends: AKM-01-002

Knowledge Boundaries

ID Priority Requirement Related
AKM-02-001 MUST NOT An Actor MUST NOT assume that a recipient has knowledge of any object that has not been explicitly included in a prior activity sent to that recipient.

A recipient holds only what it has been sent. An activity that relies on an object the recipient never received cannot be processed, or is processed against a guess, so a sender must reason from what it has actually delivered to that recipient.
AKM-02-002 MUST NOT Actors MUST NOT name an object the recipient cannot be expected to hold already by a reference the recipient cannot dereference (AKM-05-003, AKM-05-004). Such an object is carried inline, or by a reference the recipient can fetch.

A reference the recipient can neither match to an object it holds nor fetch leaves it unable to read what the activity acts on, so it must refuse the activity or guess. Carrying such an object inline, or by a reference the recipient can fetch, keeps every activity processable by its addressee.
AKM-02-003 MUST A case named in an activity's target MUST take one of two forms. It is the case URI when the recipient already holds the case. It is the case stub (VulnerabilityCaseStub, CM-11-013) that the stub Invite sends to an actor that does not yet hold the case.

A participant or CASE_MANAGER addressed about a case already holds it and needs only its URI, while an actor that does not yet hold the case needs only the stub to decide whether to join.
AKM-02-008 MUST NOT An actor MUST NOT send a full case in an activity's target.

A full case in target would either repeat what the recipient holds or hand case contents to an actor that has not joined.
Extends: AKM-02-003
AKM-02-006 MUST NOT An actor MUST NOT record or send a value it does not know for a field of an object it did not author. It leaves such a field absent. A published or updated time read from its own clock is such a value, as is an inbox or outbox derived from another actor's id.

A receiver cannot tell an invented value from an asserted one, so it records the invention as the object's own claim, and the case ledger then carries it in its snapshots. A value read from the sender's clock also differs on every build, so a rebuild of one activity under the same id does not match the first build (OX-06-002).

Outbound Activity Object Integrity

ID Priority Requirement Related
AKM-03-001 MUST Outbound initiating activities (Create, Offer, Invite, Announce, Add, Remove, Update, Join, Ignore, Leave) MUST carry, as their object, the domain object they act on, inline or by reference.

A receiver identifies what an initiating activity does from its type together with the type of its object. Without the object it cannot select the use case or apply the change. Inline or by reference leaves the form to the implementation (AKM-05-003), except where a rule fixes it, as CBT-01-002 does for the bootstrap.

Actor Addressing and Discovery

ID Priority Requirement Related
AKM-05-003 SHOULD An implementation SHOULD support object dereference: fetching the full object named by a URI through an API call to the actor or service that holds it.

Several rules let a sender name an object by reference instead of inlining it, and each is sound only where the receiver can resolve the reference. The CP-01-009 by-reference actor form is one. A prototype may inline everything and resolve nothing, so the capability is a SHOULD there (AKM-05-004 makes it a MUST in production).
AKM-05-004 MUST A production implementation MUST support object dereference (AKM-05-003). Given a URI it has not seen, it then fetches the object through an API call.

A production deployment exchanges cases with independently built implementations, which may name objects by reference instead of inline. Without dereference it cannot process such an activity, and the refusals that stand in for dereference (AKM-05-003) would block legitimate traffic.
Refines: AKM-05-003
AKM-05-007 MUST A receiver MUST fetch an object it lacks, and needs to process a received activity, from the actor that holds it. A case's object is held by the case's CASE_MANAGER, and an actor's profile by that actor. An object the protocol requires inline, as it does the bootstrap's case and participant records (CBT-01-002, CBT-01-007), is not fetched. A bootstrap missing a participant record is refused (CBT-05-008).

A sender may name by reference an object its receiver is expected to hold (CLP-07-006, AKM-02-002), and a replica that lacks it, through a gap or a lag, would otherwise refuse or drop an activity that was sound. The bootstrap is the exception: its receiver trusts no CASE_MANAGER until it accepts the bootstrap, so it has no trusted source to fetch from, and the bootstrap carries inline what the receiver cannot already hold instead (ADR-0170).
Implements: AKM-05-003
Refines: CLP-07-006
AKM-05-010 MUST While a fetch under AKM-05-007 is open, the receiver MUST defer the activity, held as received. It does not process the activity without the object, and does not drop it.

Deferring keeps the activity, as the inbox keeps one whose case is not yet known (PCR-06-001), so the activity is processed once, with the object, when the fetch settles.
Refines: AKM-05-007
AKM-05-011 MUST A receiver MUST NOT fetch an object from itself, or the entry an Announce(CaseLedgerEntry) carries.

The CASE_MANAGER holds the canonical case, so nobody holds a case object it lacks. Replication carries its entries inline (SYNC-02-005), and replay needs no fetch.
Refines: AKM-05-007
AKM-05-012 MUST A receiver MUST keep a fetched case object only when it holds no record under the object's id (MV-10-003).

A fetch answers an object the receiver lacked; a record that arrived meanwhile is the receiver's own, and a fetched copy must not overwrite it (MV-10-003).
Refines: AKM-05-007
Depends on: MV-10-003
AKM-05-005 MUST When a sender carries an actor inline in an activity, the inline actor MUST be the sender's held profile of that actor, as held.

A held profile is what the sender knows of the actor. Anything added to it on the way out would be invented (AKM-02-006).
Refines: AKM-05-001
AKM-05-017 MUST When a sender holds nothing more than an actor's URI, it MUST carry an actor reference instead of a profile. An actor reference is an inline object with exactly the actor's id and a type.

A sender may name a peer it knows only by URI (AKM-05-001), and a receiver routes Offer(Actor) and Invite(Actor) by the object's type, so the actor must travel inline with a type even when nothing else is known. Anything beyond the id and type would be invented (AKM-02-006).
Extends: AKM-05-005
Constrains: MV-10-009
AKM-05-018 MUST A receiver MUST read each field that an actor reference lacks as unknown.

A reference says nothing about the fields it lacks. A receiver that read a missing field as empty would record a value nobody asserted.
Uses definition from: AKM-05-017
AKM-05-019 MUST NOT A receiver MUST NOT let an actor reference replace a fuller record of that actor that it holds.

A reference says nothing about the fields it lacks, so it cannot erase a value the receiver already holds.
Uses definition from: AKM-05-017

Code Style Specifications

Defines code formatting, import organization, and coding style standards for Python code in the Vultron project.

Datetime and Timezone Conventions

ID Priority Requirement Related
CS-13-005 MUST A wire timestamp this node generates MUST serialize with a UTC offset (Z or +00:00). A timestamp carried from a peer MUST serialize with the explicit offset it arrived with, which need not be UTC (CLP-15-007).

UTC is the form every node can write without configuration. A carried timestamp is part of an activity that the case ledger records verbatim (CLP-07-011). A change from +05:00 to +00:00 keeps the instant but changes the offset, so replicas then hold different records. What this rule keeps is the offset value, not the spelling a peer chose for it: Z and +00:00 are one offset, and a carried Z serializes as +00:00 because that is the only form format_timestamp writes. This rule picks the offset only; CS-13-006 holds the separate rule that every wire timestamp has an explicit offset, and CS-13-007 fixes its fractional-second digits, which a carried timestamp takes like any other.
Depends on: CLP-15-007
Refines: CS-13-006 — picks which explicit offset a wire timestamp carries
CS-13-007 MUST Every datetime this node serializes MUST be written as an RFC 3339 date-time with exactly six fractional-second digits, whether or not its microseconds are zero.

The case ledger hashes and compares timestamp text, so one instant at one offset needs one string. Whole seconds gave one string but made two acts one actor sends in the same second carry equal times; the fixed six-digit form keeps that one string and keeps the order. It fixes the fraction only: a carried timestamp keeps its own offset (CS-13-005, CLP-15-007), so the same instant at another offset is still another string. The form is what this node writes, not what it requires to receive: a peer may send whole seconds (ADR-0167).
Refines: CS-13-006 — fixes the fractional-second digits of the RFC 3339 form
Constrains: CLP-15-007 — A receiver carries a nested inbound timestamp as received, including at rendering, but writes its fractional digits in this node's one form. The instant and the offset are the peer's; the number of digits is not.