# Keyword-rule migration inventory (automation-scope-1 Stage 5)

> Authored by the automation-scope-1 executor, 2026-07-20. **Inventory only —
> nothing is built into situations from here** (that is agent-scope-4's lane;
> out of scope per the kickoff). This records where each legacy `FlowRule` goes
> so the operator can make the kill/keep call. Execution of existing rules stays
> **legacy-frozen** meanwhile (the `BasicFlowEngine` path is untouched).

## The freeze (what changes now)

- **Creation of `keyword` / `any_inbound` rules is FROZEN in the v2 UI.** The v2
  rule schema (`@channels/shared/engine`) physically cannot express an inbound
  trigger — `EngineTriggerSchema` is `schedule | event` only, proven by
  `engine/types.test.ts` ("REJECTS legacy inbound-conversation triggers"). So no
  new inbound rule can be minted through the engine.
- **Existing `flow_rules` rows keep executing unchanged** via `BasicFlowEngine`
  on the WhatsApp inbound path. Stage 0 pinned that path
  (`basic-flow-engine.test.ts`, `matchesTrigger`) so v2 work cannot regress it.
- Nothing is dropped silently: every row is classified below and waits on the
  operator's ruling.

## The classification rubric

The engine's first law: **v2 owns time + events, never inbound conversation.**
Both legacy trigger kinds (`keyword`, `any_inbound`) fire on an inbound customer
message. Therefore **no legacy FlowRule can become an engine v2 event rule** —
the engine v2 inherits zero existing rules. Each row's only destinations are:

| Legacy rule shape | Proposed destination | Why |
|---|---|---|
| `keyword` → customer-visible reply (`send_template`, `start_questionnaire`) | **Situation trigger module** (agent-scope-4) | Answering an inbound keyword is the agent's lane; a situation's trigger module recognises the intent and the agent replies. |
| `any_inbound` → reply / default responder | **Situation trigger module** or the agent's default behaviour | "Reply to anything inbound" is exactly what the native agent already does; likely folds into agent default, rule then **killed**. |
| `keyword`/`any_inbound` → `apply_tag` only (internal bookkeeping, no reply) | **Operator ruling: kill or re-home.** A tag-on-inbound is still inbound-triggered, so it cannot become a v2 event rule. If the tag is still wanted, it becomes a situation-module side effect or is dropped. | Not customer-visible, but still inbound; no v2 home. |
| Duplicate / stale / test rules | **Kill** | Recorded, then deleted on the operator's word. |

**Net finding:** engine v2 starts with **only** the two seeded reminder preset
rules (Stage 3) plus whatever schedule/event rules the operator authors fresh.
Every legacy keyword/any_inbound rule routes to agent-scope-4's situations or is
killed — coordinate the exact situation-module destinations with that seat.

## Enumerating the live rows (orchestrator runs this)

The concrete rules live only in the live `flow_rules` table (they are created at
runtime through the UI — none are seeded in code). Run this read-only query on
the VPS to produce the per-rule inventory, then paste the result under
"Recorded inventory" below for the operator's ruling:

```sql
-- Read-only. Classifies each legacy rule by trigger kind + action shape.
SELECT
  id,
  name,
  is_active,
  trigger->>'kind' AS trigger_kind,
  CASE
    WHEN actions::text LIKE '%start_questionnaire%'
      OR actions::text LIKE '%send_template%'
      THEN 'situation-trigger-module (customer-visible reply)'
    WHEN actions::text LIKE '%apply_tag%'
      THEN 'operator-ruling (internal tag on inbound — kill or re-home)'
    ELSE 'operator-ruling (review)'
  END AS proposed_destination,
  actions
FROM flow_rules
ORDER BY is_active DESC, name;
```

(Equivalent Prisma: `prisma.flowRule.findMany()` then apply the same rubric — the
action union is `send_template | apply_tag | start_questionnaire`, see
`flow-rules.service.ts`.)

## Recorded inventory

_Paste the live query result here. Each row → operator ruling (kill / situation
module X / keep-frozen). Empty until the orchestrator runs the query against
live data — the executor has no DB access from the dev environment._

| id | name | active | trigger | proposed destination | operator ruling |
|---|---|---|---|---|---|
| _(pending live query)_ | | | | | |

## Live enumeration (orchestrator, 2026-07-20)

Read-only prod query run by the orchestrator:
`SELECT id, name, is_active, trigger, actions FROM flow_rules` -> **(0 rows)**.

**Inventory verdict: EMPTY.** No legacy keyword/any_inbound rule exists in
production; engine v2 inherits nothing, nothing routes to situations,
nothing needs an operator kill/keep ruling. Gate bar 7 is satisfied by this
record. The v2 schema freeze (no inbound trigger expressible) stands as the
permanent guard.
