Skip to content
Tutorial

Tutorial: Running the Other Demos

In this tutorial, we will run the remaining Vultron demo sub-commands end to end inside the demo container. By the end of this tutorial, we will have explored:

  • case initialization and participant management,
  • actor invitation, suggestion, and ownership transfer,
  • embargo establishment and lifecycle management,
  • acknowledgment, status updates, and notes,
  • the full Report Management (RM) case lifecycle, and
  • the trigger endpoints an actor uses to act on its own initiative.

What we will learn

Each demo sub-command exercises a focused slice of the Vultron Protocol. This tutorial introduces all of them except receive-report, which is covered in Run the Receive-Report Demo. Each exchange is named here by its formal protocol message and its ActivityStreams wire form, the same names the How-to guides use, so you can move between the log output and the guides.


Prerequisites

You need the following tools installed before we begin:

  • Docker (version 20.10 or later)
  • Docker Compose (version 2.x; included with Docker Desktop)
  • Git (to clone the repository)

You do not need Python installed locally; the containers include everything required.

This tutorial continues from Run the Receive-Report Demo, which clones the repository and opens a shell in the demo container. If you still have that container shell open, skip Step 1.


Step 1 — Start the demo container

If you exited the container after the previous tutorial, start it again from the repository root:

docker compose -f docker/docker-compose.yml run --rm demo

After the images are built and the API server is healthy, you should see a container shell prompt:

root@<container-id>:/app#

All commands in the remaining steps are run inside this container shell.


Step 2 — Case initialization

initialize-case

vultron-demo initialize-case

This demo walks through the steps a vendor takes to open a new case after receiving a report: create the case, which seats the vendor as Case Owner and CASE_MANAGER, link the report to the case, and invite the reporter, which joins when it accepts.

See How to Initialize a Case for the full activity-by-activity walkthrough.

initialize-participant

vultron-demo initialize-participant

This demo shows how a participant joins an existing case. The vendor asks the CaseActor to invite a coordinator, and the coordinator joins by accepting the Invite. Notice how the vendor's own copy of the case gains the coordinator from the ledger entry for the Accept, with no other message.

See How to Seat a Participant on an Existing Case for details.


Step 3 — Actor management

invite-actor

vultron-demo invite-actor

This demo shows two invitation outcomes. The Case Owner invites a coordinator, who accepts and is added to the case; then it invites a second coordinator, who rejects and is not added. Notice the log lines showing the Invite Actor to Case, Invite(Actor), and the answering Accept(Invite) or Reject(Invite). Both answers are case events, so the case ledger records the Reject just as it records the Accept.

---
title: invite-actor — accept and reject paths
---
sequenceDiagram
    participant O as Case Owner
    participant C1 as Coordinator 1
    participant C2 as Coordinator 2

    O->>+C1: Invite Actor to Case<br/>Invite(Actor)
    C1-->>-O: Accept Invite to Case<br/>Accept(Invite)
    Note over O: Coordinator 1 added as participant

    O->>+C2: Invite Actor to Case<br/>Invite(Actor)
    C2-->>-O: Reject Invite to Case<br/>Reject(Invite)
    Note over O: Reject ledgered<br/>Coordinator 2 not added

In a multi-actor deployment the invitation is sent by the Case Actor on the owner's behalf, and the answer goes back to the Case Actor. See How to Invite an Actor to a Case for that exchange.

suggest-actor

vultron-demo suggest-actor

This demo shows a reporter suggesting a coordinator for the case. The suggestion goes to the Case Actor, which forwards it to the Case Owner as a proposed participant. Two paths are demonstrated: the vendor accepts the suggestion, and the Case Actor invites the coordinator; or the vendor rejects it, and no invitation is sent. The vendor's Reject is ledgered like its Accept.

---
title: suggest-actor — accept and reject paths
---
sequenceDiagram
    participant R as Reporter
    participant CA as Case Actor
    participant V as Vendor (Case Owner)
    participant C as Coordinator

    R->>CA: Suggest an actor<br/>Offer(Actor, target=Case)
    CA->>V: Offer Case Participant Role<br/>Offer(CaseParticipant, target=Case)
    alt Vendor accepts suggestion
        V->>CA: Accept(Offer(CaseParticipant))
        CA->>C: Invite Actor to Case<br/>Invite(Actor)
        Note over CA,C: Coordinator invited to case
    else Vendor rejects suggestion
        V->>CA: Reject(Offer(CaseParticipant))
        Note over CA: Reject ledgered<br/>no invitation sent
    end

