Skip to content
Reference

Case Tracking Models

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 6 to 9 of the Vultron Protocol Specification: the state machines each participant uses to track a shared case. They are Report Management (RM), Embargo Management (EM), the Case State (CS) dimensions Vendor aware, Fix ready, fix Deployed (VFD) and Public aware, eXploit public, Attacks observed (PXA), and Participant Embargo Consent (PEC).

6. Report Management (RM) State Machine [N]

Report Management is the first of the five state machines, and the one every case starts with: before there is an embargo to negotiate or a fix to track, someone has received a report and has to decide what to do with it. This section specifies the states that decision moves through and the transitions between them.

Behavioral Layer — §6 through §11

§6 Report Management (RM) State Machine through §11 Participant Lifecycle Within a Case together specify the five state machines of the Vultron protocol: RM (§6 Report Management (RM) State Machine), EM (§7 Embargo Management (EM) State Machine), VFD and PXA (§8 Case State (CS) Dimensions, together the CS dimension), and PEC (§9 Participant Embargo Consent (PEC) State Machine). Model interactions and cascade rules are in §10 Model Interactions and Cascade Rules; the participant lifecycle is in §11 Participant Lifecycle Within a Case.

On scope: PEC. The Participant Embargo Consent machine was not part of the original four-machine design. It is fully normative here; its provenance is recorded at its definition site (§9 Participant Embargo Consent (PEC) State Machine).

Two conventions apply throughout §6 Report Management (RM) State Machine through §11 Participant Lifecycle Within a Case:

6.1 States

Report Management tracks one report inside one participant, from the moment it arrives to the moment that participant is finished with it. Every participant runs its own RM machine and is the authority on its own value: two participants working the same case are routinely at different RM states, and that is expected rather than a disagreement to reconcile.

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

The states divide into three groups. Start and Received precede any assessment. Invalid and Valid record the outcome of triage. Deferred, Accepted and Closed record what the participant decided to do about a report it considers valid — except that Received and Invalid can also be closed directly.

6.2 Transitions and Guards

A participant drives its own RM transitions. Each transition below is a participant-level state change: it records something that participant did, and it does not change any other participant's RM state.

---
title: Report Management State Diagram
---
stateDiagram-v2
    direction LR
    S: Start
    R: Received
    I: Invalid
    V: Valid
    D: Deferred
    A: Accepted
    C: Closed

    [*] --> S
    S --> R : receive
    R --> I: invalidate
    R --> V : validate
    I --> V : validate
    V --> A : accept
    V --> D : defer
    A --> D : defer
    D --> A : accept
    R --> C : close
    A --> C : close
    D --> C : close
    I --> C : close
    C --> [*]
From Trigger Message To
Start receive receipt of RS Received
Received validate RV Valid
Received invalidate RI Invalid
Received close RC Closed
Invalid validate RV Valid
Valid accept RA Accepted
Valid defer RD Deferred
Deferred accept RA Accepted
Accepted defer RD Deferred
Invalid close RC Closed
Deferred close RC Closed
Accepted close RC Closed

These twelve transitions are the complete set. Three consequences are worth stating, because each is a plausible assumption that does not hold:

  • A report cannot be closed from every state. Only Received, Invalid, Deferred and Accepted are closable. A participant at Start or Valid MUST reach one of those states before it can close. A Reject sent from Received is the Received → Closed transition, and a participant at Valid that leaves a case is recorded as Valid → Deferred → Closed.
  • Valid does not return to Invalid. Invalidation is available only from Received. Once a participant has assessed a report as valid it does not re-invalidate it; if it decides to stop work, it defers or closes.
  • Accepted and Deferred are mutually reachable. A participant may defer work it had accepted and accept work it had deferred, without passing through Valid again.

6.3 Per-Participant RM Tracking

Each participant tracks its own RM state independently; a participant is the authority on its own RM state (§5.4.1 Single-Writer Authority).

Received is the entry state for a participant joining a case, reached by several paths:

  • Invited participant — the CASE_MANAGER creates an inert participant record at RM Received when it sends the stub Invite (CM-11-006). Accept(Invite) activates the inert record, recording willingness to join and, where an embargo is active, consent to it. Reject(Invite) closes the record (RM Received → Closed) and retains it as history (CM-11-007). Neither transition constitutes validation of the report: the invitee has seen only a case stub at that point. After the participant joins, the CASE_MANAGER sends the case and the ledger, then a second Invite, the full-case Invite, carrying its ledger position as a floor (CM-11-010). The participant judges the case by answering it, with its own ledger position: Accept (RV, Received → Valid), TentativeReject (RI, Received → Invalid) or Reject (RC, Received → Closed) (CM-11-011). A reply behind the floor, or naming a ledger entry the CASE_MANAGER does not hold, is refused (CM-11-012).
  • Direct report recipient — on receiving a report.
  • Case proposal recipient — on receiving a CaseProposal.

