Skip to content
Reference

Case Management

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.4 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.4 Case Management

The requirements on cases and their participants: the case object, case proposals, participant roles, and the asks a participant answers.

Case Management

Requirements for VulnerabilityCase lifecycle management, CASE_MANAGER responsibilities, and the RM/EM/CS/VFD state model. Covers actor isolation at the protocol level, the distinction between participant-specific and participant-agnostic state, case initialization sequencing, embargo acceptance ownership, and the invitation acceptance lifecycle.

Actor Isolation

ID Priority Requirement Related
CM-01-001 MUST Each actor MUST have an isolated protocol state domain

Each actor is a separate party, with its own view of the case and its own authority over its state. If the records of one actor can change what another actor reads, a fault or a hostile peer in a shared deployment can change the case history of another party. Then no actor can trust its own copy.
CM-01-002 MUST Each actor's RM state MUST be maintained independently per case

RM is a process for each participant. Each participant receives, validates, accepts, defers and closes a report on its own schedule (CM-03-002). One RM state for each actor in each case lets an actor be at different RM stages in different cases. It also stops the progress of one actor from counting as the progress of another actor.

CASE_MANAGER Lifecycle

ID Priority Requirement Related
CM-02-001 MUST Each VulnerabilityCase MUST have exactly one associated CASE_MANAGER

The CASE_MANAGER is the only writer of canonical case state (CM-02-002). With no CASE_MANAGER, no actor can commit a case change. With two, two actors can commit changes that conflict. The copies of the case then become different, and no rule can make them agree again.
CM-02-002 MUST CASE_MANAGER MUST be the authoritative source of truth for case state

Every participant has its own copy of the case. Without one source of authority, two participants that disagree have no rule to decide which copy is correct, and the copies become different. When the CASE_MANAGER is that source, every replica gets the same state (ADR-0108).
CM-02-003 MUST CASE_MANAGER MUST persist until the associated VulnerabilityCase is closed

The authority for a case must be clear for as long as the case accepts changes. If the CASE_MANAGER role stops before the case closes, messages that need its decision have no valid recipient. Then the case cannot close in the correct sequence.
CM-02-004 MUST CASE_MANAGER MUST know the Case Owner

Only the Case Owner can do some case actions (CM-02-005). The CASE_MANAGER can enforce this only if it knows the Case Owner. The case records the Case Owner in attributed_to.
CM-02-005 MUST CASE_MANAGER MUST restrict the following activities to the Case Owner: case closure, approving an embargo proposal to become active on the case and terminating an active embargo, approving or rejecting participant invitations, case ownership transfer, and offering a participant an additional role (CM-33-002) (see CM-02-006 for the authorisation mechanism).

These actions decide who learns about the vulnerability, under what embargo, and who is responsible for the case. In CVD, the Case Owner makes these decisions. If any participant can start them through the CASE_MANAGER, a participant can close the case, change the embargo, admit a party that nobody checked, or take the case.
CM-02-006 MUST CASE_MANAGER MUST enforce case-level authorization for all case mutations

The CASE_MANAGER applies every change to canonical case state. Thus it is the one place that can stop an unauthorized change before every replica copies it. If a change path skips the check, any actor that can get to the inbox can change the case.
CM-02-007 MUST A VulnerabilityCase MUST carry a notes collection recording the case-scoped notes added to it by Add(Note) activities that target the case

Case notes are the discussion of the case by the participants, and they must stay with the case. A notes collection on the case gives each Add(Note) a location. It also lets a participant that joins later see the notes from before it joined.
CM-02-008 MUST This rule covers every path that creates a VulnerabilityCase from an Offer(VulnerabilityReport), the CASE_MANAGER's path included (CP-09-001). Each such path MUST set the case's attributed_to to the ID of the actor that received the Offer, the CASE_OWNER.

attributed_to is the owner field of the case. Ownership checks read it (MSM-07-027), and ownership transfer changes it together with the CASE_OWNER role (CM-21-002, CM-21-024). The actor of Create(VulnerabilityCase) records who created the case, not attributed_to. Thus the CASE_MANAGER does not become the owner when it creates the case.
CM-02-017 MUST A path that creates a VulnerabilityCase from an Offer(VulnerabilityReport) MUST record the actor that received the Offer, the CASE_OWNER, as the case's initial primary participant.

The actor that received the report is the first party that must act on it, and it owns the case. When it is the first participant, the case has a participant record for its owner before any other party joins.
Extends: CM-02-008
CM-02-009 MUST For each state-changing event it receives, the CASE_MANAGER MUST take the commit stamp on the event's ledger entry as its trusted time. That stamp is the envelope published of CLP-14-002.

The CASE_MANAGER's clock is the only trusted source of time for the order of events in a case. If each copy of the case used the time a participant supplied, the copies could disagree on the order of events. The ledger entry carries two times (ADR-0103): the CASE_MANAGER's commit stamp, which orders the case, and the sender's claim, which CM-02-018 keeps.
Depends on: CM-02-002
Uses definition from: CLP-14-002
CM-02-018 MUST The CASE_MANAGER MUST keep the sender's claimed time on each state-changing event as sent in the entry's payloadSnapshot, never overwriting it.

The sender's claim is evidence of when the sender says the event happened (ADR-0103). The CASE_MANAGER cannot verify it, so it does not order the case by it (CM-02-009). If the CASE_MANAGER overwrote it, the record of what the sender said would be lost, and the received body would change (CLP-07-011).
Extends: CM-02-009

Case State Model

ID Priority Requirement Related
CM-03-001 MUST The system MUST implement the three interacting state machines: RM (Report Management), EM (Embargo Management), and CS (Case State)

Report handling, embargo negotiation, and the public and fix state of a case change for different reasons and at different times. Three interacting machines let an implementation validate each one on its own. The interactions between them stay explicit and do not hide in one combined state.
Implements: VP-01-001
Implements: VP-13-001
Implements: VP-06-001
CM-03-002 MUST RM state MUST be participant-specific

Each participant moves through report handling on its own. One participant can accept a report that another has not yet validated. A single shared RM state cannot show this, and one participant's transition would overwrite the state of another (CM-01-002).
Implements: VP-01-002
Implements: VP-02-002
Implements: VP-03-001
CM-03-003 MUST A shared EM state MUST be tracked at the case level in the case's CaseStatus, to represent the collective embargo agreement

An embargo binds every participant in the case. Thus, whether an embargo is proposed, active or ended is one fact for the whole case. One case-level record gives every participant the same answer. The consent rows keep each participant's agreement separately (CM-03-008).
Implements: VP-04-002
Implements: VP-13-009
Implements: VP-09-001
CM-03-008 MUST Each CaseParticipant MUST track their own embargo consent through its per-embargo consent rows (CM-10-001, CM-18-001)

Agreement to an embargo is the decision of one participant, about one specific embargo. One row for each participant and embargo records each answer one time. Thus, a revision does not inherit an agreement that a participant gave to an earlier version (CM-18-001).
Implements: VP-04-002
CM-03-004 MUST CS (PXA sub-state) MUST be participant-agnostic (shared per case)

Public awareness, exploit publication and attacks in the wild are facts about the world, not about one participant. When the vulnerability is public, it is public for all participants. A copy for each participant can only disagree with the facts.
CM-03-005 MUST VFD (Vendor Fix Deployment) state MUST be participant-specific

Fix readiness and deployment are different for each participant. One vendor can ship a fix before another vendor, and a deployer has its own deployment state. A shared VFD state would let the progress of one participant show as the progress of another (ADR-0075).
CM-03-007 MUST RM state transitions MUST use the established protocol activity types without introducing new activity types for reverse transitions

Keeps the RM symbol set minimal and produces clean audit histories; Undo conflates historical negation with a new forward transition
Implements: VP-02-004

State Transition Correctness

ID Priority Requirement Related
CM-04-001 MUST A receiver of a participant-status message MUST record the declared RM state on the sending actor's CaseParticipant, as its model of the sender (SM-10-001).

RM state is self-declared: each participant reports its own report-handling progress (CM-03-002). A participant-status message updates the receiver's model of its sender. A record on any other participant would give one participant the progress of another. Other messages move the receiver's own state instead (SM-10-001). RSH-08-003 says which receiver writes the record directly.
Refines: SM-10-001
Implements: VP-02-002
Implements: VP-02-003
Implements: VP-03-001
Implements: VP-13-005
CM-04-002 MUST A receiver processing a VFD state transition MUST update the VFD state in the participant status of the sending actor's CaseParticipant

VFD state is participant-specific (CM-03-005). A VFD message reports the fix readiness or deployment of the sender. A record on any other participant would claim a fix that this participant did not make.
Refines: SM-10-001
CM-04-003 MUST When the Case Owner accepts an embargo, the CASE_MANAGER MUST activate it in the case's embargo register, from which the case-level EM state derives (ADR-0122). Every other replica takes that state from the CASE_MANAGER's ledger (RSH-08-003).

The shared embargo binds the case only when the Case Owner accepts it (CM-02-005). A case-level record of that acceptance puts the embargo in force for all participants at the same time. Only the CASE_MANAGER writes canonical case state (ADR-0108), and the EM state is derived from the append-only register, with CaseStatus.em only a stamped copy (ADR-0122). The acceptances of other participants stay in their own consent rows (CM-04-008).
Implements: VP-06-001
Implements: VP-09-001
Implements: VP-11-002
Depends on: CM-03-008
CM-04-004 MUST When the CASE_MANAGER adopts a PXA transition (public disclosure, exploit publication or attack observation), it MUST record the case-level PXA state in the case's CaseStatus. Every other replica takes that state from the CASE_MANAGER's ledger (RSH-08-003).

PXA state is a case-level fact (CM-03-004). A record of a reported disclosure, exploit or attack on the shared CaseStatus makes it visible to all participants at the same time. This is important because each of these events can end an embargo early. Only the CASE_MANAGER writes canonical case state (ADR-0108).
Implements: VP-03-002
Implements: VP-06-001
Implements: VP-14-004
CM-04-005 MUST A receiver processing a VFD or PXA transition MUST validate the requested transition against the authoritative current state before recording it

A transition is valid only from the current state of the case or participant. If the receiver records a transition without a check against the authoritative current state, a stale or out-of-order message can move the state backward or skip a necessary step. Every replica then copies the incorrect state.
CM-04-006 MUST NOT A participant-specific transition (RM or VFD) MUST NOT change any case-level (participant-agnostic) state (EM or PXA).

Participant-specific state and case-level state are separate by design (CM-03-002 through CM-03-005). If an RM or VFD transition of one participant wrote across this boundary, it could change the shared embargo or public state.
Implements: VP-13-009
Depends on: CM-03-008
CM-04-010 MUST NOT A case-level transition (EM or PXA) MUST NOT change any participant-specific state (RM or VFD).

Participant-specific state and case-level state are separate by design (CM-03-002 through CM-03-005). If a shared event wrote across this boundary, it could overwrite the progress of one participant.
Extends: CM-04-006
Depends on: CM-03-008
CM-04-007 MUST RM transition validation MUST treat RM.CLOSED as terminal before it applies any same-state no-op shortcut.

If the validator checks for equality first, a duplicate close retry can flow into the later append and broadcast paths as a fresh state change.
CM-04-011 MUST RM transition validation MUST treat a repeated RM.CLOSED -> RM.CLOSED update as a terminal no-op: no new participant status record, broadcast or case-ledger entry.

A participant can send its close again, for example on a retry. A repeat of the close is not a new decision. If it appended a record, broadcast or ledgered, every replica would record a second close that never happened.
Extends: CM-04-007
CM-04-008 MUST When processing an EM state transition and the accepting actor is any participant other than the Case Owner, the receiver MUST update only that participant's own consent row for the accepted embargo.

Non-owner participants do not advance the shared EM state; their individual acceptance updates only their own consent record.

Object Model Relationships

ID Priority Requirement Related
CM-05-001 MUST VulnerabilityReport, VulnerabilityCase, VulnerabilityRecord, CaseReference, CaseParticipant, and ParticipantStatus MUST be distinct, non-interchangeable object types.

Each of these objects has its own meaning and its own rules. If two of them were one type, a receiver could not tell which one a message carries. The CASE_MANAGER is not in this list: it is an ordinary actor designated for a case, not an identity or a distinct object type (ADR-0088).
CM-05-015 MUST NOT A message MUST NOT carry one of the six object types of CM-05-001 where the protocol expects another of them.

A receiver acts on an object by its type. If a message carried a case where a report is expected, the receiver would apply the rules of one object to a different object.
Extends: CM-05-001
CM-05-002 MUST NOT A VulnerabilityCase MUST NOT exist without a reference to at least one VulnerabilityReport.

A case exists to coordinate the response to a reported vulnerability. A case with no report has nothing to coordinate and gives participants nothing to act on. Also, a participant that joins later reads the report to learn what the case is about.
Implements: VP-02-015
Implements: VP-02-020
CM-05-003 SHOULD A VulnerabilityCase SHOULD reference at least one VulnerabilityRecord before closure

A VulnerabilityRecord identifies the vulnerability outside the case, for example with a CVE ID. If a case closes without one, the outcome is difficult to find and to cross-reference. Some cases close without one for a good reason, thus the rule is a SHOULD.
CM-05-004 MAY A VulnerabilityCase MAY have zero or more associated CaseReference objects

Advisories, patches and other public material can exist for a case or not. Their number depends on how many parties publish. Zero or more items let the case record what exists, with no placeholder.
CM-05-005 MUST CaseReference objects MUST include at minimum a url field.

A reference points a reader to a public resource. Without a url, it identifies nothing that a consumer can get or examine.
CM-05-006 MAY One report MAY describe several vulnerabilities.

Reports and vulnerabilities do not always map one to one. One finder can report several flaws together.
CM-05-017 MAY One case MAY group several reports, for example when the reports involve overlapping participants.

Several finders can report the same flaw. Then one case can coordinate the response to the related reports, and the same work does not divide across cases.
Extends: CM-05-006
CM-05-007 MAY Multiple CaseReference objects MAY arise from a single case (e.g., coordinated simultaneous disclosure by multiple vendors)

When several vendors fix the same vulnerability, each vendor usually publishes its own advisory. The case must record all of these advisories, not only one.
CM-05-008 MUST NOT An actor MUST NOT interpret a VulnerabilityRecord identifier, nor restrict it to the CVE ID format or any other namespace.