See How to Suggest an Actor for a Case for the corresponding activity descriptions.

manage-participants

vultron-demo manage-participants

This demo combines the full participant lifecycle: invite, accept, record a participant status, remove the participant from active participation, and reinstate it. The vendor is both Case Owner and CASE_MANAGER here, so the removal and the reinstatement are the owner's requests to itself. The participant receives the CASE_MANAGER's direct notice of each, and its record stays on the case throughout. A second path shows the participant declining the invitation: its Reject is a case event, and the CASE_MANAGER ledgers it just as it ledgers an Accept. Each step is logged so we can follow the state changes.

---
title: manage-participants — accept and reject paths
---
sequenceDiagram
    participant O as Case Owner
    participant P as Participant

    O->>P: Invite Actor to Case<br/>Invite(Actor)
    alt Participant accepts
        P-->>O: Accept Invite to Case<br/>Accept(Invite)
        Note over O: Accept ledgered<br/>participant seated
        P->>P: Add(ParticipantStatus)
        Note over O: Remove(CaseParticipant), ledgered<br/>record kept, removal fact set
        O->>P: Removal notice<br/>Remove(CaseParticipant)
        Note over O: Add(CaseParticipant), ledgered<br/>removal fact cleared
        O->>P: Missed ledger entries, then reinstatement notice<br/>Add(CaseParticipant)
    else Participant rejects
        P-->>O: Reject Invite to Case<br/>Reject(Invite)
        Note over O: Reject ledgered<br/>no participant seated
    end

See How to Manage a Case Roster for details.

transfer-ownership

vultron-demo transfer-ownership

This demo shows case ownership transfer. The current owner (the vendor) proposes the transfer to the Case Actor, which offers the case to a coordinator, who either accepts, so the case's attributedTo moves to the coordinator, or rejects, so ownership stays with the vendor. A third path has the vendor withdraw its proposal, and with it the offer, before the coordinator answers, so ownership again stays with the vendor. In the accept path the offer carries a deadline to answer, and the demo checks that the offer the coordinator receives names it; the coordinator answers in time.

Every message goes through the Case Actor, which holds the CASE_MANAGER role, so that the ledger records the transfer or the coordinator's Reject and every participant learns of it.

---
title: transfer-ownership — accept, reject and withdraw paths
---
sequenceDiagram
    participant V as Vendor
    participant CA as Case Actor
    participant C as Coordinator

    V->>CA: Propose Case Ownership Transfer<br/>Offer(Offer(VulnerabilityCase))
    CA->>C: Offer Case Ownership Transfer<br/>Offer(VulnerabilityCase)
    alt Coordinator accepts
        C-->>CA: Accept Case Ownership Transfer<br/>Accept(Offer)
        Note over CA: CASE_OWNER moves to Coordinator
    else Coordinator rejects
        C-->>CA: Reject Case Ownership Transfer<br/>Reject(Offer)
        Note over CA: Ledgers the Reject, ownership unchanged
    else Vendor withdraws
        V-->>CA: Withdraw Case Ownership Transfer<br/>Undo(Offer(Offer))
        Note over CA: Ledgers the withdrawal, ownership unchanged
    end

See Ownership Transfer for why the transfer is routed this way, and Case Management Messages for the wire format.


Step 4 — Embargo management

establish-embargo

vultron-demo establish-embargo

This demo exercises embargo negotiation. A coordinator proposes embargo terms with an Embargo Proposal (EP), Invite(Event). The vendor either accepts with an Embargo Proposal Acceptance (EA), Accept(Invite(Event)), then activates the embargo on the case with Add(Event) and announces it with Announce(Event), so the Embargo Management (EM) state becomes ACTIVE; or rejects with an Embargo Proposal Rejection (ER), Reject(Invite(Event)), and the EM state does not change.