The triage cycle (RECEIVED → VALID | INVALID → ACCEPTED | DEFERRED) is a distinct subsequent step that the participant runs after the full case replica has been delivered to it.

Two Invites, two different acts

Joining a case and judging a case are different protocol acts, and each has its own Invite. The stub Invite asks whether the actor will join: Accept(Invite) joins it and consents to the embargo, and leaves RM at Received. The full-case Invite asks whether the case is valid: its Accept is RV (Received → Valid), and RM Accepted is reached only by a later Join(VulnerabilityCase). A participant cannot accept what it has not seen, so the CASE_MANAGER MUST NOT treat a participant as having committed to the case until it receives an RM status message from that participant confirming the transition.

See §9.7 Gating Full Case Delivery for why this matters to case delivery.


7. Embargo Management (EM) State Machine [N]

An embargo is an agreement among the participants of a case not to disclose the vulnerability publicly before an agreed point. Embargo Management tracks the progress of that agreement for the case as a whole: whether one has been proposed, whether it is in force, whether it is being renegotiated, and whether it has ended.

There is exactly one embargo state per case. It is shared case state, so only the CASE_MANAGER writes it (§5.4.1 Single-Writer Authority). The CASE_MANAGER does not set it directly either: it changes the case's embargo register, and the EM state is computed from the register (§7.1.2 Deriving EM from the Register). Whether each individual participant has agreed to the current terms is a separate question, tracked per participant and per embargo by embargo consent (§9 Participant Embargo Consent (PEC) State Machine).

7.1 States

The five states below describe the case's collective position, not any one participant's, and not any one embargo's.

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

None here means no embargo is in effect for the case. A participant's consent row for an embargo has a separate start state, UNINVITED, meaning the participant has not been asked about that embargo (§9.1 States). The two can legitimately disagree: a case at Active may hold a participant whose row for the embargo in force is INVITED, because it has been invited to the case and has not yet accepted, or DECLINED.

7.1.1 The Embargo Register

A case keeps one embargo register entry for every embargo ever proposed on it (ADR-0122). An entry names the embargo, holds its status, and, for an activated revision, names the embargo it replaced. Entries are appended and never removed, so the register is the case's whole embargo history, rejected and canceled proposals included.

Status Meaning
PROPOSED An open proposal, awaiting the case owner's decision
ACTIVE In force
REJECTED The case owner declined the proposal
SUPERSEDED Was in force; a revision replaced it
CANCELLED Closed with no owner decision, because the embargo question became moot
TERMINATED Was in force when the case's embargo ended

REJECTED, SUPERSEDED, CANCELLED and TERMINATED are final. Six triggers move an entry:

Trigger Change
PROPOSE new entry → PROPOSED
ACTIVATE PROPOSED → ACTIVE
REJECT PROPOSED → REJECTED
SUPERSEDE ACTIVE → SUPERSEDED, in the step that activates another entry
TERMINATE ACTIVE → TERMINATED, recording what ended the embargo and, when known, why
CANCEL PROPOSED → CANCELLED, in the step that terminates the embargo in force, or on a threat signal while no entry is ACTIVE

A register step is the whole change one act causes, so a termination and the cancellation of every open proposal happen in one step and are checked together (EP-08-004). Every step keeps these invariants, and a step that would break one is refused:

  1. At most one entry is ACTIVE and at most one is TERMINATED, and never one of each.
  2. A SUPERSEDED entry exists only alongside exactly one ACTIVE or TERMINATED entry.
  3. No entry is PROPOSED while an entry is TERMINATED.
  4. Once a step has left an entry TERMINATED, no later step adds or changes an entry.

A termination records two things. What ended the embargo is END_TIME_REACHED or EARLY. Why, when known, is a structured cause whose kind is THREAT_SIGNAL (with the signals P, X or A), FIX_READY (with the vendors whose readiness made the quorum), FIX_DEPLOYED or OTHER (EMB-13-004). Every cause is EARLY, and an END_TIME_REACHED termination has none. The termination message carries the what as content and the why as terminationCause (EMB-13-005).

Register status is not EM state

The register statuses and the EM states share the words proposed and active, but they name different things. Register status ACTIVE is one embargo in force; EM ACTIVE is the case with an embargo in force and no revision open. A case whose register holds one ACTIVE entry and one PROPOSED entry is at EM Revised, not EM Active.

7.1.2 Deriving EM from the Register

The case's EM state is a read of its register's statuses, never set directly (EMB-18-001):

ACTIVE entries PROPOSED entries TERMINATED entries EM
0 0 0 None
0 ≥1 0 Proposed
1 0 0 Active
1 ≥1 0 Revised
0 0 1 Exited

REJECTED, SUPERSEDED and CANCELLED entries do not move EM. A register that breaks an invariant matches no row, and the derivation refuses it. The case's status carries a copy of the derived EM, stamped from the register, and a received status can report EM but never move it: an asserted EM that differs from the derived one is refused.

