Skip to content

[lifecycle-core] Standardize monetization lifecycle signals and retention recipes #1571

Description

@kang-heewon

Related: #1511, #1522, #1524, #1525, #1541, #1546, #1570

Priority

P1 — add after the underlying billing, metering, credit, and quantity read models expose stable state.

Problem

@croco/lifecycle-core can evaluate generic SaaS signals and dispatch actions, but applications still need to invent their own event names, payload shapes, threshold semantics, cooldown rules, and recovery behavior for common monetization moments such as trial expiry, payment delinquency, usage thresholds, credit depletion, delivery lag, and seat drift.

This leads to incompatible recipes across applications and makes lifecycle automation difficult to inspect, test, or expose through admin tooling. Provider-specific webhook payloads also risk leaking into product rules instead of being normalized at the billing boundary.

Desired outcome

Add a small provider-neutral monetization signal vocabulary and tested reference recipes that applications can opt into without turning lifecycle-core into a billing engine or a visual workflow product.

await lifecycle.evaluate(
  createUsageThresholdCrossedSignal({
    tenantId,
    meterKey: "ai.tokens",
    planVersionRef,
    threshold: 0.8,
    consumed: 800_000,
    limit: 1_000_000,
    periodEndsAt,
  }),
);

Scope

Signal factories

Add stable factories and payload contracts for:

  • billing.trial.ending
  • billing.subscription.past_due
  • billing.subscription.recovered
  • billing.usage.threshold_crossed
  • billing.credit.balance_low
  • billing.credit.exhausted
  • billing.usage.delivery_lagging
  • billing.usage.sync_drifted
  • billing.seat.quantity_drifted

Every signal must include:

  • tenant identity;
  • immutable PlanVersionRef where applicable;
  • effective timestamp and source timestamp;
  • deterministic signal identity or idempotency material;
  • provider-neutral reason/status fields;
  • safe evidence suitable for diagnostics and dry-run inspection;
  • explicit recovery correlation where a recovered signal exists.

Threshold semantics

  • Define one-shot threshold crossing semantics rather than emitting on every usage record above the threshold.
  • Support ordered threshold levels such as 0.5, 0.8, 1.0, and configured overage bands.
  • Scope crossings by tenant, meter, plan version, and billing period.
  • Reset crossing state only when the billing period or governing plan version changes.
  • Keep hard quota enforcement in entitlements-core; lifecycle signals are informational/action triggers only.

Reference recipes

Provide code-declared recipes for:

  • trial ending reminders;
  • past-due grace-period notification and CS follow-up;
  • usage 80% upgrade prompt;
  • quota reached operator/customer notification;
  • low-credit warning;
  • billable usage delivery backlog escalation before period close;
  • provider/local usage drift escalation;
  • licensed seat quantity drift escalation.

Recipes must:

  • use existing lifecycle cooldown and idempotency semantics;
  • declare required context and supported actions explicitly;
  • remain provider-neutral;
  • be installable individually rather than auto-enabled;
  • include recovery behavior where appropriate;
  • work with [lifecycle-core] Add versioned rule activation and dry-run evaluation #1546 versioned activation and dry-run without special cases.

Composition boundaries

  • Billing, metering, credits, and membership adapters normalize their state into these signals; lifecycle-core must not import provider SDKs.
  • Do not duplicate payment retry schedules, invoice collection, or tax behavior owned by the provider.
  • Do not change entitlements directly from signal factories; applications may register audited actions that do so under explicit policy.

Diagnostics and ContractGraph

  • Include signal descriptors and recipe IDs in deterministic lifecycle artifacts.
  • Expose recent monetization signal counts, suppressed duplicate crossings, failed actions, and latest recovery state.
  • Ensure sensitive customer or payment data is excluded from default evidence.
  • Add ContractGraph/runtime diagnostics when an enabled recipe requires an unavailable signal source or action adapter.

Acceptance criteria

  • All signal factories produce deterministic, provider-neutral descriptors and stable public types.
  • Repeated usage records above one threshold emit one crossing per billing period and plan version.
  • Crossing a higher threshold after a lower one emits the higher threshold exactly once.
  • A period or plan-version transition resets threshold state deterministically.
  • Recovery signals correlate to the original delinquency/drift condition without replaying completed actions.
  • Reference recipes are opt-in, individually testable, and compatible with versioned activation/dry-run from [lifecycle-core] Add versioned rule activation and dry-run evaluation #1546.
  • Provider webhook payloads and provider IDs do not leak into public lifecycle rule predicates.
  • Signal evidence is redacted and safe for diagnostics/admin presentation by default.
  • Missing source/action capabilities fail explicitly rather than silently disabling a recipe.
  • Tests cover duplicate events, out-of-order provider events, threshold races, billing-period rollover, plan migration, recovery, cooldown, and action failure.
  • README, API docs, public API snapshots, lifecycle artifacts, and changesets are updated.

Verification

Non-goals

  • Implementing a campaign builder or customer messaging UI.
  • Owning provider payment retry schedules or dunning decisions.
  • Predictive churn scoring or experimentation allocation.
  • Automatically enabling lifecycle actions in generated applications.
  • Replacing billing, metering, entitlement, credit, or seat reconciliation state machines.

Metadata

Metadata

Assignees

Labels

P1Priority 1 issueapi-contractAPI, RPC, route, and schema contract workfeatureProduct feature or roadmap capability

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions