Reuse validate-report for Invited Actors; Derive VultronOfferRecord from Ledger Backfill
Superseded 2026-10-06 by ADR-0121. This is the original text, restored. It was rewritten in place on 2026-10-01 (Option 1 became Option 4 for ADR-0070; the on-behalf rules changed for ADR-0084), five weeks after acceptance, with no trail. The 2026-10-02 audit (#4195) ruled that a change of chosen option is a new ADR; the 2026-10-01 decisions are carried there.
CM-11-002 requires that invited actors run the standard RM triage cycle
(RM.RECEIVED → RM.VALID or RM.INVALID → RM.ACCEPTED or RM.DEFERRED) after
accepting a case invitation. The only RM-validation trigger in the codebase is
validate-report, which resolves the case's VulnerabilityReport via a
VultronOfferRecord keyed by offer_id.
Invited actors join via Accept(Invite) and receive a case replica via
Announce(VulnerabilityCase). They never process the original
Offer(VulnerabilityReport) directly, so their DataLayer initially has no
VultronOfferRecord. Before the add_report_to_case ledger backfill mechanism
existed, this meant invited actors could not call validate-report without a
synthetic injection workaround (seed-offer-record, a demo-only scaffold).
Three options were evaluated when CONCERN-2320 was filed.
Decision Drivers
- Protocol uniformity: all participants should run the same RM triage cycle.
- Avoid forking the RM state-machine path for invited vs. direct-path actors.
- The CaseActor already backfills
add_report_to_caseledger entries to all participants; this backfill embeds the fullVulnerabilityReportand original offer metadata.
Considered Options
-
Reuse
validate-report; deriveVultronOfferRecordfrom ledger backfill — Process theadd_report_to_caseledger entry to create both theVulnerabilityReportreplica and theVultronOfferRecordin the invited actor's DataLayer, then run the standardvalidate-reporttrigger. -
Separate
engage-case/validate-casetrigger — Add a new trigger that advances RM.RECEIVED → RM.VALID for invited actors without requiring an OfferRecord, separating "validate the original report offer" from "validate the case as a joining participant." -
Exempt invited actors from RM.VALID — Review whether the full RM triage cycle is required for invited actors; since CM-11-002 says SHOULD (not MUST), allow invited actors to advance directly to RM.ACCEPTED without RM.VALID.
Decision Outcome
Chosen option: Option 1 — reuse validate-report; derive VultronOfferRecord
from the ledger backfill, because it preserves full protocol uniformity, avoids
introducing a second RM advancement path, and is already implemented as the
add_report_to_case ledger backfill mechanism (ISSUE-2134,
ApplyOfferReportFromLedgerNode).
Consequences
- Good, because all participants run the identical RM triage cycle regardless of whether they received the original Offer or joined via invitation.
- Good, because no new trigger endpoint is needed; the existing
validate-reportandengage-casetriggers work unchanged for invited actors. - Good, because the
seed-offer-recorddemo scaffold can be removed, eliminating a misleading synthetic-injection path. - Bad, because the invited actor's
validate-reportcall is gated on theadd_report_to_caseledger entry being processed first; callers must poll or gate on this event before triggering (seerun_invite_path_rm_triageinvultron/demo/helpers/workflow.py).
Validation
ApplyOfferReportFromLedgerNode
(vultron/core/behaviors/sync/nodes/offer_report_effect.py) creates the
VultronOfferRecord from the add_report_to_case ledger entry.
run_invite_path_rm_triage (vultron/demo/helpers/workflow.py) demonstrates
the full invited-actor RM triage cycle using this mechanism without any
synthetic injection; its docstring explicitly states "no spoofing via
seed-offer-record is needed or permitted."
Pros and Cons of the Options
Option 1 — Reuse validate-report; derive VultronOfferRecord from ledger backfill
- Good, because protocol uniformity: one RM path for all participants.
- Good, because no new trigger or message type is needed.
- Good, because the ledger backfill already carries all necessary data (offer ID, report, offer actor).
- Bad, because invited actors must wait for the
add_report_to_casebackfill to be processed before callingvalidate-report.
Option 2 — Separate engage-case/validate-case trigger
- Good, because semantically explicit: "validate the case" is different from "validate the original report offer."
- Bad, because it forks the RM advancement path and requires a new trigger, a new BT subtree, and potentially a new message type for the notification.
- Bad, because it introduces two distinct ways to reach RM.VALID, making the protocol harder to reason about and test.
Option 3 — Exempt invited actors from RM.VALID
- Good, because simpler: invited actors advance directly from RM.RECEIVED to RM.ACCEPTED.
- Bad, because it degrades protocol fidelity: an actor that has not validated the vulnerability is recorded as having committed to remediation.
- Bad, because CM-11-002 SHOULD is intentional — it reflects the expectation that joining a case should still involve at minimum a validation decision. Exempting invited actors silently downgrades this expectation.
More Information
Generated spec requirements: case-management.yaml CM-11-005.
Source concern: CONCERN-2320.
Implementation: ISSUE-2134 (ApplyOfferReportFromLedgerNode,
vultron/core/behaviors/sync/nodes/offer_report_effect.py).
Cleanup tracked: seed-offer-record removal issue created when CONCERN-2320
was resolved.