Because EM aggregates over every entry, the case leaves Proposed or Revised only when no proposal remains open. Rejecting one of several open proposals leaves the others open, so EM stays where it was. Activating one of several open proposals leaves the rest open as revisions of the embargo now in force, so EM moves to Revised rather than Active. An ACTIVE entry leaves that status only when another entry supersedes it or when it is terminated, so a case that has had an embargo in force never returns to None or Proposed.

7.2 Transitions and Guards

Four triggers drive the machine: propose, accept, reject and terminate. The register computes EM, and this table constrains how it may move: every register step names its EM trigger, and a step whose EM move is not in the table is refused, with nothing written (EMB-18-005, EMB-18-006, ADR-0130). A proposal is propose, an activation is accept, a rejection, or the cancellation of open proposals with no embargo in force, is reject, and a termination is terminate. Every transition is a change to shared case state, so the CASE_MANAGER applies it, and every replica applies the same check when it copies the step from the case ledger; a participant causes a transition by sending the corresponding message, not by writing the state itself.

---
title: Embargo Management State Diagram
---
stateDiagram-v2
    direction LR
    [*] --> None
    state Pre-Embargo {
        None --> Proposed : propose
        Proposed --> Proposed : propose
        Proposed --> Proposed : reject
        Proposed --> None : reject
    }
    state Active_Embargo {
        Active --> Revise : propose
        Revise --> Active : accept
        Revise --> Active : reject
        Revise --> Revise : propose
        Revise --> Revise : accept
        Revise --> Revise : reject
        Revise --> eXited : terminate
        Active --> eXited : terminate
    }
    state Post-Embargo {
        eXited
    }
    Proposed --> Active : accept
    Proposed --> Revise : accept
    eXited --> [*]

The diagram shows every move of the EM transition table. A self-loop on reject or accept is the move made while another proposal is still open, and accept from Proposed reaches Revise when the other open proposals become revisions of the embargo now in force.

From Trigger To Notes
None propose Proposed
Proposed propose Proposed A further proposal is added beside the open ones
Proposed reject None The last open proposal is rejected; no terms were ever in force
Proposed reject Proposed Another proposal is still open
Proposed accept Active No other proposal is open
Proposed accept Revised The other open proposals become revisions of the embargo now in force
Active propose Revised Proposing against an active embargo opens a revision
Revised propose Revised
Revised reject Active The last open revision is rejected; the prior terms stand
Revised reject Revised Another revision is still open
Revised accept Active The revised terms replace the prior ones; no other revision is open
Revised accept Revised The revised terms replace the prior ones; another revision is still open
Active terminate Exited
Revised terminate Exited Every open revision is canceled in the same step

Rejecting a revision does not end the embargo

Reject behaves differently depending on where it arrives, and the difference is easy to get backwards.

From Proposed, rejecting the last open proposal returns the case to None: no terms were ever in force, so refusing them leaves the case with no embargo.

From Revised, rejecting the last open revision returns the case to Active, not to None. The case owner is refusing the proposed change, not the embargo. The previously agreed terms remain in force. An implementation that returns to None here silently drops an embargo that everyone still expects to hold.

Reaching Exited. Exited means the embargo has been torn down at the case level. A participant does not put the case into Exited by deciding to stop observing the embargo. What a participant may do is withdraw its own consent, report that it intends to exit, propose a shorter embargo, or propose an earlier end date; the CASE_MANAGER then terminates the embargo and records the termination (§10.2 Embargo Revision and Termination Cascades). Terminating an embargo requires Case Owner authorization by default, on the same terms as any other change to canonical case state (§10.3 Status Adoption: The Two-Seam Model). An embargo whose end time has passed is terminated by the CASE_MANAGER itself, with the reason END_TIME_REACHED (EMB-13-003).

Open question: Case Owner discretion once a termination condition is canonical

Terminating a case-level embargo requires Case Owner authorization by default, and the CASE_MANAGER effects the teardown as the single writer of canonical case state. What is settled is who may terminate: any Participant may initiate termination (report a public/exploit/attacks condition, or notify intent to exit) and may terminate its own compliance, but only the Case Owner authorizes ending the embargo for the case.

What is not settled is whether the Case Owner retains any discretion once a termination condition has been adopted as canonical case state. When public awareness or exploit publication (\(q^{cs} \in \{\cdots P \cdots, \cdots X \cdot\}\)) is canonical, VP-14-001/VP-14-002 read as a mandatory consequence — the embargo no longer protects anything — and the working position of this specification is that teardown is then mandatory, with the Case Owner's authority exercised at the point the condition is adopted (guarding against a false report) rather than as a second, separate decision to keep the embargo. This matches the automatic-teardown path (a canonical CS.P set by the Case Owner triggers teardown).

Open: whether the Case Owner may decline to tear down even after the condition is canonical; whether the "attacks" condition (SHOULD, VP-11-006) differs from the "public/exploit" condition (SHALL) in this respect; and how a deliberate decision to keep an otherwise-terminable embargo would be recorded. This is expected to be a matter of discussion; the resolution MUST be reflected in this reference and in specs/vultron-protocol-spec.yaml (VP-11, VP-14) once reached.