---
title: establish-embargo — accept and reject paths
---
sequenceDiagram
    participant C as Coordinator
    participant V as Vendor

    C->>V: Embargo Proposal (EP)<br/>Invite(Event)
    alt Vendor accepts
        V-->>C: Embargo Proposal Acceptance (EA)<br/>Accept(Invite(Event))
        V->>V: Activate the embargo<br/>Add(Event)
        V->>C: Announce(Event)
        Note over C,V: EM state = ACTIVE
    else Vendor rejects
        V-->>C: Embargo Proposal Rejection (ER)<br/>Reject(Invite(Event))
        Note over C,V: EM state unchanged
    end

See How to Establish an Embargo for background.

report-with-embargo

vultron-demo report-with-embargo

This demo exercises the negotiated embargo path, which no other demo reaches. The Reporter proposes embargo terms with the report: the Report Submission (RS), Offer(VulnerabilityReport), carries a proposed EmbargoEvent as proposedEmbargo, sent through the submit-report trigger's proposed_embargo_end_time. The recipient's default is the EmbargoPolicy on its own actor profile, published through PUT /actors/{actor_id}/embargo-policy (EP-04-003). The recipient sends that profile inline as the actor of its case proposal, and the CaseActor reads the default from there (CP-01-010). When the case is created, the shorter of the two becomes the active embargo and the longer is left pending as a revision, so the EM state is REVISE; when the recipient has published no default, the Reporter's terms apply at their stated length and the EM state is ACTIVE. No proposal exchange precedes the case: the comparison is settled at case creation. The CaseActor then relays the pending revision to the party whose terms won, on behalf of the party whose terms lost (EP-04-011). When the Reporter proposed 60 days, that party is the Vendor, the case owner. The protocol requires no automatic answer, so the Vendor's 30-day default stays active and the Reporter's longer terms stay pending until the owner answers; the case stays at REVISE. When the Reporter proposed 10 days, the Reporter is invited instead, and its acceptance only records consent, so the case also stays at REVISE.

---
title: report-with-embargo — shortest terms win at case creation
---
sequenceDiagram
    participant R as Reporter
    participant V as Vendor
    participant CA as CaseActor

    V->>V: Publish EmbargoPolicy (30 days)<br/>on its own profile
    R->>V: Report Submission (RS)<br/>Offer(VulnerabilityReport) + proposedEmbargo
    V->>CA: Create(CaseProposal)<br/>carrying the Offer, actor = Vendor profile
    alt Reporter proposes 10 days
        Note over CA: Reporter's terms ACTIVE<br/>30-day default pending, EM = REVISE
        CA->>V: Create(VulnerabilityCase)
        CA->>R: Invite(Event)<br/>30-day default, on the Vendor's behalf
        R->>CA: Accept — consent only, EM stays REVISE
    else Reporter proposes 60 days
        Note over CA: 30-day default ACTIVE<br/>Reporter's terms pending, EM = REVISE
        CA->>V: Create(VulnerabilityCase)
        CA->>V: Invite(Event)<br/>Reporter's terms, on the Reporter's behalf
        Note over V,CA: No automatic answer — the owner decides<br/>whether to accept the longer terms
    else No policy published
        Note over CA: Reporter's terms ACTIVE<br/>nothing pending, EM = ACTIVE
        CA->>V: Create(VulnerabilityCase)
    end

See How to Report a Vulnerability for the Offer that carries the terms, and Default Embargoes for why the shorter terms win.

manage-embargo

vultron-demo manage-embargo

This demo continues from an active embargo. Two paths are shown:

  1. Activate and terminate: the coordinator proposes, the vendor accepts and activates, and the vendor then ends the embargo with an Embargo Termination (ET), Remove(Event), which moves the EM state to EXITED.
  2. Reject and repropose: the coordinator proposes 45-day terms, the vendor rejects them, the coordinator proposes 90-day terms as a new proposal, and the vendor accepts and activates.
