Skip to main content
← Back to list
01Issue
FeatureShippedSwamp ClubPublic
Assigneeskeeb

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.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 4 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/4/2026, 11:09:13 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb9/4/2026, 8:04:06 PM
keeb linked parent of #20089/4/2026, 7:41:03 PM

Sign in to post a ripple.