Status: Accepted · Date: 2026-09 · Deciders: Aarokya Engineering ·
Affects: insurance policy create, health checkup create, consultation create, Superposition config plane
Context
Creating an insurance policy registered every member at Narayana and stored their MRNs — hardcoded, for every insurance benefit, whatever its provider. Creating a health checkup registered nobody. Consultation carried its own copy of the registration, which skipped Narayana’s patient lookup and could register the same person twice. Whether a benefit needs an MRN is a property of the benefit — its provider’s integration — not of its type or of the code path that creates it. More steps of this kind are expected.Options Considered
A — Keep the step inline, gated in code
A — Keep the step inline, gated in code
Pros: no new moving parts.Cons: every benefit that needs, or must skip, the step is a release, and the gate grows a
branch per benefit.Verdict: rejected.
B — Express the step in the action engine (ADR-007)
B — Express the step in the action engine (ADR-007)
Pros: fully config-driven, no Rust per step.Cons: the engine is one templated request and one rule. MRN creation is a token exchange, a
patient lookup, a register call per missing member and database writes — chaining, secrets and
persistence the engine does not have.Verdict: rejected. Growing the engine into a workflow runtime is a larger decision than this one.
C — Run the step asynchronously from a Kafka consumer
C — Run the step asynchronously from a Kafka consumer
Pros: decoupled from the request, with retries and a dead-letter queue.Cons: the MRN is not there when the create returns, a provider rejection no longer blocks the
purchase, and it reverses the fail-loud insurance contract.Verdict: rejected.
D — Config selects named steps; steps are typed Rust, run inline (chosen)
D — Config selects named steps; steps are typed Rust, run inline (chosen)
Pros: per-benefit targeting without a release. Each step keeps compile-time types, the owner’s
transaction and the owner’s error codes. One MRN path for every flow.Cons: absent or unresolvable config silently runs nothing, and a new step still needs code.Verdict: accepted.
Decision
Superposition’sbenefit.create_side_effects is an array of step names, default empty, resolved on
benefit_type, benefit_id, user_id and benefit_availed_date. Each name is a BenefitSideEffect
variant with an exhaustive Rust arm. CREATE_MRN is the first.
IP_1013 / IP_1014 and health checkup reports BEE_1708 / BEE_1709.
Consultation is gated too. A provider other than Narayana may not need an MRN, so consultation
reads the same key; unconfigured, the Narayana adapter refuses to open a thread. Configured, it also
gains Narayana’s patient lookup before registering.
Consequences
Gained
- A benefit gains or drops MRN creation by config.
- Health checkups can register the member at the provider.
- One MRN path: consultation no longer registers a patient Narayana already knows.
Trade-offs
- Silence on missing config. No client, a failed resolution or an unknown step name all run
nothing — and for a Narayana consultation benefit that means every create fails (
CN_1600). The live override must exist before the release that reads it. - Benefit ids differ per environment, so the committed fallback targets
benefit_typewhile each live workspace targetsbenefit_id. - A step is still a release. Config chooses among steps; it cannot define one.
CREATE_MRNon a non-Narayana benefit still calls Narayana until provider dispatch lands.
Related
- ADR-007 — the action engine, for single-request integrations config can fully describe.
backend/ai-docs/domains/benefit_side_effect/AGENT-NOTES.md— config, testing and gotchas.