Skip to content
Reference

Interactions and Lifecycle

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 sections 10 and 11 of the Vultron Protocol Specification: the cascade rules that couple the state machines, including the Case State (CS) dimensions, and the lifecycle of a participant within a case.

10. Model Interactions and Cascade Rules [N]

The five state machines are not independent. A transition in one produces obligations in others, and this section specifies those couplings.

Each coupling is a cascade: a state change raises a domain event, and the CASE_MANAGER handles that event by applying further changes. Every cascade step is authorized separately, and where the order of steps matters this section says so. The cascades below are the complete set that this specification requires.

Recall: which machine owns which state

Machine Ownership Written by
Report Management (RM) Participant-specific The participant itself
Vendor aware, Fix ready, fix Deployed (VFD) Participant-specific The participant itself
Embargo Management (EM) Shared case state The CASE_MANAGER only
Public aware, eXploit public, Attacks observed (PXA) Shared case state The CASE_MANAGER only
Participant Embargo Consent (PEC) Per-participant The CASE_MANAGER only

10.1 Admitting a Participant

When an actor accepts the stub Invite, the CASE_MANAGER admits it to the case and delivers the case content. The CASE_MANAGER created the actor's inert participant record, at RM state Received, when it sent the stub Invite (§11.2 Invitation and Acceptance). The steps are ordered because each one supplies a precondition for the next.

---
title: Admitting a participant
---
sequenceDiagram
    autonumber
    participant I as Invited actor
    participant CM as CASE_MANAGER
    participant P as Active participants

    I->>CM: Accept(Invite(Actor, VulnerabilityCaseStub))
    CM->>CM: Commit ledger entry for the acceptance
    CM->>P: Announce(CaseLedgerEntry)
    CM->>CM: Record effect: RM stays Received, embargo consent AGREED
    CM->>I: Announce(VulnerabilityCase) — full case content
    CM->>I: Prior ledger entries, oldest first
    CM->>I: Invite(Actor, VulnerabilityCase) — full-case Invite

On receiving Accept(Invite), the CASE_MANAGER MUST perform these four steps in this order (CM-17-004):

  1. Commit a ledger entry recording the acceptance, and send that entry to every active participant.
  2. Record the acceptance's effect on the participant: its RM state stays Received, and its consent row for the active embargo, if any, becomes AGREED, unless the participant had declined that embargo.
  3. When the participant is now active, send Announce(VulnerabilityCase) carrying the full case snapshot, then replay the ledger entries committed before this participant joined, oldest first.
  4. Send the full-case Invite, which asks the participant to judge the case.

Recall: report management states

State Meaning
Start No report has been received
Received A report has arrived; triage is not yet complete
Invalid This participant assessed the report as invalid
Valid This participant assessed the report as valid
Deferred This participant is deferring further action
Accepted This participant accepted the report for active work
Closed This participant closed the report

Full definitions are in §6.1 States.

Step 3 delivers the content that step 2 authorizes: the embargo consent recorded in step 2 is what the delivery gate tests (§9.7 Gating Full Case Delivery). Step 4 follows the replay, so the participant judges a case it already holds.

10.2 Embargo Revision and Termination Cascades

A change to the embargo in force propagates to participants' consent, because consent is given to specific terms. A change that is only proposed does not. Each cascade below starts from a step on the case's embargo register (§7.1.1 The Embargo Register), and the EM state follows from the register.

Recall: embargo management states

State Meaning Embargo register entries
None No embargo is in effect for this case, and none is proposed none ACTIVE, PROPOSED or TERMINATED
Proposed At least one embargo is proposed, and none is in force one or more PROPOSED, none ACTIVE
Active An embargo is in force, and no revision is open one ACTIVE, none PROPOSED
Revised An embargo is in force, and at least one revision of it is open one ACTIVE, one or more PROPOSED
Exited The embargo has been torn down one TERMINATED