Embargo duration selection. Where several proposals are outstanding, participants SHOULD accept the shortest and propose the remainder as a revision (EP-08-001). Other policies are also admissible: an implementation MAY defer the choice to the Case Owner, or apply its own organizational policy. This specification does not mandate shortest-wins.

Tacit acceptance. A receiver MAY publish a default embargo policy. Where it has, a sender that submits a report without proposing terms accepts that default.

A protocol default applies when nobody proposes anything. Where neither party proposes terms and the receiver has published no default, an embargo-eligible case MUST still begin with an active embargo, at a short duration fixed by the protocol rather than by either party. The duration MUST be configurable, and MUST be no less than 72 hours and no more than 5 days. Where the case is not embargo-eligible (the vulnerability is public, an exploit is public, or attacks are observed), no embargo is established and the case remains in None.

The protocol default is not a proposal: it MUST NOT take part in the shortest-wins comparison above, because a default short enough to be useful would otherwise win against every longer proposal and cap every embargo at its own length. Nor is it a minimum: a party that proposes a shorter duration MUST get the duration it proposed.

Outside these two default paths, embargo agreement and rejection SHOULD be explicit.

The two machines answer different questions. Embargo Management says whether the case has an embargo. Embargo consent (§9 Participant Embargo Consent (PEC) State Machine) says whether a given participant has agreed to a given embargo. Neither determines the other, which is why both are needed.

Both read the same register. A participant's consent rows are one per register entry, and whether it is a signatory or has lapsed is read from its rows and the register, not stored (§9.3 Signatory and Lapsed). Two register steps change who is bound; only one of them writes rows:

  • Activating a revision that ends later than the terms it replaces binds only participants that agreed to the revision. A signatory to the replaced terms that has not agreed to the revision has lapsed: its agreement covered the previous, shorter terms. A revision that ends no later than the terms it replaces carries every signatory over, by a written CARRY_OVER on each of their rows for the revision. Proposing a revision changes no one's consent to the embargo in force.
  • Terminating the embargo leaves no entry ACTIVE, so nobody is a signatory and nobody has lapsed. The termination writes no consent row, and every row for a final entry is frozen as it stood (CM-18-003, CM-18-016).

§10.2 Embargo Revision and Termination Cascades specifies both cascades.


8. Case State (CS) Dimensions [N]

Case State records what is known about the vulnerability. It answers two different questions, and it is a compound of two machines because those questions have different owners.

The first is what each vendor has done about the vulnerability: is it aware, does it have a fix, has the fix been deployed? That is participant-specific — each participant answers for itself. The second is what the world knows: is the vulnerability public, is there a public exploit, are attacks happening? That is a fact about the world, shared by the whole case.

The two are tracked separately, as VFD and PXA. Confusing them is the most consequential error in this part of the model: it leads an implementation to record one participant's action as a change to the state of the world, or to treat a world event as though some participant caused it.

8.1 VFD — Vendor Aware, Fix Ready, Fix Deployed

VFD records what a specific participant has done, so each participant has its own value. It moves in one direction only, and through its states in a fixed order: a vendor must be aware before a fix can be ready, and a fix must be ready before it can be deployed. That ordering leaves four reachable states rather than eight.

State Vendor Aware Fix Ready Fix Deployed
vfd no no no
Vfd yes no no
VFd yes yes no
VFD yes yes yes

Because the axes are ordered, the state is written as a single three-letter token in which case indicates progress: Vfd means the vendor is aware, the fix is not ready, and nothing is deployed.

Which participant may cause which VFD transition depends on the roles it holds, and is specified at §12.4.1 Participant-Specific CS Transitions (VFD).

8.2 PXA — Public Aware, Exploit Public, Attacks Observed

PXA records the state of the world, not the state of any participant. There is one PXA value for the case, it is shared case state, and only the CASE_MANAGER writes it (§5.4.1 Single-Writer Authority).

Unlike VFD, the three axes are independent: any combination can occur, in any order.

Axis Not yet Set Meaning when set
Public Aware p P The vulnerability is known outside the case participants
Exploit Public x X An exploit for the vulnerability has been published
Attacks Observed a A Attacks exploiting the vulnerability have been seen

Independence gives PXA eight states — every combination of the three, written by setting each letter to upper or lower case: pxa, Pxa, pXa, pxA, PXa, PxA, pXA, PXA.

---
title: pxa State Diagram
---
stateDiagram-v2
    direction LR
    [*] --> pxa

    pxa --> Pxa : P
    pxa --> pXa : X
    pxa --> pxA : A

    pXa --> PXa : P
    pXa --> pXA : A

    pxA --> PxA : P
    pxA --> pXA : X

    Pxa --> PxA : A
    Pxa --> PXa : X

    pXA --> PXA : P
    PXa --> PXA : A
    PxA --> PXA : X
    PXA --> [*]

