This cookbook gives Stripe-tier adopter teams copy-paste-grade integration paths for the most common jobs-to-be-done. Every recipe follows the same fixed template — Goal → For → Prerequisites → Steps → Verification → Gotchas → Related — and uses only supported 1.x public seams from API Stability. Where a job touches a host responsibility, the recipe says so plainly in an honest boundary line so you integrate through stable seams instead of internals.
Each recipe maps to a named persona and flow from
User Flows & JTBD. Version strings are
1.x / ~> 1.0 throughout.
Gate a Stripe-tier audience
Goal
Roll out a feature to your premium accounts by targeting a Stripe-derived
tier attribute, and preview the bounded impact before you apply the change.
For
The Tech Lead working Flow 2: Target The Right Audience — moving a flag from a boolean convenience to a release control surface ("only for premium accounts?").
Prerequisites
- Rulestead installed and migrated (
mix rulestead.install,mix ecto.migrate). - A host path that maps a Stripe customer/subscription to a
tierstring (for example"pro","team","enterprise") — typically your existing Stripe webhook handler. - An admin policy wired so the acting operator may author audience mutations.
Steps
Carry the tier into evaluation as a bounded context attribute. The host resolves the tier; Rulestead just reads it:
context = Rulestead.Context.new(%{ targeting_key: "user-123", environment: "prod", attributes: %{"tier" => MyApp.Billing.tier_for(current_user)} })Before changing the audience, preview the bounded impact with
Rulestead.preview_audience_impact/3. The third argument carries options such as the environment scope:{:ok, preview} = Rulestead.preview_audience_impact("premium-accounts", :update, environment: "prod")Apply the mutation through the root facade's map form,
Rulestead.apply_audience_mutation/2. Pass the attributes as a map and the options (including the fresh preview evidence your policy requires) as the second argument:{:ok, result} = Rulestead.apply_audience_mutation( %{audience_key: "premium-accounts", operation: :update, rules: rules}, environment: "prod", preview: preview )
Verification
Rulestead.preview_audience_impact/3returns an:okpreview map whose fields declare the basis of the estimate and its uncertainty.- A premium-tier
%Rulestead.Context{}evaluates to the gated path, while a free-tier context falls through to the safe default.
Gotchas
- Boundary: the host owns the Stripe webhook and the population truth. Rulestead previews declare basis and uncertainty — they are bounded estimates, not an authoritative affected-user count. Treat preview evidence as evidence, not a census. See Product Boundary ("Host always owns → Population truth") and Footguns.
- Keep the
tierattribute stable across requests for the same actor so bucketing stays deterministic.
Related
Stream eval telemetry to Segment
Goal
Forward Rulestead's evaluation decisions into Segment (or any analytics client) so support and on-call engineers can correlate "what did this user see?" with your product timeline.
For
The Support Engineer and SRE / On-call Engineer working Flow 5: Explain One User's Reality — quoting evidence for one actor without re-running the incident in their head.
Prerequisites
- A Segment (or equivalent) client already wired in your application layer.
- A handler module attached during application startup or in your supervision tree.
Steps
Attach a handler with the safe wrapper
Rulestead.Telemetry.attach_many/4, subscribing to the public decision event:defmodule MyApp.RulesteadSegment do @events [[:rulestead, :eval, :decide, :stop]] def attach do Rulestead.Telemetry.attach_many( "my-app-rulestead-segment", @events, &__MODULE__.handle_event/4, nil ) end def handle_event([:rulestead, :eval, :decide, :stop], _measurements, metadata, _config) do MyApp.Segment.track("flag_evaluated", %{ flag_key: metadata.flag_key, environment: metadata.environment, reason: metadata.reason }) end endRoute only the bounded, stable metadata keys (
:flag_key,:environment,:reason, and friends). Correlate with your own request IDs from outside the Rulestead payload.
Verification
- After attaching, each evaluation emits a
[:rulestead, :eval, :decide, :stop]event your handler observes; a correspondingflag_evaluatedevent appears in Segment. - The forwarded payload contains only documented metadata keys — no raw actor attributes.
Gotchas
- Boundary: telemetry metadata is redacted by default. Raw actor payloads and targeting attributes are not part of the contract, so you cannot reconstruct user profiles from these events. The host owns the Segment client and any enrichment. See Telemetry and API Stability.
- Keep handler failures isolated — that is exactly what
Rulestead.Telemetry.attach_many/4buys you over a raw:telemetry.attach/4.
Related
Promote a change from staging to prod, reviewably
Goal
Move a configuration change from staging into a protected production environment as a governed, reviewable apply that leaves an audit trail — not an ad hoc SSH-and-pray mutation.
For
The Operator and Tech Lead working Flow 3: Preview Before You Regret It — making a reversible, reviewable change through an explicit approval path.
Prerequisites
- A
Rulestead.Admin.Policyimplementation wired by the host, with production marked as a protected environment. - A telemetry handler attached for the admin mutation event (see the Segment recipe for the attach pattern).
Steps
Let the policy decide whether the apply needs review. The governance callback
Rulestead.Admin.Policy.change_request_required?/4is the public seam — when it returnstruefor a protected environment, the apply is routed through review rather than executed immediately.Drive the promotion by outcome. The governed apply either lands directly or is held for an approval path according to your policy; in protected environments the system favors a reviewable change over an immediate mutation. You do not need to reach for internal command structs to get this behavior.
Observe the outcome through the public admin mutation event and the audit log:
Rulestead.Telemetry.attach_many( "my-app-rulestead-mutations", [[:rulestead, :admin, :mutation, :stop]], &MyApp.RulesteadAudit.handle_event/4, nil ) {:ok, events} = Rulestead.list_audit_events(environment: "prod")
Verification
- A protected-environment apply for which
Rulestead.Admin.Policy.change_request_required?/4returnstruesurfaces as a held / reviewable change, not an immediate write. - The
[:rulestead, :admin, :mutation, :stop]event fires for the apply, andRulestead.list_audit_events/1returns a row describing who changed what.
Gotchas
- Boundary: Rulestead provides governed apply plus Promotion / GitOps —
it is not a hosted control plane and does not authenticate actors.
Protected-environment gating is host-policy-driven through the
Rulestead.Admin.Policybehaviour. See Product Boundary ("Host always owns → Identity and authorization"). - Frame promotion by its outcome — a reviewable, audited change — rather than by any one internal entry point. The stable surface here is the policy callback, the mutation telemetry event, and the audit log.
Related
Gate an Oban background job
Goal
Gate a background job behind a flag, carrying explicit evaluation context from the enqueue site into the worker so the decision is deterministic and bounded.
For
The App Developer working Flow 1: Ship Behind A Flag — needing a safe seam in application code without building a control plane first.
Prerequisites
- Oban configured in your application.
- The Rulestead Oban middleware wired in host config.
Steps
Wire the middleware seam in host config:
config :rulestead, :host, oban: [ enabled: true, context_key: "rulestead_context", middlewares: [{Rulestead.Oban.Middleware, []}] ]Attach a bounded context when enqueueing with
Rulestead.Oban.Middleware.attach/2:job = %Oban.Job{args: %{"task" => "sync"}} |> Rulestead.Oban.Middleware.attach( context: %{targeting_key: "user-123", environment: "prod"} )Restore context in the worker via
use Rulestead.Oban.Workerand gate the work withRulestead.Runtime.enabled?/3:defmodule MyApp.SyncWorker do use Rulestead.Oban.Worker def perform(%Oban.Job{} = job) do context = rulestead_context(job) case Rulestead.Runtime.enabled?(context.environment, "sync-enabled", context) do {:ok, true} -> MyApp.Sync.run(job) _ -> :ok end end end
Verification
rulestead_context/1rebuilds a%Rulestead.Context{}from the bounded serialized payload — not from arbitrary process state.Rulestead.Runtime.enabled?/3returns a deterministic{:ok, boolean}inside the worker for the same context.
Gotchas
- Boundary: the Oban seam carries bounded context only. It is not a promise of hosted rollout orchestration, governance queues, or hidden admin mutation pipelines. If you want those workflows, build them in your own layer and keep Rulestead as the runtime decision engine plus context carrier. See Oban Background Jobs.
- Only bounded fields (
targeting_key,environment,attributes, and the like) are serialized — never a rawPlug.Connor LiveView socket.