Full definitions are in §7.1 States.

  • A revision is proposed (EM enters, or stays at, Revised). The register gains a PROPOSED entry for the revision, and every other participant gets an UNINVITED row for it (CM-18-001). No consent to the embargo in force changes: the prior embargo is still in force and every signatory to it remains one (EP-05-002). The CASE_MANAGER records the proposer's row for the revision as AGREED, because proposing terms is consenting to them.
  • The case owner activates the revision. The register activates the revision's entry and supersedes the entry in force, in one step; EM returns to Active, or stays at Revised while another revision is open (EMB-18-005). The owner's row for the revision becomes AGREED unless it already is. If the revision ends no later than the embargo it replaces, the CASE_MANAGER applies CARRY_OVER to the revision's row of every participant whose row for the replaced embargo is AGREED, a DECLINED row for the revision included (EP-05-001, MSM-07-005). If it ends later, no row is written: a signatory to the replaced terms whose row for the revision is not AGREED has lapsed, which is read from its rows and the register (§9.3 Signatory and Lapsed). A participant that has already agreed to the revision is its signatory by lookup.
  • The case owner rejects the revision. The register rejects the revision's entry; EM returns to Active once no other revision is open. No consent row changes, the owner's included, and the prior terms stand (MSM-07-009).
  • The embargo is terminated (EM enters Exited). The register terminates the entry in force and cancels every open proposal in the same step (EP-08-004). No consent row is written (CM-18-016, MSM-07-006). With no ACTIVE entry, no participant is a signatory and none has lapsed, and every row for the terminated and canceled entries is frozen as it stood, so an accept that arrives afterwards records nothing (EMB-17-004).
  • After a teardown. The termination's own ledger entry, fanned out as Announce(CaseLedgerEntry), records the end (EP-09-009). The CASE_MANAGER then sends Announce(EmbargoEvent) to every other active participant (EMB-19-001), and a direct Remove(EmbargoEvent) notice to each signatory the ledger fan-out does not reach (CM-31-009). No message is sent for consent, because none is written (MSM-07-006).

Recall: participant embargo consent states

State Meaning
UNINVITED Not asked about this embargo; the start state of every row
INVITED Asked, with no answer yet; carries that invitation's RSVP deadline
AGREED Agreed to this embargo: explicitly, as its proposer, by seeding, or by carry-over
DECLINED Explicitly refused this embargo, or withdrew from it
TIMED_OUT Invited, and the RSVP deadline passed with no answer; not a refusal

Full definitions are in §9.1 States.

Every one of these changes reaches the case ledger, and every replica applies it by copying (CM-23-016). The act's own activity carries its whole register step: a termination's entry also cancels every open proposal, and an activation's entry also supersedes the embargo it replaces (CLP-07-013). Where the act's activity states a row itself, as the owner's Accept states the owner's agreement, the act's entry carries that row (CLP-07-013). Each other consent row the CASE_MANAGER writes as a consequence of the act, such as an UNINVITED row, the proposer's AGREED row or a CARRY_OVER, is its own CASE_MANAGER entry after the act's entry, a Create(EmbargoConsent) for a new row or an Update(EmbargoConsent) for a changed one, carrying the row in full (EP-09-007). A replica copies those rows and never decides a carry-over, an expiry or a lapse itself.

10.3 Status Adoption: The Two-Seam Model

A participant's report does not become canonical case state on arrival. It passes two authorization points, which this specification calls seams. A seam is a place where the CASE_MANAGER makes an authorization decision that can be re-policied without affecting the other.

Two seams exist because "record what a participant claimed" and "act on that claim as truth" are separate decisions with separate authority.

Seam 1 — Adoption

A participant reports an observation by sending Add(ParticipantStatus) to the CASE_MANAGER. A participant does not send Add(CaseStatus): writing canonical case status is reserved to the CASE_MANAGER (§5.4.1 Single-Writer Authority).

The CASE_MANAGER records the claim in the case ledger first, and then decides whether to adopt it as canonical:

  • The CASE_MANAGER MUST adopt a Case Owner's report without seeking approval. Asking the Case Owner to approve its own report would be circular (§12.4.4 Case Owner Authority).
  • For every other sender, the CASE_MANAGER MUST obtain Case Owner authorization before adopting the report as canonical case state. This is the default posture, and it is deliberate: without it, any admitted participant could force a change to canonical case state.
  • An implementation MAY configure a permissive policy instead, where all admitted participants are trusted and the cost of Case Owner approval is unacceptable. Such a configuration MUST be explicit and documented. An implementation MUST NOT relax the default in order to unblock a path that the gate is correctly blocking.
  • The CASE_MANAGER accepts a reported status as sent or refuses all of it, and the refusal names every refused value and why. A valid vfd update is refused with an rm value in the same report that cannot be accepted, and the sender corrects and resends the status.
  • On adoption, the CASE_MANAGER commits the adopted status as its own Add(CaseStatus) case ledger entry, setting every public-state flag the report sets and clearing none. It writes the entry directly, never by sending an Add(CaseStatus) to itself, and a receiver refuses an Add(CaseStatus) from any other sender.
  • The component that records the claim MUST NOT execute side-effects.

