ADR-0001: CRM Settlement Identity Boundary
- Status: Proposed
- Date: 2026-04-03
- Author: @digiwedge/engineering
Context
The POS partner settlement flow accepts CRM-linked requests that may contain either quoteId, reservationId, or both. The reconciliation expansion under #13319 and the identity resolver work under #14033 separated trusted cross-ID identity from settlement admission by introducing a first-class SettlementAlias model and a shared SettlementIdentityResolver.
That change exposed a hard boundary that was previously blurred by acceptance-anchor backfill:
SettlementAcceptanceAnchorrows are safe as workflow ownership and idempotency state.- They are not safe as an authority for quote-to-reservation identity inference.
- Later ingress requests can repeat a known identifier while supplying a mistyped secondary identifier.
- Persisting that secondary identifier onto an existing anchor poisons future admission and replay behavior.
We removed anchor backfill from conflicting ingress for exactly that reason. That removal restores the trust boundary, but it also leaves an explicit operational window:
- request A can arrive with
quoteIdonly - request B can arrive with
reservationIdonly for the same booking - without trusted alias evidence or an upstream canonical booking identifier, the system cannot prove that both requests refer to the same booking at admission time
- exact-only admission can therefore create two separate anchors and two separate settlement attempts until reconciliation later promotes a trusted alias from a durable outcome
This is not a local implementation bug. It is an information-boundary problem. Preventing that double-admission window requires authoritative upstream identity proof.
The design options considered are:
- Add a canonical booking identifier to the partner/CRM settlement payload.
- Perform a synchronous upstream CRM lookup in the settlement hot path.
- Keep exact-only admission until a trusted alias exists, then let reconciliation catch up and normalize future traffic.
The current runtime also keeps reconciliation sync inside the API process. That is acceptable only as a temporary single-replica bridge and is already tracked separately in #14009.
Decision
We will treat CRM settlement identity as a two-tier model with an explicit trust boundary.
1. Trusted identity authority
SettlementAlias is the only trusted quote-to-reservation identity surface.
- Cross-ID enrichment must read only from
SettlementAlias. - Trust lifecycle remains explicit:
ACTIVE_TRUSTEDandINVALIDATED. - Reconciliation and admission must share the same resolver semantics through
SettlementIdentityResolver.
2. Acceptance-anchor role
SettlementAcceptanceAnchor is an ownership and idempotency surface only.
- It may store identifiers that were present on the request that created the anchor.
- It must not be mutated later with secondary identifiers copied from subsequent ingress.
- It must not be treated as trusted alias evidence.
- It must not perform cross-column identity inference.
3. Admission behavior before trust exists
Until authoritative upstream booking identity exists, CRM-linked settlement admission remains exact-only.
quoteId-only requests match only exactquoteId.reservationId-only requests match only exactreservationId.- Cross-ID normalization happens only when a trusted alias already exists.
- Reconciliation remains responsible for catch-up after a durable dual-ID outcome proves the mapping.
This means the double-debit window for one-sided requests is explicit and accepted as the current production-safe posture.
4. Long-term target
The preferred long-term design is a canonical booking identifier in the payload.
- This avoids adding CRM availability to the payment hot path.
- It removes the need for local heuristic identity inference.
- It lets admission dedupe the same booking without mutating anchors or depending on later reconciliation.
We do not choose synchronous CRM lookup as the long-term default because it adds a hard runtime dependency to the money-moving path. It remains a fallback option only if the payload contract cannot be evolved.
5. Evidence versus authority
If the business needs observability for unresolved or one-sided CRM settlements, we will add a separate evidence surface rather than reusing anchors as a trust store.
- Observed pairs and one-sided admissions may be recorded for analysis.
- That evidence must not directly drive admission, alias promotion, or replay normalization.
6. Reconciliation runtime follow-up
The current in-process reconciliation scheduler remains a bridge only.
- Same-process coalescing reduces local races.
- Cross-pod safety still requires externalizing or leasing the writer.
- That work stays tracked under
#14009.
Consequences
Positive
- The trust boundary is explicit and defensible.
- Later malformed requests cannot poison trusted identity by mutating existing anchors.
- Admission, replay matching, and reconciliation all converge on one trusted alias model.
- The architecture is honest about what the system can and cannot prove locally.
- The long-term path is clear: canonical booking identity in the payload.
Negative
- One-sided CRM admissions still have a real double-debit window until trusted alias promotion occurs.
- Some duplicate prevention moves from admission time to reconciliation time.
- Operators need visibility into unresolved identity cases and alias invalidation history.
- The platform still carries a single-writer reconciliation runtime constraint until
#14009lands.
Alternatives Considered
| Alternative | Pros | Cons | Why not chosen |
|---|---|---|---|
| Canonical booking identifier in payload | Zero hot-path lookup latency, no runtime CRM dependency, strongest admission-time identity | Requires CRM and partner contract change | Chosen as the long-term target, not immediately available |
| Synchronous CRM lookup before admission | Prevents one-sided double-admission once CRM can answer both directions | Adds CRM latency and availability coupling to payment flow, harder failure modes | Not chosen as the default long-term design |
| Continue anchor backfill from ingress | Blocks some duplicate-anchor cases without upstream changes | Persists unverified secondary identifiers and poisons future admission | Rejected as unsafe |
| Infer trust from durable anchors | Reuses existing data already in the ledger | Historical anchors contain backfilled data with unverifiable provenance | Rejected as unsafe |
| Exact-only admission plus reconciliation catch-up | Preserves trust boundary, no new upstream dependency | Leaves explicit double-debit window for one-sided requests | Chosen as the current production-safe posture until canonical identity exists |
Follow-ups
- Keep
SettlementAliasas the only trusted cross-ID authority in#14033. - Remove ingress-driven anchor backfill that mutates existing anchors with unverified secondary identifiers in
#13993. - Externalize reconciliation sync out of the API process in
#14009. - Add one-sided CRM settlement metrics and dashboards to measure the real size of the double-debit window in
#14049. - Add operator-grade alias review, invalidation, and audit tooling in
#14051. - Define and land the canonical booking identity contract change for CRM-linked settlements in
#14050. - Add unresolved-identity reporting that is observational only and does not change admission semantics in
#14052.