---
title: manage-embargo — terminate and repropose paths
---
sequenceDiagram
    participant C as Coordinator
    participant V as Vendor

    alt Path 1 — Activate then terminate
        C->>V: Embargo Proposal (EP)<br/>Invite(Event)
        V-->>C: Embargo Proposal Acceptance (EA)<br/>Accept(Invite(Event))
        V->>V: Add(Event), then Announce(Event)
        Note over C,V: EM state = ACTIVE
        V->>C: Embargo Termination (ET)<br/>Remove(Event)
        Note over C,V: EM state = EXITED
    end

    alt Path 2 — Reject then repropose
        C->>V: Embargo Proposal (EP), 45 days<br/>Invite(Event)
        V-->>C: Embargo Proposal Rejection (ER)<br/>Reject(Invite(Event))
        C->>V: Embargo Proposal (EP), 90 days<br/>Invite(Event)
        V-->>C: Embargo Proposal Acceptance (EA)<br/>Accept(Invite(Event))
        V->>V: Add(Event)
        Note over C,V: EM state = ACTIVE
    end

The second proposal in Path 2 is a new Embargo Proposal (EP) rather than an Embargo Revision Proposal (EV), because there is no active embargo to revise when it is sent.

See How to Revise or Terminate an Embargo for details.


Step 5 — Acknowledgment, status, and notes

acknowledge

vultron-demo acknowledge

This demo shows how a vendor acknowledges receipt of a report without committing to an outcome, using a Report Acknowledgment (RK), Read(Offer(VulnerabilityReport)). The reporter submits through its submit-report trigger, so it holds the Offer and accepts the acknowledgment because the vendor is the Offer's addressee. The vendor sends the acknowledgment through its ack-report trigger, which addresses the reporter alone and never the CASE_MANAGER, even though a case already exists. Three paths are demonstrated:

  1. Acknowledge only — the vendor sends the acknowledgment to the reporter.
  2. Acknowledge then validate — the acknowledgment, then a Report Valid (RV), Accept(Offer(VulnerabilityReport)), then notify the reporter.
  3. Acknowledge then invalidate — the acknowledgment, then a Report Invalid (RI), TentativeReject(Offer(VulnerabilityReport)), then notify the reporter.
---
title: acknowledge — three paths
---
sequenceDiagram
    participant R as Reporter
    participant V as Vendor

    R->>V: Report Submission (RS)<br/>Offer(VulnerabilityReport)
    V->>R: Report Acknowledgment (RK)<br/>Read(Offer(VulnerabilityReport))
    alt Path 1 — Acknowledge only
        Note over R,V: done
    else Path 2 — Acknowledge then validate
        V->>V: Report Valid (RV)<br/>Accept(Offer(VulnerabilityReport))
        V-->>R: Notify reporter (validated)
    else Path 3 — Acknowledge then invalidate
        V->>V: Report Invalid (RI)<br/>TentativeReject(Offer(VulnerabilityReport))
        V-->>R: Notify reporter (invalidated)
    end

See Acknowledge a Report for the corresponding activity descriptions.

status-updates

vultron-demo status-updates

This demo shows two workflows for updating a case record:

  1. Notes — the vendor creates a Note, adds it to the case with Add(Note), and then removes it again with Remove(Note).
  2. Status — the vendor reports public awareness in a new ParticipantStatus, added to its own participant record with Add(ParticipantStatus), and the demo checks that the case status adopted it.

A participant status update is how an actor reports its own Case State (CS) progress: Vendor Awareness (CV), Fix Readiness (CF), and Fix Deployed (CD) are all Add(ParticipantStatus) activities. So are Public Awareness (CP), Exploit Public (CX), and Attacks Observed (CA), carried in the case status the participant status embeds; only the Case Manager sends Add(CaseStatus).

See How to Post a Status Update or a Case Note for details.


Step 6 — Full RM case lifecycle

manage-case

vultron-demo manage-case

This demo exercises the Report Management (RM) state machine from submission through closure. Three paths are shown:

  1. Engage path — submit, validate, create the case, engage, close.
  2. Defer and re-engage path — submit, validate, create the case, defer, re-engage, close.
  3. Invalidate path — submit, invalidate, close the case opened for the report.

Notice the RM state transitions logged at each step, and notice that every path closes with Leave(VulnerabilityCase): the vendor opened a case for each report on receipt, and once a case holds a report its owner closes the case, never the report. Only a report that no case holds is closed with a Report Closed (RC), Reject(Offer(VulnerabilityReport)).