The default is Case Owner authorization, not automatic adoption

A permissive default would mean that any admitted participant — including a hostile or malfunctioning one admitted as an external monitor — could force a canonical PXA change and, through it, an embargo teardown, with no Case Owner confirmation. A conservative default makes that a protocol guarantee rather than a deployment posture.

Seam 2 — Side-effects

After the canonical write, the CASE_MANAGER evaluates what the new canonical state requires:

  • The CASE_MANAGER MUST check whether the adopted status set Public Aware, Exploit Public, or Attacks Observed (§8.2 PXA — Public Aware, Exploit Public, Attacks Observed).
  • If any of those is now set, the CASE_MANAGER MUST evaluate embargo teardown.
  • This check MUST run after the canonical write, never before. What is written is the CaseStatus record holding the case's shared EM and PXA values; the check reads that written record, so that the side-effect always follows from canonical state rather than from an unadopted claim.
  • Executing the teardown MUST also require Case Owner authorization by default, on the same terms as adoption above.

The two seams are independent. Seam 1 does not examine embargo teardown, and seam 2 cannot tell whether the canonical write came from an inbound message or from the CASE_MANAGER's own emission. Either seam can therefore change policy without a change to the other.

When teardown is required but not authorized

Evaluating teardown is mandatory; executing it is authorized. If the authorization is withheld, the case is left in a state where public awareness coexists with an active embargo. That combination is a contradiction, and the CASE_MANAGER records it as a diagnostic note on the case rather than resolving it silently.

Where the gate model applies

Seam 1 governs any reported status. Its authorization question matters most for the participant-agnostic PXA values, which any participant may report (§12.4.2 Participant-Agnostic CS Transitions (PXA)) — including reports about other participants.


11. Participant Lifecycle Within a Case [N]

A case is not a fixed set of parties. Actors join, take on roles, hand responsibilities to one another, and leave. This section specifies that lifecycle: how an actor acquires a role, how it is brought into a case, how case ownership moves, and how a participant is removed.

11.1 Role Assignment [N]

A role is granted through the case's authority chain. An actor does not acquire a case role by declaring that it holds one, because the other participants act on role claims: they send a Vendor the report content and treat a Coordinator's routing decisions as authoritative. A self-declared role would let an actor take on authority the Case Owner never granted.

  1. A case begins with a Case Owner — the actor whose disclosure decision the case exists to serve. Case ownership is not delegated, though it may be transferred (§11.3 Case Ownership Transfer).
  2. The Case Owner MAY offer an additional role to an actor that is already a participant. The offer is an Offer(CaseParticipantRole) addressed to the case's Case Manager only, never to the receiving actor (CM-33-001). The Case Manager records the offer and forwards it to the receiving actor, which answers the Case Manager with Accept or Reject (CM-33-004). The Case Manager records the answer and grants the role on an Accept (CM-33-006). Only the Case Owner may make a role offer (CM-33-002). The Case Owner and Case Manager roles cannot be offered (CM-33-003): ownership moves by transfer, and how the Case Manager role is handed to another actor is not yet specified.
  3. The Case Manager records each actor's roles on that actor's CaseParticipant entry in the case.

An actor MUST NOT be recorded as holding a role it did not accept, and a participant MUST NOT be recorded as holding a role the Case Owner did not grant. When an actor accepts a role offer, the roles it receives MUST be taken from the offer the case sent, not from the accepting actor's reply — otherwise an actor could widen its own grant on the way back.

An implementation MAY verify that an actor has the capability prerequisites for a role (§12.3.1 Process Roles) before completing the assignment.

Open question: acquiring a role after joining a case