Any participant MAY report a PXA observation, because a PXA fact is not any participant's to own — anyone may notice that an exploit has appeared. Reporting is not the same as the case adopting the observation (§12.4.2 Participant-Agnostic CS Transitions (PXA), §10.3 Status Adoption: The Two-Seam Model).

Two PXA states are passed through, not rested in

Publishing an exploit makes the vulnerability public. The two states in which an exploit is public while the public is unaware — pXa and pXA — therefore describe a situation that does not persist: they resolve to PXa and PXA. An implementation SHOULD resolve either in the same processing step rather than leaving a case at rest there.

The reverse does not hold. Attacks being observed does not by itself make the vulnerability public: an attack can be detected without its mechanism being understood or disclosed. pxA is a state a case can legitimately rest in.

This specification does not say where the pX→PX resolution is enforced. See the open question in §8.3 Case State as a Compound Tuple.

8.3 Case State as a Compound Tuple

Case State is the pair CS = (VFD, PXA), giving 4 × 8 = 32 compound states.

Not every ordering among them is reachable or meaningful. This specification does not yet state the ordering constraints normatively, so a conformance claim cannot cover them.

Open question: CS ordering constraints are unspecified

CS ordering constraints — including the pX→PX invariant (exploit-public implies public-aware) and the rule that CP must precede ET where public disclosure triggers embargo teardown — exist in three non-normative places: a code docstring, a measurement-oriented reference document (Possible Histories), and a design note. No spec group covers them normatively.

A conformance claim cannot meaningfully cover CS ordering until this gap is closed. Treat the material cited here as informative until a normative spec is issued.

One ordering relationship is specified, because it is a cascade rather than an ordering constraint: adopting a status that sets Public Aware, Exploit Public or Attacks Observed requires the CASE_MANAGER to evaluate embargo teardown (§10.3 Status Adoption: The Two-Seam Model). The embargo therefore ends after public awareness is recorded, not before.

8.4 Receiving CS Messages: Own State vs. Model of Others

A participant holds its own case state and a model of what it believes the other participants' states to be. Different events update each one.

  • Receiving a VFD message (CV, CF, CD) updates the receiver's model of the sender's VFD state. The receiver's own VFD state does not change. No acknowledgment is sent: a participant confirms it has the case history by the fact that its ledger replica is unbroken, not by acknowledging each entry (§4.6 Error and Acknowledgment Messages).
  • Driving one's own VFD transition happens locally, subject to the role requirements of §12.4.1 Participant-Specific CS Transitions (VFD), and is then announced.
  • PXA is shared case state, so an adopted PXA observation updates the case's canonical status rather than any per-participant model (§10.3 Status Adoption: The Two-Seam Model).

An embargo binds the participants who agreed to it. Embargo Management (§7 Embargo Management (EM) State Machine) records whether the case has an embargo; it cannot record who has agreed, because that answer differs per participant. Participant Embargo Consent records it.

The distinction has practical force. A participant invited to a case that already has an active embargo has not yet agreed to anything, and the CASE_MANAGER must not send it embargoed content until it has (§9.7 Gating Full Case Delivery). A participant that declines is still in the case and still receives the negotiation traffic (§9.5 Embargo Traffic Reaches Non-Signatories). Neither position is expressible in the case-level embargo state.

Consent is per participant, but the CASE_MANAGER writes it: it is recorded on the participant's record in the case, alongside the participant's other case state.

Informative: provenance

The original protocol design had four state machines and no consent machine. This one emerged during implementation, when the case-level embargo state proved unable to distinguish "no embargo exists" from "this participant has not yet agreed". It is normative here, because correct embargo behavior cannot be specified without it.

9.1 States

Consent is given to specific terms, so a participant has one consent row for every entry in the case's embargo register (§7 Embargo Management (EM) State Machine), and each row holds one of the five states below (CM-18-001). A row answers one question: what has this participant said about this one embargo? A participant can therefore be bound by the embargo in force and have already agreed to a proposed revision of it, on two different rows.

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

Every row is written. Proposing an embargo writes an UNINVITED row for it on every current participant, and creating a participant record writes an UNINVITED row for every entry already in the register (CM-18-001). No state is read from a missing row: a missing row is a defect, and reading one raises. A participant that joins late stays UNINVITED on embargoes that are no longer in force; it is invited only to the embargo in force and to open revisions (CM-11-006, EP-09-011).

UNINVITED and DECLINED both leave the participant unbound by the embargo the row answers, but they are distinct states, because whether the participant gave an answer matters. The lapsed read stops at a DECLINED row and passes over an UNINVITED one (§9.3 Signatory and Lapsed), and an open proposal is relayed to a joiner only while its row for that proposal is UNINVITED (CM-18-001, EP-09-011).

INVITED carries the invitation's respond-by deadline, the RSVP deadline, on the row itself (CM-28-013). The deadline belongs to one invitation, so a participant can hold several INVITED rows with different deadlines, and one passing times out only its own row.

