Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/funnel-intake.md

Funnel intake — design + guide (EPIC FUNNEL-INTAKE)

LAW 2 doc for the funnel intake feature: paste an error report or an idea straight into the funnel, and browse the intake history, from BOTH the viz Funnel tab and the nornir funnel CLI. Errors are first-class funnel items (kind error), triageable to a plan like any idea. Source: src/funnel/{event.rs,state.rs,history.rs}, src/warehouse/funnel.rs (+ iceberg_schema::funnel_events), CLI nornir funnel idea|error|history, server Funnel.SubmitIdea/History, viz src/viz/funnel_tab.rs (paste-in strip + history side-panel).

Why

A human hits an error or has an idea and wants one place to capture it that turns into tracked work. Before, the funnel only ingested "ideas" via funnel submit and had no way to browse what had been submitted. EPIC FUNNEL-INTAKE closes the loop:

Item kind: idea | error

A funnel intake item carries an ItemKind — idea (default) or error. It rides the IdeaSubmitted event and the in-memory Idea, and persists as a new optional column item_kind (field id 28) on the event-sourced funnel_events Iceberg table. Optional + defaulted means it is migration-safe: every pre-FI row (no value) replays as an idea, and a server table created before FI is auto-evolved forward (Iceberg add-column) on the first FI write — see warehouse-schema.md § On-append auto-evolution.

Derived history status

History is a pure projection over the replayed funnel (funnel::history) — never stored, always derived:

plan exists for item        ⇒ planned   (the loop closed: intake → tracked work)
else triage == accept       ⇒ accepted
else triage == drop         ⇒ dropped
else (no triage)            ⇒ untriaged

Items sort newest submission first; the projection plus its kind/status filters back the CLI, the Funnel.History RPC, and the viz history list, so the three never disagree (one inject-assert test pins the derivation).

CLI (parity with the viz)

# FI1 — paste in
nornir funnel idea  "add a dark theme"        # an idea (alias of `submit`)
nornir funnel error - < panic_report.txt      # an error from stdin
nornir funnel error "panic at foo.rs:42"      # an error inline
#   text precedence: --file <path> → positional arg (`-` = stdin) → stdin pipe

# FI2 — browse history (newest-first), JSON by default
nornir funnel history                         # everything
nornir funnel history --kind error            # only error reports
nornir funnel history --status untriaged --table   # human table
nornir funnel history --kind idea --status planned # combined filter

funnel idea/funnel error print the new item id (e.g. i-007). history emits a JSON array of {id, item_kind, text, source, submitted_at, status, plan_ids} (or a compact table with --table).

Viz (Funnel tab)

FI3 — error → plan, same as an idea

An error item is just an Idea with item_kind = Error; it has the same id space (i-NNN) and is triaged + planned with the existing verbs:

ID=$(nornir funnel error - < report.txt)      # capture
nornir funnel plan "$ID" "fix the panic"       # → a plan node like any idea

Data + control flow

intake (FI1)                         history (FI2)
├─ viz paste strip (💡 idea/🐞 error) ─┐   ┌─▶ viz history side-panel
└─ nornir funnel idea|error ──────────┤   ├─▶ nornir funnel history
                                      ▼   ├─▶ Funnel.History RPC
   IdeaSubmitted{item_kind} ──▶ funnel_events ──▶ funnel::history
                              (+ item_kind col 28)  (derive status + lineage)

edges:
  viz paste strip   → IdeaSubmitted
  nornir funnel CLI → IdeaSubmitted
  IdeaSubmitted     → funnel_events (warehouse)
  funnel_events     → funnel::history
  funnel::history   → viz history side-panel / nornir funnel history / Funnel.History RPC
  IdeaSubmitted ⋯⋯⋯▶ funnel_events  (kind=error: triage → plan)

Tests (inject-assert)