This specification says how the Case Owner assigns a role and how a role is offered and accepted. It does not say how a participant already in a case asks for an additional one.

A participant admitted as a Vendor that later also needs the Coordinator role has no protocol path to request it. In practice the request is made out of band, or as a Add(Note) on the case, and the Case Owner then issues a role offer. Neither is a specified mechanism.

Open: whether the protocol should define a role-request activity, whether a participant may decline a role it was assigned, and how a role that is removed rather than added is represented in the case history.

11.2 Invitation and Acceptance [N]

Bringing a new actor into a case has a problem to solve: the actor cannot decide whether to join until it knows something about the case, but it must not receive case content before it has been admitted and its embargo consent resolved (§9.7 Gating Full Case Delivery).

The protocol solves this with a case stub: a minimal stand-in for the case that carries enough for the invitee to decide, and no vulnerability detail. The stub is its own object, of type VulnerabilityCaseStub, and is not the case (CM-11-013). It carries exactly these fields (CM-17-010):

  • id: the stub's own identifier, which is the case's identifier with /stub appended. A receiver never derives the case from it.
  • type: VulnerabilityCaseStub. A receiver tells a stub from a case by its type alone.
  • caseId: the identifier of the case the stub stands for.
  • activeEmbargo and caseStatus: present whenever an embargo is in force, that is, while the embargo state is ACTIVE or REVISE. They state the embargo terms the invitee would agree to by accepting: the embargo in force and the case's embargo state (CM-17-002). During a revision the stub carries the embargo in force, never the open revision, which the invitee receives after joining (EP-09-011). The stub may carry the embargo inline or name it by an identifier the invitee can ask the inviter to resolve. This implementation always carries the full embargo inline, because it does not build that lookup (CM-17-011).
  • summary: required human-readable context saying what the invitee is being asked to join (MV-10-001). The sender chooses it: it may be the case name, or a deliberately less revealing string, because the case name itself can be sensitive to share with an actor not yet bound by the embargo.

The stub does not name the case's coordinator or Case Owner. The Invite that carries the stub already identifies who is asking: its actor is the CASE_MANAGER, and its attributedTo can name the Case Owner. Full content follows only after the invitee accepts.

Two paths bring an actor into a case, and they differ in who initiates:

  • Direct invitation. The CASE_MANAGER sends Invite(Actor, target=VulnerabilityCaseStub) to the actor. At the same time, it records the invitee in the case as an inert participant at RM Received (CM-11-006). The CASE_MANAGER commits the record's creation to the case ledger, and a replica applying that entry creates the same inert record. The record is not yet an active participant: it does not receive case content and is not included in embargo fan-out until the actor accepts (CM-10-004). The actor answers Accept(Invite) or Reject(Invite). Accept(Invite) activates the inert record, admitting the actor at RM Received and, where an embargo is in force, recording its consent to those terms. Reject(Invite) closes the inert record (RM Received → Closed) and keeps it as history, so the CASE_MANAGER can see that the invitation was extended and declined (CM-11-007). Every stub Invite carries a reply deadline. When it passes unanswered, the Invite closes as expired and the record is left unchanged: still inert, at RM Received, with its embargo consent row still INVITED (CM-11-014). After joining, the participant judges the case by answering the full-case Invite, not the original report Offer (CM-11-011, CM-11-020).
  • Suggested actor. An existing participant proposes a third party — "this vendor is also affected" — by sending Offer(CaseParticipant) to the CASE_MANAGER. The proposal is a recommendation, not an invitation: the Case Owner decides whether to act on it, and if it does, the CASE_MANAGER then sends the Invite above. An implementation MUST NOT treat Offer(CaseParticipant) as an invitation to the proposed actor.

Both paths converge on Accept(Invite). The suggested-actor path adds one round-trip, because the Case Owner's decision sits between the proposal and the invitation.

Recall: report management states

State Meaning
Start No report has been received
Received A report has arrived; triage is not yet complete
Invalid This participant assessed the report as invalid
Valid This participant assessed the report as valid
Deferred This participant is deferring further action
Accepted This participant accepted the report for active work
Closed This participant closed the report

Full definitions are in §6.1 States.

11.3 Case Ownership Transfer [N]

