{
  "id": "ADR-002",
  "title": "Publish accepted-rate events through an outbox",
  "status": "reconsider",
  "date": "2026-02-03",
  "owner_role": "order-consistency-owner",
  "question": "How should an accepted rate become publishable without a dual-write gap?",
  "context": "The synthetic order transaction stores the accepted rate before downstream invoicing and tracking consumers receive its event.",
  "constraints": [
    "order state and publication intent must commit atomically",
    "a broker outage must not reject an otherwise valid order",
    "duplicate delivery is acceptable only with stable event identity",
    "the database may not absorb an unbounded outbox backlog"
  ],
  "alternatives": [
    {
      "name": "direct broker publish",
      "current_state": true,
      "disposition": "rejected",
      "reason": "the database commit and broker acknowledgement leave an unclosed dual-write window"
    },
    {
      "name": "periodic order-table scan",
      "disposition": "rejected",
      "reason": "publication intent and stable event identity would be inferred after the transaction"
    },
    {
      "name": "transactional outbox",
      "disposition": "selected",
      "reason": "order state and a stable publication fact share one commit while broker delivery remains retryable"
    }
  ],
  "evidence": [
    {
      "id": "outbox-baseline",
      "observed_at": "2026-02-02T12:00:00Z",
      "source": "metrics/outbox-initial.json",
      "facts": {
        "p95_unpublished_age_seconds_30m": 18,
        "database_io_percent_30m": 11
      },
      "uncertainty": "synthetic half-hour snapshot without a broker outage"
    },
    {
      "id": "outbox-later",
      "observed_at": "2026-05-12T12:00:00Z",
      "source": "metrics/outbox-later.json",
      "facts": {
        "p95_unpublished_age_seconds_30m": 94,
        "database_io_percent_30m": 17
      },
      "uncertainty": "synthetic half-hour snapshot; cause and replacement remain undecided"
    }
  ],
  "decision": "Write an immutable accepted-rate event to the outbox in the order transaction and publish it with stable event identity.",
  "consequences": {
    "positive": [
      "order state and publication intent commit atomically",
      "broker failure becomes retryable work"
    ],
    "negative": [
      "consumers must handle duplicate delivery",
      "the database stores and scans publication backlog"
    ],
    "obligations": [
      "measure unpublished age and database IO",
      "retain idempotent publication and recovery"
    ]
  },
  "links": [
    {
      "kind": "schema",
      "path": "system/outbox/schema.sql",
      "anchor": "accepted_rate_outbox"
    },
    {
      "kind": "publisher",
      "path": "system/outbox/publisher.mjs",
      "anchor": "publishPending"
    },
    {
      "kind": "failure-test",
      "path": "system/outbox/failure-test.mjs",
      "anchor": "broker failure retains publication fact"
    }
  ],
  "reversal_conditions": [
    {
      "metric": "p95_unpublished_age_seconds_30m",
      "operator": ">",
      "threshold": 60,
      "window": "30m",
      "source": "metrics/outbox-later.json",
      "owner_role": "order-consistency-owner"
    },
    {
      "metric": "database_io_percent_30m",
      "operator": ">",
      "threshold": 20,
      "window": "30m",
      "source": "metrics/outbox-later.json",
      "owner_role": "order-consistency-owner"
    }
  ],
  "status_events": [
    {
      "status": "active",
      "at": "2026-02-03T09:00:00Z",
      "reason": "accepted from outbox-baseline evidence",
      "owner_role": "order-consistency-owner"
    },
    {
      "status": "reconsider",
      "at": "2026-05-12T12:05:00Z",
      "reason": "outbox-later crossed p95_unpublished_age_seconds_30m > 60 for 30m",
      "owner_role": "order-consistency-owner",
      "next_decision": "compare outbox partitioning, publisher scaling, and an external log; no replacement selected"
    }
  ],
  "provenance": "synthetic fixture; no Brian-supplied experience"
}
