Skip to content

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 context on Create(VulnerabilityCase) to the Accept(CaseProposal) URI was preferred over the CaseProposal URI directly, because the Accept is the proximate causal decision."

This field assignment has two problems:

  1. AS2 semantic violation: AS2 defines context as a scoping/grouping key ("not specific to the activity but generally applicable") and inReplyTo as the field for causal antecedents ("one or more Objects for which the object is considered a response"). Placing the Accept URI in context puts a causal antecedent in the scoping slot.

  2. Infrastructure conflict: The inbox deferral router uses context to determine case identity (_activity_context_id() in vultron/adapters/driving/fastapi/inbox_pending_queue.py). When context = Accept URI, the router reads the Accept URI as the "case ID", finds no VulnerabilityCase under that key, and defers the bootstrap Create(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 context is the case URI for all case-scoped activities
  • Consistency with the 29-site context = case URI convention across the codebase (enforced by CLP-07-007 for payloadSnapshot fields)
  • No information loss: causal traceability to the Accept and the original as_CaseProposal is fully preserved via in_reply_to → Accept → object_

Considered Options

  1. 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.

  2. Keep context = Accept URI, fix the router: Exempt CREATE_CASE from context-based deferral; resolve case ID from inline object_.id_ instead. Works operationally but enshrines the AS2 semantic violation permanently and makes Create(VulnerabilityCase) the sole exception to the 29-site convention.

  3. Keep context = Accept URI, add belt-and-braces object-based fallback: _activity_context_id() already falls back to event.context_id then raw context attribute. Adding a third fallback for CREATE_CASE to use object_.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 new VulnerabilityCase (consistent with all other case-scoped activities; required for inbox deferral routing)
  • in_reply_to = URI of the Accept(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. context is a scoping key; inReplyTo is the causal link.
  • Good: Inbox deferral routing works without special-casing: context = case URI passes the guard naturally after the CREATE_CASE bootstrap 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: context and object_.id_ both carry the case URI on Create(VulnerabilityCase), making context redundant with object_. This is acceptable: redundancy for structural consistency is a lower cost than an AS2 semantic violation.
  • Neutral: The PendingCreateCaseActivity retry marker's create_activity_payload format 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.yaml CP-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).