The Case Owner MAY transfer ownership to another actor via Offer(VulnerabilityCase) / Accept handshake routed through the CASE_MANAGER (ADR-0053). The Case Owner proposes the transfer to the CASE_MANAGER as Offer(Offer(VulnerabilityCase)), and the CASE_MANAGER makes the one Offer(VulnerabilityCase) the receiving actor answers (CM-21-005, ADR-0148). On acceptance, the receiving actor acquires Case Owner authority and the associated protocol responsibilities.

Open: cryptographic identity across a change of case manager

Authority over the canonical ledger follows the CASE_MANAGER role, not any actor's name or Uniform Resource Identifier (URI). Transferring case ownership, or moving the CASE_MANAGER role to a different actor, therefore does not by itself re-key the ledger.

What remains open is key handover. A future case-encryption design must define how the keys protecting existing case content pass to the new role holder, and what a participant does with ledger entries it can no longer decrypt. This specification states no rule for either.

11.4 Participant Removal [N]

Removing a participant withdraws its entitlement to case content. It does not delete the participant from the case. The participant's record, its status history, and the ledger entries it authored stay in the case, and the record carries a removal fact (CM-31-001). A removed participant is inert: it receives no case content, and it has no authority to act on the case.

Only the Case Owner requests a removal. It sends Remove(CaseParticipant, target=VulnerabilityCase) to the CASE_MANAGER, which applies it (CM-31-004). The CASE_MANAGER MUST refuse the request when the sender does not hold the Case Owner role, when it names the CASE_MANAGER's or the Case Owner's participant, or when it names no participant of the case. A second removal of an already-removed participant is a no-op, and the CASE_MANAGER reports it as skipped. The CASE_MANAGER MUST NOT remove a participant on its own initiative. A participant that wants to stop taking part leaves the case with Leave(VulnerabilityCase); that is not removal.

An accepted removal is one ledger entry: the Case Owner's received Remove(CaseParticipant), committed by the CASE_MANAGER as received (CM-31-005). The CASE_MANAGER commits no second entry of its own for the same removal. After the commit, the CASE_MANAGER sends the removed participant a direct Remove(CaseParticipant) that names it, with actor set to the CASE_MANAGER and attributedTo set to the Case Owner (CM-31-006). This notice is delivery, not a record, and is not ledgered.

The removal entry's fan-out selects its recipients before the removal is applied, so the removed participant receives that entry too. It is the last ledger entry the removed participant receives, by fan-out or by replay, until it is reinstated. Every replica, the removed participant's own included, applies the removal from that ledger entry (CM-31-007). A replica MUST NOT apply a removal from a directly received Remove(CaseParticipant): it stores the notice and takes the removal from the ledger.

Removal does not change the participant's embargo consent (CM-31-008). A removed signatory stays bound by the embargo it accepted. A removed participant receives no Invite of any kind: not a stub Invite, not a full-case Invite, and not an embargo Invite (CM-31-013).

Removal is reversible. The Case Owner reinstates a removed participant by sending Add(CaseParticipant, target=VulnerabilityCase) to the CASE_MANAGER (CM-31-011). The request follows the removal pipeline: only the Case Owner may send it, the received Add is the one ledger entry, the CASE_MANAGER sends the participant a direct Add(CaseParticipant) notice naming it, and every replica applies the reinstatement from the ledger entry. Reinstatement clears the removal fact. The participant does not accept again, because it never withdrew. The CASE_MANAGER MUST refuse an Add that names a participant that is not removed or that never joined the case: Add does not seat a new member, and an actor joins only by accepting its stub Invite. For the same reason the CASE_MANAGER does not send Add(CaseParticipant) when an invitee accepts its stub Invite; the ledger holds the messages exchanged (the Invite, the CASE_MANAGER's creation of the inert record, then the invitee's Accept, which is the entry for its consent and joined mark), and every replica applies what the entries carry (CM-31-012).

When the reinstated participant is active again, the CASE_MANAGER sends it every ledger entry committed after its removal entry, in log order, so its copy of the ledger joins the chain with no gap (CM-10-006). A participant reinstated into a case whose active embargo it has not accepted stays inert. The CASE_MANAGER sends it that embargo's Invite, and its catch-up waits until it accepts.