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:
- A participant maintains its own state and a model of other participants' states. Where a transition rule applies to one and not the other, this is stated explicitly (§8.4 Receiving CS Messages: Own State vs. Model of Others).
- Transitions listed without a named trigger are driven by the corresponding protocol message from §4 Semantic Layer — Message Meanings.
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.
See also
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
Rejectsent 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.
See also
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) orReject(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:
- At most one entry is
ACTIVEand at most one isTERMINATED, and never one of each. - A
SUPERSEDEDentry exists only alongside exactly oneACTIVEorTERMINATEDentry. - No entry is
PROPOSEDwhile an entry isTERMINATED. - 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.
7.3 Relationship to Embargo Consent
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_OVERon 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).
See also
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).
See also
9. Participant Embargo Consent (PEC) State Machine [N]
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.
See also
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_OVERto the revision's row of every participant whose row for the replaced embargo isAGREED, aDECLINEDrow 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
AGREEDhas 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_OUTorDECLINEDrow moves toAGREEDdirectly (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:
- The participant is admitted to the case: its RM state is at least Received.
- 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 notACTIVEorREVISE).
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.