ADR-0045: Correct Field Assignment on Create(VulnerabilityCase) — context to Case URI, inReplyTo to Accept URI
Context and Problem Statement
ADR-0023 established the two-activity positive-path response to a
Create(as_CaseProposal): first Accept(as_CaseProposal), then
Create(VulnerabilityCase). For the second activity, ADR-0023 specified:
"Setting
contextonCreate(VulnerabilityCase)to theAccept(CaseProposal)URI was preferred over theCaseProposalURI directly, because the Accept is the proximate causal decision."
This field assignment has two problems:
-
AS2 semantic violation: AS2 defines
contextas a scoping/grouping key ("not specific to the activity but generally applicable") andinReplyToas the field for causal antecedents ("one or more Objects for which the object is considered a response"). Placing the Accept URI incontextputs a causal antecedent in the scoping slot. -
Infrastructure conflict: The inbox deferral router uses
contextto determine case identity (_activity_context_id()invultron/adapters/driving/fastapi/inbox_pending_queue.py). Whencontext = Accept URI, the router reads the Accept URI as the "case ID", finds noVulnerabilityCaseunder that key, and defers the bootstrapCreate(VulnerabilityCase)— causing a deadlock, since nothing will ever unblock the deferred queue for a case that will never be created.
The root cause: at the time ADR-0023 was written, the 29-site context = case
URI convention and the deferral router's context-reading behavior had not yet
been articulated as a system-wide invariant. CONCERN-1832 surfaced the conflict.
Decision Drivers
- AS2 semantic correctness:
context= scoping/grouping key;inReplyTo= causal antecedent - Infrastructure correctness: the inbox deferral router assumes
contextis the case URI for all case-scoped activities - Consistency with the 29-site
context = case URIconvention across the codebase (enforced by CLP-07-007 for payloadSnapshot fields) - No information loss: causal traceability to the Accept and the original
as_CaseProposalis fully preserved viain_reply_to→ Accept →object_
Considered Options
-
Correct the field assignment (chosen):
context = case URI,in_reply_to = Accept URI. Aligns with AS2 semantics, restores deferral routing, consistent with all other case-scoped activities. Requires updating CP-05-003,case_proposal_received_tree.py,VultronCreateCaseActivity, the deferral guard, and related tests. -
Keep
context = Accept URI, fix the router: ExemptCREATE_CASEfrom context-based deferral; resolve case ID from inlineobject_.id_instead. Works operationally but enshrines the AS2 semantic violation permanently and makesCreate(VulnerabilityCase)the sole exception to the 29-site convention. -
Keep
context = Accept URI, add belt-and-braces object-based fallback:_activity_context_id()already falls back toevent.context_idthen rawcontextattribute. Adding a third fallback forCREATE_CASEto useobject_.id_is technically possible but adds branching logic to a conceptually simple function, and still leaves the semantic violation.
Decision Outcome
Chosen option: correct the field assignment (Option 1).
Create(VulnerabilityCase) emitted by the CaseActor MUST carry:
context= URI of the newVulnerabilityCase(consistent with all other case-scoped activities; required for inbox deferral routing)in_reply_to= URI of theAccept(CaseProposal)activity (causal antecedent in the AS2-correct field)
The full causal chain is preserved transitively: in_reply_to → Accept →
object_ (inline as_CaseProposal embedded at Accept time per CP-05-003).
Additionally, the deferral guard MUST exempt MessageSemantics.CREATE_CASE
alongside ANNOUNCE_VULNERABILITY_CASE as a bootstrap message type — both
introduce a case that does not yet exist in the DataLayer when they first arrive.
Consequences
- Good: AS2 field semantics are correct.
contextis a scoping key;inReplyTois the causal link. - Good: Inbox deferral routing works without special-casing:
context = case URIpasses the guard naturally after theCREATE_CASEbootstrap exemption is added. - Good:
Create(VulnerabilityCase)is structurally consistent with all other case-scoped activities. - Good: No information loss — the causal chain Accept → CaseProposal is
recoverable via
in_reply_to. - Neutral:
contextandobject_.id_both carry the case URI onCreate(VulnerabilityCase), makingcontextredundant withobject_. This is acceptable: redundancy for structural consistency is a lower cost than an AS2 semantic violation. - Neutral: The
PendingCreateCaseActivityretry marker'screate_activity_payloadformat changes. Existing persisted markers with the old format will replay the old field assignment; this is acceptable for the prototype (no migration tooling required).
More Information
- Source concern: CONCERN-1832
- Supersedes (in part):
docs/adr/0023-case-proposal-protocol.md— field assignment section only; the CaseProposal vocabulary type, two-activity flow, and reject path in ADR-0023 remain correct and unaffected. - Spec:
specs/case-proposal.yamlCP-05-003 (amended) - Notes:
notes/case-proposal.md(amended),notes/activitystreams-semantics.md(new section: "AS2 Field Conventions: context vs inReplyTo") - Implementation:
vultron/core/behaviors/case/case_proposal_received_tree.py,vultron/core/models/activity.py,vultron/adapters/driving/fastapi/inbox_handler.py,vultron/adapters/driving/fastapi/inbox_pipeline.py
Generated spec requirements: specs/case-proposal.yaml CP-05-003 (amended).