---
title: Participant Embargo Consent State Machine (one row per participant and embargo)
---
stateDiagram-v2
    direction LR
    [*] --> UNINVITED
    UNINVITED --> INVITED: INVITE
    UNINVITED --> AGREED: AGREE
    UNINVITED --> DECLINED: DECLINE
    INVITED --> AGREED: AGREE
    INVITED --> DECLINED: DECLINE
    INVITED --> TIMED_OUT: TIME_OUT
    AGREED --> DECLINED: DECLINE
    DECLINED --> INVITED: INVITE
    DECLINED --> AGREED: AGREE
    TIMED_OUT --> INVITED: INVITE
    TIMED_OUT --> AGREED: AGREE
    TIMED_OUT --> DECLINED: DECLINE

The diagram shows the five states of one consent row and the triggers INVITE, AGREE, DECLINE and TIME_OUT that move it. CARRY_OVER also moves every state except AGREED to AGREED, when the case owner activates a revision that ends no later than the embargo it replaces; those edges are omitted for legibility. No state is terminal: a row stops accepting triggers when its embargo's register entry reaches a final status.

9.2 Transitions and Guards

Five triggers drive each row (CM-18-003). INVITE records an invitation, AGREE and DECLINE record an answer, TIME_OUT fires when an invitation's RSVP deadline passes unanswered, and CARRY_OVER agrees for a participant when the case owner activates a revision that asks nothing new of it.

Trigger From To
INVITE UNINVITED, DECLINED, TIMED_OUT INVITED
AGREE UNINVITED, INVITED, DECLINED, TIMED_OUT AGREED
DECLINE UNINVITED, INVITED, AGREED, TIMED_OUT DECLINED
TIME_OUT INVITED TIMED_OUT
CARRY_OVER every state except AGREED AGREED

No other trigger is valid from any state, and an implementation rejects one as a protocol error (CM-18-009). A row for an embargo whose register entry is final (REJECTED, SUPERSEDED, CANCELLED or TERMINATED) accepts no trigger at all, so the consent given to an embargo that is no longer in play is frozen as it stood (CM-18-003).

AGREED refuses INVITE. Consent already given is not undone by asking again (CM-18-004). A participant whose terms have since changed is invited on its row for the new embargo, which leaves its AGREED row for the old one alone.

AGREE and DECLINE are valid directly from UNINVITED. Consent does not always arrive through an invitation: a finder that opens a case for its own finding and sets its default embargo was invited by nobody, and a reporter consents by reporting. The case history MUST NOT record an invitation that did not happen (ADR-0048).

A repeated answer is not a transition. When a participant sends the same answer to the same invitation again, the CASE_MANAGER changes nothing, records nothing in the case ledger and sends nothing, whatever the repeat's activity id (CM-13-005). A different answer is a new answer, and the table above applies to it. A participant that declined and then agrees has changed its answer, so its row moves to AGREED directly, without being invited again.

None of DECLINED, TIMED_OUT and AGREED is terminal. A DECLINED or TIMED_OUT row can be invited again, which is what makes renegotiation possible, and an AGREED row can be declined. What ends a row's life is its register entry reaching a final status, not any state of the row.

9.3 Signatory and Lapsed

Two positions a participant can hold are not states. Each is a read of the participant's rows and the case's register, and neither is stored or drawn on the state machine (CM-18-001).

Read Definition
Signatory The participant's row for the register's ACTIVE entry is AGREED.
Lapsed An entry is ACTIVE, the participant's row for it is neither AGREED nor DECLINED, and, following replaces back from the ACTIVE entry, the first entry whose row is AGREED or DECLINED has an AGREED row.

With no ACTIVE entry nobody is a signatory and nobody has lapsed. After an embargo is terminated no entry is ACTIVE, so the termination writes no row: every participant's position follows from the register alone (CM-18-016, MSM-07-006). Abandoning an open proposal once the case is public, an exploit is public or attacks are observed writes no row either (EMB-16-001). The CASE_MANAGER cancels the proposal with its Reject of the proposal it received, or its Undo of one it made itself, and then withdraws each invitation it relayed for the proposal with an Undo. Each row for the canceled proposal is frozen as it stood. The CASE_MANAGER's Reject is its abandonment, not its consent (MSM-07-004). A CASE_MANAGER that has a stake in an embargo therefore records its own answer as its own update of the row, never as a Reject.

A participant lapses when the case owner activates a revision that ends later than the embargo the participant agreed to, and the participant has not agreed to the revision. It did agree, and the embargo in force has since become something it did not agree to. Only the embargoes that were once in force sit on the replaces chain, so agreeing to a proposal that never took effect binds nothing to lapse from. A participant that withdrew from a later embargo has a DECLINED row on the chain, and has not lapsed when a further revision replaces that one.

Lapsed is neither the proposal state nor the deadline state

A merely proposed revision lapses nobody, and neither does a revision that ends no later than the accepted terms. A participant that lets an invitation deadline pass reaches TIMED_OUT on that invitation's row, not lapsed. A lapse is not timer-based and has no transition of its own: a lapsed participant is re-invited, on an INVITED row for the embargo in force, and the deadline attaches to that invitation (CM-18-002).

