This guide is the RUN-01 procedural companion to guides/telemetry.md. That file owns the ops event catalog for [:accrue, :ops, :*] — Accrue does not duplicate that table here. Use this document for ordered triage, Oban queue defaults, expanded Stripe verification, and the four mini-playbooks where sequence matters.
Library vs host: Accrue ships workers and suggested queue names; your host application configures and starts Oban (queues, concurrency, pruning). Queue names below are defaults Accrue documents in code — you may remap them in host config; treat symptoms and checks as patterns, not hard-coded production names.
Oban queue topology
Queue names are host-configurable; the table lists Accrue’s documented defaults from use Oban.Worker in accrue/lib today.
| Queue (default name) | Worker module | Role / when to look | Typical symptoms | Safe first checks |
|---|---|---|---|---|
:accrue_webhooks | Accrue.Webhook.DispatchWorker | Async webhook handler dispatch after ingest | Webhooks stuck :processing, DLQ growth, dead-letter ops | Inspect accrue_webhook_events, Oban retries for this queue, handler logs (no raw bodies) |
:accrue_mailers | Accrue.Workers.Mailer | Transactional email delivery | Mail backlog, PDF/email failures surfacing as ops | Oban job args shape, mailer adapter, ChromicPDF availability |
:accrue_meters | Accrue.Jobs.MeterEventsReconciler, Accrue.Jobs.MeteredRenewalReconciler, Accrue.Jobs.ProcessMeteredRenewal | Meter usage reconciliation, stale renewal repair, and metered settlement | meter_reporting_failed, metered renewal repair, metered settlement recovery | Reconciler jobs, Stripe meter API health, Braintree renewal evidence, accrue_meter_events, accrue_metered_renewals |
:accrue_dunning | Accrue.Jobs.DunningSweeper | Subscription dunning sweeps | Unexpected dunning transitions | Scheduled runs, subscription state vs Stripe |
:accrue_reconcilers | Accrue.Jobs.ReconcileChargeFees | Fee reconciliation for charges | Fee drift vs Stripe balance | Reconciler errors, Stripe charge/balance transaction lookups |
:accrue_reconcilers | Accrue.Jobs.ReconcileRefundFees | Fee reconciliation for refunds | Refund fee mismatches | Same as above for refund path |
:accrue_scheduled | Accrue.Jobs.DetectExpiringCards | Card expiry notices / hygiene | Missing expiry emails, card warnings | Job schedule, customer PM metadata (PII-safe) |
:accrue_maintenance | Accrue.Webhook.Pruner | Webhook event retention pruning | Prune telemetry anomalies | Retention config, maintenance window, dry-run if offered |
Stripe verification pattern
Use a two-layer mental model whenever Stripe is involved:
- Accrue layer (operational): local rows (
accrue_*tables), telemetry andoperation_id, foreign keys and Stripe ids stored by Accrue (cus_*,sub_*,pi_*, Connect account ids, etc.). This is application state for billing workflows — useful for triage, not a substitute for Stripe’s financial records. For customer billing portal failures, correlate[:accrue, :billing, :billing_portal, :create]:stop/:exceptionlatency withaccrue.customer.idandoperation_idpertelemetry.md— do not paste%Accrue.BillingPortal.Session{}inspect output into tickets. For Stripe Checkout sessions created viaAccrue.Billing.create_checkout_session/2, use the same pattern on[:accrue, :billing, :checkout_session, :create], confirm whether the host runsAccrue.Processor.Fakevs live Stripe, and read the PII-safe metadata contract attelemetry.md#billing-checkout-session-create— do not paste session URLs orclient_secretvalues into tickets. - Stripe layer (verification): confirm each issue against the Stripe resource type + id using canonical documentation (e.g. Webhooks, Testing webhooks, Billing meter events) and functional Dashboard paths (e.g. Developers → Webhooks → event deliveries) rather than brittle deep links.
For finance and tax reporting, use Stripe Dashboard / reporting products as your source of truth; Accrue focuses on state, webhooks, and replay in your app.
Mini-playbook: [:accrue, :ops, :webhook_dlq, :dead_lettered]
- Confirm scope: identify
event_id/processor_event_idfrom telemetry or admin (do not paste full webhook payloads or secrets into tickets). - Inspect the
accrue_webhook_eventsrow and last error; decide fix vs replay before mutating data. - Check Oban for
Accrue.Webhook.DispatchWorkeron:accrue_webhooks(see Oban queue topology); ensure the host queue is running and not wedged. - If replay is required, prefer admin-gated or documented replay flows; use dry-run when available — avoid destructive deletes from this path.
- Cross-check the same event type in Stripe via Developers → Webhooks → recent deliveries (Webhook docs).
- After fix, enqueue or allow retry; watch
[:accrue, :ops, :webhook_dlq, :replay]and related metrics for confirmation.
If the dead-lettered row is Braintree-sourced or tied to local portal checkout:
- Confirm whether the failed row should reduce into
accrue.portal.checkout.completedor a normalized subscription/invoice event before replaying it. - Fix host-local causes first:
portal_base_url,portal_mount_path, auth/session continuity, or Hosted Fields readiness. - Replay the persisted row only after the mounted path is healthy; Braintree recovery is local projection convergence, not an upstream hosted checkout retry.
Mini-playbook: [:accrue, :ops, :events_upcast_failed]
- Record
event_id,type, andschema_versionfrom the ops metadata (identifiers only). - Determine whether a deployed upcaster is missing vs bad persisted data — do not replay until the schema path is understood.
- Inspect
Accrue.Events/ event storage per your host (see catalog row intelemetry.md); align with code version in the running release. - Verify Oban or inline retry behavior will not amplify a bad version skew; pause automated replay if unsure.
- Queue topology for indirect jobs: see Oban queue topology if downstream dispatch is involved.
- Validate against Stripe only if the failing payload is a Stripe-sourced event; use Event object docs for shape, not as ledger truth.
Mini-playbook: [:accrue, :ops, :meter_reporting_failed]
Always read the contract (when the tuple fires and what each source means) at telemetry.md#meter-reporting-semantics before changing alert thresholds—this runbook is procedure only.
- Read
source(:sync,:webhook,:reconciler) plusmeter_event_id/event_namefrom metadata (identifiers only—no raw payloads). - Load the matching
accrue_meter_eventsrow and notestripe_status,stripe_error, and timestamps so you know whether the failure epoch is already terminal.
:sync (host request path)
- Correlate with the host request or job that called
Accrue.Billing.report_usage/3in the same transaction window; inspect logs aroundAccrue.Billing.MeterEventActionsfor processor errors surfaced synchronously. - Fix configuration or upstream Stripe errors, then retry the host operation with a fresh
operation_idonly when the business case requires a new attempt—idempotent replays should converge on the stored terminal row.
:reconciler (Oban :accrue_meters)
- Inspect Oban jobs for
Accrue.Jobs.MeterEventsReconcileron:accrue_meters(Oban queue topology); confirm the queue is running and not wedged behind retries. - After correcting Stripe meter setup or credentials, allow the reconciler to dequeue; watch
[:accrue, :ops, :meter_reporting_failed]and default metrics for confirmation.
:webhook (meter error report path)
- Trace the event through
accrue_webhook_eventsintoAccrue.Webhook.DefaultHandlerand the asyncAccrue.Webhook.DispatchWorkerpath; verify signature + dispatch health before mutating rows (Oban queue topology). - Resolve the upstream Stripe meter error, then replay or wait for the next reconciler pass; confirm the row leaves terminal
failedonly when business logic intentionally clears it.
Shared verification (all sources):
- Confirm API keys and Stripe meter configuration for the environment (no key material in logs).
- Cross-check Stripe usage reporting with Metered billing — operational alignment, not accounting close.
- After code or config fix, allow reconciler retry where applicable; watch ops counters and host metrics.
Mini-playbook: Braintree metered renewal and settlement recovery
These steps apply to the Braintree-local metering tuples documented in
telemetry.md. The ordering matters because Accrue's
local invoice ledger is canonical and Braintree is settlement-only in
this flow.
[:accrue, :ops, :metered_renewal_stale_repaired]
- Confirm the affected
metered_renewal_idmaps to a subscription period that should already have advanced. - Inspect the corresponding subscription in Braintree and verify the cycle actually renewed; the backstop should mirror webhook truth, not invent renewal windows.
- Check
Accrue.Jobs.MeteredRenewalReconcilerandAccrue.Jobs.ProcessMeteredRenewalon:accrue_meters(Oban queue topology) so the repaired window continues into local invoice authoring and settlement. - If the renewal only became visible after webhook backlog or replay work, pair this tuple with
[:accrue, :ops, :webhook_dlq, :replay]so the replay trail and the stale-window repair tell one story.
[:accrue, :ops, :metered_missing_definition]
- Inspect the renewal window and its unmatched meter events; identify which
event_namerows lack a local meter definition. - Add or repair the missing definition so future windows classify those events explicitly.
- Replay the same renewal window after the definition exists; do not create ad-hoc manual charges that bypass the local invoice decomposition.
[:accrue, :ops, :metered_charge_awaiting_payment_method]
- Repair or replace the customer's default vaulted payment method.
- Confirm the local invoice for that renewal window is still the correct settlement target.
- Replay the same renewal window so Accrue reuses the existing charge unit instead of creating a second
Transaction.sale. - If checkout completion is still ambiguous for the same customer, verify whether
accrue.portal.checkout.completedalready persisted locally before creating any manual recovery plan.
[:accrue, :ops, :metered_charge_failed_exhausted]
- Confirm the failure class and the current local invoice state before retrying anything.
- Decide whether to retry, write off, or pair the failed renewal with a later operator-approved recovery step.
- Preserve the original failed attempt trail; do not delete the renewal or charge-attempt rows to force a clean slate.
Mini-playbook: [:accrue, :ops, :revenue_loss]
- Capture
reason,subject_type,subject_id, and currency amounts from telemetry (aggregates / IDs only — no customer narrative in shared logs). - Triage Accrue rows (invoice, credit note, adjustment) that triggered the signal; avoid manual balance edits without a controlled procedure.
- Check related async work on
:accrue_reconcilersand:accrue_webhooksif the loss correlates with webhook or fee reconciliation (Oban queue topology). - In Stripe, locate the same business object (charge, refund, dispute) via Dashboard search or list filters; use Balance transactions categories as reference for classification, not as instructions to reproduce Sigma in-app.
- Document outcome in your ticketing system; escalate finance questions on Stripe’s side, not via Accrue as a ledger substitute.
RUN-01 coverage
- Full ops tuple list and one-line first actions live under
## Operator runbooks (first actions)intelemetry.md— bookmark that table for every RUN-01 class, including:connect_account_deauthorized,:connect_payout_failed,:dunning_exhaustion,:charge_failed,:incomplete_expired,:pdf_adapter_unavailable, replay (:webhook_dlq, :replay), and prune (:webhook_dlq, :prune). - This file adds depth for the four classic mini-playbooks above plus the Braintree metered-billing recovery sequence.
v1.59 multi-rail and offline runbooks
These procedures use the bounded actions in Accrue.Entitlements.Repair. The
host authorizes every action and supplies the account, actor, reason, operation
ID, and bounded target. Start with a read-only diagnostic and a dry run where
the action supports it. Record only the scenario/runbook ID, safe correlation,
actor, reason, and before/after revision. Stop when authorization, target
identity, provider health, or the post-action convergence check is uncertain.
No procedure here may automatically reconstruct an account, transfer or merge ownership, refund, cancel, migrate, prorate, or otherwise mutate provider or financial state. Escalate those decisions to the host's approved finance or product process.
V159-RUN-MISSED-NOTIFICATION — missed notification recovery
- Confirm
apple_purchase_to_web_loginor the affected scenario ID, account revision, and safe lineage correlation from the bounded diagnostic. - An authorized operator records the reason and runs a dry-run of
retry_missed_notificationagainst that one lineage/environment target. - If the dry run is correct, queue the named action once and wait for the diagnostic's next revision; stop on an ambiguous lineage or provider error.
- Record the post-convergence revision. Do not replay raw notification data or alter a provider purchase.
V159-RUN-CURSOR — history cursor recovery
- Confirm the affected lineage, environment, cursor age, and the
interrupted_resumeor related scenario ID. - An authorized operator dry-runs
recover_history_cursorfor the bounded lineage/environment target, then confirms the recorded reason. - Run the action only after confirming it resumes a known cursor; stop if a cursor would be guessed, rewound without evidence, or crosses environments.
- Verify the new diagnostic revision and record the safe correlation.
V159-RUN-PROVIDER-OUTAGE — provider outage or rate limit
- Confirm the provider freshness state, retry age, and safe correlation; do not copy provider payloads or credentials into the incident.
- Hold new issuance when key or provider health is unsafe, preserve the last known canonical snapshot, and use bounded retry/backoff.
- An authorized operator dry-runs
retry_provider_checkfor one lineage and environment, then queues it when the provider has recovered. - Stop on repeated rate limits or an unknown response; record the post-action
revision and open
V159-WL-APPLE-APIorV159-WL-STRIPEreassessment.
V159-RUN-OWNERSHIP-CONFLICT — ownership conflict containment
- Confirm the bounded diagnostic state and
survivor_grantscenario; retain the safe correlation and current revision. - An authorized operator dry-runs
review_ownership_conflictand records the actor and reason. - Submit the review action only to contain and audit the conflict. Stop when a resolution would require account reconstruction, transfer, or merge.
- Verify the review disposition and route any ownership decision to the host's approved product/support process.
V159-RUN-DUPLICATE — duplicate-charge escalation
- Confirm
duplicate_purchase_prevention, the bounded diagnostic, and a safe correlation; never place charge or transaction material in the ticket. - An authorized operator dry-runs
escalate_duplicate_charge, then records the actor, reason, and current revision. - Submit only the escalation action and stop. It does not refund, cancel, adjust, or mutate a provider charge.
- Verify the escalation disposition and hand finance resolution to the host's
approved process. Reassess
V159-WL-SUPPORTwhen the trigger recurs.
V159-RUN-DEVICE — stale or revoked device replacement
- Confirm
device_replacementorrefund_revocation, the device state, safe correlation, and canonical revision from the bounded diagnostic. - An authorized operator dry-runs
replace_revoked_device; it accepts no proof material, token, or credential. - Give the learner the literal next action to reconnect and register the replacement device. Stop if a stale state would expand access before reconnect.
- Verify the new revision and retain only the redacted outcome.
V159-RUN-KEY-ROTATION — signing-key compromise or rotation
- Treat a suspected compromise as a security incident. Record the affected key set identifier and safe correlation, never key material or signed proof.
- Pause issuance where the host cannot verify safe key access, then have an
authorized operator dry-run
rotate_signing_keysfor the bounded key-set target. - Run the named action after the host's key-management approval; stop on any algorithm fallback, unverifiable proof, or unknown key state.
- Verify post-rotation convergence, run the relevant golden vectors, and open
the dated
V159-WL-SECURITYreassessment.
V159-RUN-BACKLOG — reconciliation backlog drain
- Confirm backlog age, queue health, limit, and safe correlation from the bounded diagnostic; do not inspect or publish worker arguments.
- An authorized operator dry-runs
drain_reconciliation_backlogwith a small, bounded limit and recorded reason. - Run the action only while provider and queue health are stable. Stop on a growing backlog, rate limit, or post-action convergence failure.
- Verify the revision and backlog age after each bounded batch. Open
V159-WL-HOSTwhen host queue or resource changes are implicated.
V159-RUN-APPLE-INGRESS — Apple notification ingress triage
- Read response-class trends and retain only a safe correlation. Treat
429as temporary backpressure; compare it with the trusted deployment edge or shared rate policy before changing the host's single-node backstop. - Review quarantine growth, reconciliation age/backlog, and
needs_repairin the authenticated diagnostic. Do not inspect or attach provider evidence, worker arguments, or failure detail to the incident. - Confirm the named
:accrue_entitlementsqueue and reconciliation sweeper are healthy, then allow the existing bounded job to converge. Stop and escalate if backlog age grows, a dependency remains unavailable, orneeds_repairpersists. - Record the response trend, safe correlation, job state, and next action. This runbook does not authorize an automatic grant, ownership, finance, or provider mutation.
V159-RUN-APP-REVIEW, V159-RUN-PRIVACY, and V159-RUN-ROADMAP
- App Review: record the storefront change or rejection, review only supported in-app purchase/restore/management wording, obtain product/legal approval, and stop before publishing an external-purchase or runtime claim.
- Privacy: record the jurisdiction, DSR, retention proposal, or redaction finding; confirm bounded diagnostic access and remove sensitive evidence before release. Stop until the host's privacy owner signs the reassessment.
- Roadmap: when Android or a second adopter triggers Google Play work, create a dated proposal for a separate rail-policy research milestone. Do not add Play parity opportunistically.
See also
guides/telemetry.md— ops catalog SSOT and Operator runbooks (first actions) tableAccrue.Telemetry.Ops—emit/3contract (lib/accrue/telemetry/ops.exin the repo; published API on Hexdocs)- Hexdocs path pattern:
https://hexdocs.pm/accrue/(pin the version to yourmix.lock)