New customer onboard
New customer onboarding is the simpler sibling of new_assembly_item. The cascade is narrower but the duplicate check is critical — a duplicate customer creates two AR records, two pricing trees, and a downstream mess that takes hours to unwind.
The 10-step cascade culminates in a "first-quote-context bundle" — a pre-built reference dossier so when the customer's first quote drafts, the AI has the full context (terms, MDF tier, sales rep, expected SKU mix) already loaded.
Risk level 3 (medium). The block-severity duplicate check is the main guard.
Trigger conditions
- A sales intake form is submitted via /intake.html.
- A bid award arrives for a previously-unknown customer (bid_award_notification chains here).
- A K-12 district or NYC DOE feeder customer is added during bid prep.
- A wholesale account is added during M&A integration.
Duplicate check runs by name AND email — both lowercase-matched. Common false positives are franchise locations of the same parent (e.g. "Driscoll Foods" vs "Driscoll Foods - Bronx"); Mike confirms with the sales rep before overriding.
The 4 beats
-
01
Write customer record
INSERT into
customerswith company name, contact, billing/shipping addresses, terms, credit limit, and sales rep. -
02
NS customer record
Enqueue NS customer creation. The NS internal id back-fills into D1 when the push drains.
-
03
Assign default customer_programs
INSERT default
customer_programsrows: payment_terms=Net 30, default MDF tier, default rebate tier. These can be edited later via program-specific workflows. -
04
Welcome email draft
AI drafts a welcome email introducing GFS, listing the sales rep, and providing the first-quote-context. Mike approves before send.
What's different after the workflow runs
- A new customers row + NS internal id back-filled.
- Default customer_programs assigned.
- AR setup with credit limit + terms.
- Welcome email draft waiting for Mike.
- First-quote-context bundle pre-built so first quote drafts faster.
- reflexion_log carries a
new_customertagged row.
What can go wrong and how to recover
Precondition blocks. Mike confirms with sales: is this truly a new location? If yes, override with a tagged note. If no, redirect to customer_record_review for the existing record.
Precondition warns. Often a typo; sales fixes and re-stages.
Retry policy is 3 exponential attempts. D1 holds the proposed state; drainer recovers within 15 minutes.
When chained from bid_award_notification, the bid context auto-populates the first-quote-context bundle — saves Mike a few minutes.
Adjacent workflows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Workflow contract | workflow_definitions WHERE workflow_type='new_customer' |
| Duplicate check | name (lower) OR email (lower) |
| Default terms | Net 30 |
| NS RESTlet | customscript_gfs_platform_query |
| First-quote-context | pre-built dossier for first quote |
| Reflexion tag | new_customer |
| Risk level | 3 |
| Expected duration | ~20 min |
| Trigger | manual · sources=sales_intake_form |
Dated trail · spot stale claims
Dated trail of when this doc was last touched, what changed, and what to look at if it feels stale.
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R586 | Added CHANGELOG · SCHEMA · RUNBOOK · BACKLOG sections — wiki became best-in-class operating documentation. | Mike + Claude |
2026-05-25 | R584/R585 | Wiki originally shipped — 8-section structure (hero / what / when / steps / outcomes / failure-modes / related / for-developers). | Mike + Claude |
workflow_definitions WHERE workflow_type='new_customer' before acting on these claims.The machine-readable spec
Canonical fields, table names, endpoint signatures. What code should match, what tests should assert. workflow_type · new_customer · risk_level · 3.
Inputs (required + optional)
| Field | Type | Description |
|---|---|---|
legal_name | string | Legal entity name. Required. |
contact_email | string | Primary contact. |
billing_address | json | Address object. |
customer_type | string | 'school' | 'commercial' | 'distributor' |
credit_terms | string | 'net30' | 'net60' | 'prepay' |
D1 tables written
| Table | Operation | Trigger |
|---|---|---|
proposed_actions | INSERT | Customer creation proposal |
customers | INSERT (post-HITL) | D1 mirror |
ns_pending_pushes | INSERT | NS customer creation |
customer_programs | INSERT | Default programs based on type |
Endpoints called
| Method | Path | Purpose |
|---|---|---|
POST | /api/workflow/execute | Runner entrypoint |
POST | /api/proposed-actions/:id/decide | HITL approval |
Events fired
| event_type | When | Subscribers |
|---|---|---|
customer.created | Post-HITL + NS push | customer_health init |
It broke at 2am — what now
Different from "how do I use this." This is the page Mike pulls up when something is wrong: logs to check, recovery steps, who to escalate to.
Scenario · Customer created in D1 but never pushed to NS
ns_push stub.
- Check:
SELECT * FROM ns_pending_pushes WHERE entity_ref LIKE 'customer:%' - Manual: Create in NS; reconcile customers.ns_internal_id.
Scenario · Duplicate customer created (same legal name)
No dedupe check.
- Find dupes:
SELECT legal_name, COUNT(*) FROM customers GROUP BY legal_name HAVING COUNT(*) > 1 - Merge: Pick canonical; update FK references; soft-delete dupe.
- Prevent: Add dedupe check in new_customer workflow before INSERT.
Scenario · Customer type set wrong — pricing tier off
Type drives default tier.
- Update:
UPDATE customers SET customer_type=?, pricing_tier=? WHERE id=? - Push to NS:
propose_customer_update
Logs to check
workflow_run_log· top-level run auditworkflow_step_log· per-step traceworkflow_verify_results· post-window verify outcomescron_locks· stuck cron lock detectionevents· workflow.completed / workflow.failed event trailreflexion_log· per-run narrative (if reflexion_enabled)npx wrangler tail· live Worker logs
Kill switch · emergency stop
If this workflow is misbehaving in a high-impact way (creating bad proposed_actions in volume, pushing wrong things to NS), flip a kill switch:
kill:ns_writes· stops every NS push platform-widekill:proposed_apply· stops HITL approvals from executing fan-outkill:high_risk_ops· stops risk_level >= 4 fan-out
See kill-switches-state-machine.html for the full state machine + recovery procedure.
Escalation
Primary: Mike Levine (single-admin) · mikelevine@globalfoodsolutions.co. For prolonged outage during business hours, notify warehouse lead + accounting lead so they can defer dependent work.
What's not done · what's uncertain
What's not done, what's uncertain, what we punted. Captured so it survives context switches and doesn't die in someone's head.
-
STUB
Auto-dedupe on legal_name + EIN
Today no dedupe.
-
OPEN
Credit check integration
Today credit terms are entered manually. Could integrate with a credit bureau API.
-
DECISION
Default credit terms by customer_type
Schools auto-net30, commercial varies. Should be codified.