A revision does not move anyone's consent to the embargo in force until it takes effect (EP-05-002). While a revision is only proposed, the prior embargo is in force and every signatory to it remains one. The proposer's row for the revision is AGREED, because proposing terms is consenting to them, and every other participant gets an UNINVITED row for it. A signatory that agrees to the revision records that on the revision's row, and one that declines it refuses those terms and nothing more, on that row only (MSM-07-003, MSM-07-004).

A signatory withdraws by leaving the embargo in force with Leave(EmbargoEvent), not by rejecting an invitation to it (MSM-07-010). The withdrawal declines its row for the embargo in force and every open proposal's row it had agreed to, because every open proposal is a revision of the embargo it is leaving. A Reject of the active embargo from an AGREED row is refused with a reported reason (MSM-07-004). The case owner does not leave an embargo; it ends one by terminating it.

Only the case owner's decision changes the embargo on the case; the other participants' answers inform it (EP-09-005). When the owner activates a revision, the direction of the change decides what happens to the consent of the signatories to the old terms (EP-05-001, MSM-07-005):

  • A revision that ends no later than the terms it replaces asks nothing new of anyone who agreed to the old terms, since agreeing to N days is agreeing to every shorter period. 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.
  • A revision that ends later asks for more than they promised. No row is written: a signatory to the old terms whose row for the revision is not AGREED has lapsed, by the definition above.

Either way, a participant already holding an AGREED row for the revision is its signatory by lookup, whether it is the proposer or an early acceptor. Only signatories to the old terms are carried over. A participant that had lapsed from an earlier embargo stays lapsed until it is re-invited or agrees. An INVITED participant whose invitation the activation has made stale is re-invited to the current terms, as in §9.4 Deadlines and the Pocket Veto (EMB-17-003). If the owner rejects the revision instead, the old terms stand and nobody's consent changes (MSM-07-009).

9.4 Deadlines and the Pocket Veto

An embargo invitation does not stay open indefinitely. A participant that neither agrees nor declines before the RSVP deadline has its row for that invitation moved to TIMED_OUT, the pocket veto (CM-18-002). Silence is not treated as assent, because proceeding on an unanswered invitation would mean asserting agreement the participant never gave. Nor is it recorded as a refusal: DECLINED is reached only by an explicit refusal (CM-28-004).

The deadline may be explicit or defaulted:

  • An embargo invitation MAY carry its own endTime, giving an explicit respond-by instant. Where present, it takes precedence over the policy window (CM-28-001, CM-28-002).
  • Where absent, a configurable policy window applies. The default window is 7 days.

Either way the result is bounded below and above by the two rules further down this section: the explicit endTime takes precedence over the policy window, but neither escapes the minimum window or the embargo-end ceiling. The pocket veto is the implicit form of this one mechanism, not a second mechanism alongside it.

One invitation carries two different end times

The invitation's endTime is the respond-by deadline for the invitee. The embargo event nested inside the invitation has its own endTime, which is when the embargo would expire. They sit one nesting level apart and mean different things.

An implementation that reads the embargo's expiry as the RSVP deadline will hold invitations open for the entire embargo period. One that reads the RSVP deadline as the embargo expiry will tear down embargoes days after they were agreed.

Recording the deadline. When the CASE_MANAGER relays an invitation, it sets the Invite's endTime and records the deadline on the invitee's INVITED row for the invited embargo (CM-28-012, CM-28-013). Every replica records the same deadline by copying the CASE_MANAGER's ledger entry for that row. A participant does not derive or store a deadline on receipt of an Invite.

Enforcing the deadline. The CASE_MANAGER enforces it, and only the CASE_MANAGER (CM-28-003, CM-28-014). It does so lazily: when it next handles the case, it compares each deadline against its own clock, so no scheduler or timer service is required (CM-28-007, CM-28-008). Before it commits the case owner's decision on a proposal, whenever it processes any other received embargo activity for the case, and whenever it runs an embargo trigger of its own, it expires every INVITED row whose deadline has passed and whose embargo is an open proposal or the embargo in force (CM-28-015). A row of a replaced, rejected, canceled or ended embargo is frozen and is not swept. The sweep runs after the activity's sender has been admitted and before the activity's own refusals, so an expiry is recorded even when the activity is then refused.

A minimum window applies. The CASE_MANAGER MUST enforce a minimum respond-by window. The minimum is the lesser of a configured window, 72 hours by default, and the time remaining in the embargo the invitation concerns (EP-07-002). Where an invitation carries a shorter deadline, the CASE_MANAGER MUST extend the deadline to that minimum. It MUST NOT reject the invitation for this reason: a too-short deadline is the proposer's error, and refusing the invitation would penalize the invitee for it.

A deadline may not outlive its embargo. The CASE_MANAGER MUST NOT let a respond-by deadline fall after the end of the embargo it concerns (CM-28-011). Where a computed deadline would, whether it came from the invitation's explicit endTime or from the policy window, the CASE_MANAGER MUST clamp it down to the embargo's end.