---
title: manage-case — three paths through the RM state machine
---
sequenceDiagram
    participant R as Reporter
    participant V as Vendor

    R->>V: Report Submission (RS)<br/>Offer(VulnerabilityReport)
    alt Path 1 — Engage
        V->>V: Report Valid (RV)<br/>Accept(Offer(VulnerabilityReport))
        Note over V: Create Case<br/>Create(VulnerabilityCase)
        V->>V: Report/Case Accepted (RA)<br/>Join(VulnerabilityCase)
        Note over V: RM state = ACCEPTED
        V->>V: Close Case<br/>Leave(VulnerabilityCase)
        Note over V: RM state = CLOSED
    else Path 2 — Defer then re-engage
        V->>V: Report Valid (RV)<br/>Accept(Offer(VulnerabilityReport))
        Note over V: Create Case<br/>Create(VulnerabilityCase)
        V->>V: Report/Case Deferred (RD)<br/>Ignore(VulnerabilityCase)
        Note over V: RM state = DEFERRED
        V->>V: Report/Case Accepted (RA)<br/>Join(VulnerabilityCase)
        Note over V: RM state = ACCEPTED
        V->>V: Close Case<br/>Leave(VulnerabilityCase)
        Note over V: RM state = CLOSED
    else Path 3 — Invalidate
        V->>V: Report Invalid (RI)<br/>TentativeReject(Offer(VulnerabilityReport))
        V->>V: Close Case<br/>Leave(VulnerabilityCase)
        Note over V: Case never engaged<br/>RM state = CLOSED
    end

See Manage a Case for the corresponding activity descriptions and the RM state ladder.


Step 7 — Trigger endpoints

trigger

vultron-demo trigger

Most activities in the demos so far were posted to an inbox, which is how an actor reacts to another actor. The reporter's report submission already went through a trigger: its own submit-report. This demo shows the other direction: an actor initiates a protocol behavior from its own state by calling one of its trigger endpoints, and the actor's behavior tree builds the activity and queues it for delivery. Two workflows are shown:

  1. Validate and engage — the reporter submits a report; the vendor then calls POST /actors/{actor_id}/trigger/validate-report and POST /actors/{actor_id}/trigger/engage-case.
  2. Invalidate and close — the reporter submits a second report; the vendor calls trigger/invalidate-report, ends the case's embargo with trigger/terminate-embargo, and then closes the case it opened for the report with trigger/leave-case, which sends Leave(VulnerabilityCase). Once a case holds a report, its owner closes the case this way, never with Close(Report). The vendor ends the embargo first because leave-case refuses the Case Owner while the case's embargo is active.

Notice that each trigger response carries the ActivityStreams activity the actor produced, and that the activity appears in the vendor's outbox. This is the mechanism the multi-actor container demos use to drive every actor; see Running the Multi-Actor Container Demos.

See the Trigger API reference for every trigger endpoint.


Step 8 — Run all demos in sequence

To run every demo in the standard order, use the all sub-command:

vultron-demo all

The all sub-command runs each demo in turn and prints a summary table when it finishes:

==================================================
Demo Summary
==================================================
  receive-report                        ✅ PASS
  initialize-case                       ✅ PASS
  ...
  trigger                               ✅ PASS
==================================================
  <passed>/<total> demos passed
==================================================

A demo that raised an exception is listed as ❌ FAIL with the error.


Step 9 — Exit the container

When you are finished, type exit to leave the container shell:

exit

Stop the API server with:

docker compose -f docker/docker-compose.yml down

What we accomplished

We have:

  • run every remaining Vultron demo sub-command, covering the major protocol workflows,
  • observed how the Vultron Protocol handles case initialization, actor management, embargo negotiation, the RM state machine, and trigger-driven behavior, and
  • run the complete demo suite in one shot using vultron-demo all.

Next steps

  • Understand the protocol — browse the Vultron AS Activity Guides for task guides covering everything we observed.
  • See the protocol run across containers — Running the Multi-Actor Container Demos drives whole cases across isolated actors using the trigger endpoints.
  • Explore the demo scripts — the exchange demos are in vultron/demo/exchange/; shared utilities are in vultron/demo/utils.py.
  • Read the demo README — vultron/demo/README.md describes the demo architecture and available sub-commands.