Relationships
↑ child of #2008#2009 feat(nurture): the flow catalog and the qualification contract (pure domain)
Opened by keeb · 9/4/2026· Shipped 9/4/2026
Parent: the product-nurture epic.
Problem
Six nurture flows need thresholds, a qualification rule, and an answer to "what counts as a repeat". Written inline in a sweep, those rules are untestable and drift between the sweep, the admin panel and the email copy. Written once as data, they are a table you can review before anything runs.
What ships
lib/domain/nurture/ — pure TypeScript, no I/O, no Deno.*, no clock beyond a
now passed in.
The flow catalog. One entry per flow: id, segment label, thresholds,
default channels, cooldown, and how occurrenceKey is derived. Proposed
defaults, all overridable:
| flow | qualifies when | occurrenceKey | repeat |
|---|---|---|---|
no_activation_30d |
account ≥30d old, zero CLI activity ever | once |
never |
model_no_workflow |
≥3 active days running model in 30d, zero workflow run ever |
once |
never |
extension_published |
published an extension | <extension name> |
per extension |
reactivation_7d |
≥3 lifetime active days, none in 7d | <UTC month of dormancy> |
90d cooldown |
needs_help_failure |
a failed run, then no activity for 3d | <UTC date of the failure> |
30d cooldown |
The qualification result type. A discriminated union, because this is exactly the shape #1589 exists to protect:
type Qualification =
| { status: "qualified"; evidence: Evidence }
| { status: "not_qualified"; reason: string }
| { status: "unknown"; reason: "no_bound_device" | "no_username" | ... }unknown is not not_qualified. An operative with no device binding has
unknown activity, not zero — running the CLI without swamp auth login looks
identical to never having installed it. A boolean beside the data does not
enforce this; the compiler does.
Evidence. The numbers behind a decision — account age, last active day, lifetime active days, distinct commands seen, device count, the failing command — stored on the row and rendered in the panel.
Verification
tests/domain/nurture_catalog_test.ts. Table-driven over fixtures per flow,
including the boundary days on both sides of each threshold, and at least one
case per flow that must return unknown rather than not_qualified.
Independently verifiable: it is pure, so deno task test is the whole gate.
Out of scope
Reads, writes, sends, UI. This issue produces no I/O of any kind.
Shipped
Click a lifecycle step above to view its details.