An invitee must be able to answer while there is still something to answer about. Invite a participant to a 24-hour embargo with no explicit endTime and a 7-day policy window, and its inaction would be recorded as a time-out on day 7, six days after the embargo ended. This is why the minimum window above is relative rather than absolute: a 12-hour embargo grants a 12-hour answer window, and that is not an unreasonably short deadline when 12 hours is the whole embargo.

A late acceptance is not refused. The CASE_MANAGER MUST NOT refuse a late accept on deadline grounds (EMB-17-001). Three cases apply:

  • If the terms it accepts are the case's current embargo, the CASE_MANAGER records the accept exactly as an on-time accept: its entry is the accept itself, and a TIMED_OUT or DECLINED row moves to AGREED directly (EMB-17-002).
  • If the terms are stale, the CASE_MANAGER relays a fresh invitation carrying the current terms, on behalf of their proposer (EMB-17-003).
  • If no embargo remains, the accept changes nothing: the CASE_MANAGER records no entry, sends no reply and leaves the rows unchanged; the participant keeps its place in the case either way (EMB-17-004, EMB-17-008).

A time-out and a refusal reach different states. Silence records TIMED_OUT; an explicit refusal records DECLINED. The case ledger distinguishes them too, holding either the refusal that was sent or the CASE_MANAGER's record of the expiry (CM-28-005, CM-28-009), but every reader of the consent rows can tell them apart without consulting it. Both can be invited again, and both move to AGREED directly on a late accept of the current terms.

9.5 Embargo Traffic Reaches Non-Signatories

Embargo consent gates case content. It does not gate the negotiation about the embargo itself.

The CASE_MANAGER MUST deliver embargo meta-protocol activities addressed to a participant directly (an embargo Invite and its responses) to that participant even when it declined, timed out or lapsed; they are not ledger fan-out, which the content gate holds back from non-signatories (CM-10-005). A participant cannot agree to revised terms it was never told about, and one that declined the original terms may well accept the revision.

Only case content (report details, fix status, sensitive notes) is gated on being a signatory (§9.7 Gating Full Case Delivery).

9.6 Embargo Adherence

Whether a participant is currently bound by the embargo is a single yes-or-no question, and the protocol answers it from the consent rows: a participant is bound exactly when it is a signatory, that is, when its row for the register's ACTIVE entry is AGREED (CM-18-001).

This is a derived fact, not an independent one. An implementation MUST NOT record adherence as a value that can be set separately from the rows it reports, because the two would then be able to disagree, and a case that reports a participant as bound while its rows say otherwise has no correct reading.

A change of consent MUST be made either by applying a consent trigger through the transitions of §9.2 Transitions and Guards, or by copying the row a ledger entry carries after checking its starting state, through the one apply function the CASE_MANAGER and every replica share, so that every change is a valid one and is recorded as such (CM-18-005). The CASE_MANAGER commits every consent row it changes as its own Update(EmbargoConsent) ledger entry, or Create(EmbargoConsent) for a row it brings into existence, after the entry of the act that caused it (CLP-07-013, EP-09-007). Where the act's own activity states the row, such as a participant's Accept(Invite(EmbargoEvent)) or, for the case owner's own row, its Accept(EmbargoEvent, target=Case) activating an embargo, that entry is the row's entry.

Informative: how the reference implementation does this

The reference implementation exposes adherence as a computed lookup over the stored consent rows and the register, so there is no separate field to fall out of step. Any mechanism with that property satisfies the requirement above.

9.7 Gating Full Case Delivery

Before the CASE_MANAGER delivers full case content (Announce(VulnerabilityCase) carrying report details, vulnerability description, and sensitive notes), both conditions MUST hold for the recipient:

  1. The participant is admitted to the case: its RM state is at least Received.
  2. The participant is a signatory to the active embargo (its row for the active embargo is AGREED), OR no embargo is in force (EM is not ACTIVE or REVISE).

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.

The gate is admission plus consent — not completed triage

It is tempting to read condition 1 as RM.ACCEPTED. That reading is wrong and self-defeating: an invitee is at RM.RECEIVED from the stub Invite and stays there after Accept(Invite), and reaches ACCEPTED only after receiving the full case and running its triage cycle (§6.3 Per-Participant RM Tracking). Requiring ACCEPTED before delivery would mean a participant could never obtain the case it needs in order to reach the state that gates it.

Embargo consent — not RM progress — is the substantive gate on case content. The ordering is: admit the participant at RM.RECEIVED, resolve embargo consent, then deliver the full case.

This matters to §10 Model Interactions and Cascade Rules's cascade ordering: Accept(Invite) agrees to any active embargo on the joiner's row for it, which is what allows delivery to proceed immediately rather than waiting on a separate consent round-trip. A joiner whose row for the active embargo is DECLINED stays declined, because joining is not an answer to the embargo.