# Managing site form submissions (the published-site → D1 inbound channel)

The business's published website has a contact / enquiry form, and that form is a **primary inbound-enquiry channel** — equal to phone and WhatsApp, not a side channel. Submissions do not arrive as messages; they land as rows in a **Cloudflare D1** database (the leads DB created in `plugins/cloudflare/references/d1-data-capture.md`). Nothing surfaces them automatically. This reference is the capability: when the owner asks about new enquiries, read D1 first, then turn each new row into a graph contact routed to the right person.

The failure this prevents is concrete: asked "any new sign-ups?", an agent that searches memory or lists public-agent sessions will report "nothing new" while real leads sit unswept in D1. Memory and public-agent sessions are not where site enquiries live. **D1 is.**

---

## "Any new sign-ups / contacts / enquiries?" → read D1 first

When the owner asks whether anything new has come in — sign-ups, contacts, enquiries, leads — the **first action is a D1 read of the leads DB**, before `memory_search`, before `contact_lookup` sweeps, before any public-agent `session-list`. The unswept set is the answer:

```bash
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --json --command \
  "SELECT id, name, email, message, created_at FROM leads WHERE swept = 0 ORDER BY id;"
```

This composes `plugins/cloudflare/references/d1-data-capture.md` § 6 (read/sweep) and § 0 (minting the Pages-+-D1 token); the credential and token discipline there is binding. Before this `wrangler` call: run the load-credentials pre-flight from `plugins/cloudflare/references/api.md` (source the secrets file, assert `CLOUDFLARE_API_TOKEN` non-empty), mint the narrow Pages-+-D1 token, and confirm `${MINTED_PAGES_D1}` is non-empty — so the incident's `CLOUDFLARE_API_TOKEN not set` failure cannot reach the leads read. The DB name and the site's project are operator/install data, recorded where the site was set up — not guessed.

**Outcome contract for the answer:** the transcript shows a D1 round-trip (`wrangler d1 execute … SELECT … WHERE swept = 0`) as the response to "any new sign-ups?", not a memory search. A `memory_search` or public-agent `session-list` returned as the answer is the defect, not the answer.

---

## Each unswept row → dedupe, create-or-update, then route

For every row in the unswept set, in order:

1. **Dedupe against the graph.** Look the person up by the identifiers the row actually carries. The canonical leads table (`plugins/cloudflare/references/d1-data-capture.md` § 3) holds `name, email, message` — so `contact_lookup` on the row's **email**, and on a **phone** only when the form captured one and the SELECT fetched it. Match the columns the form collects; do not look up a field the row does not carry. A match means update; no match means create.
2. **Create or update the contact.** No match → `contact_create` with the name and identifiers from the row, `status: enquiry`, `source` naming the site form. Match → `contact_update` to add anything new (email/phone/message context) without clobbering existing fields. Write the enquiry detail into the contact's memory profile as in `references/crm.md`.
3. **Route to the install's recorded front-line owner** (below).
4. **Sweep the row only after the graph write is confirmed.** Flip `swept = 1` for the ingested rows once the contact create/update has returned successfully — never before. An unconfirmed write followed by a sweep loses the lead silently.

```bash
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
  "UPDATE leads SET swept = 1 WHERE swept = 0;"
```

A row is the durable record until it is swept; the sweep is the commit, and it follows the graph write, not the other way round.

---

## Front-line-owner routing is operator data, never a baked name

A new site lead is assigned to the install's **recorded front-line owner** — the person enquiries route to for first contact and follow-up. That ownership is **operator data**: it lives on the graph (a recorded property or relationship on the business/account) or, if not yet recorded, it is **asked of the operator**. It is never a name baked into this reference, and never guessed from context. Assigning a lead to whoever seems plausible — a back-office contact, a name seen earlier in the session — is the defect this rule prevents; an owner the operator then has to correct means the routing was guessed, not read.

If the front-line owner is not recorded on the install, ask the operator who new enquiries should go to and record it, then route. (Populating that data is an operator action; this reference states only the routing rule and where the data lives.)

---

## Why D1, not memory

Memory profiles and public-agent sessions describe people the business already talks to. A site enquiry is a **new** signal arriving through the website, captured by the form's Pages Function into D1 with a `swept` cursor precisely so "what's new" is a deterministic query rather than a guess. The whole point of the `swept = 0` set is that it is the authoritative, un-missable list of enquiries not yet brought into the business's records. Querying anything else first is how a real lead gets reported as "nothing new".
