# Scope board — the living roadmap

> **This replaces the sequential phase list** (target picture §10; decision
> 2026-07-15, `docs/DECISIONS.md`). Scopes are standing workstreams — never
> "closed", they idle between work-packages. Work ships in **gated
> work-packages**: Fable kickoff → executor → Fable gate, same discipline as
> before. **WIP rule:** one package in flight per scope; a package passes its
> gate before its scope starts another; different scopes may run concurrently.
> **Deferrals land here, not in prose** — anything a plan or gate rules out
> must be written into a scope backlog below.

## Intake — operator drops ideas here, anytime

- ~~**⚠⚠ The Facebook private reply has NEVER once worked, and the fix is
  Meta-side.**~~ **ROOT-CAUSED AND FIXED IN OUR CODE — 2026-08-11, deployed
  (`664e5e7`).** It was never Meta-side and it was never the Messenger product.
  `FacebookClient.sendPrivateReply` called `POST /{comment_id}/private_replies`,
  an edge Meta's own reference says **"was removed after Graph API v3.2"** — and
  prod runs **v25.0**. That is why Graph answered its generic *"Unsupported post
  request. Object with ID … does not exist, cannot be loaded due to missing
  permissions, or does not support this operation"* **16 times out of 16** since
  2026-08-05 while Instagram was **18/18 ok on the same Page token**.
  ⚠ **"Read-only probing is exhausted" was wrong, and it is what kept this
  boarded as an operator action for three days.** The probe that settles it is two
  GETs: `GET /me` → 200 (the Page) and `GET /{comment_id}` → 200 with the comment
  body. The object existed and the token could read it, so only *"does not support
  this operation"* was left. **Before boarding a failure as Meta-side, eliminate
  the clauses of Meta's error string one GET at a time.**
  The fix is the Send API addressed by `comment_id` — the exact shape
  `InstagramClient.sendPrivateReply` already used, which is the whole reason IG
  worked and FB did not. **Still unproven: whether the endpoint was the ONLY
  blocker.** The oracle is one real comment on a live FB post recording
  `dm_opener` → `ok`. Detail: `plans/rethink/activity-posts-handoff.md` § D3.

- **The Dashboard cannot show a dead database (screens lane, 2026-08-04):**
  `red.health` and `red.moderation.failsafe` fire as notifications with **no
  computed row behind them** (DECISIONS § Design & UI, defect ③), so they appear
  behind the bell and never in "Not working" — the one band whose whole job is to
  say what is broken right now. Boarded rather than left in the lane's prose
  because it is a deferral, not a bug in what shipped: it needs a new
  `BreakageRow` kind and a producer, which is backend work nobody has scoped.
  Everything else the Dashboard promised is live.

- **Persist the IG username and the FB Page name (screens lane, 2026-08-04):**
  neither is stored anywhere, so `handle` is null on both social cards of the
  Dashboard and each card falls back to the channel name alone. Not a bug and not
  invented data — a gap. Proposal: read and persist both when a connection is
  TESTED, which is the one moment the token is already in hand. Needs an operator
  yes; boarded so it stops living only in the lane's prose.

- **Queue observability + send-safety audit (operator concern, 2026-07-29 —
  "afraid the workers might cause us to get blocked"):** ① alert when the
  BullMQ failed-set grows or queue depth exceeds a seeded threshold (nobody
  is paged today); ② one-time audit before DM volume goes live post-App-
  Review: every outbound path (flow sends, FB operator replies, escalation
  DMs) provably inside Meta's 24h-window rules, plus a rate-limiter at the
  queue choke point. Keep BullMQ — ruled sufficient; the risk is send
  content/timing, not the queue.
- **Inbox needs per-channel design (operator, 2026-07-29):** the Inbox is
  WhatsApp-shaped only — Instagram and Facebook conversations render in the
  same WhatsApp-designed thread UI. Polish/redesign per channel (chips exist;
  the thread view, composer affordances and channel-specific limits — 24h
  window, no templates on IG/FB, private-reply constraints — need their own
  treatment). Routes to the sweep Phase-1 Inbox lane; HTML mockups per the
  2026-07-29 law.
- **Agent can initiate a flow (operator, 2026-07-29):** the DM replier gets
  the ability to start a flow "that is given to it for it to use" — i.e. flows
  carry an agent-usable flag; the agent gets a start-flow tool scoped to that
  allow-list (and possibly initiate a DM / default reply the same way). Seams
  that exist: the engine's `start_flow` action executes
  (`flow-engine.service.ts`, intercepted in `executeActionsNode`), and the
  agent tool vocabulary lives in `agent-tools.service`. Needs a mini-grill
  (which flows, which surfaces, does the flow take over the thread) + kickoff.
- **⭐ Activity screen overhaul (operator, 2026-07-29):** "lacking and has low
  ux stats" — and prior Activity orders were NOT all delivered. Wants: a
  functionalities mockup round, ideas, and a UX + logic review; logic and
  functionality dealt with first, possibly BEFORE the design sweep. Order-vs-
  reality audit seat spawned same day — every Activity order across scope-5/
  scope-12/scope-14/board/ORDERS checked against the code with evidence; its
  findings feed the kickoff + mockup rounds.

- **⭐ Operator glance verdicts + work order 2026-07-27 — CONSUMED same day →
  `agent-scope-14-kickoff.md`.** Situations is renamed **Behaviour**; the Global
  channel-chip banner, the Knowledge-search section and every existing KB
  document are deleted (KBs rewritten from zero, backup first); fields take full
  markdown; `See the exact prompt`/`Edit` move left; KB scoping becomes
  common→Global / situation-specific→its tab / channel+situation-specific marked
  in its tab (**the Knowledge screen and the KB scoping are DONE 2026-07-28**;
  the KB rewrite from zero is still pending his go-ahead); and the labelled activities get read, reasoned and fed back into
  the prompts. Activity carries 5 defects + 2 mockup-required redesigns (the
  review detail panel, the filtration UI).
- **Glance rulings, 2026-07-27 — these change the board, not just a package:**
  - **Automation gets NO glance and should not be polished.** "I will not glance
    at automation until the flows and related such is done… this scope should
    literally change automation." The Automation section is **superseded by
    scope-5 Stage G** (Flows · Sequences · Runs); do not spend on the current
    surface. Its open gate bar is withdrawn until Stage G lands.
  - **Analytics glance ALREADY GIVEN and the verdict is negative** — "still
    confusing and you really don't know what to look at or how". He will redo it
    later **with 21st.dev components**, and wants to **define what to show and
    how** first. So analytics-scope-2 is NOT waiting on a glance; it is waiting
    on a definition round that he owns. Stop listing it as glance-blocked.
  - **Activity's glance is given and it FAILED** — see the defect list in
    agent-scope-14.


> One line each, no format required. Every kickoff triages this section into
> the scope backlogs. Empty is fine.

- ~~**⭐ Situations rebuild — operator orders 2026-07-25**~~ **CONSUMED
  2026-07-25 → `agent-scope-13-kickoff.md`.** Two mockup rounds delivered and
  reacted to; BINDING sheet at `situations-rebuild-mockup-annotations.md`. All
  forks closed — he took every orchestrator recommendation on the three
  remaining rulings and **signed off that the assembled prompt (and therefore
  the goldens) moves**. Headline: ONE markdown field per heading, ONE page-wide
  Edit, Knowledge = KB rows (text only, ManyChat shape, per-channel), Parked
  box / add-instruction form / changed-marks deleted, nothing read-only, the
  automatic replay gate replaced by an explicit paid-on-click test, and a full
  rebuild of the 1713-line `Situations.tsx`.