Different organizations use different identifier namespaces (CVE, CERT/CC VU#, vendor-assigned IDs, etc.); requiring a specific format excludes valid records and prevents multi-namespace support
CM-05-009 SHOULD A VulnerabilityRecord SHOULD support a list of alias identifiers from different namespaces for the same vulnerability

Multiple IDs from different namespaces may refer to the same underlying vulnerability (e.g., a CVE ID and a CERT/CC VU# ID)
CM-05-010 MUST When a VulnerabilityRecord is a CVERecord (i.e., it carries a CVE ID), its data MUST conform to the CVE JSON schema (https://github.com/CVEProject/cve-schema)

Tools that read a record with a CVE ID expect the CVE JSON schema. Data that conforms goes to the CVE ecosystem without translation. Those tools refuse or misread data that does not conform.
CM-05-011 SHOULD CaseReference objects SHOULD include a name field containing a human-readable title.

A human-readable title makes references easier to identify in case summaries and reports.
CM-05-012 MAY CaseReference objects MAY include a tags field (array of one or more type descriptors, e.g., "patch", "vendor-advisory", "third-party-advisory", "exploit", "release-notes").

Type descriptor tags allow consumers to filter or categorise references without parsing the URL or name.

Case Update Broadcast

ID Priority Requirement Related
CM-06-001 MUST When the CASE_MANAGER updates canonical case state, it MUST notify every active participant (CM-10-004).

Every change the CASE_MANAGER makes to the case file is its own ledger entry (CLP-07-013), so the ledger fan-out is the notice: each change reaches the active participants as Announce(CaseLedgerEntry). The CASE_MANAGER also announces the updated case as Announce(VulnerabilityCase) after admitting a participant's engagement. No participant sends the CASE_MANAGER an Update(VulnerabilityCase); one that arrives is the shape of the CASE_MANAGER's own update_case_owner act, which no received use case handles, so it is reported "no use case found" (SE-05-002, ADR-0140).
Implements: VP-02-019
Implements: VP-03-012
Implements: VP-08-001
CM-06-002 MUST Participants receiving a case update notification MUST treat the update as authoritative only when it originates from the CASE_MANAGER

Only the CASE_MANAGER writes canonical case state, and replicas get it from the CASE_MANAGER (ADR-0108). If a replica accepts a case update from any other participant, any peer can make that replica diverge from the canonical case.
Depends on: CM-02-002
CM-06-003 MUST Case update notifications MUST include the updated CASE_MANAGER's actor URL so participants can fetch the current case state if needed

A notification can be a summary, can arrive late, or can arrive out of order. The actor URL of the CASE_MANAGER lets a participant that thinks its copy is stale get the current state from the authoritative source, not from a peer.
CM-06-004 MUST Participants MUST authenticate case update notifications before accepting them as authoritative

Participants accept an update as authoritative only when it comes from the CASE_MANAGER (CM-06-002). Without authentication, any party can claim to be the CASE_MANAGER and send false case state, for example an embargo that it says is lifted.
CM-06-005 MUST When a note is added to a case, the CASE_MANAGER MUST broadcast the note to every active participant (CM-10-004), excluding the note author.

A note is part of the case discussion, and every active participant must see it. The author already has the note. A copy back to the author adds traffic and a duplicate that the author must ignore.
Depends on: OX-03-001
Depends on: CM-02-001

Redacted Case View

ID Priority Requirement Related
CM-09-001 SHOULD The system SHOULD support a RedactedVulnerabilityCase type for sharing case information with invited-but-not-yet-accepted participants

An invitee has not yet agreed to the embargo. Thus, it must learn only enough about the case to decide whether to join. A redacted view gives it this information and does not disclose case details that it has no obligation to protect yet.
CM-09-002 MUST The ID of a RedactedVulnerabilityCase MUST be cryptographically unrelated to the full VulnerabilityCase ID. The prototype case stub (CM-11-013) is not a RedactedVulnerabilityCase, and this requirement does not apply to it.

Prevents an attacker who obtains a redacted case ID from inferring the full case ID
CM-09-003 MUST Each invitee MUST receive a distinct RedactedVulnerabilityCase ID

Prevents cross-correlation of redacted IDs to infer participant list size or identities

Per-Participant Embargo Acceptance

ID Priority Requirement Related
CM-10-001 MUST CaseParticipant MUST record its consent per embargo: one consent row for every entry in the case's embargo register, naming the embargo and the participant's answer to it, UNINVITED until it is asked (CM-18-001). A participant agrees to an embargo explicitly, by an Accept, as its proposer, by seeding (CM-14-003, CM-14-005), or by containment, when the Case Owner activates a revision that ends no later than terms the participant had agreed to (EP-05-001). Each invitation's RSVP deadline is stored on that invitation's INVITED row (CM-28-013). No other record of embargo consent may exist on the participant.

Consent is per embargo (ADR-0093, ADR-0122). The CM-10-004 content gate reads the rows. Thus, the rows must name every embargo that binds the participant. This includes an embargo that containment carries over: an agreement to N days is an agreement to every shorter period, so no explicit Accept is necessary for it. A second record of consent next to the rows can disagree with them. A reader then cannot know which record is correct.
Implements: VP-05-001
CM-10-002 MUST Embargo acceptances MUST be timestamped by the CASE_MANAGER at the time of receipt, not using the participant's claimed timestamp

The CASE_MANAGER applies the only trusted timestamp; the participant's reported time cannot be verified
Depends on: CM-02-002
Implements: CM-02-009
CM-10-004 MUST The CASE_MANAGER MUST share case content only with active participants, never with an inert one. A participant is active when three conditions hold. First, the case initialization sequence seated it (CM-14-001: the Case Owner, the CASE_MANAGER and the reporter), or it accepted the stub Invite (Accept(Invite(Actor)[target=VulnerabilityCaseStub])). Second, it has not been removed (CM-31-001). Third, while an embargo is active, its consent row for that embargo is AGREED (CM-18-001). Every other participant is inert. Case content includes CaseLedgerEntry announcements, Announce(VulnerabilityCase), status broadcasts and embargo announcements (ADR-0114).

Many cases have no active embargo: the embargo is not yet established, or it has already ended. Thus, "has accepted the active embargo" cannot be the only entitlement test. The stub Invite creates the participant record (ADR-0114), and a removed participant keeps its record (ADR-0116). Thus, the roster holds actors that have no entitlement. A sender must check entitlement, because membership does not show it. While an embargo is active, the AGREED row of the participant for that embargo is its agreement to protect what it receives (ADR-0122).
CM-10-005 MUST NOT This rule covers each Announce(CaseLedgerEntry) the CASE_MANAGER sends, as fan-out of a new entry or as a replay answering a Reject(CaseLedgerEntry) (SYNC-03-002). While an embargo is active, the CASE_MANAGER MUST NOT send one to a participant that has not accepted that embargo (the CM-10-004 content gate).

The ledger fan-out is how participants learn every accepted case change, and each entry carries its payload verbatim — an added report or note reaches the recipient whole. Fan-out is therefore "sharing case updates" in CM-10-004's sense. An entry cannot be redacted per recipient without breaking the hash chain. Skipping a single entry does not work either: the replica buffers the next one as a forward gap and Rejects to request the missing entry (SYNC-14-002), so the replay would deliver the withheld content anyway. The gate therefore pauses the participant's whole stream, replay included, rather than filtering entries; the replica stays at the last entry it was sent.
Refines: CM-10-004
CM-10-008 MUST While an embargo is active, the CASE_MANAGER MUST still deliver an embargo Invite, and its responses, to a participant that has not accepted the embargo. These are embargo meta-protocol activities addressed to the participant directly, not ledger fan-out.

These activities are not ledger fan-out, so the CM-10-005 pause does not cover them. A participant that never receives the embargo Invite cannot accept the embargo, so its paused stream could never resume.
Extends: CM-10-005
CM-10-006 MUST When the CM-10-004 content gate admits a participant whose ledger stream it had paused (CM-10-005) — whenever the active check turns from false to true for that participant, whether because it accepts the active embargo, the embargo ends, or it is reinstated (CM-31-011) — the CASE_MANAGER MUST send that participant every canonical entry it was not sent, in log order starting with the first entry withheld from it, before or together with the entry that records the change admitting it.

A paused participant's replica is a contiguous prefix of the ledger; backfilling the withheld suffix in order restores it to the leader's tip without a gap the catch-up gate (SYNC-10-004) would block on. Ending the embargo lifts the gate for everyone, so a participant that never accepted it is caught up at that point too. The trigger is the active check itself, not any one message (ADR-0116).
Depends on: CM-10-005
Depends on: SYNC-04-001

Joining a Case — Stub Invite, Inert Participant, Full-Case Invite

How an actor goes from invited to participating: the stub Invite creates an inert participant record, the participant's reply to it decides whether it joins, and a joined participant judges the case by replying to a full-case Invite (ADR-0114, ADR-0121; notes/case-joining.md).

ID Priority Requirement Related
CM-11-001 MUST When a participant accepts the stub Invite (Accept(Invite(Actor)[target=VulnerabilityCaseStub])), the CASE_MANAGER MUST leave the participant's RM state at RM.RECEIVED, set its VF state to V when it holds the VENDOR role, and, when an embargo is active, mark its consent row for that embargo AGREED, making it a signatory. A DECLINED row is the exception, and the CASE_MANAGER leaves it unchanged: an explicit refusal outranks the consent that joining implies. Accepting the stub is joining the case and, unless the row is DECLINED, consenting to the active embargo. It is not a judgement of the case.

The invitee has seen only the stub. Its judgement of the case comes after it has received the case and its history, as its reply to the full-case Invite (CM-11-010).
Implements: VP-03-012
Depends on: CM-03-001
CM-11-002 SHOULD After joining a case, a participant SHOULD judge the case by replying to the full-case Invite (CM-11-010, CM-11-011).

The CASE_MANAGER cannot count a joined participant as engaged until the participant says so (CM-11-004). The other participants plan their work from the list of engaged participants. The participant receives the full-case Invite, examines the case and its history, and then engages or defers. Every replica then records the RM decision of the participant itself, not a decision that the CASE_MANAGER infers from silence.
Depends on: OX-02-001
Depends on: CM-11-001
CM-11-022 SHOULD After it replies to the full-case Invite, a participant SHOULD engage with Join(VulnerabilityCase) (RM.ACCEPTED) or defer with Ignore(VulnerabilityCase) (RM.DEFERRED).

Judging the case says whether the report is valid. It does not say whether the participant will work on the case. Only the participant can make that choice, and Join or Ignore records it, so no replica has to infer it from silence (CM-11-004).
Extends: CM-11-002
CM-11-004 MUST The CASE_MANAGER MUST NOT treat a joined participant as having committed to the case (RM.ACCEPTED) until it receives Join(VulnerabilityCase) from that participant.

Only the participant can say it has chosen to engage. Neither accepting the stub Invite (joining) nor accepting the full-case Invite (RM.VALID) is an engagement decision.
Depends on: CM-11-001
CM-11-003 MUST A receiver processing a reply to a stub Invite (Accept or Reject of Invite(Actor)[target=VulnerabilityCaseStub]) MUST identify the case from the case the nested Invite's stub names, never by deriving it from the stub's ID; the top-level target of the reply is not populated.

The reply wraps the Invite as its object_, so the stub is on object_.target, which extract_event maps to inner_target. The stub has its own ID (<case-id>/stub) and names the case explicitly (CM-11-013, CM-11-037); parsing one ID out of the other would couple every receiver to the ID scheme.
CM-11-006 MUST When the CASE_MANAGER sends a stub Invite (Invite(Actor)[target=VulnerabilityCaseStub]), it MUST create the invitee's CaseParticipant in the same step. The participant is inert (CM-10-004).

The CASE_MANAGER needs to track every invitee from the moment it is invited — whether it has been told and how it answered — including the invitees that decline (ADR-0114).
CM-11-026 MUST The CaseParticipant a stub Invite creates MUST hold the Invite's roles, RM RECEIVED, VF v (VENDOR), and an INVITED row for an active embargo.

The invitee has been told of the case and has decided nothing. RM RECEIVED and VF v record exactly that. The INVITED row records that it was asked about the embargo in force and has not answered (CM-18-001).
Extends: CM-11-006
CM-11-023 MUST When it sends a stub Invite, the CASE_MANAGER MUST commit its own create_case_participant (Create(CaseParticipant)) entry, separate from the Invite's invite_actor_to_case entry.

The ledger holds the wire messages that the actors exchanged, and every state change has a message. The Invite is one message. Creating the invitee's record is the CASE_MANAGER's own act, so it is a second message with its own entry (ADR-0114, ADR-0124).
Extends: CM-11-006
CM-11-024 MUST The create_case_participant entry MUST carry the whole participant record as stored: its statuses and consent rows, with their ids and times.

A replica copies the record from the entry and derives nothing (CM-11-025, ADR-0124). The entry must therefore carry every field the CASE_MANAGER holds, or the replica's record differs from the CASE_MANAGER's.
Extends: CM-11-023
CM-11-025 MUST A replica MUST store an invitee's record exactly as the create_case_participant entry carries it, and derive none of it from the Invite's entry.

Replicas copy; they never mint ids, read clocks or run lifecycle logic (ADR-0124). A record a replica derived could differ from the CASE_MANAGER's in an id or a time, and the copies of the case would then disagree.
Depends on: CM-11-024
CM-11-007 MUST A stub Invite has exactly two valid replies, Accept (CM-11-001) and Reject. On Reject(Invite(Actor)[target=VulnerabilityCaseStub]), the CASE_MANAGER MUST close the participant and keep its record, which stays inert. Closing it moves its RM state to RM.CLOSED, sets VF V for a VENDOR, and records consent DECLINED when an embargo is active.

A stub reject is a hard no to joining. Keeping the record keeps the history that the actor was told and declined, which is what the early record exists for.
CM-11-027 MUST A replica MUST apply a stub Reject from two entries: the invitee's reject_invite_actor_to_case, which carries the DECLINED row, and the CASE_MANAGER's add_participant_status_to_participant closing status.

The invitee's Reject is a message, so it is its own ledger entry, and it is the entry for the DECLINED row. A replica applies that row through the same write the CASE_MANAGER uses. The closing status is the CASE_MANAGER's own act, with an entry of its own that a replica stores. Each replica then holds the record the CASE_MANAGER holds (ADR-0114, ADR-0124).
Extends: CM-11-007
CM-11-028 MUST The CASE_MANAGER MUST refuse TentativeReject(Invite(Actor)[target=VulnerabilityCaseStub]).

Accepting the stub is not a judgement of the case, so there is nothing for a tentative reject to say. Only the full-case Invite asks for a judgement (CM-11-011).
Extends: CM-11-007
CM-11-008 MUST When the CASE_MANAGER processes Accept(Invite(Actor)[target=VulnerabilityCaseStub]) and the participant is active (CM-10-004), it MUST send the participant Announce(VulnerabilityCase) and begin replaying the case ledger to it without waiting for any further signal from the participant.

The participant is entitled to case content from the moment it is active, and the history is part of what it then judges. The Accept is the readiness signal; out-of-order arrival is buffered (SYNC-14, SYNC-15) and a real gap is recovered by Reject(CaseLedgerEntry) and replay.
CM-11-009 MUST Any reply from a VENDOR-role invitee to its stub Invite MUST set its VF state to V.

Any reply is evidence the vendor is aware of the case.
CM-11-029 MAY The CASE_MANAGER or the Case Owner MAY set an inert invitee's VF state to V before the invitee replies (PRM-06-003).

The CASE_MANAGER or the Case Owner can learn that a vendor is aware of the case before the vendor answers its stub Invite, for example from a direct contact. Recording that awareness early keeps the VF history correct.
Refines: PRM-06-003
CM-11-030 MUST NOT A write that sets an inert invitee's VF state to V before it replies MUST NOT change the invitee's RM state.

RM is the invitee's own judgement (ADR-0121). Awareness of the case is not a decision about it, so the write that records awareness must not move RM.
Extends: CM-11-029
CM-11-010 MUST After CM-11-008, the CASE_MANAGER MUST send the participant the full-case Invite, Invite(Actor)[target=VulnerabilityCase], queued after the last replayed ledger entry.

The participant already holds the case from the Announce, so the Invite only asks the question and carries no copy of the case. Delivery to each recipient is ordered (ADR-0112), so queuing the Invite after the replay means it arrives once the participant can answer it.
CM-11-031 MUST The full-case Invite MUST name the case by ID and carry the log_index and entry_hash of the CASE_MANAGER's ledger tail when it issued the Invite.

The Invite's ledger position is the floor the participant must reach before answering (ADR-0121). It travels as the Invite's ledgerPosition, the property every case activity carries (VAM-04-011, SYNC-18-001, ADR-0156).
Extends: CM-11-010
CM-11-032 MUST A receiver MUST refuse a full-case Invite whose sender is not the CASE_MANAGER on its replica or, with no replica, its stub-Invite trust anchor (PCR-03-004).

Only the case's CASE_MANAGER may ask a participant to judge the case. Before the Announce arrives, the invitee has no replica, so the CASE_MANAGER it recorded from the stub Invite is the one it trusts (PCR-03-004).
Refines: PCR-03-004
CM-11-033 MUST A receiver that holds neither a replica of the case nor a trust anchor for it MUST refuse a full-case Invite.

With neither record, the receiver cannot tell the case's CASE_MANAGER from any other sender. It fails closed, so no sender can ask it to judge a case it never joined.
Extends: CM-11-032
CM-11-011 MUST The CASE_MANAGER MUST record each full-case Invite reply as a move from RM.RECEIVED: RV Accept to RM.VALID, RI TentativeReject to RM.INVALID, RC Reject to RM.CLOSED.

Each reply is the participant's own judgement of the case (ADR-0121). The three replies map to the three RM triage outcomes, so every replica records the judgement the participant made.
CM-11-034 MUST A reply to the full-case Invite MUST carry the replier's own ledger position: the log_index and entry_hash of its tail when it decided.

The position is the reply's ledgerPosition, the property every case activity carries (VAM-04-012..014, SYNC-18-001, ADR-0156), so the CASE_MANAGER judges it against the Invite's floor (CM-11-012) from the one field it reads on every case activity.
Extends: CM-11-011
CM-11-012 MUST The CASE_MANAGER MUST refuse a full-case Invite reply whose position is behind the Invite's, or names an entry missing from its ledger at that index.

The Invite's position is a floor. A reply behind it was made without seeing what the participant was asked to judge. A reply that names an entry the CASE_MANAGER's ledger does not hold was not made on its ledger.
CM-11-035 MUST The CASE_MANAGER MUST accept a full-case Invite reply whose position is at or beyond the Invite's.

The Invite's position is a floor, not a pin. A reply beyond it is better informed. Pinning the reply to the Invite's position would make every reply on a busy case stale (ADR-0121).
Extends: CM-11-012
CM-11-036 MUST The CASE_MANAGER MUST refuse a full-case Invite reply from a participant that has not joined the case (an inert participant), writing nothing.

Only a joined participant judges the case. An inert participant has not received the case or its history, so it has nothing to judge (CM-10-004).
Extends: CM-11-012
CM-11-013 MUST A case stub MUST carry its own wire type, VulnerabilityCaseStub, and its own ID, the case's ID with /stub appended.

A receiver classifies an activity by its shape. Sharing the case's type and ID made the stub Invite and the full-case Invite indistinguishable (ADR-0114).
CM-11-037 MUST A case stub MUST name the case it stands for in a field of its own.

A receiver must find the case without parsing the stub's ID (CM-11-003). Parsing one ID out of the other would couple every receiver to the ID scheme.
Extends: CM-11-013
CM-11-038 MUST A receiver MUST classify a case stub by its type, never by which fields it carries or by parsing its ID.

A sparse case and an enriched stub can carry the same fields, so only the type tells them apart. A classifier that read the fields would take a sparse VulnerabilityCase for a stub (ADR-0114).
Depends on: CM-11-013
CM-11-014 MUST A stub Invite MUST carry a reply deadline.

An invitee that never answers must not hold the CASE_MANAGER in an open wait for ever. The deadline tells both sides when the ask ends (ADR-0114).
CM-11-039 MUST When a stub Invite's deadline passes unanswered, the CASE_MANAGER MUST close the Invite as an expired ask.

An expired ask is over. Closing it ends the CASE_MANAGER's wait for an answer, so the case does not wait on an invitee that never replies (ADR-0114).
Extends: CM-11-014
CM-11-040 MUST NOT When a stub Invite expires unanswered, the CASE_MANAGER MUST NOT change the invitee's record, which stays inert at RM.RECEIVED with its consent row INVITED.

RM is the participant's own judgement (ADR-0121), so a timeout cannot stand in for one. The CM-28-004 time-out to TIMED_OUT applies to an Invite(EmbargoEvent), not to the embargo terms a stub Invite carries, so the consent row stays INVITED.
Extends: CM-11-039
CM-11-041 MUST A case-wide closure check ("all participants closed") MUST count only joined participants: those seated by case initialization (CM-14-001) or that accepted their stub Invite.

An invitee that never answered must not keep the case from closing (ADR-0114). An invitee that has not joined has made no judgement, so it has nothing to close.
Uses definition from: CM-10-004
CM-11-015 MUST When the CASE_MANAGER re-invites an unanswered or expired invitee, it MUST send a fresh stub Invite on the same record, with a new deadline.

One actor has one participant record per case. A fresh stub Invite with a new deadline gives the invitee a live ask, and keeps the record that already holds its history.
CM-11-042 MUST A re-invite's inReplyTo MUST be the id of the invitee's earlier stub Invite.

Naming the earlier stub in the standard AS2 inReplyTo property leaves the invitee one live stub. CM-11-045 then refuses an Accept of the earlier one, naming the fresh one.
Extends: CM-11-015
CM-11-043 MUST The CASE_MANAGER MUST refuse, before committing any ledger entry, every stub Invite to a participant that has joined or is at RM.CLOSED.

One actor has one participant record per case. RM.CLOSED is terminal with no rejoin path, and a stub reject is a closure (CM-11-007). The refusal comes before the commit, so a refused Invite leaves no ledger entry (ADR-0085).
Extends: CM-11-015
CM-11-016 MUST This rule covers a stub Invite still outstanding when the active embargo is activated, revised or terminated. The CASE_MANAGER MUST re-issue that stub Invite on the same participant record, with the embargo terms that now apply and a new deadline.

Accepting a stub consents to the terms it carried. Accepting stale terms would make the participant join already lapsed, or join without consent, and need a second answer at once.
CM-11-044 MUST A replacement stub Invite MUST name the Invite it supersedes by setting its inReplyTo to that Invite's id.

Naming the superseded Invite tells the invitee the old one is dead in whichever order the two arrive. A retraction (Undo) was considered and rejected (ADR-0114).
Extends: CM-11-016
CM-11-045 MUST The CASE_MANAGER MUST refuse an Accept of a superseded stub Invite, naming the replacement in the refusal.

An Accept of a superseded Invite consents to terms that are no longer in force. Naming the replacement tells the invitee which Invite it can accept.
Extends: CM-11-016
CM-11-046 MUST The CASE_MANAGER MUST honour a Reject of a superseded stub Invite.

Declining to join does not depend on the terms, so a Reject of any version of the ask is a hard no.
Extends: CM-11-016
CM-11-017 MUST The CASE_MANAGER MUST refuse a stub-Invite reply, and create or change no participant, unless the sender is the invitee its own recorded stub Invite names.

If the CASE_MANAGER trusts an Accept by itself, any actor can admit itself or a third party. That actor then receives the case and its full ledger. The recorded stub Invite is the CASE_MANAGER's own record of whom it invited (CM-11-006). Thus, it is the only source against which the CASE_MANAGER can check a reply (HP-01-006, ADR-0115).
Refines: HP-01-006
Refines: CM-11-001
CM-11-047 MUST The CASE_MANAGER MUST take a stub invitee's roles from the stub Invite it recorded, never from the copy of the Invite embedded in the reply.

The embedded copy is the sender's claim. If the CASE_MANAGER read the roles from it, an invitee could give itself any role by editing its copy. The recorded Invite holds the roles the CASE_MANAGER chose (CM-11-006, ADR-0115).
Extends: CM-11-017
CM-11-018 MUST When the CASE_MANAGER receives Reject(Invite(Actor)[target=VulnerabilityCaseStub]) for an invitee with no participant record, it MUST refuse it with a reported reason, not as a silent no-op.

The participant record created at invite-send time (CM-11-006) is the evidence that the CASE_MANAGER sent the stub Invite. A Reject with no matching record is either a protocol violation (the CASE_MANAGER never sent the Invite) or a replay of a Reject already processed to RM.CLOSED; both cases are erroneous and must be reported, not silently accepted. Accepting them would hide inconsistencies in the CASE_MANAGER's own audit trail.
Refines: CM-11-007
CM-11-021 MUST When the CASE_MANAGER receives Accept(Invite(Actor)[target=VulnerabilityCaseStub]) and holds no participant record for the invitee, it MUST refuse the Accept with a reported reason.

The CASE_MANAGER creates the participant record when it sends the stub Invite (CM-11-006). That record is the evidence that the CASE_MANAGER sent the Invite. CM-11-017 ties every reply to a recorded Invite. If an Accept rebuilt a missing record, it would admit an actor whose Invite the CASE_MANAGER's roster does not show, and it would skip that check. This rule is the Accept counterpart of CM-11-018.
Refines: CM-11-017
Refines: CM-11-001
CM-11-052 MUST NOT The CASE_MANAGER MUST NOT create a participant record, commit a ledger entry or send any activity for a stub-Invite Accept it refuses under CM-11-021.

An Accept never creates a participant: the record exists from the stub Invite (CM-11-006). A refused Accept that still wrote a record, an entry or a message would admit the actor in part.
Extends: CM-11-021
CM-11-019 MUST A stub Invite MUST carry at least one CVD role for the invitee.

A participant with no roles cannot be routed to the right recipients or granted the right access.
Refines: CM-11-006
CM-11-048 MUST The CASE_MANAGER MUST refuse to emit a stub Invite with an empty role list.

Refusing at send time catches missing roles before the Invite is delivered, keeping the error close to its source.
Implements: CM-11-019
CM-11-049 MUST NOT The CASE_MANAGER MUST NOT create a participant record with an empty case_roles list.

A participant record with no roles could not be routed or granted access. Refusing the record keeps every participant in the roster routable.
Extends: CM-11-019
CM-11-050 MUST NOT When an inviter, the Case Owner included, names no roles for a stub Invite, the CASE_MANAGER MUST NOT substitute a default role such as VENDOR.

Defaulting to VENDOR would silently assign a role the inviter never requested, violating the inviter's intent. In the Case Owner's own stub Invite the owner names the roles, so no role is guessed.
Extends: CM-11-019
CM-11-020 MUST NOT A participant that joined a case through an Invite MUST NOT send Accept, TentativeReject or Reject of the original Offer(VulnerabilityReport).

A reply must answer a message sent to the replier. A joined participant was never offered that report.
CM-11-051 MUST NOT A participant that joined a case through an Invite MUST NOT validate, invalidate or reject the case's report. It judges the case by replying to the full-case Invite (CM-11-011).

A joined participant — a late joiner especially — judges the case as it stands, with its history, not the original submission, which may have been far sparser (ADR-0121).
Extends: CM-11-020

Case Creation Timing

ID Priority Requirement Related
CM-12-002 MUST At case creation, the system MUST create two initial CaseParticipant records in the order defined by CM-14-001

At receipt, the case knows two parties: the Case Owner and the reporter. Each party needs its own RM state (CM-01-002). Thus each party needs a record before the case records anything else about it. The CM-14-001 order makes sure that an existing participant creates the embargo, and that the reporter has the case before any entry about the case arrives.
Depends on: CM-03-001
Depends on: CM-02-008
Part of: CM-14-001
CM-12-003 MUST When a case is created from an Offer(VulnerabilityReport), the Case Owner (the report receiver) MUST queue a Create(VulnerabilityCase) activity to notify the reporter that a case has been opened, naming the CASE_MANAGER it delegated to (CBT-01-001)

The reporter must learn that its report became a case, and which actor coordinates the case. The reporter does not know the CASE_MANAGER. It trusts only the actor that it reported to. Thus the report receiver introduces the CASE_MANAGER to the reporter (CBT-01-001, ADR-0132).
Depends on: OX-02-001
CM-12-004 MUST A default embargo MUST be initialized at case creation (RM.RECEIVED) following the sequence in CM-14-001 whenever the case is embargo-eligible — from the receiver's actor default where one is configured, and otherwise from the protocol default of EP-04-005

If a case has no embargo, the reporter's information has no protection when another party joins. The receiver initializes the default embargo at RM.RECEIVED, before it adds any other participant. Thus every later participant joins under the embargo (CM-14-010). The protocol default makes sure that a receiver with no configured policy also gets an embargo with a limit (EP-04-019, ADR-0096).
Refines: DUR-07-002 — which now applies at case receipt
Part of: CM-14-001
Depends on: EP-04-005

Embargo Acceptance Ownership

ID Priority Requirement Related
CM-13-005 MUST A repeated embargo answer is a second Accept, or a second Reject, of one embargo invitation by the same participant, whatever its activity id. The receiver of a repeated answer MUST change no state, add no ledger entry and send no message because of it. Answers from different participants are separate consents.

Delivery is at least once. A participant can send the same answer again under a new activity id. If a repeat counted, the receiver would add a ledger entry and a fan-out for a consent that did not change. Every replica would then record an answer that was not a new decision. Each participant gives consent for itself. Thus the answer of one participant is never a repeat of the answer of a different participant.

Case Initialization Sequence

Defines the canonical ordered sequence of initialization steps that MUST occur when a VulnerabilityCase is created at Offer(VulnerabilityReport) receipt (CM-12-001). All steps MUST complete before the case is considered fully initialized.

ID Priority Requirement Related
CM-14-001 MUST The case initialization sequence MUST execute exactly 6 ordered steps (CM-14-008 through CM-14-013) before the case is considered fully initialized. No step may be omitted or reordered.

Step CM-14-009 precedes CM-14-010 so that the embargo is always created by a participant (the Case Owner). The Create(VulnerabilityCase) notification in CM-14-011 is flushed before the reporter is added in CM-14-012 so the reporter's system receives the case object before any subsequent Announce(CaseLedgerEntry) fan-out that references it.
Depends on: CM-12-001
CM-14-002 MUST The Case Owner's CaseParticipant MUST be created before the default embargo is initialized, so that the embargo is always associated with an existing participant

An embargo without any participant lacks an owning actor. Creating the owner participant first ensures that the embargo is initialized in the context of an actor who can be responsible for it.
Depends on: CM-14-001
CM-14-003 MUST When the case initialization sequence creates a default embargo, the Case Owner MUST be seeded as a signatory on that embargo immediately after the embargo reaches EM.ACTIVE state

The Case Owner initiated the embargo by accepting the default policy on behalf of the case. Requiring a separate accept step would leave the Case Owner as a non-signatory on their own active embargo.
Depends on: CM-14-001
Refines: CM-13-006
CM-14-004 MUST The Create(VulnerabilityCase) notification MUST be queued and the outbox flushed before the reporter is added as a CaseParticipant in step 5 of CM-14-001

The reporter's system must receive and store the case object before any Announce(CaseLedgerEntry) fan-out that lists them as a participant. Reversing this order causes "case not found" errors on the reporter side when the log entry arrives referencing a case the reporter has not yet seen.
Depends on: CM-14-001
Depends on: CM-12-003
CM-14-005 MUST When the reporter is added as a CaseParticipant during case initialization, they MUST be seeded as a signatory on any active embargo

The reporter already possesses the vulnerability information before the case is created. Requiring an explicit embargo acceptance before sharing case content would create a paradoxical deadlock: the reporter has the information that the embargo is meant to protect, yet cannot receive case updates until they accept the embargo. Seeding them as signatory reflects their implicit consent, expressed by submitting the report.
Depends on: CM-14-001
CM-14-006 SHOULD If the reporter included an embargo proposal with their Offer(VulnerabilityReport), the case initialization SHOULD reconcile the reporter's proposed duration against the CASE_OWNER's published embargo policy (its actor default, EP-04-003) and initialize the embargo at the shorter of the two durations

An agreement to an embargo of N days is also an agreement to every shorter embargo. Thus the shorter of the two terms is the term that both the reporter and the Case Owner accepted (EP-04-003). The longer term would bind a party to terms that it did not offer.
Depends on: CM-14-001
Depends on: CM-12-004
Depends on: EP-04-004
CM-14-007 MUST NOT An actor MUST NOT invite an additional participant to a case until the case's initialization sequence (CM-14-001), with any applicable conditional steps, is complete.

The initialization sequence establishes the minimum valid case state: a case object, an owner, an optional embargo and a reporter. An Invite to a third party before this baseline is in place causes race conditions and protocol violations.
Depends on: CM-14-001
CM-14-014 MUST NOT An actor MUST NOT initiate an embargo modification on a case until the case's initialization sequence (CM-14-001), with any applicable conditional steps, is complete.

The initialization sequence establishes the embargo that later changes modify. A modification before this baseline is in place causes race conditions and protocol violations.
Extends: CM-14-007
Depends on: CM-14-001
CM-14-008 MUST Step 1 of the case initialization sequence MUST create the VulnerabilityCase with the receiving actor set as attributed_to.

Creating the case object first establishes the anchor to which all subsequent initialization steps (participants, embargo, ledger) are attached.
CM-14-009 MUST Step 2 of the case initialization sequence MUST create the Case Owner's CaseParticipant with RM.RECEIVED and the CASE_OWNER role.

The Case Owner's participant must exist before the default embargo can be initialized, so that the embargo is always associated with an existing participant.
CM-14-010 MUST Step 3 of the case initialization sequence MUST initialize the default embargo to EM.ACTIVE and seed the Case Owner as a signatory whenever the case is embargo-eligible, using the actor default where one is configured and the EP-04-005 protocol default otherwise.

The embargo must be in place before any participant other than the Case Owner is added, so that all subsequent participants are subject to the embargo from the moment they join.
Depends on: EP-04-005
CM-14-011 MUST Step 4 of the case initialization sequence MUST queue a Create(VulnerabilityCase) activity addressed to the reporter and flush the outbox.

The reporter's system must receive and store the case object before any Announce(CaseLedgerEntry) fan-out that references it.
CM-14-012 MUST Step 5 of the case initialization sequence MUST add the reporter as a CaseParticipant with RM.ACCEPTED and the REPORTER role, and seed them as a signatory on any active embargo.

The reporter is added after the Create(VulnerabilityCase) notification is flushed (CM-14-011) so that the case object exists on the reporter's system before the fan-out of the ledger entry that records their participation.
CM-14-013 MUST Step 6 of the case initialization sequence MUST commit a case ledger entry, triggering Announce(CaseLedgerEntry) fan-out to all participants.

The ledger entry commits the completed initialization as a single protocol-significant event and notifies all participants atomically.

Actor Suggestion Routing (Recommend-Actor Flow)

ID Priority Requirement Related
CM-16-001 MUST A participant wishing to suggest that another actor be invited to the case MUST send Offer(Actor)[target=VulnerabilityCase] to the CASE_MANAGER's inbox, not directly to the Case Owner's inbox.

All case-scoped protocol messages MUST route through the CASE_MANAGER so the event can be recorded in the canonical case ledger before any downstream action is taken (ADR-0021, PCR-08-001).
Implements: PCR-08-001
CM-16-002 MUST When the CASE_MANAGER receives Offer(Actor)[target=VulnerabilityCase], it MUST commit a CaseLedgerEntry for the received Offer and fan out the entry to every active participant (CM-10-004) BEFORE taking any downstream action.

Ledger entries record events that have already occurred; pre-committing a decision before communicating it is not permitted (CLP-10-006 commit ordering).
Implements: CLP-10-006
CM-16-004 MUST The CASE_MANAGER MUST transform the received Offer(Actor) into Offer(CaseParticipant)[target=VulnerabilityCase], whose CaseParticipant has attributed_to set to the actor and case_roles set to the roles, and send it to the Case Owner's inbox, with origin set to the ID of the original recommender's Offer activity.

The Case Owner makes a membership decision about a CaseParticipant (actor + roles), not about a bare Actor identity. The origin field preserves the causal chain without a side-channel data store.
CM-16-005 MAY The recommender's Offer(Actor) MAY include a content or summary field that explains why it suggests the actor.

The recommender often knows why the actor belongs in the case, for example that it maintains the affected code. A short reason gives the Case Owner that knowledge. The field is optional, because the actor's identity can be reason enough.
CM-16-020 SHOULD The CASE_MANAGER SHOULD forward the reason text of a recommender's Offer(Actor) as the content of the transformed Offer(CaseParticipant) it sends to the Case Owner.

The Case Owner decides about the recommendation. The recommender's reason is the main evidence that the Case Owner has. If the CASE_MANAGER removed that text when it transforms the Offer, the Case Owner would decide without it.
Extends: CM-16-005
CM-16-006 MUST After it receives Accept(Offer(CaseParticipant)) from the Case Owner, the CASE_MANAGER MUST take four steps. (1) It commits a ledger entry for the Accept. (2) It fans out the ledger entry. (3) It sends the original recommender an Accept of its Offer(Actor)[target=VulnerabilityCase], with in_reply_to set to that Offer's ID. (4) It sends the suggested actor the stub Invite (Invite(Actor)[target=VulnerabilityCaseStub], CM-11-013) per CM-17.

The ledger records the Case Owner's decision before any step acts on it (CLP-10-006). Thus every replica sees the cause before its effects. The recommender waits for an answer to its Offer, and in_reply_to tells it which suggestion the answer closes.
Implements: CM-17-001
CM-16-021 MUST When the suggested actor has joined or is at RM.CLOSED, the CASE_MANAGER MUST refuse the Accept(Offer(CaseParticipant)) before committing or sending anything, with a reported reason.

The CASE_MANAGER refuses an Accept for an actor that joined or is at RM.CLOSED. This keeps one participant record for each actor, and keeps RM.CLOSED terminal (CM-11-043). The refusal comes first, so nothing of the Accept is recorded or sent.
Extends: CM-16-006
CM-16-007 MUST After receiving Reject(Offer(CaseParticipant)) from the Case Owner, the CASE_MANAGER MUST (1) commit a ledger entry for the Reject, (2) fan out the ledger entry, and (3) send a Reject of the recommender's Offer(Actor)[target=VulnerabilityCase] to the original recommender with in_reply_to set to the ID of the original recommender's Offer activity.

The ledger records the Case Owner's refusal. Thus every replica knows that the suggestion is closed. The recommender waits for an answer to its Offer. The Reject tells the recommender which of its suggestions the Case Owner declined. Without the answer, the recommender cannot tell a refusal from a decision that is not made yet.
CM-16-008 MUST When a second Offer(Actor)[target=VulnerabilityCase] for Actor X arrives while a pending decision for Actor X is still outstanding (no Accept or Reject yet received from the Case Owner), the CASE_MANAGER MUST record the duplicate recommendation in the ledger.

A second suggestion for the same actor is a case event, so the ledger records it. The Case Owner can use it as evidence for the pending decision (CM-16-016). The CASE_MANAGER records it but does not forward a second Offer. This keeps one pending decision for each actor (CM-16-017).
CM-16-009 MUST This rule covers a second Offer(Actor)[target=VulnerabilityCase] for Actor X after an Invite went to X, so the Invite is in flight or X participates. The CASE_MANAGER MUST commit the recommendation's ledger entry, fan it out, and auto-send the second recommender an Accept of its Offer, with in_reply_to naming it.

When the CASE_MANAGER has invited Actor X, the goal of the second recommender is complete. Thus the second recommender gets the answer that it would get after the Case Owner accepted.
CM-16-022 MUST NOT When a second Offer(Actor)[target=VulnerabilityCase] for Actor X arrives after an Invite went to Actor X, the CASE_MANAGER MUST NOT send Actor X a duplicate Invite.

A second Invite would create a second open stub for the same participant record, or the receiver would refuse it (CM-11-043). In both cases, nothing changes for X.
Extends: CM-16-009
CM-16-010 MUST In every step of the actor suggestion flow, the CASE_MANAGER MUST commit the ledger entry for a protocol event AFTER the corresponding message has been sent or received, never before.

Pre-announcing a decision in the ledger before communicating the decision creates a window where the ledger shows an effect without its cause. A sent message is sent once it is in the CASE_MANAGER's outbox (SYNC-09-002).
CM-16-016 MUST When a second Offer(Actor)[target=VulnerabilityCase] for Actor X arrives while a pending decision for Actor X is still outstanding, the CASE_MANAGER MUST record a Note for the Case Owner indicating that a reinforcing suggestion was received, including the Case Owner's identity, the second recommender's identity and any content from the Offer. The note is the CASE_MANAGER's own add_note_to_case entry, attached to the case, and reaches the Case Owner's inbox as that entry's Announce(CaseLedgerEntry) (SYNC-02-002); a direct Add(Note) to the Case Owner is refused there (HP-01-005).

A note about reinforcing suggestions gives the Case Owner more context for the pending decision, without a second offer workflow (CM-16-017). Participants send messages only to the CASE_MANAGER, and the Case Owner refuses a direct Add(Note) (HP-01-005). Thus the note goes as a ledger entry. Every active participant sees it, because the protocol has no private channel to the Case Owner.
CM-16-017 MUST NOT When a second Offer(Actor)[target=VulnerabilityCase] for Actor X arrives while a pending decision for Actor X is still outstanding, the CASE_MANAGER MUST NOT send a second transformed Offer(CaseParticipant) to the Case Owner.

Sending a duplicate transformed Offer would create a second pending decision for the same actor, leading to conflicting state if the Case Owner responds to both.
CM-16-018 MUST When the CASE_MANAGER processes Accept(Offer(CaseParticipant)) from the Case Owner, the roles assigned to the new participant MUST be read from the stored outgoing Offer(CaseParticipant) that the CASE_MANAGER constructed and sent — not from the embedded CaseParticipant object inside the received Accept.

The received Accept is untrusted: the accepting actor may have modified or omitted roles, or sent only a bare ID reference. Reading roles from the received Accept would allow a malicious or minimally-conformant acceptor to substitute unauthorized roles. The stored Offer is the CASE_MANAGER's own record of the intended participant roles and is the authoritative source of truth.
Refines: CM-16-006
CM-16-019 MUST The CASE_MANAGER MUST refuse Accept(Offer(CaseParticipant)) or Reject(Offer(CaseParticipant)) from any sender but the case's Case Owner, and then invite no one and notify no recommender.

The Case Owner decides about a recommendation (CM-16-006). If any sender could answer it, any actor that knows a recommendation id could accept it, and the CASE_MANAGER would invite whoever that actor chose (HP-01-006).
Refines: HP-01-006
Refines: CM-16-006
CM-16-023 MUST The CASE_MANAGER MUST take the actor to invite from the Offer(CaseParticipant) it recorded, never from the Case Owner's reply.

The reply carries a copy of the Offer, and the copy is the sender's claim. Assume that the CASE_MANAGER checked only that the recommendation is recorded, and read the invitee from the reply. Then the reply could choose the invitee. The recorded Offer is the CASE_MANAGER's own record (CM-16-018).
Extends: CM-16-019

Invitation Flow — Enrichment and Roles

ID Priority Requirement Related
CM-17-001 MUST The stub Invite sent by the CASE_MANAGER to a new actor MUST be Invite(Actor)[target=VulnerabilityCaseStub], with the stub shaped as CM-11-013 requires. This requirement is extended by CM-17-002 and CM-17-003.

An AS2 Invite names the invited party as its object, and the thing it is invited to as its target. When the target is a stub, no case content goes to an invitee that is not yet entitled to it (CM-10-004, CM-17-022). The stub's own type lets a receiver tell this Invite from the full-case Invite (CM-11-013).
CM-17-002 MUST This rule covers a stub Invite sent while an embargo is in force. Then EM is ACTIVE or REVISE, and the case's embargo register holds an ACTIVE entry. Its case stub MUST carry the active embargo (activeEmbargo), inline or by an ID that the invitee can ask the inviter to resolve.

The invite-accept cycle is the only consent signal for a late joiner. An Accept without the embargo terms is not informed consent (MV-10-006). The invitee has only the invitation, so the invitation must let it learn the terms before it accepts: inline in the stub, or by the ID that the stub names. CM-17-011 says which form this prototype sends. In EM REVISE, the prior embargo stays in force (EP-05-002), so the invitee needs its terms then too.
Implements: MV-10-006
CM-17-013 MUST When an embargo is in force as the CASE_MANAGER sends a stub Invite, the case stub MUST carry the case's EM state (caseStatus.emState).

The EM state tells the invitee whether a revision of the embargo is open. In EM REVISE, the invitee learns from it that a proposal is pending, which it receives after it joins (EP-09-011).
Extends: CM-17-002
CM-17-014 MUST NOT A case stub MUST NOT carry an open embargo revision proposal. The invitee learns of the proposal from caseStatus.emState and receives it after joining (EP-09-011).

The revision is not yet binding, and CM-17-022 keeps other case content out of the stub. When the joiner holds the case, EP-09-011 relays to it each open proposal that it has not answered.
Extends: CM-17-002
CM-17-004 MUST When the CASE_MANAGER receives Accept(Invite(Actor)[target=VulnerabilityCaseStub]) from an invitee, it MUST, in this order: (1) commit a ledger entry for the Accept and fan it out to every active participant (CM-10-004); (2) record the Accept's effect on the participant (CM-11-001); (3) when the participant is now active, send it Announce(VulnerabilityCase) and replay all prior CaseLedgerEntry records to it in log-index order (CM-11-008); and (4) send it the full-case Invite (CM-11-010).

Each step needs the step before it. The ledger records the Accept before any step acts on it (CLP-10-006). The participant's record must show that it joined before the participant is active and entitled to content (CM-10-004). The participant must hold the case and its history before it can make a decision about the case. Delivery to each recipient is in order (ADR-0112). Thus the full-case Invite, which the CASE_MANAGER queues last, arrives when the participant can answer it (CM-11-010).
Implements: CM-11-001
Implements: CM-11-008
Implements: CM-11-010
Implements: PCR-08-010
CM-17-005 MUST NOT Presence in the case participant list MUST NOT be treated as acceptance of the invitation or as entitlement to case content. An invitee is in the list from the stub Invite onward (CM-11-006); only an active participant (CM-10-004) is entitled to case content.

The CASE_MANAGER tracks every invitee from the moment it is invited, including one that declines, so the participant list holds inert participants. Any consumer that reads membership as "accepted" would hand case content to an actor that has not consented.
CM-17-009 MUST Any canonical CaseLedgerEntry the CASE_MANAGER commits while processing a stub Invite's Accept whose fan-out reaches the joining participant — every entry committed once that participant is active (CM-10-004) — MUST be committed after CM-17-004 step (3), the Announce(VulnerabilityCase) and the start of the ledger replay, so the participant's first ledger entries arrive after its case seed and in log-index order.

CM-17-004 puts the announce and the replay in order, with no fan-out to the joiner between them. But an entry committed during the same admission can have a fan-out that includes the new active participant. That entry would then get to the participant before its case. A replica that receives a ledger entry before its case cannot anchor the entry. It rejects the entry and asks for a replay from genesis, and that replay can mix with the backfill (SYNC-15). When the CASE_MANAGER commits the entry last, the entry gets to the invitee as the next entry in chain order.
Refines: CM-17-004
Depends on: SYNC-15-004
CM-17-010 MUST A sender's case stub MUST carry exactly id, type, caseId (CM-11-013), a non-empty summary (MV-10-001) and, with an embargo in force, the CM-17-002 embargo terms. The JSON-LD @context is not content.

An invitee is not yet entitled to case content (CM-10-004, CM-17-005). For informed consent, the invitee needs the case's identity, a summary that a person can read of what it is asked to join, and the embargo terms (CM-17-002).
Refines: CM-11-013
Refines: MV-10-001
Depends on: CM-17-002
CM-17-020 MAY The sender chooses the stub's summary, which MAY be the case name or a deliberately less revealing string.

The case name can be an opsec risk. Thus the sender writes the summary for the invitee, and does not have to copy it from the case.
Extends: CM-17-010
CM-17-021 MUST NOT The sender MUST NOT assume the case name is safe to share with an invitee not yet bound by the embargo.

The case name can reveal what the embargo protects. An invitee that has not joined has not agreed to the embargo, so the sender decides what the stub's summary reveals to it.
Extends: CM-17-020
CM-17-022 MUST NOT A sender MUST NOT put any other case content in a case stub.

An invitee is not yet entitled to case content (CM-10-004, CM-17-005). This constrains what is sent.
Extends: CM-17-010
CM-17-023 MUST NOT A sender MUST NOT name the case's coordinator or Case Owner in a case stub.

The Invite that carries the stub already identifies the inviter in its actor and attributedTo. If the stub named the coordinator or the Case Owner, it would repeat what the Invite states.
Extends: CM-17-010

Participant Embargo Consent (per-embargo rows)

ID Priority Requirement Related
CM-18-001 MUST Each participant record MUST hold one embargo consent row for every entry in the case's embargo register. Each row holds one of five states. The UNINVITED state means not asked, and is the start state. The INVITED state means asked, with no answer yet. The AGREED state records agreement, and DECLINED records an explicit refusal or a withdrawal. The TIMED_OUT state means invited, with the RSVP deadline passed and no answer. A participant is a signatory when its row for the register's ACTIVE entry is AGREED. A participant has lapsed when three things hold. An entry is ACTIVE, and the participant's row for it is neither AGREED nor DECLINED. Following replaces back from the ACTIVE entry, the first entry whose row is AGREED or DECLINED has an AGREED row. Both positions are read from the rows and the register, never stored (CM-18-016). With no ACTIVE entry, nobody is a signatory and nobody has lapsed.

One row for each pair of participant and embargo records each answer one time. To find if a participant is bound by the active embargo, a reader looks at the row for the active embargo. Thus there is no second record to reconcile, and a first proposal and a revision use the same rules (ADR-0122). TIMED_OUT is different from DECLINED, so that every reader can tell silence from a refusal (ADR-0118). A terminated embargo is a fact of the register, not a fact about each participant. A lapse follows the replaces chain. Thus only an embargo that was in force can bind, and a withdrawal from a later embargo ends the binding of an earlier embargo.
CM-18-019 MUST Proposing an embargo MUST write an UNINVITED consent row for it on every current participant.

A participant that was not asked holds a row that says so. Thus no reader infers "not asked" from a missing record, and a check can use the register to find a row for each participant and each embargo (CM-18-001).
Extends: CM-18-001
CM-18-020 MUST Creating a participant record MUST write an UNINVITED consent row on it for every entry already in the case's embargo register.

A participant that joins after embargoes were proposed was asked about none of them. A row for each entry records that, so the participant holds a row for every entry from its creation on (CM-18-001), and no reader meets a missing row.
Extends: CM-18-001
CM-18-021 MUST NOT A reader MUST NOT read a missing consent row as any consent state. A missing row is a defect.

Every row is written (CM-18-019, CM-18-020), so a missing row comes only from a fault. A reader that treated it as UNINVITED, or as any other state, would hide the fault, and could count a participant as asked, bound or free when it is not.
Extends: CM-18-001
CM-18-002 MUST When an invitation's RSVP deadline passes with no answer, the CASE_MANAGER MUST apply the TIME_OUT trigger to that invitation's row only, recording TIMED_OUT, never DECLINED. A lapse has no timer and is not a stored transition (CM-18-001, CM-18-016). A lapsed participant's deadline is the one on its re-invitation to the embargo in force (CM-28-013).

Per ADR-0093, ADR-0122 and notes/participant-embargo-consent.md, a participant has lapsed when the active embargo was replaced by longer terms it has not accepted. While a revision is only proposed, the prior embargo is still in force and binds every signatory, so nothing about their consent has changed. Per ADR-0118, a participant that did not answer has made no decision. Thus a timeout records TIMED_OUT, distinct from an explicit DECLINED, and both are re-invitable. Nothing stalls on a lapsed participant: the CM-10-004 gate already excludes it from embargoed content, and no invitation gives it a deadline. An RSVP deadline belongs to one invitation and is stored on that invitation's INVITED row (CM-28-013). Its passing times out only that row, never the participant's other invitations.
CM-18-022 MAY A timer expiry (pocket veto) MAY drive a row's INVITED → TIMED_OUT transition, with no explicit participant action.

A pocket veto exists so that an unanswered invitation cannot stall coordination. A participant that does not answer has made no decision, so the transition needs no act from it (ADR-0118).
Extends: CM-18-002
CM-18-023 SHOULD The pocket-veto timer period SHOULD be a policy window of 7 days.

An Invite that names no deadline still needs one, or an unanswered invitation can stall coordination. A fixed default window gives every such invitation the same deadline, so every participant can predict when silence becomes TIMED_OUT.
Extends: CM-18-022
CM-18-003 MUST The consent row machine MUST implement exactly the following valid triggers, each applied to the row for one embargo: INVITE (from UNINVITED, DECLINED or TIMED_OUT, to INVITED), AGREE (from UNINVITED, INVITED, DECLINED or TIMED_OUT, to AGREED), DECLINE (from UNINVITED, INVITED, AGREED or TIMED_OUT, to DECLINED), TIME_OUT (from INVITED only, to TIMED_OUT) and CARRY_OVER (from every state except AGREED, to AGREED). No other trigger is valid from any state, and a row for an entry in a final register status (SUPERSEDED, REJECTED, CANCELLED, TERMINATED) accepts no trigger at all.

DECLINED and TIMED_OUT are not terminal. The CASE_MANAGER can invite again a participant that declined or that let an invitation time out, for example when the embargo terms change. AGREED does not accept a new invitation, so a second request does not cancel a consent that the participant gave. AGREE is valid from DECLINED, because an Accept after a decline is a change of answer by the participant. The participant has just said that it accepts. Thus its row moves directly, with no re-invite step. Only the participant's own answer moves a DECLINED row in this way. Seeding (CM-14-003, CM-14-005) and the Accept of the full-case Invite never cancel a decline. Per ADR-0048, AGREE and DECLINE are valid directly from UNINVITED, because consent does not always come through an invitation. A Finder who sets the embargo on its own case has no inviter. Participants added during case initialization already have an embargo in scope (ADR-0041). The reporter gives its consent when it submits the report (CM-14-005). A required synthetic INVITED step for these paths would record an invitation that did not occur. Per ADR-0093, DECLINE is also valid from AGREED. A participant bound by an embargo MAY withdraw its consent when it rejects the active embargo (VP-13-007, VP-13-008). A rejection of a proposed embargo declines only the row for that embargo. It does not change the participant's row for the embargo in force (MSM-07-004). Per ADR-0118, TIME_OUT is only the timer path (CM-18-002). A late Accept that the CASE_MANAGER honours moves TIMED_OUT → AGREED directly (EMB-17-002), and a late explicit Reject moves TIMED_OUT → DECLINED. Only the activation of a revision that ends at or before the end of the embargo it replaces causes CARRY_OVER (EP-05-003). CARRY_OVER also lifts a DECLINED row. An agreement to N days is an agreement to every shorter period. A decline of a shorter revision objects to the loss of time, not to the embargo. A row whose embargo is final records the past. Thus a participant cannot give more consent to it. The CASE_MANAGER answers a late Accept of such terms with an invitation to the Active Embargo (EMB-17-003). A lapse and a termination are not triggers. They are derived (CM-18-001, CM-18-016).
Refines: CM-18-001
CM-18-004 MUST NOT A participant's consent row MUST NOT move from AGREED to INVITED directly. The only paths back to INVITED from a resolved row are DECLINED → INVITED (re-invite after explicit decline) and TIMED_OUT → INVITED (re-invite after an unanswered invitation timed out); a participant that has lapsed is re-invited on its row for the new embargo (an INVITED row for it), which leaves its AGREED row for the old embargo alone.

Consent already given cannot be withdrawn by re-inviting the participant. A signatory to the active embargo is bound by it; only replacing that embargo with longer terms (which lapses the participant by derivation — ADR-0093, ADR-0122) or the participant's own withdrawal (DECLINE) ends that position.
Refines: CM-18-003

Coordinator-as-Participant Role Constraints

ID Priority Requirement Related
CM-20-001 MUST A Coordinator invited to a case as a regular participant MUST hold COORDINATOR in their case_roles list.

CASE_OWNER confers case administration authority (CM-02-001, CM-02-004, CM-02-005). CASE_MANAGER is a distinct role: it grants single-writer authority over the canonical ledger (CLP-09) and is held by the case-creating actor enacting CVDRole.CASE_MANAGER, which SHOULD also carry CVDRole.COORDINATOR (CBT-01-015). It is not conferred merely for being invited as a facilitation participant, so a Coordinator invited as a regular participant holds neither CASE_OWNER nor CASE_MANAGER.
Refines: CBT-01-003
CM-20-002 MUST When the Case Owner approves a Coordinator suggested through the Offer(Actor)[target=VulnerabilityCase] path (CM-16), the Coordinator's roles MUST include COORDINATOR.

Role assignments at invite time must reflect the invited actor's actual function in the case, not its organizational identity. A Coordinator invited to facilitate holds the COORDINATOR role, so role checks find it as one.
Refines: CM-20-001
Refines: CM-16-004
CM-20-006 MUST NOT When the Case Owner approves a Coordinator suggested through the Offer(Actor)[target=VulnerabilityCase] path (CM-16), the Coordinator's roles MUST NOT include CASE_MANAGER or CASE_OWNER.

Invitation and case ownership transfer are distinct protocol operations. An Offer(Actor)[target=VulnerabilityCase] invite adds a participant. It does not transfer CASE_OWNER authority (CM-02-001) or the CASE_MANAGER single-writer authority (CM-20-001).
Extends: CM-20-002
CM-20-003 MUST A Coordinator participant that holds COORDINATOR without CASE_MANAGER MUST route its Offer(Actor)[target=VulnerabilityCase] suggestion to add another actor through the CASE_MANAGER inbox, per ADR-0026 (CM-16-001).

A Coordinator-as-participant should be able to exercise their facilitation role (suggesting additional vendors) without requiring ownership transfer. The CASE_MANAGER-routed suggest flow (ADR-0026) already provides this: the Coordinator sends Offer(Actor)[target=VulnerabilityCase] to the CASE_MANAGER, which records and forwards it to the Case Owner for approval.
Refines: CM-16-001
Refines: CM-20-001
CM-20-007 MUST The Case Owner, not the Coordinator, MUST remain the sole decision-maker on approving or rejecting an invitation that a Coordinator participant suggests (CM-02-001).

A Coordinator facilitates the case; it does not own it. Approving a new participant is Case Owner authority (CM-02-001), and a suggestion transfers none of that authority.
Extends: CM-20-003
CM-20-004 MUST NOT A Coordinator invited to a case as a regular participant MUST NOT hold the CASE_MANAGER role in its case roles.

CVDRole.CASE_MANAGER grants single-writer authority over the canonical ledger and is held by the actor that created the case and enacts that role (CBT-01-014, ADR-0088). It MUST NOT be conferred on an organizational or human actor invited to the case as a regular facilitation participant.
Refines: CM-20-001
CM-20-005 MUST NOT The CASE_MANAGER role MUST NOT be assigned to any organizational or human participant joining a case as a facilitation actor rather than as the actor enacting the CASE_MANAGER role (CBT-01-003).

CVDRole.CASE_MANAGER grants single-writer authority over the canonical ledger (CLP-09). Conferring it on a participant invited for facilitation would let them author ledger entries that only the actor enacting CVDRole.CASE_MANAGER is authorized to write.
Refines: CM-20-004

Ownership Transfer Protocol Effects

ID Priority Requirement Related
CM-21-001 MUST A VulnerabilityCase MUST have at most one participant that holds CASE_OWNER at any point in time.

CASE_OWNER confers exclusive case administration authority (CM-02-004, CM-02-005). Two simultaneous holders would create conflicting authority: both could terminate embargoes, authorise invitations, and mutate case state independently. The invariant must be enforced at the data-layer level, not just assumed by convention.
Refines: CM-02-004
Refines: CM-02-005
CM-21-023 MUST When a new owner is designated, the system MUST remove CASE_OWNER from the previous holder before, or atomically with, granting it to the new owner.

A grant made before the strip would leave two holders for a moment, which CM-21-001 forbids. Stripping first, or in the same step, keeps the invariant at every point in time.
Extends: CM-21-001
CM-21-002 MUST When an Accept(Offer(VulnerabilityCase)) ownership-transfer activity is processed, the system MUST add CASE_OWNER to the accepting actor's case_roles.

Role-gated nodes such as CheckIsCaseOwnerNode read Case Owner authority from CaseParticipant.case_roles. A transfer that did not grant the role would leave a new owner whose CASE_OWNER-gated acts are refused, such as embargo teardown and case closure.
Refines: CM-21-001
Refines: TRIG-11-002
CM-21-024 MUST When an Accept(Offer(VulnerabilityCase)) ownership-transfer activity is processed, the system MUST update the case's attributed_to to the accepting actor's ID.

attributed_to is the owner that the case names to every reader (MSM-07-027). A transfer that grants the role (CM-21-002) but leaves attributed_to alone makes the case name an owner that no longer holds the role. Thus a transfer writes both.
Extends: CM-21-002
CM-21-003 MUST When an Accept(Offer(VulnerabilityCase)) ownership-transfer activity is processed, the system MUST remove CASE_OWNER from the previous owner's case_roles.

Transferring ownership does not evict the previous owner from the case; they continue to participate but no longer hold administrative authority. Stripping only CASE_OWNER (and not other roles) preserves their standing as a Coordinator, Vendor, or other role while satisfying the at-most-one invariant (CM-21-001).
Refines: CM-21-001
Refines: TRIG-11-002
CM-21-005 MUST A Case Owner MUST propose an ownership transfer by sending Offer(Offer(VulnerabilityCase)), with the case as context, to the CASE_MANAGER only, never to the transferee (PCR-08-001).

Routing the transfer through the CASE_MANAGER lets it ledger the offer in flight and broadcast it to every active participant (CM-10-004), so no participant is unaware a transfer is being negotiated. The CASE_MANAGER cannot be the transferee: its Offer would have itself as its only recipient, which BT-17-008 forbids, the same ground on which CM-33-003 refuses a role offer to it.
Refines: CM-21-001
CM-21-025 MUST The inner Offer of an ownership-transfer proposal MUST name the case as its object and the transferee as its target. It optionally carries the deadline to answer as its end_time (CM-21-016).

The inner Offer says what the owner asks the CASE_MANAGER to offer and to whom. Without the case and the transferee in it, the CASE_MANAGER cannot make the Offer the owner asked for (CM-21-028).
Extends: CM-21-005
CM-21-026 MUST The CASE_MANAGER MUST refuse an ownership-transfer proposal, with no ledger commit and no outbound Offer, unless its inner Offer passes two checks. The inner Offer's object is the proposal's case. Its target is an actor the CASE_MANAGER can address (present, neither itself nor the proposer) and an active participant of the case (CM-21-021).

A proposal is a request the CASE_MANAGER acts on. An inner Offer that names another case, or a transferee the CASE_MANAGER cannot address, would make an Offer nobody can answer, or one about a case the proposal did not concern.
Extends: CM-21-005
CM-21-027 MUST When it admits an ownership-transfer proposal, the CASE_MANAGER MUST ignore any id, actor or to inside the inner Offer.

The CASE_MANAGER makes the Offer itself (CM-21-028). An id, actor or recipient list that the owner wrote into the inner Offer would let the owner choose fields that only the CASE_MANAGER sets, such as an id that collides with a stored object.
Extends: CM-21-005
CM-21-028 MUST On admitting a proposal, the CASE_MANAGER MUST make the single ownership-transfer Offer(VulnerabilityCase) itself, under an id it derives from the proposal, addressed to the transferee. The Offer's actor is the CASE_MANAGER and its attributed_to the owner (CM-24-001, CM-24-002).

Making the Offer itself rather than forwarding the owner's gives a transfer one Offer, under an id no other party chose, told apart from the proposal by its shape (ADR-0148, DL-06-006).
Extends: CM-21-005
CM-21-029 MUST The CASE_MANAGER MUST ledger the Offer it made from a proposal once that Offer is in its outbox (SYNC-09-002). That Offer is the one that is ledgered, answered, closed and expired.

Ledgering the Offer after it is queued keeps the ledger from recording an Offer that was never sent. One ledgered Offer gives every reader one transfer to answer, close or expire (CM-21-014).
Extends: CM-21-028
CM-21-006 MUST An actor emitting Accept(Offer(VulnerabilityCase)) (ownership-transfer variant) MUST address the activity only to the CASE_MANAGER (PCR-08-001), not directly to the offerer.

Routing the Accept through the CASE_MANAGER is required for the AcceptCaseOwnershipTransferReceivedUseCase to run on the authoritative CASE_MANAGER and trigger the post-transfer CaseLedgerEntry cascade (CM-21-007). Addressing the Accept to the offerer's inbox means the use case only runs there; the CASE_MANAGER never learns of the completed transfer and cannot broadcast it.
Refines: CM-21-001
Refines: CM-21-005
CM-21-007 MUST After the CASE_MANAGER applies the ownership-role changes of an admitted ownership-transfer Accept (CM-21-002, CM-21-003, CM-21-004), it MUST commit a CaseLedgerEntry recording the completed transfer.

The ledger is the record every replica follows (PCR-03-001). Without an entry for the completed transfer, no replica can learn from the ledger that the Case Owner changed.
Refines: CM-21-002
Refines: CM-21-006
CM-21-030 MUST After it commits the entry for a completed ownership transfer (CM-21-007), the CASE_MANAGER MUST broadcast Announce(CaseLedgerEntry) to all case participants.

The CaseLedgerEntry broadcast is the only way that participants outside the transfer learn that the Case Owner changed. Examples are a Finder or a second Vendor. Without it, these participants cannot reliably find the actor that is responsible for the case. They then send messages to the wrong actor, and coordination fails.
Extends: CM-21-007
CM-21-008 MUST When an ownership transfer is processed, the previous owner MUST retain any other roles they held (e.g., COORDINATOR, VENDOR) in their case_roles.

Stripping only CASE_OWNER (and not other roles) preserves the previous owner's standing as a Coordinator, Vendor, or other role while satisfying the at-most-one CASE_OWNER invariant (CM-21-001).
Refines: CM-21-003
CM-21-009 MUST When an ownership transfer is processed, the previous owner MUST remain a participant in the case.

Ownership transfer is not case departure. The previous owner has institutional knowledge of the case and continues to participate; only their administrative authority is transferred.
Refines: CM-21-003
CM-21-010 MUST An actor emitting Reject(Offer(VulnerabilityCase)) (ownership-transfer variant) MUST address the activity only to the CASE_MANAGER (PCR-08-001), not directly to the offerer.

Rejection routing must be symmetric with the Accept path (CM-21-006): the transfer was proposed to the CASE_MANAGER (CM-21-005), so its rejection must be too. If the transferee's Reject goes only to the offerer, the CASE_MANAGER — the authoritative record of ownership — never learns the transfer was declined and its view of who owns the case can silently diverge from reality.
Refines: CM-21-001
Refines: CM-21-006
CM-21-011 MUST The CASE_MANAGER MUST refuse an Accept(Offer(VulnerabilityCase)), leaving the owner unchanged, unless its sender is the transferee of an unanswered Offer it recorded for the case. The owner is the case's attributed_to and every participant's CASE_OWNER role.

Without the match to a recorded, unanswered Offer, a receiver takes the sender of the Accept to be the new owner. Then any actor that knows a case id can claim ownership at every receiver that holds the case.
Refines: HP-01-006
Refines: CM-21-002
CM-21-031 MUST A participant replica MUST apply an ownership change only when it comes from the CASE_MANAGER (PCR-03-001).

A replica that takes the change only from the CASE_MANAGER keeps one authority for the owner of the case (PCR-03-001). A transferee's Accept sent to a replica would otherwise move ownership there without the CASE_MANAGER.
Refines: PCR-03-001
Extends: CM-21-011
CM-21-012 MUST When the CASE_MANAGER admits an ownership-transfer Reject(Offer(VulnerabilityCase)) under CM-21-013, it MUST commit a CaseLedgerEntry recording the Reject, as it does for the Accept (CM-21-007). From that entry on, the Offer is closed and no longer pending on any replica (CM-21-014).

The Offer is ledgered and broadcast (CM-21-029), so every replica holds a transfer in progress. A Reject that adds no ledger entry never closes that transfer. The offerer does not receive the Reject (CM-21-010), so it never learns of the decline. A participant's Reject is a case event, as its Accept is. It is ledgered for the same reason (CLP-02-012).
Refines: CM-21-010
Refines: CLP-02-012
CM-21-032 MUST After it commits the entry for an admitted ownership-transfer Reject (CM-21-012), the CASE_MANAGER MUST broadcast Announce(CaseLedgerEntry) to all case participants.

The broadcast is how every replica learns the Offer is closed, as it learns of a completed transfer (CM-21-030). Without it, replicas keep showing a transfer in progress.
Extends: CM-21-012
CM-21-033 MUST NOT A replica that applies the entry of an ownership-transfer Reject MUST NOT change the case's attributed_to or any participant's CASE_OWNER role. The entry only closes the Offer (CM-21-014).

A Reject declines the transfer, so the owner stays who it was. A replica that moved any ownership state on the entry would disagree with the CASE_MANAGER about who owns the case.
Extends: CM-21-012
CM-21-013 MUST The CASE_MANAGER MUST refuse, with nothing committed or sent, a Reject(Offer(VulnerabilityCase)) whose sender is not the transferee of an open Offer it recorded. The Offer then stays pending. CM-21-014 defines when an Offer is open, read from the ledger.

The same standing CM-21-011 requires for the Accept. Without it any actor that knows an Offer id could close a transfer it is not party to, and the closed Offer could no longer be accepted by the transferee it names.
Refines: HP-01-006
Refines: CM-21-011
CM-21-014 MUST The CASE_MANAGER MUST refuse an ownership-transfer proposal (CM-21-005) for a case while an ownership-transfer Offer for that case is open, making no Offer from it. An Offer is open until the ledger holds an Accept (CM-21-007), Reject (CM-21-012) or withdrawal (CM-21-015) entry for it. It is also closed once the end_time the ledgered Offer carries has passed (CM-21-017).

Two pending Offers would let two transferees accept the same case, and CM-21-001 allows one owner. Withdrawal and expiry exist so that a transferee who never answers cannot block every later transfer of the case.
Refines: CM-21-001
Refines: CM-21-005
Refines: CLP-11-001
CM-21-034 MUST A reader MUST read whether an ownership-transfer Offer is open from the case ledger (CLP-11-001).

Reading "open" from the ledger rather than from local offer records keeps the CASE_MANAGER and every replica in agreement (CLP-07-014).
Refines: CLP-11-001
Extends: CM-21-014
CM-21-035 MUST Once a case's ownership-transfer Offer is closed, the CASE_MANAGER MUST admit a new proposal for the case, to any transferee, with no waiting period. This includes the transferee that declined, and nothing has to be cleared first. A redelivered proposal whose Offer the ledger already records is not a new proposal: it changes nothing (HP-01-003).

A decline does not bar a later offer, so no memory of who declined is needed. A closed Offer no longer blocks the case (CM-21-014), so the owner can try again at once.
Extends: CM-21-014
CM-21-015 MUST When the CASE_MANAGER admits the proposing Case Owner's Undo(Offer(Offer(VulnerabilityCase))), it MUST commit a CaseLedgerEntry recording the withdrawal. The entry closes the Offer the CASE_MANAGER made from that proposal.

Without a withdrawal, one open Offer per case (CM-21-014) lets a transferee who never answers block every later transfer of the case. Ledgering the withdrawal tells the transferee and every replica that the Offer can no longer be accepted, the same way the Reject entry does (CM-21-012).
Refines: CM-21-014
Refines: CLP-02-012
CM-21-036 MAY The proposing Case Owner MAY withdraw the Offer made from its proposal while that Offer is open (CM-21-014), by sending the CASE_MANAGER Undo(Offer(Offer(VulnerabilityCase))).

The owner names its own proposal, the one activity of the transfer it holds, so it needs nothing it learned from the ledger to withdraw (ADR-0148).
Extends: CM-21-015
CM-21-037 MUST The CASE_MANAGER MUST resolve the Offer that an owner's Undo withdraws by the id it derived for that Offer from the proposal (CM-21-028).

The owner names only its proposal. The derived id links the proposal to the one Offer made from it, so the CASE_MANAGER closes that Offer and no other.
Extends: CM-21-015
CM-21-038 MUST After it commits a withdrawal entry (CM-21-015), the CASE_MANAGER MUST broadcast Announce(CaseLedgerEntry) to all case participants.

The broadcast is how the transferee and every replica learn that the Offer can no longer be accepted.
Extends: CM-21-015
CM-21-039 MUST The CASE_MANAGER MUST refuse, with nothing committed or sent, an ownership-transfer Undo from any sender but the proposing owner, or for a closed Offer.

Only the owner that proposed the transfer may withdraw it. An Undo of a closed Offer has nothing left to close, so recording it would add an entry that changes nothing.
Extends: CM-21-015
CM-21-016 MUST The CASE_MANAGER MUST refuse a late Accept or Reject of an ownership-transfer Offer, with no role change and no ledger entry for the answer. An answer is late when the CASE_MANAGER processes it after the Offer's end_time. That end_time is the deadline to answer: the instant after which the Offer can no longer be accepted or rejected.

An embargo deadline is a strong suggestion, so participants may still answer late (CM-28-001). A transfer deadline has to hold, so that the offerer, the transferee and every other participant know who decides for the case once it passes. end_time keeps the meaning CM-28-001 gives it on an Invite — the deadline to answer — so it adds no new meaning to the field.
Refines: CM-21-011
Refines: CM-21-013
CM-21-040 MAY An ownership-transfer Offer(VulnerabilityCase) MAY carry an activity-level end_time. An Offer without one stays open until it is answered or withdrawn (CM-21-014).

An owner may want the transfer settled by a date, or may leave it open. A deadline lets a transfer end without an answer (CM-21-017).
Extends: CM-21-016
CM-21-041 MUST When the inner Offer of the owner's proposal carries an end_time, the CASE_MANAGER MUST copy it onto the Offer it makes (CM-21-028).

The deadline is the owner's choice, stated in its proposal. The Offer the transferee answers is the CASE_MANAGER's, so the deadline must travel onto it.
Extends: CM-21-016
CM-21-017 MUST The CASE_MANAGER, never a replica, MUST evaluate ownership-transfer Offer expiry, lazily, from the ledgered Offer's end_time and its own clock, behind its role gate (BT-17-001). Lazily means when it processes an Accept, Reject or Undo of the Offer, or a new proposal for the case, with no scheduler. An Offer past its end_time is closed: a late answer is refused (CM-21-016), an Undo is refused (CM-21-039) and a new proposal admitted (CM-21-035).

Expiry evaluated in one place keeps the decision about a late answer with the actor that judges answers. Lazy evaluation needs no timer, and the next act on the case is the first moment the expiry matters. Unlike an embargo Invite's expiry, which moves a consent row (CM-28-009), an Offer's expiry changes no case-file record.
Refines: CM-21-014
Refines: CM-21-016
CM-21-043 MUST NOT The CASE_MANAGER MUST NOT commit a ledger entry or send an activity to record an ownership-transfer Offer's expiry (ADR-0152).

The expiry is an observation, not an act. Anything that holds the case and its ledger can read the deadline from the Offer, and no receiver changes on it. An expiry entry would record a fact every holder of the ledger can already derive, so it would be noise on the wire and in the ledger (ADR-0152).
Extends: CM-21-017
CM-21-019 MUST The CASE_MANAGER MUST refuse an ownership-transfer proposal (CM-21-005), writing nothing, unless its sender holds CASE_OWNER on the case when the proposal is processed. Writing nothing means no ledger commit, no Offer made, and the case's attributed_to and every CASE_OWNER role unchanged. A redelivered proposal the CASE_MANAGER already made its Offer from keeps the standing it was admitted with (HP-01-003).

CM-21-011 admits an Accept from the transferee of a recorded Offer. Thus the actor that gets an Offer recorded chooses the next owner. Without a sender rule, any actor that knows a case id can propose the case to itself and accept it, even from outside the roster. Only the Case Owner has the authority to transfer the case (CM-02-005). The redelivery clause makes a late copy of an answered proposal a duplicate, not a refusal, after its sender transfers ownership.
Refines: HP-01-006
Refines: CM-21-005
Refines: CM-21-011
CM-21-044 MUST The CASE_MANAGER MUST refuse every ownership-transfer Offer(VulnerabilityCase) it receives, from any sender. It makes that Offer itself (CM-21-028).

An Offer the CASE_MANAGER admitted from another sender would be a second Offer for the transfer, under an id it did not derive, and would bypass the owner's proposal and its sender rule (CM-21-019).
Refines: HP-01-006
Extends: CM-21-019
CM-21-045 MUST NOT A participant replica MUST NOT admit an ownership-transfer proposal (CM-21-005).

A proposal is sent to the CASE_MANAGER alone (CM-21-005). A replica that admitted one would act for the CASE_MANAGER, which only the CASE_MANAGER may do.
Refines: HP-01-006
Extends: CM-21-019
CM-21-046 MUST A participant replica MUST take an ownership-transfer Offer only from the case's CASE_MANAGER (PCR-03-001), refusing it before anything is written. A replica that holds no such case, or whose case names no CASE_MANAGER, has none to recognise, so it refuses the Offer.

The CASE_MANAGER makes the one Offer of a transfer (CM-21-028). An Offer from any other sender is not that Offer, and a replica that took it would hold a transfer the CASE_MANAGER never offered.
Refines: PCR-03-001
Extends: CM-21-019
CM-21-021 MUST The CASE_MANAGER MUST refuse an ownership-transfer proposal (CM-21-005) whose transferee may not act on the case, with no ledger commit and no Offer made. A transferee may act when it is an active participant (CM-10-004) whose closure the case's ledger does not record (CM-23-018). An owner that means to transfer a case to an actor not active in it invites that actor first.

The new owner administers the case, its active embargo included, so it must first have agreed to that embargo and be entitled to the case content, as joining requires. An active participant holds the case, which is also what lets the Offer name the case by reference rather than carry it (ADR-0165). CASE_OWNER is held on a participant record (CM-21-002), so only a transferee with one can end the transfer as the single owner CM-21-001 requires. A closed participant stays active by CM-10-004, but it receives no further case content (CM-23-004) and may not act on the case (CM-23-018), so it cannot take the case on. The test is the one every sender guard and case-content selection uses, may_act_on_case in vultron/core/participants/recipients.py.
Refines: CM-21-005
Depends on: CM-10-004
CM-21-022 MUST The CASE_MANAGER MUST refuse, with ownership unchanged, the transferee's Accept(Offer(VulnerabilityCase)) (CM-21-011) when the transferee may no longer act on the case (CM-21-021) as the Accept is processed. Ownership unchanged means the case's attributed_to and every participant's CASE_OWNER role stay as they were.

The proposal check (CM-21-021) runs when the Offer is made. A participant can be removed, lapse by an embargo revision, or close between the Offer and its answer, so the Accept is checked again with the same test, may_act_on_case. A closed transferee's Accept is also refused by its sender check (CM-23-018).
Refines: CM-21-011
Refines: CM-21-021

CASE_MANAGER-Authoritative Case Creation

Requirements on the CASE_MANAGER's obligations when accepting a CaseProposal, per ADR-0041. These replace the receiver-authored case-creation described in ADR-0015 and govern how the CASE_MANAGER initializes CaseStatus, participants, and embargo in its DataLayer before emitting Create(VulnerabilityCase).

ID Priority Requirement Related
CM-22-001 MUST When it accepts a Create(CaseProposal), the CASE_MANAGER MUST create the VulnerabilityCase in its own store, with attributed_to the proposing actor's ID (the CASE_OWNER, CM-02-008).

ADR-0041 supersedes ADR-0015. The CASE_MANAGER is the case's authoritative creator. attributed_to records the Case Owner, which ownership checks read and ownership transfer rewrites (MSM-07-027, CM-21-024).
Implements: CP-09-001
Supersedes: CM-14-001 — In multi-actor deployments, where the CASE_MANAGER creates the case (ADR-0041)
CM-22-005 MUST The CASE_MANAGER MUST be the actor of the Create(VulnerabilityCase) it emits for a case it creates from a proposal (CM-22-001).

AS2 records the authoritative creator of an object as the actor of its Create (ADR-0023). The CASE_MANAGER creates the case, so it is that actor, while attributed_to names the owner.
Extends: CM-22-001
CM-22-006 MUST The CASE_MANAGER MUST address that Create(VulnerabilityCase) (CM-22-005) to the report receiver, the CASE_OWNER, only. The report receiver introduces the CASE_MANAGER to the reporter (CBT-01-001, ADR-0132).

The report receiver proposed the case and owns it. The reporter has not yet met the CASE_MANAGER, so the report receiver introduces it (ADR-0132), and a Create sent to the reporter first would come from an actor it does not know.
Extends: CM-22-005
CM-22-002 MUST After it creates the VulnerabilityCase, the CASE_MANAGER MUST create CaseParticipant records for at least the report receiver (CASE_OWNER, RM.RECEIVED) and the reporter (RM.ACCEPTED).

The report receiver and the reporter are the case's first participants. Without their records, role-guard checks find no Case Owner and no reporter in the case (CP-09-002, CP-09-004).
Refines: CM-22-001
Implements: CP-09-002
CM-22-007 MUST The CASE_MANAGER MUST write the participant records of CM-22-002 before it emits Create(VulnerabilityCase) to the report receiver.

The Create(VulnerabilityCase) payload embeds participants inline so the receiver's store_embedded_participants can seed its local replica. Absent participant records at emission time produce an empty replica and break role-guard checks (CP-09-002, CP-09-004).
Extends: CM-22-002
CM-22-003 MUST The CASE_MANAGER MUST commit every canonical ledger entry for case initialization natively in its own ledger, never authored by the report receiver and back-filled. The entries are create_case, create_embargo_event, add_report_to_case, the participant-record and participant-status entries, and add_case_status_to_case.

The ledger of the CASE_MANAGER is the authority for case state (CLP-07-003). Only the store that holds the canonical ledger may assign its indexes and extend its hash chain. An entry that the report receiver writes and then copies in gives the case a second writer. Its ids, times and chain positions then come from outside the CASE_MANAGER. Replicas that copy the ledger then cannot agree on the first entries of the case.
Refines: CM-22-001
CM-22-004 MUST The CASE_MANAGER MUST initialize the default embargo for the new case before emitting Create(VulnerabilityCase) to the report receiver (the CASE_OWNER).

Embargo state is protocol-significant. Deferring initialization to the report receiver or to a later message would produce a window in which embargo-gated operations are undefined (ADR-0041, CP-09-003). The CASE_MANAGER is the only actor that can initialize it authoritatively at case creation; the actor default it applies is the CASE_OWNER's policy, carried on the proposal (CP-01-009, CP-01-010).
Refines: CM-22-001
Implements: CP-09-003

RM Case Closure via Leave(VulnerabilityCase)

Requirements for the canonical RM case closure path: Leave(VulnerabilityCase) is the sole mechanism for advancing a participant's RM state to CLOSED. An Add(ParticipantStatus) whose status carries rmState=CLOSED is not a valid closure trigger. See ADR-0050 (Leave is canonical for RM closure) and ADR-0051 (CASE_MANAGER RM lifecycle). Decided: the maintainer decided the closure semantics in CM-23-002, CM-23-013 and CM-23-014 (what the owner's close does to pending Invites and to later Leaves) on 2026-10-12. ADR-0085 sets the owner-close write boundary. ADR-0131 sets the CM-23-013 reply to late traffic: by default it is dropped, and only a late Leave may get an as:Reject (#4065). The CM-23-014 settle step is #4066 (PR #4489).

ID Priority Requirement Related
CM-23-001 MUST The CASE_MANAGER MUST close a participant's RM state only in response to a Leave(VulnerabilityCase), never to a participant-sent Add(ParticipantStatus) whose status carries rmState=CLOSED. Its ledger commits are the authority for the closure.

The Leave round trip makes the CASE_MANAGER the authority for closure. It also prevents false departures that lost messages cause. A participant cannot silently reach RM.CLOSED at its peers unless the CASE_MANAGER commits and broadcasts a ledger entry (ADR-0108). See ADR-0050.
CM-23-021 MUST On a Leave(VulnerabilityCase), the CASE_MANAGER MUST commit a close_case entry that records the received Leave.

The close_case entry is the ledger's record that the participant asked to leave. Every replica learns of the departure from it, and the closure steps that follow are its recorded consequence (CLP-07-001, CLP-07-002, CLP-07-003).
Extends: CM-23-001
CM-23-022 MUST After the close_case entry, the CASE_MANAGER MUST commit one add_participant_status_to_participant entry per RM transition it writes for the leaver, in write order. A Leave from RM.VALID, recorded V → D → C (CM-23-012), gives two entries.

These status entries are the Leave's recorded consequence. The CASE_MANAGER writes them as its own RM writes for the leaving participant (CLP-07-001, CLP-07-002, CLP-07-003). The ledger records each change when the CASE_MANAGER makes it. Thus a replica applies changes and cannot add a step that no entry supports (ADR-0114, ADR-0124).
Extends: CM-23-021
CM-23-023 MUST A replica that still receives case content (CM-23-004) MUST move a leaving participant to RM.CLOSED only by applying the ledger's status entries, in ledger order.

The status entries reach the replica in the Announce(CaseLedgerEntry) fan-out. A replica that works out a closure path from its own stored state can write an RM step the ledger never recorded, so it diverges from the CASE_MANAGER (ADR-0114, ADR-0124).
Extends: CM-23-022
CM-23-024 MUST NOT A replica applying a close_case entry MUST NOT change any participant's RM state from that entry.

The close_case entry records the request to leave. The status entries after it carry the RM steps (CM-23-022). If the close_case entry moved RM state too, a replica would write a step that no status entry records.
Extends: CM-23-021
CM-23-025 MUST The CASE_MANAGER MUST take back each RM transition, the leaver's or its own, whose status entry for a Leave did not commit (CLP-07-013).

A status entry that cannot be committed leaves its RM transition unwritten. If the CASE_MANAGER kept the transition, its RM state would be ahead of the ledger that the replicas copy (ADR-0146).
Extends: CM-23-022
CM-23-026 MUST NOT The CASE_MANAGER MUST NOT report a Leave as applied while any status entry for it failed to commit.

A Leave reported as applied tells the sender that its departure is on the ledger. If a status entry did not commit, that is false, and a redelivery of the Leave would not walk the closure again.
Extends: CM-23-025
CM-23-002 MUST When the CASE_MANAGER receives Leave(VulnerabilityCase) from the Case Owner, it MUST run the following steps in order. Step (0): it commits the close_case entry recording the Leave (CLP-10-006), then expires every embargo Invite still awaiting a reply (CM-23-014). Step (1): it advances the Case Owner participant to RM.CLOSED, with one status entry per RM transition, in order (CM-23-022). Step (2): it advances its own CaseParticipant to RM.CLOSED in the same way (CM-23-005). In each step it fans each entry out to every active participant whose closure the ledger does not already record (CM-10-004, CM-23-004). The owner-close write boundary (ADR-0085) is the CASE_MANAGER's own status entry reaching RM.CLOSED. It is the last entry of step (2), or of step (1) when the Case Owner is the CASE_MANAGER itself. Once the ledger holds it, the case is fully closed, and no later entry records an external participant's act (CM-23-013).

The Case Owner's departure is the authoritative termination event for a case. The CASE_MANAGER's own RM.CLOSED is the last thing the closure changes, so its entry marks the boundary. See ADR-0050, ADR-0051, ADR-0085.
Refines: CM-23-001
CM-23-027 MUST NOT The CASE_MANAGER MUST NOT commit a separate entry that marks a case's closure.

The CASE_MANAGER's own RM.CLOSED status entry already marks the boundary (CM-23-002). A marker entry records nothing that a replica does not already hold, because every holder of the ledger could derive it (ADR-0152).
Extends: CM-23-002
CM-23-029 MUST NOT A ledger consumer MUST NOT treat the owner-close boundary entry as the case's highest logIndex, or as proof that a replica holds the whole ledger.

ADR-0085's boundary refuses external participants' appends, not the CASE_MANAGER's own housekeeping. Thus a consumer that stopped reading at the boundary could miss an entry that the CASE_MANAGER writes later. The guarantee is the boundary, not chain finality.
Extends: CM-23-002
CM-23-003 MUST When the CASE_MANAGER receives Leave(VulnerabilityCase) from a non-owner participant while the case is open, it MUST advance only that participant's RM state to RM.CLOSED. It records the departure as CM-23-021 and CM-23-022 require: the close_case entry first (CLP-10-006), then one status entry per RM transition, in order. A non-owner Leave that arrives after owner close falls under CM-23-013.

Non-owner departure is a participant-scoped event, not a case termination event. Treating a non-owner Leave identically to an owner Leave would prematurely close the case and violate the Case Owner's authority over the case lifecycle. See ADR-0050.
Refines: CM-23-001
CM-23-030 MUST When a non-owner participant leaves an open case, the CASE_MANAGER MUST keep the case open for the remaining participants.

Only the Case Owner's Leave ends the case (CM-23-002). A non-owner Leave ends one participant's part in it, and the other participants still coordinate.
Extends: CM-23-003
CM-23-004 MUST NOT The CASE_MANAGER MUST NOT send any case content to a participant whose closure an earlier ledger entry records, except as CM-23-031, CM-31-009 and CM-31-030 allow. That entry is the participant's status entry with rmState=CLOSED. Case content is CaseLedgerEntry announcements, Announce(VulnerabilityCase), status broadcasts and embargo announcements. A participant that has left receives no entry after those that record its departure, the CASE_MANAGER's own closure included.

RM.CLOSED is the participant's own statement that it stops its work on the case, possibly while the case is still open. Its replication process is then assumed to have stopped, as the CASE_MANAGER's is once the case is fully closed (CM-23-013). A closed participant receives no more content, so it does not see embargoed material after it leaves. Only the ledger advances a replica, so a closed replica does not converge on later closures, by design.
Refines: CM-23-001
Refines: CM-10-004
Refines: CM-31-009
CM-23-031 MUST The CASE_MANAGER MUST send each entitled leaver the entries that record its own closure, chosen by their subject, not by the leaver's status. These are the close_case entry and each status entry after it, up to the one reaching RM.CLOSED (CM-23-021, CM-23-022). They never widen entitlement (CM-10-004): an inert or removed leaver is not sent them.

The entries that record a participant's own closure come before it stops, so its replica still receives every step, the one that reaches RM.CLOSED included. The leaver's status is already RM.CLOSED when the last of them is fanned out. Thus a choice by status would withhold that last step.
Refines: CM-23-004
Uses definition from: CM-10-004
CM-23-032 MUST NOT A closing participant MUST NOT write its own RM.CLOSED to its replica in place of the CASE_MANAGER's closure entries (CM-23-031).

The CASE_MANAGER writes the leaver's closure and commits each step (CM-23-022). A participant that wrote its own RM.CLOSED would hold a step that no ledger entry records, and its replica would diverge from the CASE_MANAGER (ADR-0114).
Extends: CM-23-031
CM-23-005 MUST The CASE_MANAGER MUST track its own RM lifecycle on a CaseParticipant record, through RM.RECEIVED, RM.VALID and RM.ACCEPTED during case initialization and RM.CLOSED on owner Leave.

Uniform RM lifecycle tracking across all participants (including the coordinator) enables case-level RM state to be read consistently from the ledger without special-casing the CASE_MANAGER. See ADR-0051.
Refines: CM-23-002
CM-23-033 MUST The CASE_MANAGER MUST record each transition of its own RM state as a CaseLedgerEntry.

A ledger entry for each of the CASE_MANAGER's own RM transitions lets every replica read its RM lifecycle from the ledger, the same way as any other participant's. Its own RM.CLOSED entry is also the owner-close write boundary (CM-23-002). See ADR-0051.
Extends: CM-23-005
CM-23-006 MUST For the CASE_MANAGER's CaseParticipant, RM.RECEIVED MUST denote that a CaseProposal has been received and is being evaluated.

The RM state machine models a lifecycle that applies to any actor processing a case, but its transition labels map to case-level events for the coordinator rather than to report-validation events as for other participants. See ADR-0051.
Refines: CM-23-005
CM-23-007 MUST During case initialization (after accepting a CaseProposal), the Case Actor MUST emit CaseLedgerEntry records for each of its own RM transitions: (a) CASE_MANAGER→RM.RECEIVED when the CaseProposal arrives, (b) CASE_MANAGER→RM.VALID when the proposal is validated and case creation
begins,
(c) CASE_MANAGER→RM.ACCEPTED when the VulnerabilityCase is successfully
created and initialized.

Ledger entries for the CASE_MANAGER's own RM transitions create a traceable, timestamped protocol-significant history of case initialization, consistent with CM-02-009 (CASE_MANAGER applies its own timestamp to every state-changing event). See ADR-0051.
Refines: CM-23-005
Refines: CM-02-009
CM-23-008 MUST For the CASE_MANAGER's CaseParticipant, RM.VALID MUST denote that the CaseProposal is correctly formatted and actionable by this CASE_MANAGER, and that case creation has begun.

RM.VALID is the CASE_MANAGER's signal that the proposal passed structural validation and that initialization is in progress; it distinguishes a validated-but-incomplete case from one still under evaluation. See ADR-0051.
Refines: CM-23-006
CM-23-009 MUST For the CASE_MANAGER's CaseParticipant, RM.ACCEPTED MUST denote that the case has been successfully created and is being coordinated.

RM.ACCEPTED marks the end of the initialization phase and the start of active coordination; it is the CASE_MANAGER's counterpart to a reporter's or vendor's RM.ACCEPTED (engagement decision). See ADR-0051.
Refines: CM-23-006
CM-23-010 MUST For the CASE_MANAGER's CaseParticipant, RM.CLOSED MUST denote that the Case Owner has left the case and the case is fully closed.

RM.CLOSED on the CASE_MANAGER's own participant record is the terminal state; it signals that no further ledger entries will be authored for this case. See ADR-0051.
Refines: CM-23-006
CM-23-011 MUST When the Case Owner's Leave(VulnerabilityCase) arrives while the case's EM state is EM.ACTIVE or EM.REVISE, the CASE_MANAGER MUST decline it with an as:Reject (MSM-05-001).

Owner-close is a hard, terminal write boundary (ADR-0085). VP-13-009 keeps an embargo alive across a participant close only while others remain engaged. Owner-close leaves none, and no authority to lift it, so an embargo left active could never end. This raises the participant-level VP-13-005 guidance to a hard refusal. Leave is understood but declined, hence as:Reject, not Create(ProcessingFault) (MSM-05-001).
Refines: CM-23-002
Refines: VP-13-005
CM-23-034 MUST NOT When it declines an owner-close under CM-23-011, the CASE_MANAGER MUST NOT run the CM-23-002 closure sequence: no participant reaches RM.CLOSED, itself included.

If any step of the closure ran, the Case Owner or the CASE_MANAGER would be closed while the embargo is still active. Then no authority would remain to end the embargo (CM-23-011).
Extends: CM-23-011
CM-23-035 MUST The CASE_MANAGER MUST suppress the receipt commit of an owner-close it declines (CM-23-011), so the ledger holds no close_case entry for it.

The receipt commit opens the closure. It is committed before any role-specific effect (CLP-10-006), and the status entries after it (CM-23-022) advance the departing participant to RM.CLOSED on each replica (CLP-10-001, CM-23-003). Skipping only the CM-23-002 steps while still committing the receipt would leave a recorded departure that the case then declined (ISSUE-3137).
Extends: CM-23-034
CM-23-036 MUST To close a case that holds an active embargo, the Case Owner MUST first terminate the embargo and then send its Leave(VulnerabilityCase) again.

The CASE_MANAGER declines an owner-close while an embargo is active (CM-23-011). Thus the only path to a closed case is to end the embargo first through the normal EM path.
Depends on: CM-23-011
CM-23-037 MUST NOT The CASE_MANAGER MUST NOT terminate the embargo as part of declining an owner-close (CM-23-011). The embargo stays active until the normal EM path ends it.

The decline answers the close; it is not a decision on the embargo. Only the EM path ends an embargo, so its participants see the termination in the usual way.
Extends: CM-23-011
CM-23-012 MUST NOT Case closure MUST NOT advance the RM state of a participant that did not send a Leave(VulnerabilityCase): such a bystander keeps its last RM state. On an owner Leave, only the Case Owner and the CASE_MANAGER advance to RM.CLOSED (CM-23-002).

A participant's RM state is its own record of how it handled the report (ADR-0121). A bystander that never sent Leave has made no closure declaration, and forcing it to RM.CLOSED would fabricate one. It stays at its last rung when the case ends around it. The demos' "all participants RM.CLOSED" milestone (DEMOMA-07-003) holds because each participant closes its own handling before the Case Owner closes the case (CM-23-013).
Refines: CM-23-001
Refines: CM-23-002
CM-23-038 MUST The CASE_MANAGER MUST advance a leaving actor to RM.CLOSED from any RM rung it holds, using only transitions the RM transition function permits. Thus a Leave from RM.VALID is recorded V → D → C (RMB-14-005). A Leave from RM.START, by an actor that has not yet recorded receiving the case, is recorded S → R → C.

A Leave is the participant's own closure act, so closing it from any rung is self-declaration, not an imposed jump (ADR-0121). The RM transition function closes from Received, Invalid, Accepted and Deferred (ADR-0114). A Leave from Valid passes through Deferred, and one from Start through Received, since S → C is not a transition. Thus no write site overrides the transition table (RMB-14-005).
Extends: CM-23-012
CM-23-013 MUST NOT Once the CASE_MANAGER has committed its own status entry reaching RM.CLOSED (CM-23-002 step 2), the case is fully closed. For any later inbound activity for the case, the CASE_MANAGER MUST NOT commit any CaseLedgerEntry, the receipt entry included. Any inbound activity includes a non-owner participant's Leave(VulnerabilityCase), and every activity class: RM (R), EM, CS and general-inquiry (GI) traffic such as Add(Note).

A deployment's process for a closed case can exit once closure has fanned out. The prototype's CASE_MANAGER behaves toward the case as if its process had exited: nothing reaches the ledger. The boundary is a hard kill switch, not a selective gate (ADR-0085). The receipt commit is suppressed because it opens the closure whose status entries would close the sender on the replicas (CLP-10-006, CM-23-022). A participant that wants its departure recorded leaves first (CM-23-015).*
Refines: CM-23-003
Refines: CM-23-002
CM-23-039 MUST NOT Once a case is fully closed (CM-23-013), the CASE_MANAGER MUST NOT change any case or participant state for an inbound activity for the case. The sender's RM state on the canonical ledger stays at its last rung (CM-23-012).

A state change with no ledger entry would leave the CASE_MANAGER's store ahead of every replica, which can no longer catch up. The closed case behaves as if its process had exited (ADR-0085).
Extends: CM-23-013
CM-23-040 MAY Once a case is fully closed (CM-23-013), the CASE_MANAGER MAY answer an inbound activity for it with an as:Reject (MSM-05-001), or drop it unanswered.

A reply is a courtesy, since nothing reaches the ledger either way. An as:Reject means "received and understood but declined" (MSM-05-001). A drop is what an exited process does.
Extends: CM-23-013
Depends on: MSM-05-001
CM-23-041 MUST NOT A participant MUST NOT rely on receiving a reply to an activity it sends after the case is fully closed (CM-23-013).

The CASE_MANAGER may drop a late activity with no reply (CM-23-040), so a participant that waits for one may wait forever.
Depends on: CM-23-040
CM-23-042 MAY Once a case is fully closed (CM-23-013), the CASE_MANAGER MAY still answer a replica's resync request (Reject(CaseLedgerEntry)) by replaying entries the ledger already holds.

A replay appends nothing, so it does not cross the boundary. The CASE_MANAGER still appends no new entry on behalf of an external participant (CM-23-013).
Extends: CM-23-013
CM-23-014 MUST This rule covers the CM-23-002 owner-close sequence, after the close_case entry and before any closure step. There the CASE_MANAGER MUST expire every embargo Invite on the case still awaiting a reply, without waiting for its RSVP deadline. Each consent row that is INVITED moves to TIMED_OUT. A pending case Invite carries no RSVP deadline and needs no settling. An answer to it after close is an external append, refused at the ADR-0085 boundary.

After close a pending Invite can only expire. Waiting for its RSVP deadline would keep the CASE_MANAGER running for a closed case, possibly for years, only to write one more entry after the write boundary. Settling first leaves no timer, so the CASE_MANAGER can stop once the closure has fanned out. This keeps the boundary clean in practice; it does not promise chain finality (CM-23-002).
Refines: CM-23-002
Depends on: CM-28-009
CM-23-043 MUST The CASE_MANAGER MUST commit each owner-close expiry (CM-23-014) as its own Update(EmbargoConsent) that carries the TIMED_OUT row (CM-28-009, CM-28-014).

Each expiry changes one consent row, and every case-file change is its own ledger entry (CLP-07-013). Thus the replicas copy each TIMED_OUT row, and the expiries are on the ledger before the write boundary.
Extends: CM-23-014
Depends on: CM-28-014
CM-23-015 SHOULD The Case Owner SHOULD leave a case last, or nearly last, after the other participants have left, so that their departures are recorded before the case is fully closed; a departure sent after closure is not recorded (CM-23-013).

The owner's Leave closes the case for everyone (CM-23-002), and nothing a participant sends afterwards reaches the ledger. A participant that has not left by then keeps its last RM state on every replica that receives it, which is harmless but leaves the ledger without a record of its departure. Leaving last is the owner's normal course, not a hard ordering: a participant that never leaves cannot hold a case open. Every demo scenario closes through close_case_owner_last (vultron/demo/helpers/closure.py), which sends the owner's Leave only once the CASE_MANAGER has recorded each other departure.
Refines: CM-23-002
Depends on: CM-23-013
CM-23-016 MUST A participant's case replica is the case as it stood when the replica was seeded, plus the ledger entries since. A replica MUST apply the entries in ledger order by copying the objects they carry, so replicas at one ledger position hold identical case files.

The CASE_MANAGER is the single writer (ADR-0108), and the ledger records each change when the CASE_MANAGER makes it (CLP-07-013). If each replica copies what the entries carry, in the same order, two replicas at the same position hold the same case file as each other and as the CASE_MANAGER. CM-23-023 gives the rule for closure. This requirement gives it for every replicated change (ADR-0124).
Extends: CM-23-023
CM-23-044 MUST NOT A replica applying an entry MUST NOT mint an id, read a clock, read local policy or state outside the case, or run lifecycle logic. Walking the RM transition table from its own stored state to a target state is such logic.

A replica can find its own path from its own state, which can be out of date. It can then write an RM state that the ledger never recorded. A replica that builds a record itself gives it an id and a time that differ from those of the CASE_MANAGER. In both cases, the replicas diverge from the CASE_MANAGER (ADR-0124).
Extends: CM-23-016
CM-23-018 MUST The CASE_MANAGER MUST refuse every activity for a case from a sender whose closure that case's ledger records, committing no entry and sending nothing. That record is the sender's status entry with rmState=CLOSED. The refusal is the sender guard's, so no refusal notice is sent either (CLP-05-001, HP-01-006). CM-23-048, CM-23-049 and CM-23-050 give the exceptions.

RM.CLOSED is terminal (ADR-0085). The CASE_MANAGER sends no more case content to a closed participant (CM-23-004). Thus an act from that participant would be recorded in a case that it cannot see. CM-23-013 stops writes only when the case is fully closed.
Refines: CM-23-004
Refines: HP-01-006
Refines: CM-10-004
CM-23-048 MUST The CASE_MANAGER MUST answer a closed sender's Reject(CaseLedgerEntry) with a replay of only the entries that record that sender's own closure (CM-23-031). The active check of SYNC-03-005 admits that resync request, so it is not refused as a closed sender's (CM-23-020).

The resync request stays available to a closed participant whose replica missed one of its own closure entries. Its replay adds nothing to the ledger and has a limit: the entries that record the sender's own closure.
Extends: CM-23-018
Refines: SYNC-03-005
CM-23-049 MUST The CASE_MANAGER MUST report as SKIPPED a repeated act that would close a closed sender, whatever its activity id, committing no entry and sending nothing. Such an act is a Leave(VulnerabilityCase), or a Reject of the sender's stub or full-case Invite. A Reject(Offer(VulnerabilityReport)) closes no sender on a case, so it is not such a repeat (CM-23-019).

A second closing act changes nothing, whichever act closed the sender first: its effect is already in place (ID-05-003). The effect of an act, not its activity id, decides if it is a repeat.
Extends: CM-23-018
CM-23-050 MUST While a case is not fully closed, the CASE_MANAGER MUST process a closed Case Owner's Leave(VulnerabilityCase) to complete the case's closure (CM-23-002).

The Case Owner's Leave includes the case's closure, which is not in place while the case is open, so it is not a repeat. A failed owner Leave keeps none of the closure (CLP-07-015). Thus this owner's closure was recorded by an act other than its Leave, and only its Leave can close the case.
Extends: CM-23-018
CM-23-019 MUST Once a report belongs to a case, only that case's Case Owner holds the report. The Case Owner closes the case by Leave(VulnerabilityCase) (CM-23-001, CM-23-002), never by Reject(Offer(VulnerabilityReport)). The CASE_MANAGER, and each participant holding a case replica, MUST refuse a Reject(Offer(VulnerabilityReport)) for a report that belongs to the case, from every sender. From the Case Owner it is the wrong closing act. From any other sender it is a message about a report that sender does not hold. A Reject(Offer(VulnerabilityReport)) for a report that belongs to no case is unaffected.

A Reject(Offer(VulnerabilityReport)) moves its sender to RM.CLOSED without the Leave round trip. Thus a Case Owner that sends one can close itself while its case stays open. It can then continue to act as Case Owner, and the case is never fully closed. If the Case Owner's Reject(Offer(VulnerabilityReport)) became a full close, the case would have two closing messages. If only the later acts of a closed Case Owner were refused, the wrong message would stay admitted (ADR-0144).
Refines: CM-23-001
Refines: CM-23-018
CM-23-051 MUST On refusing a Reject(Offer(VulnerabilityReport)) (CM-23-019), the CASE_MANAGER or replica holder MUST write no RM state, commit no entry and send nothing, a refusal notice included. The refusal is the sender guard's: no sender is entitled to send it to a case actor (CLP-02-005, HP-01-006).

A sender guard refuses before any write (HP-01-006), so the refused act leaves no trace. A refusal notice would answer a message that no sender may send.
Extends: CM-23-019
CM-23-052 MUST NOT An actor MUST NOT send Reject(Offer(VulnerabilityReport)) for a report that belongs to a case it holds.

Every case actor refuses such a Reject(Offer(VulnerabilityReport)) (CM-23-019). The actor closes its part in the case by Leave(VulnerabilityCase) instead (CM-23-001).
Extends: CM-23-019
CM-23-053 MUST An actor's report-closing trigger MUST fail, before it writes or sends anything, for a report that belongs to a case the actor holds.

The trigger is where the actor would build the Reject(Offer(VulnerabilityReport)) that CM-23-052 forbids. Failing there, before any write, leaves no local closure that the case never recorded.
Implements: CM-23-052

CASE_MANAGER-Delegated Activity Authorship Contract

Requirements for how trigger use cases assign actor and attributed_to when the CASE_MANAGER emits an Activity on behalf of a requesting participant (delegated message). The reference implementation is delegated_authorship() (vultron/core/behaviors/delegated_authorship.py). Codified from CONCERN-2170.

ID Priority Requirement Related
CM-24-001 MUST When the CASE_MANAGER emits a protocol Activity on behalf of a requesting participant (delegated message), the actor field of the emitted Activity MUST be set to the CASE_MANAGER's actor ID, not the requesting participant's actor ID.

Receiving actors authenticate messages: they compare the actor field with the known case participants. Case-scoped messages come to participants from the CASE_MANAGER (PCR-08-001). An Activity whose actor names the requesting participant is not a delegation that the CASE_MANAGER authorized. Thus the receiver can reject it or route it incorrectly. For example, a Coordinator refuses an ownership-transfer Offer whose actor names the Finder.
Refines: CM-02-009
CM-24-002 MUST When a delegated Activity is emitted by the CASE_MANAGER, the attributed_to field MUST be set to the requesting participant's actor ID so that receivers can recover the originating identity.

attributed_to is the AS2-standard field for recording who delegated an action. Without it, the intent of the message cannot be attributed to the correct participant, and audit trails are incomplete.
Refines: CM-24-001
CM-24-006 MUST A case MUST have a participant holding CASE_MANAGER from case creation onward. Both creation paths register one: a dedicated CASE_MANAGER actor holding COORDINATOR + CASE_MANAGER under ADR-0041, or the creating actor as CASE_OWNER + CASE_MANAGER under CM-02-015. Role delegation transfers it without a gap.

Both creation paths register a CASE_MANAGER, and delegation transfers the role without a gap. Thus no correct path creates a case without one, and every case-scoped message has the CASE_MANAGER to go through (PCR-08-001).
Refines: CM-24-001
Depends on: CM-22-002
CM-24-014 MUST An actor that finds no CASE_MANAGER for a case MUST fail the act, never sending the activity directly to another participant instead (CM-24-008).

An actor that finds no CASE_MANAGER reads a corrupt roster, since a case always has one (CM-24-006). A direct send to a participant would bypass the ledger and the recipient rules (PCR-08-001). It would also hide the corruption behind an apparent success. A failed act shows the corruption (ADR-0113).
Depends on: CM-24-006
CM-24-007 MUST When the CASE_MANAGER forwards a recorded activity for its sender (CM-33-008), the forward MUST carry one id, derived from that activity, on every attempt.

One id makes a resend safe. The outbox holds one pending entry for each id (OX-06-002). The addressee commits nothing, and it builds its answer again with the same id.
Refines: CM-24-004
Depends on: OX-06-002
CM-24-016 MUST A forwarded activity's record and its forward (CM-24-007) MUST commit as one act (CLP-07-015), so a failure to build or send the forward keeps neither.

The record comes before the forward (CLP-10-006). If a forward fails after the CASE_MANAGER keeps its record, the ledger holds a request that its addressee never received and cannot answer. When the record and the forward are in one act, every recorded request has its forward.
Extends: CM-24-007
Depends on: CLP-10-006
CM-24-017 MUST NOT The CASE_MANAGER MUST NOT report a failure to build or send a forward (CM-24-016) as a refusal of the sender (HP-01-003).

A failed forward is a fault of the CASE_MANAGER, not of the sender. A refusal would tell the sender that its request is not valid, which is false.
Extends: CM-24-016
CM-24-018 MUST The CASE_MANAGER MUST process a redelivery of an activity whose first delivery failed to forward (CM-24-016) as a new delivery.

The failed act kept nothing, so the redelivery has no record or forward to complete. Processing it afresh records the activity once and sends its forward.
Extends: CM-24-016
CM-24-019 MUST The CASE_MANAGER MUST send nothing for a redelivery of an activity it has already recorded and forwarded (CM-24-007).

Every recorded request already has its forward (CM-24-016). Thus a redelivery never has a forward to complete, and a second forward would only repeat the first.
Extends: CM-24-016

Observer Participant Semantics

Requirements for the CVDRole.OBSERVER role (formerly CVDRole.OTHER): what an Observer IS, how it is admitted, what content it receives, and how the RM lifecycle applies. Codified by ADR-0057 (docs/adr/0057-observer-participant-role.md). Source: CONCERN-2093.

ID Priority Requirement Related
CM-25-001 MUST An Observer (OBSERVER) is a Tier-1 case participant: it MUST hold a CaseParticipant record, run the standard RM lifecycle, and satisfy the same embargo-consent gate (MV-10-005) as any other participant. OBSERVER is the base role — the lowest non-null privilege set in the Vultron protocol. All other CVD roles are additive to it.

Defining Observer as a full participant (not a passive consumer or an external T0 actor) ensures it is subject to the same admission and embargo-consent rules as Vendors and Coordinators. The "base role" framing establishes OBSERVER as the correct fallback when no other role applies, rather than leaving CVDRole.OTHER as an unspecified catch-all. See ADR-0057.
CM-25-002 MUST An Observer MUST be admitted to a case via the standard Invite/Accept flow defined in CM-17.

Requiring a non-empty case_roles value is load-bearing: the participant record created with the stub Invite (CM-11-026) carries the roles that Invite names. A missing or empty list would create an Observer participant with no role — indistinguishable from a defect.
Refines: CM-17-003
Refines: CM-16-003
CM-25-003 MUST An Observer MUST receive full case content once it is an active participant (CM-10-004). That check is the sole gate on case content for an Observer. No additional or reduced delivery tier applies.

An Observer that accepts the stub Invite consents to the active embargo (MV-10-006). The absence of a remediation function does not change what the Observer is entitled to receive once it is active. Introducing a separate reduced delivery tier for Observers is deferred: notes/selective-disclosure.md describes the potential future mechanism but it is not yet implemented.
Depends on: CM-10-004
Depends on: MV-10-005
Depends on: MV-10-006
CM-25-004 SHOULD After accepting a case invitation and receiving the full case replica, an Observer SHOULD run the standard RM triage cycle (CM-11-002): RM.VALID or RM.INVALID, then RM.ACCEPTED or RM.DEFERRED. For an Observer, RM.ACCEPTED means "I am actively engaged with / monitoring this case" — it does NOT imply commitment to develop or deploy a fix.

The RM triage cycle represents an Observer's assessment of whether the case is relevant to them and whether they intend to remain engaged. An Observer that determines the case is outside their scope should transition to RM.INVALID or RM.DEFERRED, not silently stay at RM.RECEIVED. This preserves the RM machine's semantics as a participation-intent signal.
Depends on: CM-11-002
CM-25-005 MUST NOT A participant whose only case role is OBSERVER MUST NOT emit VFD state-transition messages (CV, CF, or CD). An Observer has no vendor-fix-deployment obligations and no authority to advance the VFD dimension on behalf of a product or service.

An Observer joined the case to monitor it, not to own a fix path. Permitting VFD emissions from a role with no fix obligations would corrupt the VFD state machine for the case (e.g., a monitoring actor falsely claiming fix deployment).
Refines: CM-03-005
CM-25-006 MUST The Invite activity sent by the CASE_MANAGER for an Observer MUST carry case_roles naming OBSERVER only.

The stub Invite creates the participant record and gives it the roles that the Invite names (CM-11-006). An Invite without CVDRole.OBSERVER would create a participant with an empty role list. Role checks cannot tell that record from a record that lost its roles. An Invite that names other roles would give the Observer obligations or authority that it was not invited to hold.
Refines: CM-25-002
CM-25-007 MUST NOT An Observer invitation MUST NOT carry an empty case-roles list: an empty list names no role, so the invitee's participant record would violate the CM-16-003 non-empty invariant.

[] provides no role information to the CASE_MANAGER's participant-creation path on the stub Invite (CM-11-006), producing a participant record that violates the non-empty role invariant enforced by CM-16-003.
Refines: CM-25-006

Role Stacking — Union of Permissions

Requirement governing how conflicting obligations are resolved when a participant holds multiple CVD roles simultaneously. Codified by ADR-0057. Source: CONCERN-2093.

ID Priority Requirement Related
CM-26-001 MUST Each actor MUST judge a participant's permitted and obligated actions by the union of what its case roles permit. A MUST NOT attached to one role gives way when another of its case_roles carries a MAY, SHOULD or MUST for the same action class.

CVD roles represent orthogonal functions a participant may perform. When a participant holds more than one function (e.g., a security researcher who is also a Deployer for an affected product), restricting them based on their least-capable role would incorrectly limit their protocol participation. The union model prevents role restrictions from blocking obligations that exist under a co-role.
Refines: CM-25-005

Embargo Invite RSVP Deadlines

Requirements for the optional RSVP-by deadline carried on an embargo invitation, the authority relationship between that wire value and the CM-18-023 pocket-veto policy window, and how an expiry is recorded. Codified by ADR-0065. Source: IDEA-2066.

ID Priority Requirement Related
CM-28-001 MUST An actor that reads an Invite(EmbargoEvent) MUST read its activity-level end_time, when present, as the RSVP-by deadline for that invitation. After that instant the invitation is no longer open.

as_Object.end_time is inherited by as_Activity and therefore by every Vultron activity, so no vocabulary extension is required. On an Activity, AS2 end_time already means "valid until", making the RSVP reading idiomatic rather than a repurposing. Placing the deadline on the activity rather than on the embargo object is what allows the same embargo to be offered to several actors with different deadlines, and to one actor twice. See ADR-0065.
CM-28-017 MAY An Invite(EmbargoEvent) activity MAY carry an activity-level end_time.

Every AS2 activity inherits end_time from as_Object, so a sender can state the RSVP-by deadline with no vocabulary extension. The field stays optional: without it, the CM-18-023 policy window sets the deadline (CM-28-019). See ADR-0065.
Extends: CM-28-001
CM-28-018 MUST NOT An actor that reads an Invite(EmbargoEvent) MUST NOT read its activity-level end_time as the embargo's expiry.

An embargo invitation carries two end_time fields one nesting level apart: the Invite's is the RSVP-by deadline, and the nested EmbargoEvent's is when the embargo ends. A reader that takes one for the other lapses the invitation or the embargo at the wrong instant. See ADR-0065.
Extends: CM-28-001
CM-28-002 MUST When an Invite(EmbargoEvent) carries an end_time, that value MUST be authoritative for the invitation's expiry, superseding the receiver's local policy window. The resulting deadline stays subject to the EP-07-006 ceiling.

An explicit wire value lets both parties calculate the same lapse time from the same field, with no more messages. A window that only the receiver knows does not let the two parties agree if an invitation is still open. See ADR-0065.
Refines: CM-18-002
Depends on: EP-07-006
CM-28-019 MUST When an Invite(EmbargoEvent) carries no end_time, the CM-18-023 policy window MUST set the invitation's expiry. The resulting deadline stays subject to the EP-07-006 ceiling.

An Invite without a deadline still needs one, and a sender that predates ADR-0065 sends none. The policy window keeps the behaviour those invitations had before the wire value existed. See ADR-0065 and ADR-0096.
Extends: CM-28-002
Depends on: EP-07-006
CM-28-003 MUST The actor holding CASE_MANAGER MUST be the enforcement authority for embargo invite expiry.

Centralising enforcement at the CASE_MANAGER matches the case-communication model (PCR-08) and the CASE_MANAGER's role as canonical ledger author (ADR-0019, ADR-0021). See ADR-0065.
CM-28-004 MUST The CASE_MANAGER MUST record an expired invitation as the consent row's TIMED_OUT state, by the TIME_OUT trigger (INVITED → TIMED_OUT), never as DECLINED.

A participant that did not answer made no decision, and code, logs, demos and replicas read the consent row, not ledger provenance (ADR-0118). An expired participant is re-invitable (CM-18-003), excluded from embargoed content like any non-signatory (CM-10-004), and made a signatory by a late Accept the CASE_MANAGER honours (EMB-17-002). ADR-0118 reverses ADR-0065's choice to record a lapsed invitation as DECLINED.
Refines: CM-18-003
CM-28-020 MUST A consent row MUST reach DECLINED only by an explicit refusal.

Reserving DECLINED for an explicit refusal lets the consent row tell "never responded" from "explicitly declined". Code, logs, demos and replicas read the row, not ledger provenance, so the row must carry the difference (ADR-0118).
Extends: CM-28-004
CM-28-005 MUST An expired embargo invitation MUST be distinguishable from an explicit refusal both in the participant's consent row (TIMED_OUT versus DECLINED, CM-28-004) and in the canonical case ledger (CM-28-009).

The canonical ledger is the project's history mechanism (ADR-0019), so expiry-versus-refusal is recoverable from it; since ADR-0118 the consent row carries the distinction too, so a reader that consults only the participant record also sees it. See ADR-0065, ADR-0118.
CM-28-009 MUST When an RSVP deadline passes unanswered, the CASE_MANAGER MUST record the expiry as its own Update(EmbargoConsent) with the invitee's full TIMED_OUT row, committed as sent. The entry differs from the invitee's Reject(Invite(EmbargoEvent)), whose row is DECLINED.

The expiry is the occasion and the Update is the CASE_MANAGER's act on it, a real AS2 activity (ADR-0140). The TIMED_OUT row it carries makes expiry-versus-refusal distinguishable in the ledger (CM-28-005), and it is how a replica learns the TIMED_OUT state without computing it (CM-28-014). A second, invitee-attributed entry would record an act nobody sent. See ADR-0065, ADR-0118.
Refines: CM-28-005
CM-28-022 MUST NOT The CASE_MANAGER MUST NOT record an invitation's expiry as an activity attributed to the invitee, which sent nothing.

An invitee-attributed entry would record an act that nobody sent, and a reader of the ledger would take the silence for the invitee's answer. The expiry is the CASE_MANAGER's own act on its own clock (ADR-0140). See ADR-0065, ADR-0118.
Extends: CM-28-009
CM-28-011 MUST NOT The CM-18-023 policy window MUST NOT be applied so as to place the effective RSVP deadline after the embargo's own end_time. Where the window would overshoot, the deadline is the embargo's end_time.

The policy window is a fixed duration (by default, 7 days) from the published timestamp of the invitation. It does not refer to the embargo that the invitation concerns. On a short embargo, the window causes a pocket veto after the embargo ends. It records an expiry for an invitation whose embargo is already over. EP-07-006 gives the general rule. This requirement applies it to the specific fallback path that CM-18-023 defines. See ADR-0096.
Refines: CM-18-002
CM-28-012 MUST When the CASE_MANAGER relays an Invite(EmbargoEvent) to a participant (EP-09-002) it MUST set the Invite's end_time to that Invite's published time plus the configured RSVP window, subject to the EP-07-002 minimum and the EP-07-006 ceiling. The Invites of one relay round are emitted in sequence and need not share one deadline.

If the wire carries no deadline, each receiving store uses the EP-07-001 window from its own ActorConfig. Two replicas can then disagree about when the same invitation closes. The relaying CASE_MANAGER is the enforcement authority (CM-28-003). It states the deadline one time, and every participant reads the same value from the same object. ASK-03-004 requires the same of an ask (ADR-0065, ADR-0113).
Refines: CM-28-002
Depends on: EP-09-002
Depends on: EP-07-002
Depends on: EP-07-006
CM-28-013 MUST The CASE_MANAGER MUST record an Invite's RSVP deadline on the invitee's INVITED row for that embargo, in an Update(EmbargoConsent) entry right after the Invite's (CLP-07-013).

The deadline is case state about the invitee. As with all other case state, the CASE_MANAGER writes it one time, and it gets to replicas through the ledger (ADR-0108, ADR-0124). A deadline that a receiver writes would be in one store only. Replicas could then disagree about whether the invitation is open (ADR-0113).
Depends on: CM-28-012
CM-28-023 MUST Every replica MUST record an Invite's RSVP deadline by copying the CASE_MANAGER's CM-28-013 Update(EmbargoConsent) entry (EP-09-007, ADR-0124).

The deadline is case state about the invitee, so it reaches replicas through the ledger like all other case state (ADR-0108, ADR-0124). A replica that copies the entry holds the same deadline as the CASE_MANAGER, so the two agree on whether the invitation is open (ADR-0113).
Depends on: CM-28-013
Depends on: EP-09-007
CM-28-024 MUST NOT A participant MUST NOT derive or store an RSVP deadline on receipt of an Invite (EP-09-003).

A deadline that a receiver writes would be in one store only, and replicas could then disagree about whether the invitation is open. The CASE_MANAGER writes the deadline once (CM-28-013), and replicas copy it (CM-28-023; ADR-0113).
Extends: CM-28-013
Refines: EP-09-003
CM-28-014 MUST Only the CASE_MANAGER evaluates invite expiry (CM-28-003). Its Update(EmbargoConsent) carrying the invitee's TIMED_OUT row (CM-28-009) MUST be committed by the CASE_MANAGER before the row is written (ADR-0124, CLP-07-013), and reaches replicas as that ledger entry (CM-28-016). A replica learns an expiry from that entry, copying the row, and never computes one.

Only the CASE_MANAGER can write a ledger entry. Thus an expiry that another store calculates has one of two results. It is an entry that the store has no authority to commit, or it is a private difference that no replica shares. The CASE_MANAGER commits the expiry first, and replicas copy it by replay. Thus the consent row of every replica is equal to that of the CASE_MANAGER, and one clock decides (ADR-0113, ADR-0124).
Refines: CM-28-009
Depends on: RSH-08-004
Depends on: BT-17-001

Sender Entitlement for Case Content Changes

Who may remove a note from a case or attach a report to it. Neither message has a trigger today; these entries name the sender a receiver accepts (HP-01-006).

ID Priority Requirement Related
CM-30-001 MUST The CASE_MANAGER MUST act on Remove(Note)[target=VulnerabilityCase] only when the sender is the note's author and an active participant (CM-10-004), or the case's Case Owner.

A note is a participant's contribution to the case record. If any sender could remove any note, a participant could delete evidence or advice that it disagrees with. An author can withdraw its own note. The Case Owner decides what the case holds, so it can remove any note. No other participant has a reason to remove a note.
Refines: HP-01-006
CM-30-003 MUST A participant replica MUST apply a Remove(Note)[target=VulnerabilityCase] only when the CASE_MANAGER sent it (PCR-03-001).

Replicas take case changes from the CASE_MANAGER only (PCR-03-001). A replica that applied a removal from another sender would let that sender delete a note that the CASE_MANAGER keeps.
Extends: CM-30-001
CM-30-004 MUST The CASE_MANAGER MUST refuse a Remove(Note)[target=VulnerabilityCase] from any sender that CM-30-001 does not name, with the note left in the case.

A refusal that still removed the note would give the unentitled sender what it asked for. The REFUSED result tells the sender that nothing changed.
Extends: CM-30-001
CM-30-002 MUST The CASE_MANAGER MUST act on Add(VulnerabilityReport)[target=VulnerabilityCase] only when the sender is the case's Case Owner.

A report defines what the case is about. A new report makes the scope of the case larger and adds to the content that every participant receives. That is the decision of the Case Owner. If another sender could add a report, a participant could change the scope of the case alone.
Refines: HP-01-006
CM-30-005 MUST A participant replica MUST apply an Add(VulnerabilityReport)[target=VulnerabilityCase] only when the CASE_MANAGER sent it (PCR-03-001).

Replicas take case changes from the CASE_MANAGER only (PCR-03-001). A replica that applied an addition from another sender would widen the case's scope on that replica alone.
Extends: CM-30-002
CM-30-006 MUST The CASE_MANAGER MUST refuse an Add(VulnerabilityReport)[target=VulnerabilityCase] from any sender other than the Case Owner, with the case's reports unchanged.

A refusal that still attached the report would give the unentitled sender what it asked for. The REFUSED result tells the sender that nothing changed.
Extends: CM-30-002

Participant Removal and Reinstatement

Removing a participant withdraws its entitlement to case content and keeps its record. The Case Owner requests removal and reinstatement; the CASE_MANAGER applies them, records each as one ledger entry, and tells the affected participant directly (ADR-0116).

ID Priority Requirement Related
CM-31-001 MUST An actor that applies a participant's removal MUST record a removal fact on the participant's CaseParticipant.

Removal is a decision about entitlement to case content, not about membership. Thus it is a fact on the record that the active check reads (ADR-0116).
Refines: CM-10-004
CM-31-015 MUST NOT An actor that applies a participant's removal MUST NOT delete the participant's record from case_participants.

If the record is deleted, the case loses the history of who took part. Ledger entries then name an actor that is not on the roster (ADR-0116).
Extends: CM-31-001
CM-31-016 MUST An actor that applies a participant's removal MUST keep the participant's status history and the ledger entries it authored.

The status history and the authored entries are the record of what the participant did in the case. Removal withdraws entitlement from now on; it does not rewrite the past (ADR-0116).
Extends: CM-31-001
CM-31-017 MUST Every actor MUST treat a removed participant as inert (CM-10-004).

A removed participant has lost its entitlement to case content. If any actor still treated it as active, it would keep receiving embargoed content.
Extends: CM-31-001
Uses definition from: CM-10-004
CM-31-003 MUST VulnerabilityCase MUST carry a computed activeParticipants collection on the wire holding exactly the participants the CM-31-002 check finds active, computed from the participant records (CM-31-014).

case_participants stays the full roster that role resolution and actor_participant_index read (CM-19-001). Publishing the active set as its own collection makes Remove(CaseParticipant) literally true: the participant leaves that collection.
CM-31-020 MUST A receiver reading a case MUST recompute its activeParticipants collection rather than refuse it as an unknown key.

Core types refuse unknown keys on input, so a computed field in the dump would refuse the case's own serialized form unless input handles it.
Extends: CM-31-003
CM-31-021 MUST A receiver reading a case MUST refuse a supplied activeParticipants value that contradicts the one it recomputes (ARCH-23-005).

A supplied collection that disagrees with the participant records it travels with would give two answers to who is active. The records are the facts, so the contradicting value is refused.
Extends: CM-31-003
CM-31-004 MUST Remove(CaseParticipant)[target=VulnerabilityCase] is the Case Owner's request to the CASE_MANAGER to remove a participant from active participation. The CASE_MANAGER MUST refuse such a request in three cases. The sender does not hold CASE_OWNER, the named participant holds CASE_MANAGER or CASE_OWNER, or the request names no participant of the case.

The Case Owner decides who takes part in the case; the CASE_MANAGER applies case changes and decides none (ADR-0108). The CASE_MANAGER role is never unfilled (CM-24-006), and the Case Owner leaves only by transferring ownership (CM-21). A participant that wants to stop taking part sends Leave(VulnerabilityCase) (CM-23), not Remove.
Depends on: PCR-03-006
CM-31-022 MUST The CASE_MANAGER MUST report a removal of an already-removed participant as skipped.

A repeated removal changes nothing, so it is neither applied nor refused. A skipped result tells the Case Owner that the participant is already removed, and the CASE_MANAGER commits no second entry and sends no second notice.
Extends: CM-31-004
CM-31-023 MUST NOT The CASE_MANAGER MUST NOT remove a participant on its own initiative.

The Case Owner decides who takes part in the case; the CASE_MANAGER applies case changes and decides none (ADR-0108).
Extends: CM-31-004
CM-31-005 MUST The CASE_MANAGER MUST record an accepted removal by committing the Case Owner's received Remove(CaseParticipant) as its entry, on receipt (CLP-10-006).

The Remove states the change, so it is the change's entry (CLP-07-013), authored by the actor that made it. The ledger therefore shows the removal as the Case Owner's decision rather than the CASE_MANAGER acting alone (ADR-0108).
Depends on: CLP-10-006
CM-31-024 MUST Remove(CaseParticipant) MUST be a ledgered activity, with a canonical ledger signature.

Without the canonical signature a removal cannot be committed, so it never reaches a replica.
Extends: CM-31-005
CM-31-025 MUST NOT The CASE_MANAGER MUST NOT commit a second entry of its own for a removal that it records under CM-31-005.

The Case Owner's Remove is already the change's entry. A second entry would record the same move twice and show it as the CASE_MANAGER's own act.
Extends: CM-31-005
CM-31-006 MUST After a removal commits, the CASE_MANAGER MUST send the removed participant a direct Remove(CaseParticipant) naming it. The notice's actor is the CASE_MANAGER and its attributedTo the Case Owner (CM-24).

Learning of removal only from silence is indistinguishable from a network fault, and the removed party needs to know it is still bound and how it will hear of the embargo's end.
Depends on: CM-31-005
CM-31-026 MUST NOT The CASE_MANAGER MUST NOT ledger its CM-31-006 removal notice.

The notice is delivery, not a second record of the move. The Case Owner's Remove is already the move's entry (CM-31-005).
Extends: CM-31-006
CM-31-027 MUST The CASE_MANAGER MUST include the removed participant in the removal entry's fan-out.

The ledger entry keeps the removed party's replica continuous up to the removal, so that replica records its own removal like every other replica.
Extends: CM-31-006
CM-31-028 MUST The removal entry MUST be the last canonical entry that the CASE_MANAGER sends the removed participant, by fan-out or by replay, until it is reinstated.

A removed participant has lost its entitlement to case content. An entry sent after the removal would leak case content to it.
Extends: CM-31-006
CM-31-007 MUST A replica MUST apply a removal from its ledger entry, recording the same removal fact the CASE_MANAGER recorded.

Replicas take case state from the ledger. The entry is what changes every replica, the removed party's included.
Depends on: RSH-08-004
CM-31-029 MUST NOT A replica MUST NOT apply a removal from a directly received Remove(CaseParticipant) (RSH-08-003).

The direct notice tells the removed party what happened; it is not the change. A replica that applied it would take case state from outside the ledger.
Extends: CM-31-007
Refines: RSH-08-003
CM-31-008 MUST Removal MUST NOT change the participant's embargo consent rows. A removed signatory remains bound by the embargo it accepted.

Releasing a removed participant from the embargo would reward the conduct that got it removed. Consent is the participant's own statement (CM-18-005); removal is a separate fact.
Extends: CM-18-005
CM-31-009 MUST This rule covers each signatory that the ledger fan-out does not reach: one removed (CM-31-001), or one at RM CLOSED that the CM-23-004 fan-out skips. When the active embargo is terminated, the CASE_MANAGER MUST send each such signatory a direct Remove(EmbargoEvent). Termination includes the case becoming public.

A participant that stopped receiving case content while bound stays bound, and without this it never learns that the obligation ended early. A past-dated Announce(EmbargoEvent) is not used: its meaning would depend on the receiver's clock, and an early end is an event, not a date that passed.
Refines: CM-10-007
CM-31-030 MUST When a shorter revision replaces the active embargo, the CASE_MANAGER MUST send each signatory that CM-31-009 covers a direct Announce(EmbargoEvent) with the new terms. No notice is owed for a longer revision, or for expiry on the agreed date.

A participant bound by the embargo must learn that its obligation shortened, and a paused replica cannot learn it from the ledger. A longer revision does not bind a participant that never agreed to it, and expiry on the agreed date is already known to it. A revision that ends at the same time as the embargo it replaces is on the EP-05-003 carry-over arm, so it is sent like a shorter one.
Extends: CM-31-009
CM-31-031 MUST NOT The CASE_MANAGER MUST NOT put any other case content in a CM-31-009 or CM-31-030 notice.

The notice exists only to tell a bound participant that the embargo ended or shortened. The recipient has stopped receiving case content, so anything more would leak content it is not entitled to.
Extends: CM-31-009
CM-31-010 MUST This rule covers a participant whose ledger stream is paused: withheld under CM-10-005, removed (CM-31-001), or skipped at RM CLOSED (CM-23-004). It MUST apply a CM-31-009 or CM-31-030 notice that the CASE_MANAGER sends for the case.

A paused replica cannot take the change from the ledger, so the notice is its only channel.
Refines: RSH-08-003
CM-31-032 MUST A participant whose ledger stream is paused (CM-31-010) MUST ignore a CM-31-009 or CM-31-030 notice from any sender other than the CASE_MANAGER.

The exception is to the channel, not the authority: the notice still originates from the CASE_MANAGER (PCR-03-001).
Extends: CM-31-010
CM-31-033 MUST After a paused participant becomes active again, replaying the ledger entry that matches an applied CM-31-009 or CM-31-030 notice MUST change nothing further.

The notice already made the change, so the entry, which states the same change, must not apply it twice when the participant's ledger stream resumes.
Extends: CM-31-010
CM-31-011 MUST Add(CaseParticipant)[target=VulnerabilityCase] is the Case Owner's request to the CASE_MANAGER to reinstate a removed participant. The CASE_MANAGER MUST handle it through the CM-31-004 to CM-31-007 pipeline. That is Case Owner only, one ledger entry, a direct notice, and replicas applying it from the entry.

Owners make mistakes and disputes resolve, so removal is reversible. Reinstatement reuses the removal pipeline, so it is the Case Owner's decision, recorded once and reaching every replica the same way.
Depends on: CM-31-004
CM-31-034 MUST A reinstatement MUST clear the participant's removal fact.

The removal fact is what makes the participant inert (CM-31-017). Clearing it lets the active check count the participant again.
Extends: CM-31-011
CM-31-035 MUST NOT A reinstatement MUST NOT require the participant to accept again.

The participant never withdrew, so it does not rejoin; Leave remains open to it. A participant reinstated into a case whose embargo it has not accepted stays inert, and its CM-10-006 backfill waits for its consent.
Extends: CM-31-011
CM-31-036 MUST The CASE_MANAGER MUST refuse an Add(CaseParticipant)[target=VulnerabilityCase] naming a participant that is not removed or that never joined.

Joining requires accepting a stub Invite (ADR-0114), and Add must not become a way around that. Add means reinstatement only, so a participant that is not removed has nothing to reinstate.
Extends: CM-31-011
CM-31-012 MUST NOT The CASE_MANAGER MUST NOT emit Add(CaseParticipant), or commit an add_case_participant ledger entry, when an invitee accepts the stub Invite. The invitee's Accept (accept_invite_actor_to_case) is already the ledger entry for the consent row it signs and the joined mark.

CM-17-004 does not call for it, the Accept message is already the entry for its effects, and a replica usually refuses the direct message for want of the participant record it names. It also records one move twice, and Add(CaseParticipant) now means reinstatement only (CM-31-011).
Refines: CM-17-004
CM-31-041 MUST This rule covers a VENDOR-only invitee that accepts the stub Invite. The CASE_MANAGER MUST commit its VF Vf status entry (CM-11-009) after it announces the case to the invitee and backfills the prior ledger (#2898).

The Accept is not the entry for the VF move, so the CASE_MANAGER records it as its own add_participant_status_to_participant entry. Committing it after the announce and the backfill lets the invitee's replica apply it on top of the case it already holds.
Extends: CM-31-012
Refines: CM-11-009
CM-31-037 MUST A replica MUST apply the stub-Invite Accept entry's consent-row and joined writes as the CASE_MANAGER does, setting updated to the Accept's published as received.

The Accept is the one entry for both writes (CM-31-012), so a replica that applied them differently would hold a participant record that differs from the CASE_MANAGER's. Taking updated from the Accept as received keeps the record equal on every replica (ADR-0103, ADR-0124).
Extends: CM-31-012
CM-31-038 MUST NOT A replica seeded with the case MUST NOT move a record it holds backwards when it replays an older entry over the seed (ADR-0124). Moving backwards means clearing joined, or changing a consent row by a move that CM-18-003 does not allow.

A replica seeded with the case already holds a later state than an older entry describes. Replaying that entry must not undo what the seed holds, or the replica would fall behind the CASE_MANAGER.
Extends: CM-31-012
CM-31-039 MUST When a replica holds no record for the invitee, its apply of the stub-Invite Accept entry MUST fail.

The Accept entry changes an existing record; it does not create one. A replica that created a record from the Accept would hold a participant the CASE_MANAGER never seated.
Extends: CM-31-012
CM-31-013 MUST NOT The CASE_MANAGER MUST NOT send a removed participant a stub Invite, a full-case Invite or an embargo Invite. The CM-31-009 and CM-31-030 notices are its only exception.

Invites reach inert participants because they ask them to join or consent (CM-10-007). A removed participant was taken out by the Case Owner's decision; inviting it to vote on embargo revisions would undo that decision.
Refines: CM-10-007
CM-31-040 MUST When the Case Owner reinstates a participant that has not accepted the case's active embargo, the CASE_MANAGER MUST send it that embargo's Invite.

A reinstated participant that is not a signatory stays inert until it consents, so it must be asked.
Extends: CM-31-013
Depends on: CM-31-011

Case Split — Child Case Creation, Links, Inheritance, and Sibling Notices

How the parent case's Case Owner splits off a child case through the parent's CASE_MANAGER, what the child records and inherits, how its participants stay apart from the parent, and how the cases of one split family tell each other about embargo changes (ADR-0125, VP-10).

ID Priority Requirement Related
CM-32-001 MUST To split a case, the parent case's Case Owner MUST send Create(CaseProposal) to the parent's CASE_MANAGER, with the activity's context naming the parent case.

The parent's Owner decides what the parent's coordination becomes, as it does for an ownership transfer or a merge (ADR-0053, ADR-0105). The parent context is what tells a split apart from a report-born proposal, whose sender entitlement differs (HP-01-006).
Refines: CP-05-002
CM-32-017 MUST The CASE_MANAGER MUST refuse a split proposal whose sender does not hold CASE_OWNER on the parent case, creating no case and leaving the parent unchanged.

Only the parent's Owner may decide to split the parent (CM-32-001). A refusal that created a case, or changed the parent, would let any participant reshape a case it does not own.
Extends: CM-32-001
Refines: HP-01-006
CM-32-002 MUST The CASE_MANAGER MUST answer a split proposal (CP-05) to the Owner with Reject(CaseProposal), or with Accept(CaseProposal) naming the child in result and then Create(VulnerabilityCase).

Reusing the proposal answer gives the split the admission decision and the one-act creation the creation flow already has, rather than a second creation path to keep in step with it.
Depends on: CP-05-003
CM-32-018 MUST NOT The CASE_MANAGER MUST NOT create a second child case for a redelivered split proposal with the same proposal identifier (CP-05-013).

A split proposal is a Create(CaseProposal), so it takes the duplicate handling the creation flow already has. A second child for one request would split one family into two.
Extends: CM-32-002
Depends on: CP-05-006
CM-32-003 MUST The parent's CASE_MANAGER MUST create the child case in its own store.

The split creates a case like any other (CM-22-001, ADR-0041). Creating it in the store that already holds the parent keeps the whole family at one CASE_MANAGER (CM-32-019).
Refines: CM-22-001
CM-32-019 MUST The parent's CASE_MANAGER MUST be the CASE_MANAGER of each child case it creates.

The child has a CASE_MANAGER from creation (CM-24-006). Hosting the whole family at one CASE_MANAGER (CM-32-016) is what lets siblings be read from the parent (CM-32-006).
Extends: CM-32-003
CM-32-020 MUST The CASE_MANAGER MUST set the child case's attributed_to to the Case Owner that requested the split.

As in any case creation, the proposer owns the case it asked for (CM-22-001). The splitter is the Owner the child's ledger and roster start from.
Extends: CM-32-003
CM-32-021 MUST The CASE_MANAGER MUST derive the child case's genesis hash from the child's own ID, creation time and Owner (CLP-08-002), never from the parent's.

The child has a ledger of its own that shares no chain with the parent's. A genesis hash taken from the parent would tie the two chains together.
Extends: CM-32-003
Depends on: CLP-08-002
CM-32-004 MUST The CASE_MANAGER MUST create a child case with parent_cases holding exactly the parent case's ID, and with child_cases and sibling_cases empty.

The child must know the case its embargo came from, so its CASE_MANAGER can route VP-10 notices.
CM-32-022 MUST The CASE_MANAGER MUST record a split's case cross-references as case IDs, never as embedded cases.

Related cases are the exception ADR-0017 made to the full-object rule. An embedded parent would also carry the parent's content into the child (CM-32-009).
Extends: CM-32-004
CM-32-005 MUST After it commits the child case's creation entry, the CASE_MANAGER MUST add the child's ID to the parent's child_cases, committed as one parent ledger entry.

Ordering the parent entry after the child's creation means the parent never names a case that does not exist. One write per split keeps the links consistent.
Depends on: CLP-02-001
CM-32-023 MUST A replica of the parent case MUST take the child link from the parent ledger entry of CM-32-005.

A replica copies the case from the CASE_MANAGER's ledger and never writes the case itself (ADR-0124). A link a replica wrote on its own could name a child the CASE_MANAGER never committed.
Depends on: CM-32-005
CM-32-024 MUST NOT The CASE_MANAGER MUST NOT write sibling_cases on any case as part of a split.

No existing sibling changes when a new child appears, because siblings are read from the parent (CM-32-006). That leaves sibling_cases free for the merge spec (ADR-0105).
Extends: CM-32-005
CM-32-006 MUST The CASE_MANAGER MUST read a split family as a parent case with the cases its child_cases lists. A child's siblings are the other cases in its parent's child_cases. A case that is both a child and a parent belongs to both families. A notice it owes under CM-32-013 or CM-32-014 goes to every other member case of each.

The parent stays open after a split, so it is coordinated alongside its children and counts as their sibling for VP-10-002 and VP-10-003. Reading siblings from the parent needs no stored sibling list, because one CASE_MANAGER hosts the parent and every child (CM-32-019, CM-32-016).
Refines: VP-10-002
Refines: VP-10-003
CM-32-025 MUST NOT When it merges cases, the CASE_MANAGER MUST NOT record a merged case in any case's child_cases.

Only a split writes child_cases (CM-32-005), so a merged case never joins a split family. Keeping merges out of child_cases leaves the merge spec the other two link fields (ADR-0105).
Extends: CM-32-006
CM-32-007 MUST This rule covers a split whose parent case has an active embargo. The CASE_MANAGER MUST give the child a new EmbargoEvent, scoped to the child, with the active embargo's end time, active from creation.

VP-10-005 transfers the embargo; ADR-0125 limits the transfer to its terms. A new embargo object keeps consent per (participant, embargo) (ADR-0122): a child participant consents to the child's embargo, and its answer says nothing about the parent's.
Refines: VP-10-005
Supersedes: CM-14-010
CM-32-026 MUST When the CASE_MANAGER gives a child case a new embargo under CM-32-007, it MUST write the child Owner's consent row for that embargo as AGREED.

The Owner asked for the split and so agrees to the terms the child inherits. Its AGREED row is what makes it read as the new embargo's signatory (ADR-0122).
Extends: CM-32-007
CM-32-027 MUST NOT When it splits a case with an active embargo, the CASE_MANAGER MUST NOT carry the parent embargo's consent rows over to the child.

Consent is per (participant, embargo) (ADR-0122). A parent participant's answer to the parent's embargo says nothing about the child's.
Extends: CM-32-007
CM-32-028 MUST NOT When it splits a case with an active embargo, the CASE_MANAGER MUST NOT give the child the parent's open embargo proposals or revisions.

ADR-0125 limits the transfer to the active embargo's terms. A proposal still open in the parent belongs to the parent's negotiation.
Extends: CM-32-007
CM-32-008 MUST NOT When the parent case has no active embargo at the split, the CASE_MANAGER MUST NOT give the child an embargo.

The child inherits the parent's embargo state, not a default: a parent whose embargo has ended or was never agreed must not gain an embargoed child the parent's participants never agreed to.
Refines: VP-10-005
CM-32-029 MUST NOT When a split's parent case has no active embargo, the CASE_MANAGER MUST NOT run the creation-time default embargo initialization (CM-14-010, EP-04-012) for the child.

The default would give the child an embargo that CM-32-008 forbids. The parent's participants never agreed to an embargoed child.
Extends: CM-32-008
Supersedes: CM-14-010
CM-32-009 MUST NOT The CASE_MANAGER MUST NOT give a child case any of the parent's reports, notes, ledger entries, participants, case statuses, vulnerability records or case references.

The split exists to keep the child's participants apart from the parent's content. Creating the child empty and adding only what the splitter wrote makes isolation the default; copying and pruning would disclose whatever the pruning missed.
CM-32-030 MUST The CASE_MANAGER MUST begin a child case's ledger at the child's own genesis hash.

A child ledger that chained from a parent entry would carry the parent's history into the child (CM-32-009).
Extends: CM-32-009
CM-32-010 MUST The Case Owner MUST include in its split proposal a new VulnerabilityReport attributed to itself, and the stub summary for the child's stub Invites (CM-17-010).

Every case has a report (CM-05-002). A report the splitter writes for the child is the child's fresh description. The splitter, not the CASE_MANAGER, decides how much an invitee not yet bound by the embargo may learn from the stub (CM-17-010).
Depends on: CM-05-002
CM-32-031 MUST The CASE_MANAGER MUST make the split proposal's report the child case's only report.

The splitter's report is the child's description. Any other report would come from the parent (CM-32-009).
Extends: CM-32-010
CM-32-032 MUST The CASE_MANAGER MUST put the split proposal's stub summary in each stub Invite it sends for the child case.

The splitter chose what an invitee not yet bound by the embargo may learn (CM-17-010). A summary the CASE_MANAGER chose for itself could reveal more.
Extends: CM-32-010
Depends on: CM-17-010
CM-32-033 MUST The CASE_MANAGER MUST refuse a split proposal whose report is one of the parent case's reports, or that carries no stub summary.

Refusing a parent report keeps the parent's description out of the child (CM-32-009). With no stub summary, the child's stub Invites could carry nothing the splitter chose (CM-17-010).
Extends: CM-32-010
CM-32-011 MUST The CASE_MANAGER MUST initialize a child case by the sequence of CM-14-001, with CM-32-007 or CM-32-008 in place of its embargo step (CM-14-010).

A child is a case like any other, so it starts from the same sequence. Its embargo comes from the parent, not from the creation-time default.
Supersedes: CM-14-001
CM-32-034 MUST The CASE_MANAGER MUST skip the reporter steps of case initialization (CM-14-011, CM-14-012) when it initializes a child case.

The child has no outside reporter; the Owner wrote its description.
Extends: CM-32-011
Supersedes: CM-14-011
Supersedes: CM-14-012
CM-32-035 MUST The CASE_MANAGER MUST seat only itself and the Owner when it initializes a child case, the Owner holding CASE_OWNER at RM.RECEIVED (CM-14-009). These two are the participants seated by case initialization for CM-10-004.

Every other participant joins the child by accepting its stub Invite (ADR-0114), so it gets the child's content only once it is active (CM-10-004).
Extends: CM-32-011
Refines: CM-10-004
CM-32-036 MUST NOT The CASE_MANAGER MUST NOT give the splitter the Reporter role for authoring the child's report.

The splitter wrote the child's description as its Owner, not as an outside reporter. A Reporter role would claim a report the child never received from outside.
Extends: CM-32-011
CM-32-037 MUST NOT The CASE_MANAGER MUST NOT invite a participant to a child case before it commits the child's creation entry (CM-14-007).

An Invite sent before the creation entry would name a case the ledger does not yet hold, as for any case (CM-14-007).
Refines: CM-14-007
CM-32-012 MUST NOT The CASE_MANAGER MUST NOT add a participant to the parent case's roster because of a child-case activity.

Joining the child is joining the child only. A participant in both cases, such as the Owner, is a participant of each separately.
CM-32-038 MUST NOT The parent case's recipient selection (CM-10-007) MUST NOT return a participant of the child case that is not separately a participant of the parent.

The recipient selection is the one place that decides who gets case content (CM-10-007). A child participant it returned would get the parent's content.
Refines: CM-10-007
CM-32-039 MUST NOT The CASE_MANAGER MUST NOT send a parent ledger entry to a child-case participant that is not separately a participant of the parent.

Parent content reaches a participant in both cases only as a parent participant. Keeping the child apart from the parent's content is why the split exists.
Refines: CM-10-004
CM-32-013 MUST This rule covers a child case's CASE_MANAGER that commits an embargo proposal or revision ending earlier than the end time the child inherited. Before the proposal is activated, it MUST commit a split-family embargo notice to each other member case's ledger in the child's family (CM-32-006).

This is how VP-10-002 is carried out. An earlier end in one child can reveal the vulnerability in the products the others cover (VP-10, split discussion), so the other cases hear of it while it is still a proposal.
Implements: VP-10-002
CM-32-040 MUST A split-family embargo notice under CM-32-013 MUST carry the child's ID, the proposed end time and the proposer's rationale.

The other cases need to know which case wants the earlier end, how much earlier, and why, to weigh the risk to their own products.
Extends: CM-32-013
CM-32-041 MUST For a proposal under CM-32-013, the child's CASE_MANAGER MUST commit the proposer's rationale to the child's ledger.

The reason for the earlier end is then on record in the case that asked for it, as well as in the cases it was sent to.
Extends: CM-32-013
CM-32-042 MUST The rationale of a proposal under CM-32-013 is the proposing activity's content. The child's CASE_MANAGER MUST refuse such a proposal whose content is empty.

The activity's content carries the rationale because embargo proposals have no rationale field of their own. With no rationale, the notice would tell the other cases nothing about why.
Extends: CM-32-013
CM-32-014 MUST When the active embargo of any member of a split family changes — a revision is activated or the embargo is terminated — the family's CASE_MANAGER MUST commit a split-family embargo notice to the ledger of every other member case of the family (CM-32-006), carrying the changed member's ID, its new end time or the fact of termination, and the rationale when the change's proposal gave one.

This is how VP-10-003 is carried out. Participants address case messages only to their CASE_MANAGER (PCR-08-001), and every member case has the same CASE_MANAGER (CM-32-016), so a change agreed in one case reaches the others' participants through their own ledgers rather than from participant to participant.
Implements: VP-10-003
CM-32-015 MUST The CASE_MANAGER of a member case MUST be the only actor that commits a split-family embargo notice to that case's ledger.

Committing the notice to the receiving ledger delivers it through the shared recipient selection (CM-10-004). The participants of one case thus learn of another's change without ever being addressed by it.
Implements: VP-10-003
Refines: CM-10-004
CM-32-043 MUST NOT The CASE_MANAGER MUST NOT change the receiving case's EM state or any consent row when it commits a split-family embargo notice.

A notice informs and proposes nothing: a case's embargo changes only through its own negotiation (ADR-0125).
Extends: CM-32-015
CM-32-044 MUST NOT The CASE_MANAGER MUST NOT put content of the reported case in a split-family embargo notice beyond three items. These are that case's ID, its new end time or the fact of termination, and the rationale.

The participants of the receiving case may not be participants of the reported one. They learn of its change, and nothing else of that case.
Extends: CM-32-015
CM-32-016 MUST NOT While a case belongs to a split family, its CASE_MANAGER role MUST NOT be delegated to any actor but the CASE_MANAGER of the other members.

Siblings are read from the parent (CM-32-006), and notices are ledger commits by the one CASE_MANAGER hosting the family (CM-32-013, CM-32-014). A member moved to another CASE_MANAGER could neither find its siblings nor reach them. ADR-0125 leaves a family spread across CASE_MANAGERs to a later decision.
Extends: CM-24-006
CM-32-045 MUST The CASE_MANAGER MUST refuse a delegation that CM-32-016 forbids, leaving the case's CASE_MANAGER unchanged.

A refusal tells the requester that the delegation did not happen, rather than leaving it to find out from a family split across CASE_MANAGERs.
Extends: CM-32-016

Role Delegation Offers

How a role offer, Offer(CaseParticipantRole) (ADR-0039), and its answer travel through the CASE_MANAGER, which records both. The offer adds a role to an actor that is already a participant; the roles an Invite carries at joining are CM-17. The routing follows PCR-08 and the ownership-transfer model (CM-21).

ID Priority Requirement Related
CM-33-001 MUST A Case Owner offering a role MUST address Offer(CaseParticipantRole) to the case's CASE_MANAGER only, never to the target.

A participant sends case-scoped messages only to the CASE_MANAGER (PCR-08-001). An Offer that goes directly to the target bypasses the CASE_MANAGER. The CASE_MANAGER then cannot check the answer against a recorded Offer, and cannot ledger it.
Implements: PCR-08-001
CM-33-007 MUST When the CASE_MANAGER admits a role Offer, it MUST commit a CaseLedgerEntry for that Offer.

The recorded Offer is what the CASE_MANAGER checks the target's answer against (CM-33-005). An Offer it never ledgered could not be answered.
Extends: CM-33-001
CM-33-008 MUST After it commits an admitted role Offer, the CASE_MANAGER MUST forward the Offer to the target, with inReplyTo set to the recorded Offer's id. The forward's actor is the CASE_MANAGER and its attributed_to the Case Owner (CM-24-001, CM-24-002).

The target takes case messages from the CASE_MANAGER only (PCR-03-001), so it learns of the Offer from the forward. The inReplyTo lets the CASE_MANAGER resolve an answer to the forward back to the Offer it recorded (CM-33-005).
Extends: CM-33-001
Refines: CM-24-005
CM-33-009 MUST NOT The CASE_MANAGER MUST NOT grant an offered role when it receives the role Offer. It grants the role when it commits the Accept (CM-02-016).

An Offer is a proposal, not the grant. A role granted on the Offer would hold even if the target declined it.
Extends: CM-33-001
CM-33-002 MUST The CASE_MANAGER MUST refuse a received Offer(CaseParticipantRole) whose sender is not the case's Case Owner, committing no ledger entry and sending no forward.

A role grant decides who runs part of the case. That is the decision of the Case Owner, as ownership transfer is (CM-02-005).
Refines: HP-01-006
Refines: CM-02-005
CM-33-010 MAY Where its policy delegates role offers to it, a CASE_MANAGER MAY commit a role Offer on its own case and send it to the target. The checks of CM-33-003 apply to that Offer.

A Case Owner can delegate its decisions to the CASE_MANAGER in advance. The Offer is then the CASE_MANAGER's own, so it has no Case Owner's Offer to forward.
Extends: CM-33-002
CM-33-011 MAY An implementation MAY omit the CASE_MANAGER-originated role Offer path of CM-33-010.

The Case Owner path alone lets every case grant roles. Thus a path that starts at the CASE_MANAGER adds convenience, not capability, and an implementation can omit it.
Extends: CM-33-010
CM-33-003 MUST The CASE_MANAGER MUST refuse a role Offer in any of five cases. In two, the Offer carries no readable CVD role, or it offers CASE_OWNER or CASE_MANAGER. In three, its target is not a participant in the case, already holds the offered role, or is the CASE_MANAGER itself.

A grant only adds a role. Thus an accepted CASE_OWNER or CASE_MANAGER offer would give the case two holders of a role that it has exactly one of (CM-21-001, CM-02-001). Ownership moves by ownership transfer (CM-21). The CASE_MANAGER role moves only by delegation (CM-24-006), never by a role Offer, because it carries ledger authority. The CASE_MANAGER takes no other role by Offer. The forward of CM-33-008 would go to itself (BT-17-008), and the answer of CM-33-004 would also go to itself.
Constrains: CM-02-016
Refines: CM-02-001
CM-33-012 MUST NOT The CASE_MANAGER MUST NOT commit a ledger entry for a role Offer that it refuses under CM-33-003.

The ledger records what the case admitted. An entry for a refused Offer would read as an open Offer that the target could answer.
Extends: CM-33-003
CM-33-013 MUST NOT The CASE_MANAGER MUST NOT forward a role Offer that it refuses under CM-33-003.

A forward invites the target to answer. A forwarded refused Offer would ask the target about a grant the case will never make.
Extends: CM-33-003
CM-33-014 MUST NOT An actor handling a role Offer MUST NOT read a missing CVD role as any default role.

If a missing role were read as a default, the case would grant a role that nobody offered.
Extends: CM-33-003
CM-33-004 MUST An actor named as the target of a role Offer MUST refuse it, sending no answer, when the CASE_MANAGER did not send it.

A replica takes case changes from the CASE_MANAGER only (PCR-03-001). An Offer from any other actor was never recorded by the CASE_MANAGER, so an answer to it could not be ledgered.
Refines: PCR-03-001
Refines: HP-01-006
CM-33-015 MUST The target of a role Offer MUST address its Accept or Reject to the CASE_MANAGER only, never to the Case Owner that made the Offer.

The answer is a case-scoped message (PCR-08-001). An answer that goes only to the Case Owner is never ledgered.
Implements: PCR-08-001
Extends: CM-33-004
CM-33-005 MUST The CASE_MANAGER MUST act on an answer to a role Offer only when its sender is the target of an open Offer it recorded. An Offer is open until the ledger holds an Accept or Reject entry for it (CLP-11-001).

The answer decides if the role is granted. Thus only the actor that received the Offer can give the answer, and only one time. The check uses the Offer that the CASE_MANAGER recorded, never the copy in the answer. CM-11-017 and CM-21-011 do the same check.
Refines: HP-01-006
Depends on: CM-33-001
CM-33-016 MUST The CASE_MANAGER MUST refuse an answer that CM-33-005 does not let it act on, committing no ledger entry and changing no role.

An entry or a role change made for a refused answer would grant, or record as decided, an Offer its target never answered.
Extends: CM-33-005
CM-33-017 MUST The CASE_MANAGER MUST resolve an answer that names its forward to the recorded role Offer through the forward's inReplyTo.

The target answers the forward it received, which the CASE_MANAGER sent, not the Case Owner's Offer. Only the stored forward's inReplyTo links that answer to the recorded Offer.
Extends: CM-33-005
CM-33-018 MUST The CASE_MANAGER MUST report a redelivery of the answer that closed a role Offer as SKIPPED (HP-01-003).

A redelivery is the same answer again, not a new one. Reporting it REFUSED would tell the target its answer was declined after it was admitted.
Extends: CM-33-005
CM-33-006 MUST When the CASE_MANAGER admits an answer to a role Offer under CM-33-005, it MUST commit a CaseLedgerEntry for it (accept_case_participant_role or reject_case_participant_role).

A participant's answer to an Offer is a case event (CLP-02-012), so it is ledgered as all other case events are.
Implements: CLP-02-012
Implements: PCR-08-003
Depends on: CM-33-005
CM-33-019 MUST When the CASE_MANAGER commits an answer to a role Offer under CM-33-006, it MUST broadcast Announce(CaseLedgerEntry) to the case's participants.

Replicas learn of a granted role only when they replay the announced entry (CM-02-016, RSH-08-004). Thus if the answer is not announced, the roles in every replica are out of date.
Extends: CM-33-006
CM-33-020 MUST When the CASE_MANAGER commits an Accept of a role Offer, it MUST grant the role on its own replica, as CM-02-016 requires.

The CASE_MANAGER's replica is the one its later checks read, such as whether the target already holds a role (CM-33-003).
Extends: CM-33-006
CM-33-021 MUST NOT The CASE_MANAGER MUST NOT change any role when it commits a Reject of a role Offer.

A Reject declines the Offer, so the target's roles stay as they were.
Extends: CM-33-006

CaseProposal Protocol

Requirements for the CaseProposal protocol flow, which separates the act of requesting case initialization from the act of creating the case. Covers the wire vocabulary type, the Create/Accept/Reject message flow, and the corresponding MessageSemantics values and received-side use cases.

Wire Vocabulary

ID Priority Requirement Related
CP-01-006 MAY A CaseProposal MAY include an optional summary field with a human-readable description of the proposal.

A person who reviews proposals at the Case Actor Service may need to know why the report receiver asks for a case. A short description gives that context. The field is optional because the proposal is complete without it.
CP-01-009 MAY The actor of Create(CaseProposal) MAY be the proposing actor's full actor profile inline, or a reference to that profile which the CASE_MANAGER dereferences by an API call (AKM-05-003, AKM-05-004).

The proposing actor becomes the CASE_OWNER (CP-09-001), and its published embargo policy lives on its profile (EP-01-001), in its own store, which the CASE_MANAGER cannot read (PCR-01-003). That policy is the CASE_OWNER's actor default at case creation (EP-04-003), so it has to reach case creation; whether the profile travels inline or is fetched is an implementation choice. This codebase sends the inline form and accepts either (CP-01-010).
Depends on: EP-04-003
Depends on: EP-01-001
Depends on: AKM-05-003
CP-01-011 MUST A CaseProposal MUST carry, as proposerCaseRoles, the CVD roles the proposing actor holds in a case it owns. An absent or empty proposerCaseRoles states no role beyond CASE_OWNER.

The CASE_MANAGER seats the proposer with these roles (CP-09-007) and has no other source for them: it may run in a different container, and it cannot read another actor's configuration or store (ADR-0073). Carrying them follows the rule CP-01-010 applies to the proposer's embargo policy.
Refines: CP-01-001
CP-01-012 MUST A Create(CaseProposal) whose proposerCaseRoles names CASE_MANAGER MUST be refused before anything is written, with a reason naming the fault.

Only the CaseActor holds CASE_MANAGER. A proposer that could state it would seat itself as the manager of the case it proposes.
Extends: CP-01-011

Protocol Flow — Sending

ID Priority Requirement Related
CP-04-001 MUST A report receiver that wishes to request case initialization at a dedicated case-actor service MUST send Create(CaseProposal) to the case-actor service's inbox, with actor set to the receiver's own URI.

Using Create with the report receiver as actor accurately declares that the receiver created the proposal object, not the case itself. Sending Create(VulnerabilityCase) from the receiver would violate AS2 semantics because the receiver is not the authoritative creator of the case.
Implements: CP-01-001
CP-04-003 MUST A report receiver MUST address Create(CaseProposal) to the case-actor service's own actor identity, never to an identity derived per case or per report. That identity exists before the report does.

A derived identity names an actor that no container hosts. The sender computes it, so no container has registered it, and delivery to it fails with a permanent 404. It also cannot be set up in advance, because the sender can compute it only after the report exists, and by then the proposal is already sent. A container identity comes from configuration, so the container can register it once, when it is seeded. The proposal loses nothing: it carries its report (CP-01-004), and every later case-scoped message carries activity.context.
Refines: CP-04-001
Refines: BT-10-008

Protocol Flow — Receiving (Case-Actor Side)

ID Priority Requirement Related
CP-05-002 MUST The case-actor service MUST evaluate each Create(CaseProposal) it receives and either accept or reject the proposal, emitting an Accept(CaseProposal) or Reject(CaseProposal) activity accordingly.

A report receiver that sends a proposal treats the case as absent until the CASE_MANAGER's Create(VulnerabilityCase) arrives (PCR-01-003), and it holds the proposal open until an answer comes (CP-06-003). If the CASE_MANAGER stays silent, the receiver waits until its deadline (CP-05-007). Each proposal needs a decision because the CASE_MANAGER can refuse one, for example a proposal for a fully closed case (CP-05-008).
CP-05-003 MUST When accepting a proposal, the case-actor service MUST send two activities in sequence: first Accept(CaseProposal) (with the CaseProposal, inline or by reference, as object), then Create(VulnerabilityCase) (with actor set to the case-actor URI, context set to the URI of the new VulnerabilityCase, and inReplyTo set to the URI of the Accept(CaseProposal) activity).

Accept(CaseProposal) acknowledges the report receiver's proposal. Create(VulnerabilityCase) with actor=case-actor preserves AS2 "I created this" semantics: only the authoritative creator MUST be the actor of a Create activity. context MUST be the case URI (consistent with all other case-scoped activities; required for inbox deferral routing). in_reply_to carries the causal antecedent (the Accept URI) in the AS2-correct field for responses. See ADR-0045.
CP-05-004 MUST When rejecting a proposal, the case-actor service MUST send Reject(CaseProposal) with the CaseProposal, inline or by reference, as object.

The Reject is how the report receiver learns that no case will be created for its proposal, so it can stop waiting (CP-06-004). The object names which proposal the answer is for, because one receiver can have more than one proposal open.
CP-05-005 MUST The case-actor service MUST queue Accept(CaseProposal) and Create(VulnerabilityCase) for an accepted proposal together or not at all, never sending the Accept without the Create.

The two sends are not atomic on the network, so they are made atomic where they are queued: an Accept with no Create behind it tells the report receiver of a case nothing announces. Once both are queued, the outbox retries each one, so a failed Create needs no retry of its own. A second Accept reads as a second, independent decision.
CP-05-011 MUST The case-actor service MUST retry each queued Accept(CaseProposal) and Create(VulnerabilityCase) until it is delivered or its delivery attempts are exhausted (OX-13).

The two sends are made atomic where they are queued, not on the network. Once both are queued, the outbox retries each one, so a failed Create needs no retry of its own and still reaches the report receiver.
Extends: CP-05-005
CP-05-012 MUST On a retry or re-send, the case-actor service MUST send the same Accept(CaseProposal) under its original identifier, never a second one (CP-05-006).

The Accept is irrevocable once sent. A second Accept under a new identifier reads as a second, independent decision, which the report receiver cannot tell from the first.
Extends: CP-05-005
CP-05-006 MUST For a duplicate Create(CaseProposal) of a proposal it has already accepted, the CASE_MANAGER MUST re-send the stored Accept(CaseProposal) unchanged, with its original identifier. The one exception is a fully closed case (CP-05-014). A duplicate carries the same proposal identifier.

A duplicate proposal is the only signal the CASE_MANAGER gets that its acceptance was lost, so the answer to it is how the proposer recovers. The stored original, sent again unchanged (VM-08-003), reads as the first acceptance arriving late. A new Accept reads as a second, independent decision, which CP-05-005's irrevocability rule forbids.
Refines: ASK-08-001
Depends on: CM-23-013
CP-05-013 MUST NOT The CASE_MANAGER MUST NOT create a second VulnerabilityCase for a duplicate Create(CaseProposal) of a proposal it has already accepted.

A duplicate is a re-delivery of an ask already answered, not a new ask. A second case for it would split the one proposal's coordination across two cases.
Extends: CP-05-006
CP-05-014 MUST This rule covers a duplicate of an accepted proposal whose case is fully closed, with the CASE_MANAGER's own RM.CLOSED status entry in its ledger (CM-23-013). The CASE_MANAGER MUST answer that duplicate with Reject(CaseProposal).

The stored Accept would reach the accept path, which writes to the case, and a fully closed case accepts no writes (CM-23-013). The Reject declines only the re-delivery: it does not revoke the stored Accept (CP-05-005).
Extends: CP-05-006
Depends on: CM-23-013
CP-05-015 MUST NOT For a duplicate under CP-05-014, the CASE_MANAGER MUST NOT commit any CaseLedgerEntry for the closed case or change its state (CM-23-013).

A fully closed case accepts no further writes (CM-23-013, ADR-0085). The decline is recorded on the proposal, so declining needs no write to the case.
Extends: CP-05-014
Depends on: CM-23-013
CP-05-007 MUST A report receiver that has sent Create(CaseProposal) and has no reply by the proposal's deadline MUST stop waiting and treat the proposal as expired. A reply is an Accept(CaseProposal) or a Reject(CaseProposal).

Without a deadline, a lost Accept stops the handshake for good, because no party waits with a timeout. The proposal is an ask (ASK-01-001), so it carries a deadline (ASK-03-004) and the report receiver records it with its other outstanding asks (ASK-04-001). That record belongs to the actor, so it works before a case exists. To propose again is a new decision, not an automatic retry (ASK-05-005), and a repeated proposal is also how the CASE_MANAGER gets a chance to answer again (CP-05-006).
Depends on: ASK-05-001
CP-05-008 MUST The case-actor service MUST subject a Create(CaseProposal) under a new proposal identifier to the admission decision (CP-05-002), even when its report already has a case. That decision begins with the closed-case decline of CP-05-018.

The report identifier is chosen by the sender. A reuse keyed on the report alone lets a proposer join another party's case by naming its report, and collapses the one-case-per-recipient rule (CBT-06-002) into a shared case. The proposer is the second half of the key that makes the reuse safe: the same proposer re-proposing after its first proposal expired (CP-05-007) converges on its own case, and a different proposer gets its own.
Refines: CP-05-002
Depends on: CM-23-013
CP-05-016 MUST The case-actor service MUST answer each Create(CaseProposal) under a new identifier with its own Accept(CaseProposal) or Reject(CaseProposal), even if its report has a case.

A new proposal identifier is a new request the proposer is tracking on its own (CP-05-007). The stored answer to an earlier proposal does not answer it.
Extends: CP-05-008
CP-05-017 MUST This rule covers a new proposal whose proposer owns the report's existing case. The case-actor service MUST reuse that case, never creating a second VulnerabilityCase. The one exception is a fully closed case (CP-05-018).

The same proposer re-proposing after its first proposal expired (CP-05-007) converges on its own case. The proposer is the second half of the key that makes the reuse safe.
Extends: CP-05-008
CP-05-018 MUST This rule covers a new proposal whose proposer owns the report's existing case, when that case is fully closed. A case is fully closed when its ledger holds the CASE_MANAGER's own RM.CLOSED status entry. The case-actor service MUST answer the proposal with Reject(CaseProposal), deciding so before any other step of the admission decision.

A fully closed case accepts no further writes (CM-23-013, ADR-0085), so the owner's new proposal is declined rather than answered from the closed case. The closure is part of the admission decision and comes first, because no answer from the rest of the decision can change the Reject (ADR-0131).
Extends: CP-05-008
Depends on: CM-23-013
CP-05-019 MUST NOT For a new proposal declined under CP-05-018, the case-actor service MUST NOT commit any CaseLedgerEntry for the closed case or change its state (CM-23-013).

The decline is recorded on the proposal, not the case, so writing it is neither a CaseLedgerEntry nor a change to case or participant state. CM-23-013 bars every write to a fully closed case.
Extends: CP-05-018
Depends on: CM-23-013
CP-05-020 MUST This rule covers a new proposal whose proposer does not own the report's existing case. The case-actor service MUST create a separate case for it, never adding the proposer to the existing case.

The report identifier is chosen by the sender. A reuse keyed on the report alone lets a proposer join another party's case by naming its report, and collapses the one-case-per-recipient rule (CBT-06-002) into a shared case.
Extends: CP-05-008
CP-05-010 MUST The CASE_MANAGER MUST act on a Create(CaseProposal) under a proposal identifier it has already recorded only when the sender is that identifier's recorded proposer. Acting on it means admitting, declining or storing the proposal. A proposal identifier it has not recorded is a new proposal, which any actor may send (CP-05-002).

The sender chooses the proposal identifier, and every record the CASE_MANAGER keys on a proposal uses that identifier alone. If any sender could reuse an admitted identifier, the CASE_MANAGER would treat the delivery as a redelivery: the other actor could get a stray case, or a closed case's decline could be recorded under the first proposer's identifier.
Refines: HP-01-006
Refines: CP-05-006
CP-05-022 MUST The CASE_MANAGER MUST refuse without effect a Create(CaseProposal) that reuses a recorded proposal identifier from any sender but its recorded proposer. Without effect means no case created, no proposal-keyed record written and nothing sent.

If the refusal left any effect, another actor could get a stray case, or record a closed case's decline under the first proposer's identifier, by reusing that identifier.
Extends: CP-05-010

Protocol Flow — Receiving (Report-Receiver Side)

ID Priority Requirement Related
CP-06-005 MUST The report receiver MUST act on Accept(CaseProposal) or Reject(CaseProposal) only when the sender is the actor it addressed the CaseProposal to (CP-06-006).

The accept handler records the replying actor as the report's CASE_MANAGER, and routing and Announce trust both read that record. If the report receiver acted on a reply from any sender, an actor that knows a report id could redirect the receiver's case traffic to itself.
Refines: HP-01-006
CP-06-006 MUST The report receiver MUST record the actor it addressed each CaseProposal to.

The sender check of CP-06-005 compares each reply's sender with this record. A receiver that kept no record could only trust what the reply claims about itself.
Extends: CP-06-005
CP-06-007 MUST NOT Once a case is established for the report, the report receiver MUST NOT let a reply to the proposal replace the trusted CASE_MANAGER.

Routing and Announce trust read the trusted CASE_MANAGER from the report's case link. A late reply that could rewrite it, even from the addressee, would move trust away from the actor that created the case.
Extends: CP-06-005
CP-06-008 MUST The report receiver MUST refuse a reply to its proposal from any other sender, leaving the report's case link unchanged.

An actor that knows a report id could otherwise redirect the receiver's case traffic to itself. Leaving the link unchanged keeps a refused reply from having any effect.
Extends: CP-06-005

CASE_MANAGER-Authoritative Initialization on Accept(CaseProposal)

Requirements for the CASE_MANAGER to perform all case initialization natively when accepting a CaseProposal, per ADR-0041. These replace the receiver-side case-creation and prologue back-fill that ADR-0015 introduced.

ID Priority Requirement Related
CP-09-001 MUST When the CASE_MANAGER accepts a Create(CaseProposal), it MUST create the VulnerabilityCase with attributedTo set to the proposing actor, never to its own actor ID. The proposing actor is the report receiver, who becomes the CASE_OWNER.

VulnerabilityCase.attributed_to records the Case Owner (CM-02-008), and ownership checks and ownership transfer read and write it as such (MSM-07-027, CM-21-024). AS2's "I created this" is carried by the actor of Create(X) (ADR-0023), not by the created object's attributedTo, so the CASE_MANAGER creating the case and the report receiver owning it are both recorded without conflict (ADR-0041).
Refines: CP-05-003
Refines: CM-02-008
CP-09-011 MUST When the CASE_MANAGER accepts a Create(CaseProposal), it MUST itself be the actor of the Create(VulnerabilityCase) it emits.

AS2's "I created this" is carried by the actor of Create(X) (ADR-0023). The CASE_MANAGER creates the case, so it is the actor, while the created object's attributedTo names the owner (ADR-0041).
Extends: CP-09-001
CP-09-002 MUST When the CASE_MANAGER creates the VulnerabilityCase on proposal acceptance, it MUST add the report receiver as a CASE_OWNER participant at RM.RECEIVED and the reporter as a participant at RM.ACCEPTED to the case.

The receiver's replica is seeded from the embedded participant objects in Create(VulnerabilityCase). If participants are absent at emission time the replica starts with no participant records and downstream operations that require them (e.g. embargo consent, role guards) fail. Hard-coding CVDRole.VENDOR for the receiver additionally mislabels coordinators, which makes VFD fix-lifecycle checks demand a fix they never produce.
Refines: CP-09-001
CP-09-007 MUST The CASE_MANAGER MUST seat the report receiver with only the additional CVD roles its proposal states in proposerCaseRoles (CP-01-011), never roles from its own configuration.

Only the report receiver knows the roles it holds; a report receiver may be a coordinator rather than a vendor, so role inference is unsound. The CASE_MANAGER is a different actor and may run elsewhere, so nothing it holds of its own says anything about the receiver (ADR-0133).
Refines: CP-09-002
Depends on: CP-01-011
CP-09-008 MUST NOT Additional CVD roles for the report receiver MUST NOT be inferred.

Inferring roles (e.g. hard-coding CVDRole.VENDOR) mislabels coordinators and causes VFD fix-lifecycle checks to demand a fix they never produce.
Refines: CP-09-002
CP-09-003 MUST The CASE_MANAGER MUST initialize the default embargo for the new case before emitting Create(VulnerabilityCase).

Embargo state is protocol-significant. Deferring initialization to the report receiver or to a later message would produce a window in which embargo-gated operations are undefined (ADR-0041).
Refines: CP-09-001
CP-09-004 MUST The Create(VulnerabilityCase) activity emitted by the CASE_MANAGER MUST carry the case's participant records inline, so that the report receiver can seed its replica's participants (CP-09-002).

The report receiver seeds its Participant Case Replica from the Create(VulnerabilityCase), and it cannot read the CASE_MANAGER's store (PCR-01-003). Without the participant records, the replica starts with no participants, and checks that need them, such as role checks and embargo consent, fail. It seeds from this Create before it trusts the CASE_MANAGER, so it has no trusted source to fetch a record from (CBT-01-007, ADR-0170).
Refines: CP-09-002
CP-09-009 MUST When the CASE_MANAGER accepts a Create(CaseProposal), it MUST enqueue Accept(CaseProposal) and Create(VulnerabilityCase) before it fans out any Announce(CaseLedgerEntry) for the new case. The canonical ledger entries that record the initialization are committed after the Create(VulnerabilityCase) is queued, so that every recipient holds the case object before its first ledger entry arrives.

The outbox is FIFO (OX-01-002), so the order of the queue is the order on the wire. If a ledger entry is committed before the Create is queued, its Announce reaches each recipient before the case does. Each recipient then holds an entry for a case it does not have and takes the SYNC-15 path for such entries (hold the entry, Reject, replay from the genesis entry), while the Create waits behind the whole fan-out. That path, meant for recovery, would then run for every case creation and delay the Create. This is CM-14-011's rule applied to the CASE_MANAGER path: the receiver must hold the case object before any fan-out that references it. It is also SYNC-09-002's order: the entries record a case whose creation was sent, and only the Announce(CaseLedgerEntry) fan-out follows the commit. The create-case trigger uses the same order. On both paths, the case, its records, the queued Accept and Create, and every creation entry are written in one DataLayer transaction (ADR-0146). If any creation entry fails, all of them roll back before delivery is told of either activity (DL-02-004, CLP-07-013). If the Create were held back until after the commit, the genesis entry's fan-out would go ahead of it, which is the path this rule prevents.
Refines: CP-05-003
Depends on: CM-14-011
Depends on: OX-01-002

Participant Role Management

Defines the API for reading and mutating CVD roles on CaseParticipant, the one participant class shared by the core domain and the wire layer (ADR-0099). Requires the role-management methods, prohibits direct attribute mutation after construction, and mandates test coverage for each method.

Assertion Authority and On-Behalf Exceptions

ID Priority Requirement Related
PRM-06-001 MUST Participant status is self-declaratory by default: each participant asserts its own RM and VFD state without external approval. This is why they are participant status items (ADR-0121). The one write about a participant that the participant does not make is its birth: the CASE_MANAGER creates the record at RM RECEIVED (and VF vf for a vendor) when it sends the stub Invite (CM-11-006, ADR-0114).

The self-declaratory model keeps state authority with the role holder. Deviations must be narrow, externally evidenced, and explicitly named (ADR-0121).
PRM-06-002 MUST NOT A participant holding the VENDOR role MUST NOT self-report a vendor-fix state of vf (vendor unaware). A vendor's valid self-reports are Vf and VF. The only vf a vendor participant carries is the one the CASE_MANAGER writes when the stub Invite creates its record (CM-11-006); any reply to that Invite sets V (CM-11-009) (ADR-0121, ADR-0114).

A vendor that has answered its Invite, or otherwise acknowledged a message about the case, is aware of it. Allowing a vendor to self-report the vendor-unaware state would create an impossible protocol record.
PRM-06-003 MAY A CASE_MANAGER or Case Owner MAY assert v→V (Vf) on behalf of a Vendor-role participant when the notification or invite is evidenced. Any vendor acknowledgement, including Read(Invite(Actor)[target=VulnerabilityCaseStub]), is evidence. The target is typically an inert invitee that has not yet replied, and it must already be a participant (PRM-06-006).

Without this path, the case cannot record that an invited vendor is aware until the vendor replies to the Invite. A vendor acknowledgement, such as Read(Invite(Actor)[target=VulnerabilityCaseStub]), already shows that the vendor is aware. The stub Invite creates the participant record (ADR-0114), so the assertion has a target, and no new participant is necessary. RM stays the same, so the assertion does not speak for the report decisions of the vendor.
PRM-06-007 MUST NOT An on-behalf v→V write by a CASE_MANAGER or Case Owner MUST NOT change the target's RM state (ADR-0121).

RM records the vendor's own report decisions. The on-behalf assertion speaks only for the vendor's awareness, which its acknowledgement shows, so it leaves RM where the vendor put it.
Extends: PRM-06-003
PRM-06-004 MAY A CASE_MANAGER or Case Owner MAY assert d→D (D) on behalf of a Deployer-role participant, under the same externally-evidenced pattern as PRM-06-003. The target must already be a participant (PRM-06-006).

Usually the deployer reports its own deployment. But a deployer that joined and has since gone quiet can leave the case unable to record a deployment, although the case has evidence of it. A narrow, rare exception path covers this. The on-behalf limit keeps it from becoming a general proxy capability. A deployer that did not join is not in the case, so the case has nothing to record about it.
PRM-06-008 MUST The actor processing an on-behalf d→D assertion MUST apply the cross-machine entailments, so it succeeds only for a deployer whose RM state fits deployment.

The on-behalf path is an exception to self-declaration, not a way around the rules every status write obeys. Without the entailments, it could record a deployment for a deployer that never accepted the report.
Extends: PRM-06-004
PRM-06-006 MUST An actor processing an on-behalf status assertion MUST refuse it before any write if its target is not a case participant, creating no participant.

A status update is never a way into a case; joining is the Invite flow (CM-11, ADR-0114). If a status update creates the participant that it names, it is a second path into a case. That path skips the Invite flow and its consent and entitlement checks. If the record is created before the status write, and the write is then refused, a stray participant stays. For example, the RM-to-D entailment refuses d to D for a target that did not accept the report.
PRM-06-005 MUST NOT The f→F transition (fix ready, VF) MUST NOT be asserted by any actor other than the Vendor-role holder. Fix readiness is not externally knowable; no on-behalf assertion is ever permitted for this transition (ADR-0121).

Allowing a non-vendor to assert fix-readiness would let the CASE_MANAGER fabricate protocol-visible fix state, undermining the meaning of the F event.

Protocol Asks and Outstanding-Ask Registers

Requirements for the protocol-ask primitive: how an actor requests something it lacks authority to do, how outstanding asks are recorded and enumerated in both directions, how asks expire, and how a receiver reports that it could not process an inbound message. Derived from ADR-0080 and the G01 planning session (CONCERN-2829).

Ask and Terminate

An ask is an ordinary protocol message that completes the asking behavior. Nothing suspends awaiting a reply.

ID Priority Requirement Related
ASK-01-001 MUST An actor that cannot perform an action on its own authority MUST request authorization with an ask activity addressed to the authorizing actor.


*Separates requesting authority from holding it, so the action cannot occur on the asker's own initiative.
*
ASK-01-005 MUST NOT An actor that asks for authorization of an action MUST NOT perform the action before it has recorded a reply that authorizes it.

An ask suspends nothing, so the action would otherwise run on the asker's own initiative while the answer is still pending.
Extends: ASK-01-001
ASK-01-004 MUST An actor MUST determine the action that a reply authorizes from the ask the reply answers, not from the content of the reply.

*Generalizes ADR-0026's trust rule; reading the action from the reply lets the answerer unilaterally alter what it authorizes.
*

Conversation-State Routing

A gated interaction is one tree that determines where the conversation stands before taking any action.

ID Priority Requirement Related
ASK-02-003 MUST When an ask for the same kind and subject is already outstanding and unexpired, the actor MUST NOT emit a second ask.


*Re-asking an open question produces duplicate decisions for the answerer and duplicate register entries for the asker.
*
ASK-02-004 MUST NOT An actor MUST NOT decide whether a gated action is authorized from its record of outstanding asks, but only from the case ledger.

*The register is a working set that may be rebuilt or corrupted; making it authoritative for authorization would let a forged entry permit an action.
*
ASK-02-005 MUST While an ask for authorization (ASK-01-001) is outstanding, the asking actor MUST leave the case in a state the protocol defines.

*An ask suspends nothing, so the case sits in the pre-authorization state for the whole deadline; that state must be legitimate.
*
ASK-02-006 MUST An actor MUST refuse, not ask about, an action whose not-yet-authorized state the protocol cannot represent.

Asking would leave the case in an undefined state for the whole ask deadline (ASK-02-005), so such an action can only be refused.
Derives from: ASK-02-005

Ask Kinds

Each kind of ask declares the replies that close it, its expiry consequence, and a default deadline.

ID Priority Requirement Related
ASK-03-001 MUST Each ask kind MUST declare the set of reply semantic types that close it.


*Pairing a reply to an ask requires a declared closing set rather than hard-coded reply names at call sites; ProtocolPair already carries this field.
*
ASK-03-002 MUST Each ask kind MUST declare whether a reply arriving after the ask's deadline authorizes the action (stale) or does not (void).


*Admitting a participant remains useful when authorized late; adopting a case status does not, because the case state the decision was made against has moved on.
*
ASK-03-003 MUST NOT The expiry consequence of an ask kind MUST NOT be deployment-configurable.

*Two actors disagreeing about whether a late reply authorized an action is a divergence in canonical case state, not a deployment preference.
*
ASK-03-009 MUST NOT An ask activity MUST NOT carry the expiry consequence of its ask kind.

Letting the asker declare the expiry consequence would let it extend its own authorization, and two actors could then disagree about a late reply.
Extends: ASK-03-003
ASK-03-004 MUST An ask activity MUST carry its own deadline in the AS2 endTime field.


*Placing the deadline on the wire means the answerer reads the same value as the asker, so a configurable duration cannot cause disagreement.
*
ASK-03-006 MUST NOT An action gated by an ask whose kind declares void expiry MUST NOT be performed on the strength of a reply recorded after the ask's deadline.


*An authorization granted against a case state that no longer holds is a standing permission, which is the hazard ADR-0026's trust rule addresses in the identity dimension.
*
ASK-03-007 MUST Invite(EmbargoEvent) is an ask kind. Its closing replies are Accept(Invite(EmbargoEvent)) and Reject(Invite(EmbargoEvent)) from the invitee; its deadline is the Invite's endTime (CM-28-001, CM-28-012); its expiry consequence is stale, not void: a reply after the deadline is still honoured under EMB-17 once the expiry has been recorded as TIMED_OUT (CM-28-004).


*The embargo invitation already has every property an ask kind declares, and the relay (EP-09) emits one per participant per proposal, so the outstanding-ask register must track it without a special case. Declaring the kind here keeps find_protocol_pair's one-open-request assumption from being applied to N simultaneous Invites of one semantic type.
*
ASK-03-008 MUST Invite(Actor)[target=VulnerabilityCaseStub] (the stub Invite) is an ask kind. Its closing replies are Accept(Invite(Actor)[target=VulnerabilityCaseStub]) and Reject(Invite(Actor)[target=VulnerabilityCaseStub]) from the invitee; its deadline is the Invite's endTime (CM-11-014); its expiry consequence is stale, not void: expiry only means the CASE_MANAGER stops waiting, so a reply that arrives after the deadline is processed like a live one (an Accept joins the invitee, a Reject closes the record). Expiry closes the ask only; it writes no participant state and refuses no later reply (CM-11-014).


*The hazard a late Accept could carry is joining on stale embargo terms, and the supersede rule already covers it: an embargo change or a re-invite replaces the stub, naming the old one in inReplyTo, and an Accept of a superseded stub is refused (CM-11-045). Voiding a late reply would add no protection and would turn away an invitee that answered after the CASE_MANAGER stopped waiting.
*

Outstanding-Ask Register

A durable, actor-scoped record of asks outstanding in both directions.

ID Priority Requirement Related
ASK-04-010 MUST This rule covers the CASE_MANAGER relaying an ask on a participant's behalf (CM-24-001, CM-24-002; EP-09-002). It MUST record each relayed ask in its own outstanding-ask register as the asker.

*ASK-04-001 says the emitter records; a relayed Invite's actor is the CASE_MANAGER with the proposer in attributedTo, so the manager is the emitter and the one that owes the bookkeeping.
*
ASK-04-013 MUST NOT A participant whose proposal the CASE_MANAGER relays MUST NOT record the relayed Invites in its outstanding-ask register. It records only its own proposal to the CASE_MANAGER.

The participant did not emit the relayed Invites (ASK-04-001). Giving it a register entry for each one would let it reap or re-ask on the manager's behalf.
Extends: ASK-04-010

Expiry and Reaping

How an expired ask is noticed, given that elapsed time is not an event.

ID Priority Requirement Related
ASK-05-004 MUST NOT Reaping an expired ask MUST NOT re-emit it automatically.

*Mirrors CLP-06-005 — a timeout is a signal, not an instruction to retry.
*
ASK-05-005 MUST After an ask expires, the actor MUST re-ask only when it decides anew to perform the gated action.

The tree owns the decision because only it knows whether the action is still wanted.
Extends: ASK-05-004

Ask Visibility

Asks replicate like any other canonical entry, which constrains what they may carry.

ID Priority Requirement Related
ASK-06-001 MUST A case-scoped ask MUST be recorded as a canonical CaseLedgerEntry.

*An outstanding decision, and a stalled one, is protocol-relevant to every participant.
*
ASK-06-003 MUST The ledger entry of a case-scoped ask MUST replicate to case participants on the normal fan-out path.

Every participant needs to see an outstanding decision. A directed-delivery variant would require redaction with proofs, which does not exist.
Extends: ASK-06-001
ASK-06-002 MUST NOT An ask MUST NOT carry content that cannot be disclosed to every participant of its case.


*Asks replicate to all participants (ASK-06-001), so private rationale must travel by a directed Note instead.
*

Processing Faults

How a receiver tells an authenticated sender that it could not process what was sent.

ID Priority Requirement Related
ASK-07-001 MUST An actor that cannot process an inbound activity from an authenticated sender MUST notify that sender by emitting Create(ProcessingFault).


*Without a notice, a failure that took milliseconds costs the sender its full ask deadline in silence.
*
ASK-07-002 MUST NOT An actor MUST NOT emit a processing fault in response to an inbound activity whose sender is not authenticated, or whose failure occurred before authentication.


*Explaining a parse or validation failure to an unauthenticated sender is a parser oracle, and an unauthenticated sender identity cannot be trusted as a reply address.
*
ASK-07-003 MUST ProcessingFault MUST be a single dedicated object type carrying a machine-readable failure class, rather than a reason field on an existing reply activity.


*SE-08-003 prefers a dedicated object type over field-level discrimination; Reject additionally presupposes a rejectable object, which unreadable traffic does not provide.
*
ASK-07-004 MUST ProcessingFault MUST identify the activity that failed by reference.

*A reference names the failed activity without the receiver having to rebuild or echo it.
*
ASK-07-012 MUST NOT ProcessingFault MUST NOT require an inline copy of the activity that failed.

When a payload fails validation, a typed copy of it cannot be constructed — constructing it is what failed — and echoing sender-supplied content is a reflection hazard.
Extends: ASK-07-004
ASK-07-005 MUST Each failure class of ProcessingFault MUST be identified by a URI minted in the Vultron namespace.

*A URI-identified, dereferenceable problem type is extensible without a schema change and lets a receiver that does not recognise a class resolve its meaning.
*
ASK-07-013 MUST A ProcessingFault MUST be structured according to RFC 9457 Problem Details.

RFC 9457 is the standard for machine-readable error reporting over HTTP.
Extends: ASK-07-005
ASK-07-006 MUST NOT ProcessingFault MUST NOT carry implementation diagnostics such as stack traces, parser internals, or the identity of the failing code path.


*Faults replicate to all case participants and are also read by an untrusted-by-default peer; the diagnosis of a failure belongs in the actor's own log, governed by specs/structured-logging.yaml.
*
ASK-07-007 MUST Receiving a processing fault that refers to an outstanding ask MUST close that ask's register entry.


*The exchange cannot complete, so leaving the entry open costs the asker its whole deadline for a failure already reported.
*
ASK-07-008 MUST A processing fault that can be attributed to a case MUST be recorded as a canonical CaseLedgerEntry, unless it reports a failure to apply a CaseLedgerEntry, which SYNC-16-002 keeps out of the ledger.


*The statement that a message arrived and could not be processed is true, legible protocol history, and it explains why a subsequent retransmission occurred; it is not per-actor observability content, so CLP-07-004 does not exclude it. A fault about applying a ledger entry is replication signalling instead: ledgering it loops, because the replica that sent it cannot apply the entry that records it. Whether the remaining faults belong in the ledger at all is an open question (#4800).
*
ASK-07-009 MUST NOT The activity that could not be processed MUST NOT be recorded as a canonical CaseLedgerEntry.


*There is no legible assertion to record; a snapshot of an unreadable payload cannot satisfy CLP-02-003, and CLP-03-002 already excludes unrouteable inbound from the ledger.
*
ASK-07-010 MUST This rule covers a receiver that fails on its own internal error while it processes an inbound activity from an authenticated sender. The receiver MUST answer that sender with an internal-error Create(ProcessingFault). It sends the fault only after it has undone every change the failed processing made.

*A failed received act keeps only intake's archive (CLP-07-015), and the inbox answered before processing, so without this fault the sender waits out its ask deadline in silence. Emitting after the rollback keeps the fault from being undone with the act. ASK-07-008 decides whether the fault is ledgered, as for any fault.
* Refines: ASK-07-001
ASK-07-014 MUST NOT A receiver MUST NOT answer a failure that it will retry until it stops retrying that failure.

Answering only when no retry follows keeps a retry that later succeeds from sending a false fault that closes the ask early (ASK-07-007).
Extends: ASK-07-010
Depends on: IE-06-004
ASK-07-015 MUST NOT A repeated failure of the same activity MUST NOT produce a second fault. A fault sent again carries the identifier of the first.

One fault per failed activity answers the sender once, however often the activity fails.
Extends: ASK-07-010

Lost-Reply Recovery

What an answerer does when a request is repeated because its reply was lost.

ID Priority Requirement Related
ASK-08-001 MUST An actor that receives a duplicate request it has already replied to MUST re-send its stored reply unchanged, with its original identity, rather than producing a new reply.


*Re-sending the frozen original (VM-08-003) reads to the requester as the original arriving late rather than a second decision, which is what CP-05-012's irrevocability rule requires.
*
ASK-08-002 MUST NOT An actor that receives a duplicate request it has already replied to MUST NOT create a second instance of the object the request concerned.


*A repeated proposal after acceptance is a re-send, not a new request; treating it as new produces duplicate cases or duplicate participants.
*