Rename CVDRole.OTHER to CVDRole.OBSERVER and Define Observer Participant Semantics
Context and Problem Statement
CVDRole.OTHER was introduced as a catch-all placeholder for case participants
that do not fit any of the standard CVD roles (FINDER, REPORTER, VENDOR, DEPLOYER,
COORDINATOR). The draft Vultron Protocol spec (PR #2078, §7.3.3) proposed
renaming this role to "Observer" to give it a meaningful semantic identity. In
that draft the rename had been left open, pending a definition of what
an Observer is — the definition this ADR now supplies. The open questions at
that point were:
- What
case_rolesvalue does theInvitecarry for an Observer? (Empty list[]was indistinguishable from thecaseRoles=[]admission defect documented in issue #1288.) - Does an Observer receive full case content, or a reduced/redacted view?
- What does the RM triage cycle mean for a participant with no remediation obligations?
- When a participant holds OBSERVER alongside another role (e.g., DEPLOYER), which obligations apply?
This ADR resolves all four questions and formally renames the role.
Decision Drivers
- The rename cannot land in the spec without a definition: renaming an ambiguity is just relabelling it.
- CM-17-003 requires the
Inviteto carry a non-emptycase_roleslist; the admission path for an Observer must produce a valid, non-empty value. - The sentinel design (issue #2092) depends on knowing whether a sentinel, when admitted as a case participant, holds a defined role.
- The
coerce_cvd_rolesfallback inparticipant_status.pytreatsNoneand[]as[CVDRole.OTHER]; once renamed, the fallback must use the new value.
Considered Options
- Rename CVDRole.OTHER → CVDRole.OBSERVER — give the role a meaningful name and define its semantics explicitly (admission, content, RM, VFD).
- Keep CVDRole.OTHER; add CVDRole.OBSERVER as a separate new value — retain OTHER as a generic fallback for truly unclassified actors and add OBSERVER as a distinct named role for monitoring participants.
- Keep CVDRole.OTHER unchanged; define Observer in spec docs only — no code
change; Observer is just a name for
case_roles=[CVDRole.OTHER].
Decision Outcome
Chosen option: Rename CVDRole.OTHER → CVDRole.OBSERVER, because:
- The role's defining characteristic (monitoring / observing without remediation
obligations) is what
OTHERwas always intended to capture for the monitoring use case. Renaming makes the intent explicit. - Option 2 (keep OTHER, add OBSERVER) creates two roles whose distinction is unclear at admission time; agents and implementers would need to decide which to use, recreating the ambiguity.
- Option 3 (docs-only) leaves the enum name misleading for all future implementers.
Observer semantics (normative requirements in CM-25):
- An Observer is a full Tier-1 case participant: it holds a
CaseParticipantrecord, runs the RM lifecycle, and must satisfy the same embargo-consent gate as any other participant. - Observer is the base role — the lowest non-null privilege set in the protocol. All other CVD roles are additive to it.
- An Observer MUST NOT emit VFD state-transition messages (CV, CF, CD) when
OBSERVER is its only role. The RM cycle applies with "engagement not
remediation" semantics:
RM.ACCEPTEDmeans the Observer is actively engaged with the case, not that it has committed to developing or deploying a fix. - Full case content is delivered via the existing MV-10-005 gate (admitted + embargo-signatory). No new delivery tier is needed.
- Admission is the standard Invite/Accept flow; the
Invitecarriescase_roles=[CVDRole.OBSERVER], satisfying CM-17-003 and the CM-16-003 non-empty invariant.
Role stacking (normative requirements in CM-26):
- When a participant holds multiple roles, their permitted actions are the union of what each role permits. A MUST NOT restriction attached to one role is superseded when any other role in the list carries MAY, SHOULD, or MUST for the same action class.
- Example: OBSERVER + DEPLOYER can and should emit VFD/D transitions because DEPLOYER carries that obligation.
Consequences
- Good, because the
Invitewire format gains a clear, non-emptycase_rolesvalue for monitoring participants, closing the issue #1288 hole. - Good, because the sentinel question (issue #2092) is unblocked: a sentinel admitted as a case participant holds CVDRole.OBSERVER.
- Good, because
coerce_cvd_rolesfallback and field defaults remain correct (they just reference the renamed value). - Neutral, because the rename is a mechanical refactor across ~10 Python files and tests; no behavioral change.
- Watch out: when checking whether the VFD exclusion applies, code MUST test whether OBSERVER is the participant's only role, not merely whether they hold OBSERVER. A participant with OBSERVER + VENDOR must not be blocked from emitting VFD transitions.
Validation
- Spec-check CI (
uv run spec-dumplint) passes with CM-25 and CM-26 entries. test/core/models/test_case_participant.pywill be updated to useCVDRole.OBSERVER(replacingCVDRole.OTHER) andObserverParticipant(replacingOtherParticipant) as part of the code rename in #2192.test/core/behaviors/report/test_report_to_others_tree.pywas deleted by issue #1848 (ADR-0047 removal); no update in #2192 is needed.
More Information
Generated spec requirements: specs/case-management.yaml CM-25-001 through
CM-25-005 (Observer semantics) and CM-26-001 (role-stacking union principle).
Related issues: CONCERN-2093 (this decision), CONCERN-2092 (sentinel — blocked
by this ADR), #1288 (empty caseRoles defect), #1752 (role-request mechanism).