Stop overdue janitorial invoices from lingering in the owner's inbox by sending only the notices policy allows, then stopping when payment or a dispute arrives.
Canonical Maple Dental follow-up on the operator console
Canonical workflow: invoice overdue → context/policy → permitted follow-up → wait → new evidence → stop/continue/escalate → audit
Maturity: fixture-backed + internally tested + browser-verified. No live mailbox or paid model was called.
./scripts/bootstrap
./scripts/demo # http://127.0.0.1:3452
./scripts/verifyProof: proof/report.md
An overdue Harbor & Pine invoice (Maple Dental, previously reliable) → decide(state, now) selects one polite reminder after 3 days, only inside weekday 14:00–22:00 UTC → one customer-visible email keyed invoiceId:step → payment or dispute stops the sequence; a second nag is not permitted → remaining is 0ドル or the owner reviews, with a replayable event log.
Not accounting, bookkeeping, payroll, tax, invoice creation, or a general AR suite. Not a CRM. It is the follow-up spine for already-overdue commercial cleaning invoices.
| Situation | What decide does |
|---|---|
| Newly late + previously reliable | One reminder on day 3 overdue, wait 10 days, then owner review. No step-2 nag. |
| Chronic lateness | Reminder on day 1, second on day 7, then owner. Still no threats. |
| Dispute flag or inbound dispute classification | Stop all sends. Flag → stopped_dispute. Model-classified dispute → waiting_human. |
| Payment in full | Stop, stopped_paid, remaining = 0. |
| Partial payment | Remaining reduced; continue only if cadence still permits, using the remaining amount. Never thank-for-paid-in-full. |
| Remaining ≥ 10,000ドル.00 | No automatic send; waiting_human. |
| Outside weekday 14:00–22:00 UTC | waiting_window until the next window (deterministic). |
| Downstream send failure | Retry once, then waiting_human. |
Forbidden customer copy: legal threats, "final notice" that implies a lawsuit, fees added, "we will send to collections." Templates are factual and scanned.
| Kind | Examples |
|---|---|
| Authoritative facts | Remaining amount, due date, payments, dispute flag, send history |
| Deterministic policy | Cadence, templates, stop rules, send window, high-value threshold |
| Model inference | Reply label promise_to_pay / dispute / noise; rejected if schema-invalid |
| Human decisions | Halt, escalate, mark dispute, record that a call happened |
Humans cannot waive balances in this system. Invalid model output fails safe to a human and is not persisted as a label.
Harbor & Pine Commercial Cleaning, a B2B janitorial shop. Overdue invoices currently consume owner attention while collection delay persists.
- Reliable-but-newly-late (Maple Dental Group)
- Chronic lateness (Dockside Warehouse)
- Dispute (Riverside Pediatrics)
- Partial payment (Eastbay Offices)
- Payment arriving during an active sequence (Dockside after a notice)
- High-value requiring human review (City Annex Facilities, remaining ≥ 10,000ドル)
Event log: data/store.json after a demo run. Domain: lib/domain/. Evaluation goldens: lib/fixtures.ts. Pure policy: decide(state, now) in lib/domain/policy.ts.