- **Operator live-use findings 2026-07-22 ROUND 2** (second session —
  **consumed same day → `agent-scope-9-kickoff.md`**, except the overhaul
  which grills separately): ① right/wrong label UX bad (hard to see if
  labelled, hard to clear; examples learn from these) ② Feed vs Needs-action
  label inconsistency, Needs-action missing the mark ③ Playground case
  settings incomplete + allows impossible channel×case combos ④ comment
  replies should CTA to DMs (live "Details" case) ⑤ no auto-interpretation
  of new posts on live (code wiring verified; prod-side) ⑥ throwaway-comment
  witness cancelled — organic comments arriving.

- **Operator live-use findings 2026-07-22** (prompt line, during the rescope
  session — next agent kickoff triages these):
  ① **Shadow mode becomes propose-and-approve** — a shadow action must create
  an approval request the operator can accept to actually execute, not a mute
  log. Also investigate: shadow replies not visible in Activity at all (bug?).
  ② Activity outcome column cluttered — needs a disciplined vocabulary; and
  right/wrong labels cannot be removed once set.
  ③ Moderation examples learning WRONG labels — two kept comments should be
  deleted; operator needs a way to correct example labels.
  ④ Situations: fixed (non-editable) blocks render too dominantly — demote.
  ⑤ "Callback number and time" carries WhatsApp-only wording as base text —
  belongs in per-channel instructions.
  ⑥ "See exact prompt" view is non-scrollable (bug).
  ⑦ **BIG — standardize instruction fields**: stop minting a bespoke field per
  instruction ("Callback number and time", "How it opens a chat", …); one
  standardized instruction shape with headings so the operator adds/edits
  instructions himself. Sustainability overhaul — app must be
  operator-extensible, not developer-dependent. Needs its own kickoff.
  ⑧ DMs tab renamed "Conversations".
  ⑨ (Blocked on re-send: one UI screenshot [Image #1] never reached the
  session — operator to re-paste.)

- **⭐⭐ STANDING ORDER (operator, 2026-07-21) — FINISH THE AGENT, ENTIRELY.**
  **BUILD SIDE COMPLETE (verified 2026-07-27); the "nothing else starts" clause
  no longer binds** — ① moderation-first is implemented and pinned
  (`comment-pipeline.ts:134-139`), ② the Agent pill/button sweep is done (zero
  `so-btn`/`so-pill` under `routes/agent/`), ③ the copy diet shipped. What is
  left is operator witnesses only, and several scopes have deliberately run
  concurrently since. Kept for its ordering, not as a gate.
  In order: ① **get moderation running
  cleanly** end-to-end and witnessed (built + deployed, never proven clean —
  this is the headline) ② **sweep the Agent menu: kill the pills, redo the
  button** — build the `--cm-*` button primitive, then retire `so-btn`/`so-pill`
  across the Agent surfaces (this was the DS-convergence backlog item; he moved
  it up) ③ **improve Agent UI/UX** while touching surfaces — plainer words,
  fewer visible concepts, no engineering vocabulary; never a design round.
  Everything else on this board waits behind ①–③. See `HANDOFF.md`.

- **Operator directives 2026-07-20** (DECISIONS same date): ① interpretation
  REPLACES caption in all agent-visible context (seat in flight, incl. the
  max_tokens fail-safe root-cause) ② stored beside-caption interpretations
  rewritten standalone by a Claude seat, no LLM re-spend (follow-up seat)
  ③ **Facebook posts into the Posts section** — queued behind ① (shared
  posts fence), needs a mini-kickoff + one grill question (discovery: mirror
  IG sync vs comment-time only).

- **Operator steer 2026-07-18:** "the agent section is getting crammed and
  unorganized… can the other scopes be ran to at least help on the website
  feel" → three green-lights, all launched same day: ① appshell de-WhatsApp
  IA (round-14 mocks in flight; kickoff after reaction; agent-nav diet lands
  via scope-4's Situations collapse) ② Analytics five questions (round-16
  mocks in flight) ③ channels filters + Segments→Contacts fold — green-lit
  but QUEUED behind the new nav (standing collision note). Also reported:
  **console keyboard shortcuts dead on live** (fix seat in flight; tests
  passed via direct element events — live focus gap).

- **⭐ TOP PRIORITY (operator, 2026-07-17): improve the UX and capability of
  moderation + reply for BOTH Instagram and Facebook** — "anything that goes
  with it or helps it is a priority with it." → `agent-scope-3-kickoff.md`:
  **GATE A PASSED (Fable, 2026-07-17)** — threshold editor, moderation log,
  real-time escalation alerts LIVE and witnessed (escalation email delivered
  to the operator). ~~Remaining: the Activity fold then Stages 4–7 inside
  Activity's moderation view.~~ **Corrected 2026-07-27: that remaining work was
  delivered by agent-scope-5 + agent-scope-12** (one screen, one list, every
  console power-tool folded in). agent-scope-3 is now open on the operator
  click-through ONLY. Round-10 reacted; rails-editability reversed to
  editable-with-typed-confirm (DECISIONS).

- ~~Facebook comments moderation~~ **CONSUMED 2026-07-17** → grill-locked
  (docs/DECISIONS.md) + `channels-scope-2-kickoff.md` written. IG moderation
  "completed" = operator's gate flip (Agent → Channels) — still his.
- **Knowledge screen below the transparency/customizability bar** (operator,
  2026-07-17, same steer as the Behaviour ruling): "behaviour and knowledge
  is not at the level of customizability and transparency that I want" —
  Behaviour is answered by agent-scope-2 Stage 6 (F1–F6); Knowledge needs its
  own package. Candidate agent-scope-4 (scope-3 = the moderation console).
- **Ads/boosted posts tab is empty** (operator, 2026-07-17): ROOT-CAUSED —
  ad posts enter only at comment-time; the ingest deployed after the only 3
  ad comments arrived (dedupe blocks retro-ingest; 0 ads rows live, verified).
  Page token PROVEN to read ad media fully (no new scopes). **Fix in flight**
  (executor): A orphan backfill on sync + B webhook ad_id/original_media_id
  capture (boosted-organic comments attach to the organic post — kills the
  duplicate-row bug) + "discovered via comment" label. Option C (Marketing
  API sync) ruled unnecessary — comment-time discovery is honestly sufficient.
  **FIXED 2026-07-17** (`2fe8d64`,`77008aa`,`15f7b96`, Opus seat): 3 ad posts
  live with captions+thumbnails — Fable-verified in prod DB (`ads|3`).
- **apps/web adopt audit** (Fable, 2026-07-17): 28 verified findings
  (3 Critical / 8 High) with scope-tagged candidate board lines in
  `plans/rethink/web-adopt-audit-2026-07-17.md` — next kickoff per scope
  triages its lines from there. Top Criticals: three competing styling systems
  (converges with appshell-scope-2's light-shell fold-in, operator-ruled);
  Settings.tsx 3,115-line god-module; gold focus ring fails WCAG contrast.

---

## Scopes

### Agent — ACTIVE

The AI section: pipeline, prompts, traces, review, posts, triggers, behaviour,
playground, models. On the new design system (8 screens shipped, Phase 1A–1C).

**In flight (2026-07-21): `agent-scope-6-kickoff.md` — the FINISH-THE-AGENT
order** (see Intake ⭐⭐). **Executor half DONE + gated same day**: 7 defects
(A–G: honest failures, fail-open detector, silence mute, trace-less review
items, external-deletion sync + backfill, review audit spine + plain 100/33
copy, Home fold) + cm-btn primitive + pill retirement + flag/keep copy diet,
all live, own-oracle verified. **Open only on the operator witness** (token
rescope → throwaway IG + FB comment; closes bar 5 here, scope-3 bars 5/6/9,
channels-scope-2 bar 2).

- **agent-scope-10 (instruction overhaul): GATE PASSED 2026-07-22, LIVE**
  — operator builds instructions under fixed noun headings (**five today —
  Role · Goal · Tone · Knowledge · Guardrails**; the "7" this line used to claim
  was the pre-scope-11 draft, corrected 2026-07-25), everywhere-by-
  default + optional channel targeting, migration-free, goldens byte-identical.
  Review found + same-day-fixed 1 MAJOR (comment channel targeting) + 1 MINOR
  (safety confirm). Trailing: operator's first real self-added instruction.
  **Backlog from gate:** instructions are removable but not DISABLEABLE
  (mute-without-delete) — build when the operator wants it.
- **agent-scope-9 (live-use fixes rounds 1-3): all seats landed + verified
  2026-07-22** — label UX (per-turn labels on every row kind, Mark dropdown),
  from-name fix, row-type chips, toolbar one-language + replay-on-selection,
  FB excuse deleted (FB acts un-gated), Playground matrix + FB conversations
  (sim-only) + server-side pair validation, comment DM-CTA seed, auto-interpret
  404 root-cause + drift guard, platform comment counts. Formal gate note owed
  after the operator's glance.

Package status:
- **agent-scope-3 (moderation console): CONDITIONAL PASS, DEPLOYED LIVE**
  (`901cd7a`). Bars 5/6/9 still open on the operator's authenticated
  click-through. **The "run moderation cleanly" order supersedes waiting on
  those bars** — prove the pipeline end-to-end first, then his click-through
  closes the gate.
- **agent-scope-4 (situations/MODULE engine): Stages 0–6b COMPLETE + LIVE**
  (2026-07-20). Module registry, situations engine, two-gesture channel
  variants, `/agent/situations`, Examples + replay-gate, Triggers/Knowledge/
  Behaviour folded + redirected with editing parity. Golden pin byte-identical
  throughout. **Only Stage 6c deferred** — routing.ts internals, a byte-noop
  with 0 custom cases; guarded solo stage, and it waits behind the ⭐⭐ order.
- **agent-scope-5 (Activity end-state): COMPLETE + LIVE, GATED (2026-07-21
  night)** — ONE screen, ONE list at `/agent/activity` (Feed / Needs-action
  presets, in-place acts, nav badge). ReviewQueue AND the moderation console
  both retired behind redirects; every console power-tool folded (bulk, mute
  lens, replay, undo/history, act-on-any-comment, keyboard). Parity table
  closed. Open only on the operator glance.
- **agent-scope-8 (shadow propose-and-approve + live-use fixes): GATE PASSED
  2026-07-22, LIVE** — shadow reply = approval request (accept posts, reject
  discards, no-send invariant pinned), historical drafts surfaced, outcome
  vocabulary, labels clear/correct, prompt drawer scrolls, DMs→Conversations,
  fixed blocks demoted. Own-oracle 1422 api + 602 web. **2.7-A DONE + LIVE
  same day** (`d81e59a`, Fable-verified: shared 963/963, WhatsApp goldens
  byte-identical — only 4 IG/FB external pins moved; prod at `d81e59a`,
  api+worker online, health green). Open: operator glance; operator pointer
  to the two mislabelled comments. DECISIONS entry 2026-07-22 (shadow
  semantics).
- **agent-scope-7 (Situations UX redo): Stages 0–4 COMPLETE + LIVE**
  (2026-07-21). Operator verdict "great idea, bad execution → all of it and
  more." Surface rebuilt web-only, engine/shared/api untouched (goldens
  969/969 byte-identical). Merged one-surface blocks (base wording + per-channel
  differences in one place), one save, one revert ("Undo my change"), readable
  composed prose + product labels (zero engine-label leaks), tab/block
  changed-marks + view-difference, managed apps collapsed, in-app confirm.
  Concept count 14→7; capability parity fully retained (35/35 tests). Deployed +
  published; evidence in `evidence/agent-scope-7/`. **Open only on the operator
  glance** (gate bar 5, auth-gated).

**Moderation defects found by the 2026-07-21 diagnostic** (full evidence in
`HANDOFF.md` § "Moderation: live-state diagnostic" — these ARE order item 1):
- **A failed moderation action reads as a success.** Both live FB escalations
  hid nothing (`outcome=failed`, `(#200) Permissions error`) yet the operator's
  notification said only "comment escalated". Notification + console must
  surface the outcome and error. Honest-feedback law.
- **Silent fail-open when the moderator returns nothing.** 5 of 9 moderated
  turns were `empty_output:max_tokens` → forced `leave`, no review item, no
  alert. Fixed at source 2026-07-20 (`moderation-policy.ts:159`, cap 1024) and
  proven by the 07-21 turns — but **no detector exists if it recurs**.
- **Escalations are buried by known-dead-number noise.** 20+ `red.silence`
  criticals for the Meta-disabled WhatsApp number (every ~4h) share the stream
  with real escalations. Collapse or suppress the known-dead case.
- **Blocked on the operator:** FB hide *and* delete both need
  `pages_manage_engagement`, absent from the live Page token. **IG delete has
  never once been executed** — scope is present, capability unproven.

- **Replay gate does not cover base-text edits** (scope-5/7 review,
  2026-07-21 night, pre-existing): `saveContent`→`patchAgentConfig`
  (sectionText) saves unconditionally; only the situations-config path can
  409 on the gate. A base-text edit to a replay-gated situation goes live
  ungated. Policy question for the operator: should the gate cover base
  edits too? (Extending it touches gate semantics — not done autonomously.)

**Low-severity notes from the scope-6 independent review (2026-07-21, none
blocking — full report in the kickoff's gate section):**
- Success copy for a live moderation DELETE still says "hidden pending your
  review"/"flagged" (`escalation-notifier.service.ts:122-131`) — currently
  unreachable (the pipeline only pages on delete FAILURE) but delete-blind if
  ever wired; one-line copy fix next time the notifier is touched.
- Home attention fold skips a `moderate` review item with null `igCommentId`
  (unreachable today) — would silently hide an actionable item if the
  invariant ever breaks; add a generic fallback row when touching HomeService.
- `classify.ts` IG dedup now verb-qualifies ALL non-add verbs, not just
  `remove` — deliberate-looking but wider than the stage log claimed; if IG
  ever sends `edit`/`hide` verbs they now enter the pipeline instead of
  being ingest-deduped. Revisit when IG edit handling lands.

**Done (was "ordered, not built"):**
- ~~Internal staff AI chat REMOVAL~~ **DONE + LIVE 2026-07-18** (`5028cfa`,
  same deploy as scope-3): staff DMs flow the customer pipeline; legacy
  stored `internal` case stripped on normalize; orchestrator-verified.

**Backlog (operator orders 2026-07-24 — DECISIONS same date):**
- ~~Situations hierarchy redesign~~ **GATE PASSED 2026-07-24, LIVE**
  (`agent-scope-11-kickoff.md`). Follow-ups: re-park control for a SAVED
  restore; Facebook/InstagramChannelCard "Comment moderation" strings
  (host sweep); Global-tab curation = operator glance.
- ~~Activity end-state 2~~ **GATE PASSED 2026-07-24, LIVE**
  (`agent-scope-12-kickoff.md`). Follow-ups: TriggerModuleEditor raw post
  `<select>` → PostPicker; "failed" filter reaches failsafe-failures only
  (needs a comment-status join); flow-step shows raw node id until
  scope-5 Stage E. Operator ruling owed: IG pink stop 4.48:1.
- **Activity's `label` rows say "label the withheld reply" and throw away the
  reason** (operator-spotted on Facebook rows, traced by the dm-app-review lane
  2026-08-03; handed to the activity lane as a message, boarded here so it
  survives if that message is never pasted). `label` means a review floor
  tripped: the agent's fuller reply was withheld and a safe rewrite was sent, so
  nothing was skipped. But `needsLabel` (`Activity.tsx:204-217`) is the only
  review kind with no plain-words sentence, and the row already carries
  `decisionReason` (`lib/agent-api.ts:627-638`) which the screen never renders —
  it renders the raw float instead (`· conf 0.42`, `Activity.tsx:1274`), against
  the no-engineering-vocabulary law. Not verified: which `decisionReason` values
  actually occur live, and whether `agent.service.ts:270` hardcoding
  `surface: "dm"` for every channel is right.
- ~~**Posts: show comment replies (anyone's)** + hard rule: the AI never
  replies to a comment-reply but moderation still runs on it~~ **ALREADY
  WORKS — delete this line.** The root-cause check the item asked for, done
  2026-08-05: the pipeline DOES ingest replies with their `parentId`
  (`comment-pipeline.ts:746`); the hard rule is implemented and commented as
  such at `comment-pipeline.ts:604-626` (the reply pass yields on `parentId`
  *below* the moderation pass, so moderation has already run and enforced);
  and the Posts query excludes only `status: "deleted"`
  (`posts.service.ts:461`), so replies are shown and `Posts.tsx` threads them
  under their parent.
- **⚠ Four UNOWNED Activity/Posts defects, verified on prod 2026-08-11, none
  fixed** — a verification session changed no code and proved four things nobody
  owns. Full evidence in `plans/rethink/activity-posts-handoff.md` § D1–D4.
  **(D1) Moderation falls open SILENTLY in live mode** — empty output on
  `max_tokens` forces `leave` with no alert and no review item; this board already
  records the source fix of 2026-07-20 *and* that "no detector exists if it
  recurs", and it has now recurred twice live (FB 08-07 02:58, 08-08 00:59).
  **(D2) The review route's delete never writes the row status** —
  `review-items.service.ts:443-454`; its sibling branches in the same method both
  do, so 3 production rows still read `hidden` after a successful delete at Meta.
  **(D3) The Facebook private reply has never worked** — see the Intake item above.
  **(D4) The Source platform link shipped on Activity only, not Posts** —
  `externalSource` is absent from `Posts.tsx` and `posts.service.ts` while
  `posts.html` promises it. None of the four needs an operator ruling except D3.
- **Two NEW Activity/Posts forks, 2026-08-11:** **the DM opener has no
  de-duplication of any kind** (one person got 2 openers after commenting 6 times
  on one post; 8 people have commented 2–6 times on a single post) and
  **shift-click collides with a documented Chrome shortcut** and currently means
  two different things depending on whether the row has a platform link. Both are
  forks for the operator, drawn as decisions 10 and 11.
- **⚠ The lane's ELEVEN open forks are now ONE file** —
  `plans/rethink/activity-posts-decisions.html` (2026-08-11). It replaces the three
  pages below **because they contradicted each other on four points**, most sharply
  on whether the comment seed policy is editable. The three sources are left in
  place pending the operator's word on retiring them; the two board lines below are
  kept for their history, **not as the current ask**.
- **The Activity/Posts lane's REMAINING five unplanned items are DRAWN, not
  built** — `plans/rethink/activity-posts-unauthored-2.html` (2026-08-06, after
  the deploy that closed the six fidelity gaps and both priority items): (A)
  everything the agent says is invisible in Behaviour — the DM opener, the new
  DM fallback and the comment rules are all settings rows with no screen, which
  is the direct answer to the operator's own question that day, (B) a skipped
  comment shows `· conf 0.42` instead of a reason, (C) the mockup registry and
  the ported one can drift with nothing enforcing it, (D) the Kind filter can
  disagree with the Kind column, (E) a correction's note is written and read
  nowhere. Each carries its fork.
- **The four unauthored Activity/Posts items are now DRAWN, not built** —
  `plans/rethink/activity-posts-unauthored.html` (2026-08-05), each with the
  fact that makes it necessary and a fork to rule: (a) `commonOverrides()` is
  still unenforced server-side beyond the deleted-comment guard, (b) the IG DM
  thread id is not stored so a DM has no absolute Source link, (c) corrections
  are kept and never collected, so the head's "N corrections" line has nothing
  behind it, (d) the comment seed policy + output contract + safety rail are
  invisible in Behaviour (operator order, 2026-08-05: "all the prompt should be
  organized and shown in behaviour and not hidden").
- **The six unauthored FLOWS items are now DRAWN, not built** —
  `plans/rethink/flows-canvas-unauthored.html` (2026-08-06), each with the fact
  that makes it necessary and a fork to rule: (a) the AI node — the brief is
  captured (`manychat-capability-catalog.md:443-455`) and settles the FORM but
  not what Generate DOES, and this repo already ships an `ai_handover` node
  that is arguably the same thing under another name; (b) four match modes can
  be seen and removed, never created, and `is_about` costs a model call per
  message; (c) the three wizard forks, live on the recommendation, each a small
  edit to flip; (d) Ref URL + QR need nothing from Meta we lack, Ads needs Ads
  Manager; (e) one human is two `Contact` rows, so a tag, a field and a CRM
  lead all double across channels — the consequence the 2026-08-03 channel
  ruling parked; (f) Sent/Delivered are one nullable `wamid` from working,
  Opened/Clicked are not obtainable on these surfaces at all.
- **WhatsApp silence notifications** — keep the detector, change the
  output: standing surfaced status in the UI, notifications for the
  known-dead number collapsed/suppressed (operator order; upgrades the
  "escalations buried by known-dead-number noise" defect above).

**Backlog:**
- ~~Activity end-state package~~ **DELIVERED — delete this line.** Verified
  2026-07-27: agent-scope-5 (ONE screen / ONE list) and agent-scope-12 shipped
  it; `apps/web/src/routes/agent/Activity.tsx` is the only surface and there is
  no `ReviewQueue` or moderation-console route left in `routes/`.
- **Field-delete bug — FIXED + LIVE** (`8076270`, deployed, health green):
  explicit per-field REMOVED state (sentinel), distinct from revert-to-seed;
  rails/machine-contracts non-removable; golden pins held. ⏳ needs the
  operator's authenticated click-through to confirm interactively (delete →
  reload stays deleted; restore; revert) — sessions can't log in for him.
- **Auto-interpretation + caption cache — SHIPPED + LIVE** (`8cf3d2e`,
  deployed, migration applied, health green): NEW posts auto-interpret on
  arrival (NO backfill — operator-corrected); `caption_interpretations` cache
  dedups identical captions (zero repeat LLM, provenance `llm|cache`); grid
  polls so interpretation appears with no click. First live auto-interpret
  fires on the next genuine sync / ad comment.
- **Interpreter prompt is a hardcoded literal** (interpretation seat flag,
  2026-07-20): posts.service.ts interpret-2 prompt violates prompt-as-data;
  seed it when the prompt next changes. Same seat flagged the interpreter
  role's own `maxTokens: 300` — same reasoning-model starvation class if a
  reasoning model is ever assigned to the interpreter role.
- ~~Channel-card copy diet~~ **DONE 2026-07-21** (scope-6 Stage 3, `7fac52f`):
  MODE_HINT/MODERATION_HINT rewritten plain + short, Fable-reviewed at gate.
- `SocialPost.caption_edited_at` (additive) so the per-post caption block can
  show an honest "Edited" line — Graph exposes no edit flag; set it when a
  re-sync sees a changed caption. (1E gate, 2026-07-16.)
- Structural Behaviour editing (add/remove cases, branches, data-driven
  routing) — operator-ruled "both, phased" 2026-07-15: gets its own kickoff
  after 1D's transparency half is used. Real engine work.
- ManyChat keyword/trigger import — **pre-cutover prerequisite for the reply
  gate going live.** Starts with a verification spike: does ManyChat's API
  expose keyword rules at all? (Unverified.) Fallback: export/paste.
- Agent → Overview screen (§5.1) — "is the bot okay?" landing, automation-rate
  headline. Promised, never assigned.
- ~~Markdown trace export (§5.2's second half)~~ **DONE 2026-07-21**
  (scope-5: `exportSelected("markdown")` on the one list, Activity.tsx:340).
- IG mentions + other non-`comments` fields: surface instead of silent drop.
- ~~Trace-write failure orphans review items~~ **FIXED 2026-07-17**
  (`5f4b788`, agent-scope-2 Stage 1). ~~Successor policy question~~
  **ANSWERED 2026-07-21 (overtaken by the live flip — DECISIONS "trace-less
  review item"): live-hidden/deleted with a failed trace write now creates a
  trace-less review item (turnId null). Consumed by agent-scope-6 Stage 1
  defect D.** ReviewQueue web pin against legacy null rows owed to
  the web seat (kickoff stage log).
- Rail-edit hardening BEFORE any non-operator admin exists: the typed
  confirm on safety-rail prose edits is client-side UX only; server-side it's
  the ordinary admin JWT. Fine while the operator is the only admin
  (his informed reversal, 2026-07-17); add a server-side confirm/audit step
  when team logins arrive.
- Root-cause follow-ups from 1D's `empty_output` investigation, if any.
- ~~Moderation bypasses deterministic-trigger-matched comments~~ **OBSOLETE,
  verified 2026-07-27 — the opposite is now true and it is pinned.** The
  2026-07-15 Option-B consult was reversed by the moderation-first law
  (DECISIONS 2026-07-18) and the code implements it: `comment-pipeline.ts:134-139`
  states the ordering laws at the call site — "MODERATION FIRST — `claim` is
  offered a comment only after the moderation pass has run and only when its
  verdict was `leave`" — with invariant tests in the pipeline suite. Note the
  line's stated precondition had ALSO gone false: the triggers table is no
  longer empty (3 live seeded rows — SG, TORONTO, INVITE). Had the old ordering
  survived, flipping `comment_to_dm` would have silently un-moderated every
  comment matching those keywords. It does not.

### App shell & Home — scope-1 GATE PASSED, scope-2 KICKOFF WRITTEN

**Backlog:**
- **Form-control primitive + unstyled-field sweep** (operator, 2026-07-24):
  native `<select>`/`<input>` render raw across the app — Settings is full
  of them, dropdowns "everywhere". Build `cm-select`/`cm-input`/`cm-field`
  primitives on the tokens (same discipline as the cm-btn sweep), then
  retire raw controls screen by screen. Candidate: fold into the
  appshell-scope-5 conformance ratchet so regressions can't return.
- ~~Responsive / mobile layout~~ **CONSUMED 2026-07-17** →
  `appshell-scope-2-kickoff.md` (768px standard token, ALL screens must-fix,
  light-shell rebuild folded in — operator rulings in docs/DECISIONS.md).
  Round-7 mock reaction decides the collapse pattern.

Home (health + attention + today strip), email alerting (red-tier + digest),
and the app-wide nav restructure to the target IA (§3) — today's nav is still
the WhatsApp-shaped tree §1 forbids. Absorbs the "silence detector" and the
needs-attention feed that 1D's provenance data will make possible.

**In flight:** **appshell-scope-4** (token conformance sweep — operator
steer 2026-07-20, kickoff + executor same day; presentation-only, fenced
off Analytics/Contacts/Situations). **appshell-scope-3 bar 2 CLOSED
(2026-07-20, sweep `30853bb`):** 15 routes × 320/375/768 clean, drawer per
round-14 locks; only the operator phone glance trails. One pre-existing
finding filed below.
**Backlog (new):** `/agent/overview` overflows ~6px at 320 (non-wrapping
"Canary (1)" gate pill; pre-existing, NOT a scope-3 regression — evidence
in appshell-scope-3-sweep/). Candidate for the scope-4 sweep seat.
**Earlier:** appshell-scope-3 GATE CONDITIONAL PASS — LIVE (2026-07-18)
(`fa48c1d`+`10de2ec`+`f72d49f`): grouped IA per the eight round-14 locks;
in-page AgentNav retired. **scope-2 GATE PASSED (Fable, 2026-07-17)**: light shell + drawer
live, all 14 screens overflow-green at 320/375/768. Trailing: authenticated
Posts-390 glance (operator's phone). **scope-1: GATE PASSED (2026-07-17)** —
trailing: digest 08:30 send observation (TZ unverified).

### Channels & Connections — KICKOFF WRITTEN (was Phase 3)

Connections multi-channel + webhook status surface, channel filters
everywhere, Segments → Contacts fold. Plus: **IG DMs** — ~~currently ingested
and dropped whole (`entry[].messaging[]` → classified `unknown` → vanishes)~~
**STALE, corrected 2026-07-25**: channels-scope-1 shipped that arm.
`entry[].messaging[]` classifies `instagram_message`
(`packages/shared/src/meta/classify.ts:42-47`), routes to `IgDmService`
(`webhook-processor.service.ts`), and stores contact + conversation + message.
The remaining gap was `entry[].standby[]` (Conversation Routing — another app
owns the thread), now handled as `instagram_message_standby`: stored, never
replied to. **Meta still delivers zero `messaging`/`standby` rows** — 715
webhook_events over 30 days, none containing either. The break is upstream at
Meta, not in our receive path.

**In flight (2026-07-23): `channels-scope-5-kickoff.md` — ManyChat GRADUAL
replacement** (operator ruling, DECISIONS 2026-07-23: buy-vs-keep dropped,
no cutover day — ManyChat runs alongside until replaced piece by piece).
Stage 1 (comment→DM opener) live gates-off; design round for the rest now
in flight: exhaustive grill + BINDING/DEMO mockups (DECISIONS 10 contract).

**Earlier:** `channels-scope-1-kickoff.md` — **build-complete, DEPLOYED
LIVE (2026-07-17).** Gate open on the operator-witnessed DM spike (script
ready, never run; token rescope pending — now FOUR scopes: pages_messaging,
instagram_manage_messages, pages_manage_engagement, pages_read_user_content)
→ then the `instagram:dms` shadow flip (spend pre-approved) and the gate
verdict. ⚠ VPS spike WIP parked in `stash@{0}` (deploy 819c8fb stashed a
conflicting `ig-dm-spike.mjs` edit) — reconcile before the spike run.
**Also in flight (operator WIP-override, 2026-07-17):**
`channels-scope-2-kickoff.md` (FB comments moderation, grill-locked +
red-teamed) — Stages 0–2 executing now; Stage 3+ and the gate wait until the
scope-1 gate closes. Override reason: scope-1's blocker is the operator's own
Meta rescope; Stages 0–2 are shadow-only and cannot collide with the DM spike.

**Also in flight (operator WIP-override #2, 2026-07-20):**
`channels-scope-3-kickoff.md` — channel filters everywhere + Segments →
Contacts fold (executor spawned 2026-07-20). Override reason: scope-1/2
gates are blocked on the operator's Meta half; this package is web-side
with ~zero collision. Was this scope's queued package 2 (nav now live).

**✅ The "FOUR scope token rescope" blocker DOES NOT EXIST — settled against Meta,
2026-07-27.** The operator supplied the App ID (`1040420455120273`), it is now
stored on the `instagram` connection, and `debug_token` answers directly:

```
instagram/primary: valid=true  type=PAGE  expires=never
  ALL SCOPES: business_management, instagram_basic, instagram_manage_comments,
  instagram_manage_messages, pages_manage_engagement, pages_manage_metadata,
  pages_messaging, pages_read_engagement, pages_read_user_content,
  pages_show_list, public_profile
```

All four "missing" scopes are present, and the token never expires. **Every
board and README line gating channels-scope-1 on a Meta rescope is dead** — the
rescope happened 2026-07-22 (`docs/archive/HANDOFF-history.md:3471-3480`) and
nothing has needed it since. Why it drifted for days: the IG connection stored
no `appId`, so `webhook-status.service.ts` could only probe liveness and
reported scopes as an honest `"unknown"`. Fixed at the source in `91657ea` —
the App ID is now a connection field and the panel reads the real list.
Also stale on the same lines: the DM spike is described as "script ready, never
run" while this board records an operator test DM actually sent 2026-07-23.

**Backlog:**

**⭐ From the ManyChat parity gap (`manychat-parity-gap.md`, 2026-07-25).** 38
capabilities measured against the LIVE ManyChat account (API, read-only) and our
code: **BUILT 5 · BUILT-UNGATED 13 · PARTIAL 11 · SPEC'D 4 · MISSING 5.** Only
five things are live and proven. These are the gaps with **no owner anywhere** —
they are not deferrals, they were never planned:
- **⭐ Sequences — needs its own work-package, not a backlog line.** No model, no
  action, no scheduler, no kickoff; the word appears nowhere else on this board.
  Meanwhile `contact.sequence_subscribed` already ships in our trigger vocabulary
  pointing at an object that does not exist. His live *Client Follow-up #1* is
  triggered ONLY by a sequence subscription, so it has no path to working.
- **✅ A sender that can emit a tappable button — BUILT (ORD-038, verified
  2026-07-29).** `buildFunnelInteractive`
  (`packages/shared/src/funnel-flows/render-message.ts:109`) builds a real
  WhatsApp `type:"button"` interactive payload from a node's buttons,
  `funnel-engine.service.ts:809` calls it on every message-node send, and
  `gatedSend` (`funnel-engine.service.ts:935-951`) sends it as
  `type:"interactive"` with a plain-text retry on failure — every button
  branch port on WhatsApp is now reachable. Quick-replies are still unsent (no
  id to route a tap to, `render-message.ts:105-107`).
  **The "contradiction to settle first" is SETTLED (research, 2026-07-27) — both
  claims were true about different Meta surfaces, and the answer shapes the
  package.** Meta's Send API decides button legality by how the recipient is
  ADDRESSED, not by channel: `recipient.comment_id` (a *private reply*, our
  comment→DM opener) was claimed here to be text-only — **that claim is an
  unsourced inference and is withdrawn** (`6905542`, 2026-08-01). Meta documents
  exactly four private-reply limits and message type is not among them, and the
  operator progressed a real ManyChat conversation with a button on one. It is
  not proven either way and cannot be until App Review lands; build it, then
  prove it. `recipient.id` (an IGSID — an ordinary DM) does
  support quick replies (≤13, 20-char titles), the generic template (≤10 cards,
  ≤3 buttons each) and the button template (≤3 buttons). ManyChat's
  comments-to-DMs flow uses the comment only as a TRIGGER and delivers the
  button message over the IGSID path, which is why his SG flow has buttons and
  `triggers.ts:70-72` is *also* correct.
  **Therefore: buttons are unreachable on the opener BY DESIGN and no code can
  fix that** — the first tappable button can only land on the follow-up DM, once
  a session holds an IGSID. The sender that lands it there is now built:
  `supportsTappableButtons()` (`packages/shared/src/funnel-flows/render-message.ts:60-63`)
  defaults TRUE for `whatsapp`, `buildFunnelInteractive`
  (`render-message.ts:109`) builds the interactive payload from the
  `FunnelButton`/quick-reply schema (`funnel-flows/types.ts:95-124`), and
  `funnel-engine.service.ts:809` calls it on every message-node send.
- **Message-text personalization (`{{field}}`)** — his live follow-up addresses
  the contact by name; ours cannot.
- **Real media sends** — image/file/video blocks render as `[image] <url>` text.
- **Delivery analytics (Sent/Delivered/Opened/Clicked)** — Stage E promises the
  stats strip; nothing records the data, so flow-level CTR has no source.
- **Reaction and story-mention auto-responders** — both ON in his account today;
  "No Basics tab" deletes them silently.
- **Contact-event triggers have no emitter** — the whole tab can never fire.
- Smaller: the "called by" cross-reference; folders for tags and user fields.

**Sequencing recommendation (orchestrator, 2026-07-25):** the graph-WRITE path +
canvas (Stage E) and the button sender must land BEFORE Stage F seeds his real
flows — seeding flows into a system that cannot create a flow or send a button
produces flows that look right and cannot run.

### ⚠ The parity gap was RE-AUDITED 2026-07-27 — four rows were overstated

The gap document had one row proven wrong by a plain code read (`start_flow`,
below), so every MISSING / SPEC'D / PARTIAL row was re-checked against the repo.
Most held. **Four did not, and all four make the remaining work SMALLER than the
board says.** Each was re-verified by the orchestrator directly, not taken on a
seat's word:

- **Buttons (rows B2–B5) — BUILT (ORD-038, verified 2026-07-29), no longer
  unwired.** The board previously said button support needed the IG button
  spike *and* a sender that emits an interactive payload. **The funnel engine
  now has its own**: `buildFunnelInteractive`
  (`packages/shared/src/funnel-flows/render-message.ts:109`) builds a real
  WhatsApp `type:"interactive"` payload from a node's buttons, and
  `funnel-engine.service.ts:809` calls it on every message-node send —
  `gatedSend` (`funnel-engine.service.ts:935-951`) sends it with a plain-text
  retry on failure, the same fail-safe pattern as the sibling agent-path
  sender (`basic-flow-engine.ts:401-407`). `supportsTappableButtons`
  (`render-message.ts:60-63`) now defaults TRUE for `whatsapp` — no flag flip
  needed on the live channel. `interactiveId` branch routing
  (`funnel-engine.service.ts:466`) is no longer waiting for it: it is wired
  end to end. Quick-replies (B4) remain unsent — bare strings carry no id to
  route a tap to (`render-message.ts:105-107`) — and are tracked separately.
- **Sequences: "no scheduler" is wrong.** The durable delay/resume half is built
  and running — `FunnelSmartDelayNode` (`funnel-flows/types.ts:608-641`) allows
  delays up to 365 days on a BullMQ job with
  `POST /internal/funnel-flows/resume-delay`
  (`internal-funnel-flows.controller.ts:31-37`). A `smart_delay(1 day) → message`
  flow already reproduces his live *Client Follow-up #1* behaviour. What is
  genuinely missing is only the reusable named-subscription object, not the
  wait/resume engine.
- **Delivery analytics: half the data is already captured.** `Message.deliveredAt`
  / `readAt` (`schema.prisma:445-446`) are populated for EVERY message from the
  real Meta status webhook (`webhook-processor.service.ts:147-149`, keyed by
  `wamid`, not campaign-scoped) — funnel sends included. The real gap is the
  missing join (`FunnelFlowRunEvent` stores no `messageId`/`wamid`) plus click
  tracking, which is genuinely `ShortLink`/campaign-only.
- ~~**Canvas (E1) does not start from zero.**~~ **STALE, and the fork it posed
  is DEAD — corrected 2026-08-01.** `routes/FlowEditor.tsx` and
  `components/flows/FlowCanvas` were **deleted in `81dcbe8`**, so neither side
  of the "retarget or rebuild" fork exists to choose between. The `cm-*` rebuild
  already happened and ships at `routes/automation/FlowCanvasScreen.tsx`. Do not
  re-ask this question.
  **CLOSED 2026-08-01:** that screen has now been REDRAWN from the round-4 sheets
  and is deployed on `main` at `e33ee87` — the node is the message, per-kind
  bodies, wires with arrowheads, a pushing panel, numbers-first published view.
  What remains in the scope is in `plans/rethink/flows-canvas-handoff.md`; the
  largest piece is wiring the trigger wizard, which is drawn but not connected.

**Row still CONFIRMED wrong in the gap doc:** `start_flow` "has no live executor
— `funnel-engine.service.ts:1314-1341` switch has no case". It IS executed,
intercepted at `funnel-engine.service.ts:1265-1268` inside `executeActionsNode`
before `executeOneAction` is ever reached, with tests at
`funnel-engine.service.test.ts:1304,1398,1554`. The audit read the wrong
function. **Nothing is latent here; delete this worry.**

**~~CONFLICT~~ RESOLVED 2026-07-27 — the code was the stale side, as suspected.**
The TORONTO conflict needed no operator ruling: this board's own backlog already
carried his 2026-07-23 order ("TORONTO trigger keeps ONLY the correct spelling"),
and `packages/shared/src/funnel-flows/types.ts:279` already encoded exact-keywords
-only for `FunnelTrigger` — only the comment→DM *seed* had never been updated.
Fixed in `a877cb1`. Because the seeder is marker-guarded and one-shot, the live
row did not follow the constant either; prod trigger `d2756e40` was corrected in
place to `{TORONTO}` (inert — the `instagram:comment_to_dm` gate is off).

**Also established 2026-07-25:** the captured `manychat-breakdown.md` was already
out of date — the live API shows 5 comment flows not 3, and `Keyword "YARDS"`
(created 2026-07-23) appears in **no document and no seed**.

- **n8n fan-out check — ANSWERED 2026-07-27, Stage G is unblocked.** (The
  HANDOFF pointed at `scripts/audit-legacy-workflows.ts`, which is a *secrets*
  audit over 13 lead/reminder workflow ids and cannot answer this; read via the
  read-only n8n MCP instead.) `instagram-client-contact-us` is a webhook PATH,
  not a workflow — it lives in **`manychat-client-contact-only`**
  (`1SKZqFJVSePi5v4W`, ACTIVE) alongside a second path,
  `instagram-client-contact-us-toronto`. Each path runs an identical 3-node
  chain — extract `{name: custom_fields["Full Name"], phone}` → LeadRat auth →
  `POST /api/v1/lead` — and stops. **No fan-out whatsoever**: no branches, no
  sub-workflow calls, no other consumers, no INVITE-specific behaviour. The only
  behaviour to preserve when the legacy path dies is one LeadRat lead with
  `enquiry.leadSource="instagram"` and `enquiry.subSource` =
  `"contact-us"` / `"contact-us-toronto"`, assigned to
  `9a33c8c8-2c89-429e-b9bf-9260afac44bd`. Credentials come from
  `$env.LEADRAT_*` — nothing hardcoded.
- **~~⭐ Comment → DM, done properly — NEEDS ITS OWN PLANNING~~ BUILT, verified
  by code read 2026-07-30. It needs no planning and no build — only the gate,
  and the gate needs App Review.** The operator's 2026-07-27 target shape ships
  in full: the private reply goes **first** (`comment-pipeline.ts:897-909`, whose
  comment states the ordering law), then the public reply, whose seed swaps to
  the "check your DMs" wording (`DEFAULT_COMMENT_FLOW_WE_OPEN_DM`,
  `comment-behaviour.ts:74`). Both effects hang off ONE read —
  `weOpenTheDm: dmMode === "live"` (`comment-pipeline.ts:746-748`) — so the
  DM-CTA can never be stripped ahead of the path that justifies it; that is the
  old "ordering constraint" expressed in code. `sendDmOpener`
  (`comment-pipeline.ts:1478`) handles off / shadow (propose-and-approve review
  item, never touches the client) / live (send + audited `dm_opener`
  moderation-action + failsafe trace on failure). ~~The opener is text-only by
  Meta's schema~~ **WITHDRAWN 2026-08-01 (`6905542`)** — that was an inference
  presented as research. Whether a button rides on the opener is **unproven in
  both directions** and untestable until App Review lands.
  **The one thing standing between this and live is App Review** — a private
  reply is addressed to a member of the public (DECISIONS 2026-07-30). Until
  then ManyChat must keep owning comment → DM.
- ~~**⚠ The ManyChat comment guard is mis-classifying his live keyword flows**~~
  **FIXED + LIVE 2026-07-30 (`b5ab31b`).** The guard read `surface` off the flow
  NAME prefix, so `SG`, `TORONTO`, `YARDS` and `INVITE` were all filed `dm` while
  `instagram:comments` was LIVE — ManyChat and our agent both answering the same
  comment. Operator confirmed all four are comment-triggered. Root cause was that
  we were reading the wrong half of the API payload: `getFlows` returns a
  `folder_id` per flow **and** a `folders` array, and he files by surface
  (`Comments-to-DMs` vs `DMs-automation`); the service discarded `folders`
  entirely. `surface` is now folder-first with the name prefix kept as a second
  signal (`keywords.ts`, `surfaceFor`). Verified against the LIVE payload with
  the deployed build: all five keywords derive `comment`. The stale cache row was
  deleted so the next inbound refetches. `getGrowthTools` remains useless here —
  five `feed_comment_trigger` rows, no keyword text, no flow reference.
- **⚠ A ManyChat flow that is switched OFF still suppresses us** (operator,
  2026-07-30: *"I thought it was off in manychat but turns out the keyword invest
  is"*). `Comment "INVEST"` is OFF in ManyChat, but its name still yields the
  keyword, so our comment agent stands down on any comment saying "invest" and
  **nobody answers** — the exact mirror of the DM bug `252fd65` fixed. Not a
  regression from `b5ab31b`: the `Comment "` prefix already filed it `comment`.
  **No API call can detect this** — published-vs-draft is exposed by no ManyChat
  endpoint (re-probed 2026-07-30). Two ways out, operator's pick: ① he deletes,
  renames or re-folders the dormant flow in ManyChat and the derivation drops it
  — zero code, recommended; ② we add an operator exclusion list beside
  `manychat.keywords.manual`, which is more machinery for a problem one click
  solves. **Until then the "invest" comment gap is live.**
- **Ref URL / QR code / Ads triggers** (operator, 2026-07-24): deferral
  accepted but "don't abandon it" — greyed-with-reason in the flow trigger
  picker until built; needs link-tracking infra (Ref/QR) and Meta Ads
  Manager wiring (Ads).
- **Small task (operator, 2026-07-23):** TORONTO trigger keeps ONLY the
  correct spelling — remove the 7 misspelling variants (ManyChat-side is
  his own click; our seed uses exact keywords).
- **IG DM receive — NOT an open mystery any more** (corrected 2026-07-27; the
  old wording called it undiagnosed and "parked"). Everything reachable has been
  eliminated with evidence: app + Page subscriptions read back, Conversation
  Routing not set up, 379 webhook POSTs all 200, ingest persists every envelope
  before the signature decision, and the token is now PROVEN to carry
  `pages_messaging` + `instagram_manage_messages`. Remaining is two operator
  actions, not investigation: Instagram → Settings → Messages and story replies
  → **Connected tools → Allow access to messages** (mobile only), then add the
  missing **Messenger product** to the app. Reconcile VPS `stash@{0}`
  (`ig-dm-spike.mjs`) before any spike run.
- **Facebook Messenger real pipeline** (from the 2026-07-22 `facebook:dms`
  ruling, DECISIONS 8): the gate + channel profile exist for Playground
  simulation ONLY — no webhook ingestion, no inbound adapter, no sender.
  Wiring real Messenger receive+send is this scope's package when the
  operator calls it.
- Proactive post-publish webhook for page/ads posts (from 1D kickoff session:
  no such webhook exists today; posts enter via on-demand Graph pull only).
- Subscribe IG `message_edits` webhook field WHEN the DM pipeline handles
  edits (operator asked 2026-07-17 while in the dashboard; ruled skip-for-now
  — unhandled events = stored `unknown` noise; one dashboard click later,
  same materiality logic as comment edits).

### Analytics — ACTIVE (2026-07-20)

The five questions (§8) as internal design frame — **never as UI copy**
(operator, 2026-07-22). Nothing else.

**In flight:** `analytics-scope-2-kickoff.md` (2026-07-22) — live-reaction
redo: kill the "five questions"/"QUESTION n" framing in UI, labels = nouns
("Identity" not "who is this bot"), **no question-phrased copy anywhere,
headings included** (operator amendment same day), full-width layout (was
~half width — screenshot in kickoff), every card leads with its answer. Operator verdict on scope-1's screen: data
presentation "bad, hard to get the info you want immediately".
**Earlier:** `analytics-scope-1-kickoff.md` (2026-07-20) — aggregation
endpoints + question-first screen; presentation superseded by scope-2, its
genuine-computed-empty-states bar carries forward.

### Automation & Reminders — ACTIVE (2026-07-20)

Engine v2 (trigger → condition → action + run log), Reminders as a lens on it,
the old black-box page dies then.

**automation-scope-1: ALL STAGES COMPLETE + LIVE (Stage 2 landed 2026-07-21
night).** Event emission wired (lead.created/captured/quiet,
campaign.finished, crm.state_changed via guarded receiver), decoupled bus,
seeded quiet-window setting (migration 55 applied). **Live event-rule e2e
CLOSED 2026-07-22** (proven twice on prod: real internal receiver → bus →
engine → run row, and the new Test-run endpoint; run rows verified by
orchestrator SQL; test rule deleted). Plain-words UX pass + per-rule
"Test run" button live same day. Open: operator live look; LeadRat-side
webhook wiring to the state-change receiver.

**In flight:** `automation-scope-1-kickoff.md` (round-17 REACTED + LOCKED
same day — Q0 synergy model with the CRM correction, Q1–8 recommended with
Q2 amended to AND/OR groups; executor spawned). Engine owns time + events,
never inbound conversation; keyword FlowRules legacy-frozen behind a Stage-5
migration inventory coordinated with agent-scope-4.

### WhatsApp surfaces — DORMANT, operator-triggered

Templates, Campaigns (builder/CSV/QR), Flows/FlowEditor — dormant while the
number is Meta-disabled. When the operator executes this scope: reskin onto
the tokens + revalidate against the returned number. Operator-ruled
2026-07-15; **no Meta Advanced Access / App Review is needed** (own-account
management only), so nothing external gates this but the number itself.

---

## How a work-package runs (unchanged discipline)

1. **Kickoff** — Fable writes `plans/rethink/<scope>-<package>-kickoff.md`,
   triaging Intake first. No plan on disk = no execution.
2. **Execute** — executor seat, standing rules (TDD, per-package tests, deploy
   loop, tokens are law, no unapproved spend, commit+push per verified step).
   Unconfident forks → Fable consult; scope changes → operator.
3. **Review** — independent read-only reviewer over the landed diff
   (operator rule 2026-07-22, DECISIONS 6b: "don't skip reviewing changes").
   Findings route back to the owning seat and get fixed BEFORE the gate.
   Why structural: an executor's tests share the executor's blind spots —
   every "unknown bug" of 2026-07-22 was reachable by a plain read.
4. **Gate** — Fable verifies against the kickoff's bar with own-run oracles;
   verdict recorded in the plan + HANDOFF. Deferred items get written into a
   backlog above before the gate closes. No gate without step 3's findings
   resolved or explicitly boarded.
