Skip to content
How-to guide

How to Seat a Participant on an Existing Case

Use this guide when an actor must join a case that already exists. An actor joins only by accepting the stub Invite the CASE_MANAGER sends it; no other message seats a member. You finish with the actor on the case roster, holding the roles you assigned, and on every participant's replica.


Prerequisites

  • A running Vultron instance you can drive. The demo containers are the quickest route — see Tutorial: Run the Receive-Report Demo for the two steps that get you a container shell.
  • An actor identity on that instance, holding the role each step names.
  • Working knowledge of the ActivityStreams verb, object, and target shape, summarized in Vultron and ActivityPub.
  • Working knowledge of the case model: the objects a case is made of, in The Case Model, and who is allowed to change them, in The CASE_MANAGER and the Case Ledger. Where a guide's messages are case-scoped, they route through the CASE_MANAGER and their results appear on the case ledger.

  • An existing case, and the Case Owner role on it.

  • The actor's Uniform Resource Identifier (URI).
  • The set of roles the actor will hold on this case.

The exchange

The flowchart below shows the two activities that seat the actor. The CASE_MANAGER sends the Invite at your request; the actor's Accept seats it.

---
title: Seating a Participant on an Existing Case
---
flowchart LR
    subgraph as:Invite
        RmInviteToCase["Invite Actor to Case<br/>Invite(Actor)"]
    end
    subgraph as:Accept
        RmAcceptInviteToCase["Accept Invite to Case<br/>Accept(Invite(Actor))"]
    end
    RmInviteToCase --> RmAcceptInviteToCase

Seat the participant

  1. Ask the CASE_MANAGER to invite the actor, naming the roles it will hold — see How to Invite an Actor to a Case. The CASE_MANAGER sends the stub Invite(Actor) and records an inert CaseParticipant for the actor, with the roles the Invite names.

    Invite actor to case

    Sends a Stub Invite carrying the case stub and the embargo terms the invitee would agree to.

    • When… the Case Manager invites an actor to join the case
    • Send: Invite(Actor)[target=VulnerabilityCaseStub]
    • Send to: a participant
    • Sent by: the case manager
    • Reference: Invite actor to case
    Example activity
    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Invite",
      "id": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/invitation/1",
      "name": "https://vultron.example/organizations/vendorco Invite Coordinator LLC to urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/stub",
      "context": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0",
      "content": "We're inviting you to participate in VendorCo Case #70930046.",
      "to": "https://vultron.example/organizations/coordinator",
      "actor": "https://vultron.example/organizations/vendorco",
      "target": {
        "@context": "https://certcc.github.io/Vultron/ns/context.jsonld",
        "type": "VulnerabilityCaseStub",
        "id": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/stub",
        "summary": "A memory-corruption vulnerability in widget — details shared after acceptance.",
        "caseId": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0"
      },
      "object": {
        "@context": "https://www.w3.org/ns/activitystreams",
        "type": "Organization",
        "id": "https://vultron.example/organizations/coordinator",
        "name": "Coordinator LLC",
        "published": "2026-10-23T20:17:06+00:00",
        "updated": "2026-10-23T20:17:06+00:00",
        "inbox": {
          "@context": "https://www.w3.org/ns/activitystreams",
          "type": "OrderedCollection",
          "id": "https://vultron.example/organizations/coordinator/inbox",
          "items": []
        },
        "outbox": {
          "@context": "https://www.w3.org/ns/activitystreams",
          "type": "OrderedCollection",
          "id": "https://vultron.example/organizations/coordinator/outbox",
          "items": []
        }
      }
    }
    
  2. The actor answers Accept(Invite(Actor)) to the CASE_MANAGER. The CASE_MANAGER commits the Accept as a ledger entry and seats the actor.

    Accept invite to case

    Accepts the Stub Invite. The actor joins the case at RM Received.

    • When… an invited actor agrees to join the case
    • Send: Accept(Invite(Actor)[target=VulnerabilityCaseStub])
    • Send to: the case manager
    • Sent by: the invitee
    • Reference: Accept invite to case
    Example activity
    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Accept",
      "id": "urn:uuid:98e201c4-4aa1-4f5b-ad44-e1f7fe4a41a6",
      "name": "https://vultron.example/organizations/coordinator Accept https://vultron.example/organizations/vendorco Invite Coordinator LLC to urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/stub",
      "inReplyTo": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/invitation/1",
      "content": "We're accepting your invitation to participate in VendorCo Case #70930046.",
      "to": "https://vultron.example/organizations/vendorco",
      "actor": "https://vultron.example/organizations/coordinator",
      "object": {
        "@context": "https://www.w3.org/ns/activitystreams",
        "type": "Invite",
        "id": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/invitation/1",
        "name": "https://vultron.example/organizations/vendorco Invite Coordinator LLC to urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/stub",
        "context": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0",
        "published": "2026-06-01T19:12:00+00:00",
        "updated": "2026-06-01T19:12:00+00:00",
        "content": "We're inviting you to participate in VendorCo Case #70930046.",
        "to": "https://vultron.example/organizations/coordinator",
        "actor": "https://vultron.example/organizations/vendorco",
        "target": {
          "@context": "https://certcc.github.io/Vultron/ns/context.jsonld",
          "type": "VulnerabilityCaseStub",
          "id": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0/stub",
          "summary": "A memory-corruption vulnerability in widget — details shared after acceptance.",
          "caseId": "urn:uuid:d483d06d-400e-47fd-9b8a-9a2643e3a4b0"
        },
        "object": {
          "@context": "https://www.w3.org/ns/activitystreams",
          "type": "Organization",
          "id": "https://vultron.example/organizations/coordinator",
          "name": "Coordinator LLC",
          "published": "2026-10-23T20:17:06+00:00",
          "updated": "2026-10-23T20:17:06+00:00",
          "inbox": {
            "@context": "https://www.w3.org/ns/activitystreams",
            "type": "OrderedCollection",
            "id": "https://vultron.example/organizations/coordinator/inbox",
            "items": []
          },
          "outbox": {
            "@context": "https://www.w3.org/ns/activitystreams",
            "type": "OrderedCollection",
            "id": "https://vultron.example/organizations/coordinator/outbox",
            "items": []
          }
        }
      }
    }
    
  3. The CASE_MANAGER sends the new participant the case and the ledger it missed, then the full-case Invite that asks it to judge the case. The ledger holds the messages exchanged: the CASE_MANAGER's entry for creating the inert record, then the invitee's Accept(Invite(Actor)) itself, which every other replica applies, and the CASE_MANAGER's entry for a vendor's VF status.

If all participants are known when the case is created, seat them inline on the Create(VulnerabilityCase) activity instead — see How to Initialize a Case.

Add(CaseParticipant) does not seat a member

Add(CaseParticipant) is the Case Owner's request to reinstate a participant it removed earlier. The CASE_MANAGER refuses an Add that names an actor that never joined, so an Add cannot take the place of the Invite. See How to Manage a Case Roster.

The binding is per case

A CaseParticipant binds one as:Actor to one VulnerabilityCase, so the same long-lived actor identity can hold different roles and statuses in each case it works. Invite the actor again, and it receives its own CaseParticipant, for each case.


Verify

The case roster holds the new CaseParticipant with the roles you assigned. The Accept(Invite(Actor)) appears as a ledger entry, and each participant's replica lists the new member.


See it end to end

Try it: vultron-demo initialize-participant

vultron-demo initialize-participant

Or with Docker Compose:

DEMO=initialize-participant docker compose -f docker/docker-compose.yml run --rm demo

Further reading