aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/design/wrapup-routing-spec-review.org172
-rw-r--r--docs/design/wrapup-routing-spec.org110
-rw-r--r--todo.org9
3 files changed, 72 insertions, 219 deletions
diff --git a/docs/design/wrapup-routing-spec-review.org b/docs/design/wrapup-routing-spec-review.org
deleted file mode 100644
index 0a2fb8d..0000000
--- a/docs/design/wrapup-routing-spec-review.org
+++ /dev/null
@@ -1,172 +0,0 @@
-#+TITLE: Review: Wrap-Up Inbox/Transcript Routing
-#+AUTHOR: Claude Code (rulesets)
-#+DATE: 2026-06-21
-#+STARTUP: showall
-
-* Scope reviewed
-
-Spec under review: =docs/design/wrapup-routing-spec.org= (read twice — once for problem/design/decisions, once for gaps).
-
-Code and conventions read, with the facts each contributes:
-
-- =.ai/workflows/wrap-it-up.org= — Step 3 is the attach point. Step 3 already runs four =todo-cleanup.el= passes (hygiene, =--archive-done=, =--sync-child-priority=, =lint-org=), the roam inbox sweep, and the *Inbox sanity check* (lines 209-233), which counts unprocessed =inbox/= files and marks the wrap incomplete if non-zero. The router would be a new sub-step after that check.
-- =.ai/workflows/process-inbox.org= — the per-item disposition engine. Value gate (3 questions), Skeptical Review, Phase B classification (implement / fold / file as TODO), Phase B.1 priority-scheme check, Phase D apply, and the *reject-from-another-project* flow (Phase D, lines 172-182): write a response file, deliver via =inbox-send <sender> --file=, delete the local copy. This is the existing, tested recovery path for a mis-routed handoff.
-- =.ai/scripts/inbox-send.py= — the cross-project handoff mechanism. =_is_project= (line 60-62) requires *both* =.ai/= AND =inbox/=. =discover_projects= scans =DEFAULT_ROOTS= (=~/projects/=, =~/code/=). Filenames follow =YYYY-MM-DD-HHMM-from-<source>-<slug>.<ext>= (lines 176, 196). =build_text_org= stamps =#+SOURCE: from <source>= — provenance is already baked into every inbox-send drop.
-- =claude-rules/cross-project.md= — inbox-send is the *sanctioned* cross-project write path; doing work directly in another project's scope is otherwise stop-and-ask. A proactive inbox-send needs no confirmation; a cross-scope edit does.
-- =.ai/scripts/todo-cleanup.el= — =tc--find-section= (line 141) matches the unique level-1 "Open Work"/"Resolved" heading, returning =nil=/='multiple=. =--archive-done= (=tc-archive-done-in-file=, line 207) is an *intra-file* subtree move: cut from "Open Work", reinsert under "Resolved" *in the same buffer*. There is no cross-file or cross-repo move primitive today — Phase 2 would be net-new.
-
-Live precondition check run during this review (=ls= over =~/code/=):
-- =little-elisper=, =rsyncshot=, =winvm= — have =inbox/= AND =todo.org= with one "Open Work" heading.
-- =chime=, =yt-sync= — have =inbox/= but *no =todo.org= at all*.
-
-* Implementation-readiness
-
-*Not ready.* One blocking architecture finding (H1: the direct-move design vs the inbox-route alternative) must be dispositioned before Phase 2 can be built, because the answer reshapes D2 and Phases 2-4. A second blocking finding (H2: the candidate-set marking mechanism, which the spec itself flags for spec-review to pin) has no decision recorded. The six =* Decisions= all read =DONE= ([6/6]), so the spec passes the decision-cookie gate — but D2 ("move atomically through a helper") is the decision H1 challenges, and the spec explicitly defers the candidate-set marking to "Phase 3/4" without choosing, which is the unresolved core of H2.
-
-* Overall assessment
-
-The problem is real and well-scoped: an accepted keeper filed into the wrong project's =todo.org= has no clean home, and the manual two-repo edit is error-prone. The interaction design (batch go/skip, no per-item triage, reviewable surfaced list, confidence labels) is sound and matches Craig's stated ask. The decision record is unusually thorough, and the Reading-A-vs-B resolution (router acts on filed keepers, not raw inbox files) cleanly separates the router from the inbox gate.
-
-The risk is the central mechanism. The spec chooses to build a *new* primitive — an atomic helper that edits another repo's =todo.org=, inserts an externally-authored task the destination never gated, removes the source, and leans on a "load-bearing, not decorative" provenance note (D3) to make the boundary crossing legible after the fact. That primitive is data-loss-adjacent (source deletion + foreign-file write across two repos), it bypasses the destination project's value gate / priority scheme / Skeptical Review, and it duplicates a capability the codebase already has in a tested, convention-sanctioned form: =inbox-send= + =process-inbox=. The spec's own *Alternatives Considered* section names "Reuse process-inbox's file-as-TODO with a destination argument" and rejects it on cadence grounds (per-item mid-session vs batch-at-wrap) — but that rejection argues against reusing process-inbox's *caller*, not against reusing inbox-send's *delivery channel*. The two are different. Inbox-routing keeps the batch-at-wrap interaction and swaps only the delivery: drop a file, let the destination's own next session file it. That alternative was not evaluated as such, and the live precondition facts (below) tilt the decision toward it.
-
-* High-priority findings
-
-** H1 — Inbox-route supersedes the direct-move helper; D2 and Phases 2-4 should be reshaped [BLOCKING]
-
-*Spec text.* D2 ("Move atomically through a helper, never hand-edit two repos") and the Implementer "move helper" / "cross-project write discipline" notes build a new atomic primitive that writes into project X's =todo.org='s Open Work section and removes the source, authorized by the batch "go", made legible by a provenance note (D3).
-
-*Why it matters.* The inbox-route alternative — for each routable keeper, =inbox-send <destination> --file= (or --text) a handoff into the destination's =inbox/=, then *leave the source where it is* until the destination's own next session runs =process-inbox= and files it per that project's value gate, priority scheme, and =todo-format.md= — beats the direct-move design on five of the six axes the review weighed, and the deciding axis (precondition) was discovered to run opposite to the spec's assumption.
-
-1. *Safety / data-loss.* Direct-move is a cross-repo write to a foreign =todo.org= plus a source deletion, in one helper — the worst-case failure (a confidently-wrong destination, which the spec names as "the worst failure" in Readiness dimensions) corrupts another project's tracker with an externally-authored task and removes it from the source. Inbox-route's worst case is a file dropped in the wrong inbox/, which the receiving session simply *rejects via process-inbox's existing reject-from-another-project flow* (process-inbox.org Phase D, lines 172-182) — a tested recovery path that already exists. Dropping a file is additive and non-destructive at the destination; editing its =todo.org= is not. The source-deletion half can also wait: under inbox-route the source keeper stays put until the destination confirms by filing, so a mis-route never both corrupts the destination and loses the task.
-
-2. *Reuse / does inbox-route make Phase 2 unnecessary.* Yes. =inbox-send.py= already does cross-project delivery with from-prefix, date stamp, and =#+SOURCE:= provenance — the exact provenance D3 calls "load-bearing" is already emitted by =build_text_org= (inbox-send.py line 152-160) and the filename convention (line 176/196). =process-inbox= already does the per-item filing with the destination's conventions. Phase 2 (the new atomic move helper) and the load-bearing-provenance design (D3) both become unnecessary under inbox-route — the spec would build and test a new data-loss-adjacent primitive to do what two tested tools already do together. This is the strongest argument: the codebase already sanctions this exact path (=cross-project.md=: "Dropping a proposal in its inbox is the sanctioned alternative" to cross-scope work).
-
-3. *Conventions.* Direct-move inserts a task the destination *never gated* — it skips that project's value gate (does this advance a TODO / improve the project / serve the mission), its Phase B.1 priority scheme (an ungraded TODO in a project with a legend is a defect per process-inbox.org Common Mistake 3), and its Skeptical Review. The provenance note (D3) is an honest admission of exactly this gap: "the destination's next session sees an externally-authored task it didn't file." Inbox-route closes the gap by construction — the destination's own session, which understands its context, files the item through its own gate.
-
-4. *Latency.* This is the *only* axis direct-move wins. It lands the task in =todo.org= immediately; inbox-route defers filing to the destination's next session. But the task being routed is, by definition, *not* this project's work — it's homeless backlog headed elsewhere. There is no actor in the current session who needs it filed now. Immediacy buys nothing here and costs the safety, reuse, and convention wins above. (The startup workflow auto-runs process-inbox when a destination's inbox is non-empty — Phase C step 2 — so the deferral self-resolves on the destination's next launch with no manual step.)
-
-5. *Precondition — discovered fact, reverses the spec's assumption.* Direct-move requires the destination to have a =todo.org= with a clean "Open Work" heading (D1, via =tc--find-section=); a destination lacking it is "surfaced and skipped, never guessed at" — i.e. *unroutable*. Inbox-route requires the destination to have an =inbox/=. Live check today: =chime= and =yt-sync= have =inbox/= but *no =todo.org= at all*. Under direct-move, a keeper whose home is chime or yt-sync is silently unroutable — the discovery filter excludes them. Under inbox-route, it is delivered fine. The spec assumed "destination has an inbox" was the fragile precondition; the opposite is true — inbox/ was just created for these projects, while todo.org is the one that's missing. Inbox-route degrades gracefully where direct-move drops the task on the floor.
-
-*The one counter to weigh honestly:* inbox-route requires the destination's session to *eventually run*. A project Craig never opens accumulates unfiled handoffs in its inbox. But that is strictly better than direct-move's failure mode (a project Craig never opens gets a foreign task silently inserted into its tracker that no session gated), and the inbox sanity check on *that* project's next wrap surfaces the backlog. Stale-inbox is visible and recoverable; a wrongly-filed foreign todo is neither until someone notices it.
-
-*Recommendation.* Reshape D2 to "deliver routable keepers to the destination's inbox via =inbox-send=; do not edit the destination's =todo.org= directly." Drop Phase 2 (the atomic move helper) and D3's load-bearing-provenance burden — inbox-send provides provenance for free. Phase 4 wiring calls =inbox-send= per surfaced item instead of the helper. Keep Phase 1 (widened discovery) but change its filter to "has =inbox/=" (matching =_is_project=) rather than "has =todo.org= with Open Work" — though see H1a below on whether discovery needs widening at all. Keep Phase 3 (recommendation engine) unchanged; it is orthogonal to the delivery mechanism and remains the interesting, uncertain part.
-
-*** H1a — file-per-task vs one batch file: file-per-task fits process-inbox's per-item disposition
-
-If H1 is accepted: =process-inbox= dispositions *per item* (Phase B: read each, value-gate each, classify each). One file per routed task maps one-to-one onto that loop — each becomes its own act/fold/file/reject decision at the destination. A single batch file forces the destination session to split it back into items before disposition, re-deriving the boundaries the source already knew. Emit one =inbox-send --file= (or --text) per routable keeper. The batch "go" in the wrap interaction stays a single keystroke; it just fans out to N inbox-send calls instead of N todo.org edits.
-
-*** H1b — under inbox-route, "widened discovery" may be unnecessary
-
-The spec widens discovery from ".ai marker" to "todo.org with Open Work" so a plain code repo with a todo.org is routable. But =inbox-send='s existing =discover_projects= (=.ai/= + =inbox/=) is already the destination set for inbox-route, and it is tested. If the goal is "any project Craig keeps a todo.org in," note that today every code project with a todo.org checked here also has =.ai/= — so the existing filter likely already covers the real destination set. Phase 1 may reduce to "reuse =inbox-send='s discovery," eliminating new discovery code entirely. Confirm the destination universe before building a widened filter; if a real destination has =todo.org= but no =.ai/=, name it.
-
-** H2 — Candidate-set marking mechanism is unspecified [BLOCKING — spec flags this for spec-review to pin]
-
-*Spec text.* The Implementer "candidate set" note (and Phase 3) say the router considers only "keepers =process-inbox= filed this session whose inferred home differs from the current project," but explicitly leaves *how those are marked* to "Phase 3/4: either =process-inbox= tags a cross-project-candidate keeper at file time, or the router infers from a =CREATED= stamp dated this session plus content." The spec asks spec-review to pin it.
-
-*Why it matters.* Without a definite marking mechanism, Phase 4 cannot know which local tasks to surface, and the design constraint ("session-filed inbox keepers only, never the standing backlog") is unenforceable. The two options are not equivalent:
-
-- *Option A — tag at file time.* =process-inbox= Phase D ("File as TODO") stamps a property (e.g. =:ROUTE_CANDIDATE: <inferred-project>=, or a =:cross-project-candidate:= tag) on any keeper whose inferred home differs from the current project. The router then greps for that marker. *Pro:* precise — only genuinely-routable, this-session-filed keepers carry it; zero false positives from the standing backlog; the inference happens once, at file time, by the session that read the handoff (best context). *Con:* requires a paired edit to =process-inbox.org= Phase D (File-as-TODO), so the two workflows ship coupled.
-
-- *Option B — infer from CREATED-this-session + content.* The router scans =todo.org= for tasks with a =:CREATED:= (or =CLOSED:='s analog) stamp dated today and re-runs content inference. *Pro:* no process-inbox change. *Con:* fragile — depends on every filed keeper carrying a dated stamp (process-inbox.org Phase D does *not* currently stamp =:CREATED:= — verified: the File-as-TODO step adds priority/tags/body, no creation timestamp), so this option requires *adding* that stamp anyway, which is the same paired edit as Option A but with weaker precision; and "this session" is ambiguous across same-day sessions.
-
-*Recommendation — pin Option A.* Tag at file time. It is the most precise, it puts the inference where the context is richest (the session reading the handoff), and Option B's "no process-inbox change" advantage is illusory because process-inbox doesn't stamp =:CREATED:= today, so Option B *also* needs a process-inbox edit. Given both need the paired edit, choose the one with no false positives. Make this a new decision task (D7) and add the =process-inbox.org= Phase D edit to the implementation phases (currently absent — see M3). Under H1's inbox-route, the marker doubles as the inbox-send trigger: at wrap, any local keeper carrying =:ROUTE_CANDIDATE:= is the candidate set, and "go" inbox-sends each to its tagged destination.
-
-* Medium-priority findings
-
-** M1 — Recommendation-engine confidence has no defined threshold or low-match behavior [non-blocking]
-Phase 3 and the Risks section lean on a "confidence label" and "low-confidence flagged" as the safety mechanism, but the spec never defines what produces high vs low confidence (project-name match in item text = high? topic-word overlap = low?) or what "go" does with a *no-match* item. Acceptance criterion "a low-confidence recommendation is visibly labeled" is untestable without the threshold. Define the tiers concretely: strong = destination project name or path appears literally in the item; weak = topic-word overlap only; none = stays put, never surfaced as a move. State whether "go" moves low-confidence items or only high-confidence ones. (This finding stands regardless of H1 — it's about the engine, which both designs share.)
-
-** M2 — "Empty routable set → router stays silent" is good; make it a tested case [non-blocking]
-The Readiness dimensions say an empty routable set produces no prompt. Confirm Phase 4 surfaces nothing (not "0 items to route") so the common case — every wrap where nothing is routable — adds zero interaction. This mirrors =--archive-done='s real-mode no-op silence (=tc--emit-archive-report=, todo-cleanup.el line 399-409). Add it to the test surface.
-
-** M3 — Implementation phases omit the paired process-inbox edit [non-blocking, but fix before Ready]
-H2's Option A (and Option B) require an edit to =process-inbox.org= Phase D to stamp the route-candidate marker at file time. No phase covers it. Whichever design wins H1, the candidate-set marker is filed by process-inbox, so a phase (or a line in Phase 3) must cover that paired workflow edit and its =.ai/= mirror sync.
-
-** M4 — Documentation plan names a cross-project.md edit; under H1 it changes [non-blocking]
-The spec plans "a note in =cross-project.md= that the router is a sanctioned cross-project write path." Under direct-move that note documents a *new* write path (todo.org edit). Under inbox-route (H1) the router uses the *already-sanctioned* path (inbox-send), so the note becomes "the wrap-up router uses inbox-send to deliver routable keepers" — lighter, and consistent with the existing rule rather than extending it. Adjust per the H1 outcome.
-
-* UX observations
-
-- The batch go/skip with a reviewable surfaced list is the right shape and matches Craig's explicit ask; keep it under either design. The single keystroke fans out to N inbox-sends (H1) or N moves (as-written) — the user-facing interaction is identical, which is what makes H1 a clean swap behind the interaction.
-- Surface the destination *and* the delivery mode in the list line so the user knows what "go" will do: e.g. =justin-onboarding → work (inbox handoff, high confidence)=. Under direct-move the parenthetical would read =(todo.org move)=; the user should see the consequence before the keystroke, especially since one writes a foreign tracker and one drops a file.
-
-* Architecture observations
-
-- The recommendation engine is correctly identified as the weak point and is well-isolated as a pure function =(item, project-list) → (destination, confidence)= (Phase 3). Keep it pure and unit-test it directly; it is the same under both designs.
-- The spec's instinct to reuse =tc--find-section= and =--archive-done='s subtree logic is good *for an intra-file move* — but =--archive-done= moves within one buffer (todo-cleanup.el line 228-255 cut-and-reinsert in the same file). Extending it to cross-*file*, cross-*repo* moves is a larger change than "reuse the existing logic" implies: two buffers, two save points, two repos' git state, and atomicity across all of it. This under-stated delta is itself an argument for H1 — inbox-send is already a clean cross-repo file write with none of that coupling.
-- Under H1, the only new code is the recommendation engine (Phase 3) + thin wrap-up wiring that shells out to =inbox-send= (Phase 4) + the process-inbox marker stamp (M3). That is materially less new surface than the as-written design (new atomic cross-repo move helper with ERT coverage for partial-move/rollback), and it reuses two tested tools.
-
-* Robustness and performance observations
-
-- Performance is a non-issue (inbox single-digits, projects in the tens, no hot path) — the spec's read is correct.
-- Robustness flips decisively to inbox-route: the as-written design must prove "no partial move on failure" (acceptance criterion + Phase 2 ERT) for a two-repo write+delete. inbox-route has no partial-move state to defend — a file either copied or didn't (=shutil.copy2=, inbox-send.py line 198), and the source is untouched until the destination files it. The hardest test case in the spec (atomic no-op on failure) largely evaporates under H1.
-
-* Test strategy recommendations
-
-Under the recommended (H1 / inbox-route) design:
-
-- *Unit — recommendation engine (Phase 3):* strong match (destination project named literally in item) → high confidence + correct destination; weak match (topic-word overlap only) → low confidence; no match → stays put, not surfaced. Boundary: item naming *two* discovered projects (tie-break / lowest-confidence). Error: empty project list → all items stay put.
-- *Unit — candidate-set marking (H2/Option A):* a keeper whose inferred home differs from current is stamped =:ROUTE_CANDIDATE:=; a keeper whose home *is* the current project is not; a standing-backlog task (no this-session marker) is never a candidate.
-- *Integration — wrap-up wiring (Phase 4):* router surfaces N candidates → "go" issues N =inbox-send= calls landing N dated =from-<source>= files in the right inboxes, sources untouched; "skip" leaves everything in place, wrap completes clean; empty candidate set → zero interaction (M2).
-- *Integration — destination lacking todo.org:* a candidate routed to chime/yt-sync (inbox/ but no todo.org) still delivers (proves H1's graceful-degradation win).
-- *Recovery path (no new code, but assert it):* a mis-routed file at the destination is rejectable via process-inbox's reject-from-another-project flow — document this as the recovery story in the spec rather than testing it here (it's process-inbox's existing behavior).
-
-If the as-written (direct-move) design is kept instead, the test surface must add: successful cross-repo move, missing-destination-heading refusal, source-removal-only-on-success, and no-partial-move-on-failure across two repos — the harder surface H1 removes.
-
-* Documentation and tooling recommendations
-
-- =wrap-it-up.org= Step 3: add the router sub-step prose after the Inbox sanity check, stating clearly it is *optional* (skip is a clean wrap) versus the sanity check which *gates*. The spec's D1 already calls for this; make sure the prose names the gate-vs-optional split so a future reader doesn't collapse the two inbox-touching steps (the spec's own Risk #2).
-- =cross-project.md=: per M4, adjust the planned note to the H1 outcome.
-- =make test= picks up new ERT/ bats files by glob (confirmed in CLAUDE.md) — no Makefile edit needed for the new test files. Note this in the spec so the implementer doesn't add one.
-- If Phase 3's engine is Python (to sit beside =inbox-send.py=) vs elisp (to sit beside =todo-cleanup.el=), the spec should pick — the language choice follows H1: under inbox-route the wiring shells to a Python script, so a Python engine co-located with inbox-send is the natural home; under direct-move it's elisp beside todo-cleanup. Name it.
-
-* Suggested spec edits
-
-1. Reopen D2 as a decision between direct-move and inbox-route, record the H1 analysis, and (recommended) resolve it to inbox-route. If inbox-route is chosen: mark the old D2/D3 SUPERSEDED, drop Phase 2, rewrite Phase 1 (discovery filter) and Phase 4 (wiring calls inbox-send), and update Acceptance criteria (the "moves into Open Work section / removes from source / no partial move" criteria become "delivers a from-<source> handoff to the destination inbox / source untouched / destination files via its own process-inbox").
-2. Add D7 pinning the candidate-set marking mechanism to Option A (tag at file time), per H2.
-3. Add a phase (or fold into Phase 3) for the paired =process-inbox.org= Phase D marker-stamp edit + =.ai/= mirror sync, per M3.
-4. Define the confidence tiers and no-match behavior concretely in Phase 3 / Acceptance criteria, per M1.
-5. Add the "empty routable set → zero interaction" acceptance criterion, per M2.
-
-* Agreed decisions
-
-None reached interactively — this is a fresh-context review. The recommendations above are proposals for the author (spec-response) to accept/modify/reject.
-
-* Open questions
-
-1. *Direct-move or inbox-route?* (H1) — the one answer that reshapes the spec. Recommendation: inbox-route.
-2. *Candidate-set marker: tag-at-file-time (Option A) or CREATED-inference (Option B)?* (H2) — recommendation: Option A.
-3. *Does the real destination set include any project with =todo.org= but no =.ai/+inbox/=?* (H1b) — if no, drop the widened-discovery phase and reuse =inbox-send='s discovery.
-
-* vNext candidates
-
-- Transcript routing (already D4-deferred in the spec; carry to =todo.org= as =[#D]=).
-- Per-item destination correction loop (spec's Alternatives "Neutral" note) — only if batch-only proves too blunt.
-
-* Implementation tasks (drop-in for todo.org)
-
-Reflecting the recommended inbox-route design (H1). If the author keeps direct-move, swap Phase 2 back in and adjust Phase 1/4 accordingly.
-
-#+begin_example
-** TODO [#B] Wrap-up routing — destination discovery :feature:
-Reuse inbox-send's project discovery (=.ai/= + =inbox/=) as the destination set; confirm whether any real destination has todo.org but no =.ai/= before building a widened filter. Spec: [[file:wrapup-routing-spec.org]] (Implementation phases, phase 1; review H1b).
-
-** TODO [#B] Wrap-up routing — candidate-set marking in process-inbox :feature:
-Stamp =:ROUTE_CANDIDATE: <inferred-project>= on any keeper process-inbox files whose inferred home differs from the current project (Option A). Paired edit to process-inbox.org Phase D + =.ai/= mirror sync. Spec: [[file:wrapup-routing-spec.org]] (review H2/M3).
-
-** TODO [#B] Wrap-up routing — recommendation engine :feature:
-Pure function (item, project-list) → (destination, confidence) with concrete strong/weak/none tiers; no-match stays put. Unit-tested. Spec: [[file:wrapup-routing-spec.org]] (Implementation phases, phase 3; review M1).
-
-** TODO [#B] Wrap-up routing — wrap-up step wiring :feature:
-Add the optional router sub-step to wrap-it-up.org Step 3 after the inbox sanity check: surface candidates (destination + delivery mode + confidence), batch go/skip, "go" fans out one inbox-send per candidate, source untouched. Empty set = zero interaction. Sync the =.ai/= mirror. Spec: [[file:wrapup-routing-spec.org]] (Implementation phases, phase 4; review M2/M4).
-
-** TODO [#D] Wrap-up routing — transcript filing (vNext) :feature:
-File a meeting recording into the destination assets/ per working-files.md, gated on the source-location decision. Spec: [[file:wrapup-routing-spec.org]] (Decisions D4, Implementation phases, phase 5).
-
-** TODO [#B] Wrap-up routing — test surface :test:
-Unit: recommendation engine (strong/weak/none, two-project tie, empty list); candidate-set marker (cross-project keeper tagged, local keeper not, standing backlog never). Integration: go issues N inbox-sends to correct inboxes with sources untouched; skip leaves all in place; empty set = no interaction; candidate routed to a destination with inbox/ but no todo.org still delivers. Spec: [[file:wrapup-routing-spec.org]] (Acceptance criteria; review Test strategy).
-#+end_example
-
-* Rubric
-
-*Not ready.* Two blocking findings (H1 reshapes the central mechanism; H2 pins the candidate-set marking the spec itself flagged) must be dispositioned before implementation. The most useful outcome of spec-response is to resolve H1 (recommend inbox-route) and H2 (recommend Option A), at which point the spec moves to Ready — the remaining findings (M1-M4) are author-discretion improvements that don't gate the rubric.
diff --git a/docs/design/wrapup-routing-spec.org b/docs/design/wrapup-routing-spec.org
index 08cdf1f..434f8d9 100644
--- a/docs/design/wrapup-routing-spec.org
+++ b/docs/design/wrapup-routing-spec.org
@@ -3,12 +3,8 @@
#+DATE: 2026-06-13
#+TODO: TODO | DONE SUPERSEDED CANCELLED
-#+begin_src cj: comment
- this spec is now approved. take it through the next steps and begin implementation per the spec response process.
-#+end_src
-
* Metadata
-| Status | reviewed 2026-06-21 — Not ready (H1/H2 blocking; see -review.org) |
+| Status | Ready — review incorporated (spec-review, 2026-06-21) |
|----------+-----------------------------------------------------|
| Owner | Craig Jennings |
|----------+-----------------------------------------------------|
@@ -19,7 +15,7 @@
* Summary
-At wrap-up, an inbox handoff that belongs to another project has nowhere to go but the current project's =todo.org= or a deferral. This adds an optional routing step to =wrap-it-up.org=: surface the items that belong elsewhere, recommend a destination project for each, and move the whole batch there on one confirmation. A parallel step files meeting-transcript recordings into the right project's =assets/=.
+At wrap-up, an inbox handoff that belongs to another project, once accepted and filed locally, has no clean home in the current project's =todo.org=. This adds an optional routing step to =wrap-it-up.org=: surface the filed keepers whose home is elsewhere, recommend a destination for each, and on one confirmation deliver each to that project's =inbox/= via =inbox-send= (one handoff per task), removing it from the local =todo.org=. The destination's own next session files it through =process-inbox=, applying that project's value gate, priority scheme, and =todo-format.md=. A parallel step (vNext) files meeting-transcript recordings into the right project's =assets/=.
* Problem / Context
@@ -34,11 +30,11 @@ The friction is small per-item but recurring, and the manual cross-project edit
* Goals and Non-Goals
** Goals
-- At wrap-up, surface inbox items (and transcripts) whose home is a different project, with a recommended destination each.
-- Move the whole batch on one confirmation ("go with recommendations") or leave it entirely ("skip"). No per-item triage.
-- Move a task/event item as a proper org task into the destination's "Open Work" section per =todo-format.md=; move a transcript as a flat-filed artifact per =working-files.md=.
-- Keep the move atomic and visible (it shows in the destination's next git diff, with a provenance note).
-- Discover any project with a =todo.org= as a candidate destination, not only =.ai/= projects.
+- At wrap-up, surface filed keepers whose home is a different project, with a recommended destination each.
+- Route the whole batch on one confirmation ("go with recommendations") or leave it entirely ("skip"). No per-item triage.
+- Deliver each routable keeper to the destination's =inbox/= via =inbox-send=, one handoff per task, and remove the keeper from the local =todo.org= on send. The destination files it through its own =process-inbox=.
+- Provenance is automatic: =inbox-send= stamps the source project and date on every handoff (the =from-<source>= filename and =#+SOURCE:= line). The delivery shows in the destination inbox; the removal shows in the source's git diff.
+- The destination set is any project with an =inbox/= — reuse =inbox-send='s existing discovery.
** Non-Goals
- Not a wrap gate. A skip is a clean, complete wrap.
@@ -47,8 +43,8 @@ The friction is small per-item but recurring, and the manual cross-project edit
- Not a confidence-free auto-mover. A low-confidence destination recommendation says so, and the batch "go" stays trustworthy because the surfaced list is reviewable before the keystroke.
** Scope tiers
-- v1: task/event routing to a destination project's =todo.org=. The interaction, the recommendation engine, the atomic move helper, the widened project discovery.
-- Out of scope: per-item destination editing, an interactive correction loop, moving items that aren't accepted keepers.
+- v1: task/event routing by =inbox-send= delivery to the destination's =inbox/=. The interaction, the recommendation engine, the candidate-set marker stamped at file time, reusing =inbox-send='s discovery and delivery.
+- Out of scope: per-item destination editing, an interactive correction loop, moving items that aren't accepted keepers, a new cross-repo =todo.org= move primitive (the superseded direct-move design).
- vNext: meeting-transcript filing (gated on the unresolved source-location decision and the file-vs-file+extract question — see Decisions).
* Design
@@ -57,26 +53,26 @@ The friction is small per-item but recurring, and the manual cross-project edit
The router is a new sub-step of =wrap-it-up.org='s Step 3, running after the existing inbox sanity check. Its input is filed keepers, not raw inbox files (decision: Reading B): tasks =process-inbox= accepted and filed into the local =todo.org= this session whose inferred home is a different project. When the router finds such a keeper, it surfaces it in a list, one line each: the task, the recommended destination project, and a confidence marker when the inference is weak. Then two options, batch-level:
-1. Go with the recommendations — apply every recommended move.
+1. Go with the recommendations — route every recommended item (inbox-send to the destination + local removal).
2. Skip — leave the whole batch in place. A skip is a clean wrap.
That is the entire interaction. No per-item walk. The surfaced list is the review surface; the single keystroke is trustworthy because the list was reviewable and low-confidence recommendations flagged themselves.
-A move of a task/event relocates it into the destination project's "Open Work" section as a proper org task (terse heading, body for detail, tags on the heading line, per =todo-format.md=), and removes it from the source. A skipped or unroutable item stays where it is; the existing sanity check still governs whether the wrap is clean.
+On "go", each routable keeper is delivered to its recommended destination's =inbox/= via =inbox-send= (one handoff per task) and removed from the local =todo.org=; the destination's own next session files it through =process-inbox=. A skipped or no-match item stays where it is; the existing sanity check still governs whether the wrap is clean.
** Implementer (the mechanics)
-*Candidate set (what the router considers).* Reading B means the router does not scan the whole local backlog — it would otherwise suggest moving legitimate local tasks every wrap. The candidate set is keepers =process-inbox= filed this session whose inferred home differs from the current project. How those are marked is an implementation detail for Phase 3/4: either =process-inbox= tags a cross-project-candidate keeper at file time, or the router infers from a =CREATED= stamp dated this session plus content. The reviewer should pin which; the design constraint is "session-filed inbox keepers only, never the standing backlog."
+*Candidate set (what the router considers).* Reading B means the router does not scan the whole local backlog — it would otherwise suggest moving legitimate local tasks every wrap. The candidate set is keepers =process-inbox= filed this session whose inferred home differs from the current project, identified by a marker stamped at file time (decision D8): =process-inbox='s "file as TODO" step stamps =:ROUTE_CANDIDATE: <inferred-project>= on any keeper whose inferred home is not the current project. At wrap, the router's candidate set is exactly the local tasks carrying that property — never the standing backlog.
-*Destination discovery.* Widen the project-discovery filter from "directory with a =.ai/protocols.org= marker" (what =inbox-send.py= and the =ai= launcher use) to "directory with a =todo.org= containing a level-1 'Open Work' heading." A plain code repo Craig keeps a =todo.org= in is a valid destination; an =.ai/= directory is not required.
+*Destination discovery.* Reuse =inbox-send.py='s existing =discover_projects= (a project is a directory with =.ai/= AND =inbox/=). The destination must have an =inbox/= to receive a handoff, so that is the natural destination set — no new discovery code. A project with a =todo.org= but no =inbox/= cannot receive an inbox handoff and must be bootstrapped first; in practice every active project has an =inbox/=.
-*Destination anchor.* Reuse =todo-cleanup.el='s existing matcher: =tc--find-section= locates the unique level-1 heading containing "Open Work" (case-insensitive) and returns =nil= / ='multiple= when absent or ambiguous. A destination whose =todo.org= lacks a clean Open Work heading is surfaced and skipped, never guessed at.
+*Delivery.* For each candidate, on "go": (1) =inbox-send <destination> --file= a one-task handoff into the destination's =inbox/= (one file per task, so the destination's =process-inbox= dispositions it as a single item), then (2) remove the keeper from the local =todo.org=. Step 1 is a cross-project write, but it uses the =cross-project.md=-sanctioned path (dropping a file in another project's inbox needs no confirmation); step 2 is a single-file edit in the current project's own =todo.org=, which the wrap is already committing. No new cross-repo move primitive, no foreign =todo.org= edit.
-*The move helper.* A small tool inserts a task subtree under a named project's "Open Work" heading and removes the source atomically — extend =todo-cleanup.el= (it already owns the section matcher and the subtree-move logic for =--archive-done=) or add a sibling =.ai/scripts= tool. Hand-editing across two repos is the error-prone path this replaces.
+*Provenance and filing.* =inbox-send= stamps the source and date automatically (=from-<source>= filename + =#+SOURCE:= line), so the destination's session knows where the item came from. That session files it through its own =process-inbox= — value gate, priority scheme, =todo-format.md= — so the task lands per the destination's conventions rather than as an externally-authored insertion.
-*Recommendation engine.* Infer the destination from the item's content — project names, file paths, topic words — matched against the discovered project list. Conservative by design: a weak match is labeled low-confidence so "go" stays a safe single keystroke. The engine is the interesting, uncertain part; it earns the spec.
+*Recovery (mis-route).* If the recommendation engine picks a wrong destination, the receiving session rejects it via =process-inbox='s reject-from-another-project flow (write a response, =inbox-send= it back to the source named in the provenance, delete the local copy). The task returns to the source project's inbox; nothing is lost or corrupted. This is why removing the source on send is safe — the reject path is the undo.
-*Cross-project write discipline.* Moving an item into project X's =todo.org= writes into X's scope (=cross-project.md=). The batch "go" authorizes it, but the move stays visible (X's next git diff) and leaves a one-line provenance note on the moved task naming the source project.
+*Recommendation engine.* Infer the destination from the item's content — project names, file paths, topic words — matched against the discovered project list, with a confidence tier: *strong* = a destination project's name or path appears literally in the item; *weak* = topic-word overlap only; *none* = no match, the item stays put and is never surfaced as a route. "Go" routes strong and weak items (weak visibly labeled); a no-match item is left in place. Pure function =(item, project-list) → (destination, confidence)=, unit-tested directly. The engine is the interesting, uncertain part; it earns the spec.
* Alternatives Considered
@@ -95,22 +91,22 @@ A move of a task/event relocates it into the destination project's "Open Work" s
- Bad, because =process-inbox= runs per-item mid-session against the local project; the router runs at wrap, batch-level, cross-project. Different cadence, different scope.
- Neutral, because both ultimately call the same atomic move helper — the helper is the shared primitive, the two callers stay distinct.
-* Decisions [6/6]
+* Decisions [9/9]
** DONE Reuse the Open Work matcher for destination anchoring
- Context: the move needs a reliable insertion point in the destination =todo.org=; guessing risks corrupting another project's file.
- Decision: We will reuse =todo-cleanup.el='s =tc--find-section "open work"= matcher, which already handles the unique / missing / ambiguous cases, and skip+surface any destination without a clean Open Work heading.
- Consequences: easier — no new parser, consistent with =--archive-done=. Harder — destinations must carry the "Open Work" heading convention, so a project with a differently-named section is silently unroutable until it conforms.
-** DONE Move atomically through a helper, never hand-edit two repos
+** SUPERSEDED Move atomically through a helper, never hand-edit two repos
+Superseded 2026-06-21 by "Deliver via inbox-send" below. The original plan built a new atomic helper to insert a subtree into a foreign =todo.org= and remove the source. The inbox-route delivers the keeper to the destination's inbox instead, so no cross-repo move primitive is built.
- Context: a move touches two files in two repos; a half-done move loses or duplicates a task.
-- Decision: We will route every move through one helper (extend =todo-cleanup.el= or a sibling =.ai/scripts= tool) that inserts under the destination's Open Work heading and removes the source as one operation.
-- Consequences: easier — no partial-move corruption, one place to test. Harder — a new helper to build and cover with tests before the router can ship.
+- Decision (superseded): route every move through one helper that inserts under the destination's Open Work heading and removes the source as one operation.
-** DONE Cross-project writes stay visible and carry provenance
+** SUPERSEDED Cross-project writes stay visible and carry provenance
+Superseded 2026-06-21 by "Deliver via inbox-send" below. =inbox-send= already stamps provenance (=from-<source>= filename + =#+SOURCE:= line), so the hand-stamped note is unnecessary; the destination files the item through its own gate rather than receiving an externally-authored insertion.
- Context: writing into another project's =todo.org= crosses the =cross-project.md= scope boundary.
-- Decision: We will treat the batch "go" as the authorization, leave the move visible in the destination's git diff, and stamp a one-line provenance note (source project + date) on each moved task.
-- Consequences: easier — the boundary rule is honored without a per-move prompt. Harder — the destination's next session sees an externally-authored task it didn't file, so the provenance note is load-bearing, not decorative.
+- Decision (superseded): treat the batch "go" as authorization, leave the move visible in the destination's git diff, and stamp a one-line provenance note on each moved task.
** DONE Separate router step, operating on filed keepers (Reading B)
- Context: the sanity check gates the wrap on inbox/ contents; the router is optional. The deeper question was the router's input — raw inbox files (Reading A, which overlaps the sanity check) or already-filed keepers that belong elsewhere (Reading B, a todo-routing concern).
@@ -127,31 +123,50 @@ A move of a task/event relocates it into the destination project's "Open Work" s
- Decision: We will keep them distinct. Defer-and-stage parks a proposal-under-review locally as a VERIFY; the router moves an accepted keeper to its home project as a TODO. They differ on review status (proposal vs accepted) and destination (local vs cross-project), and share only the atomic move helper, not the policy. Reading B makes the split clean: the router acts on accepted keepers, never on proposals under review.
- Consequences: easier — two clear, non-competing policies on one shared primitive. Harder — the workflow prose must name the boundary so a future reader doesn't collapse them and reintroduce the ambiguity.
+** DONE Deliver via inbox-send to the destination's inbox, not a direct todo.org move (supersedes D2/D3)
+- Owner / by-when: Craig / ratified 2026-06-21 (spec-response)
+- Context: D2/D3 built a new atomic helper that edits a foreign =todo.org= and removes the source, with a hand-stamped provenance note. =inbox-send= + =process-inbox= already do cross-project delivery: inbox-send writes the handoff with =from-<source>= provenance, and the destination's process-inbox files it through that project's own gate. =cross-project.md= names the inbox as the sanctioned cross-scope write path. A verified precondition reversed the old assumption — some projects have =inbox/= but no =todo.org=, so direct-move's discovery silently drops keepers headed there while inbox-route delivers.
+- Decision: We will route each keeper by =inbox-send= into the destination's =inbox/= (one handoff per task) and let the destination's own =process-inbox= file it; we will not edit the destination's =todo.org= directly. D2 (atomic move helper) and D3 (hand-stamped provenance) are superseded — the helper isn't built, and provenance is inbox-send's by construction.
+- Consequences: easier — no new cross-repo write primitive, no foreign-tracker corruption risk, provenance and per-project filing for free, graceful when the destination lacks a =todo.org=. Harder — filing is deferred to the destination's next session (self-resolving, since startup auto-runs =process-inbox= on a non-empty inbox), and a project never opened accumulates a visible inbox backlog rather than a silent foreign insertion.
+
+** DONE Candidate-set marking: tag :ROUTE_CANDIDATE: at process-inbox file time (Option A)
+- Owner / by-when: Craig / ratified 2026-06-21 (spec-response)
+- Context: the router must consider only this-session-filed inbox keepers whose home is elsewhere, never the standing backlog. Two options: tag at file time (process-inbox stamps a marker) or infer from a =CREATED=-this-session stamp + content. =process-inbox= does not stamp =:CREATED:= today, so the inference option would need that paired edit anyway, removing its only advantage.
+- Decision: We will tag at file time. =process-inbox='s "file as TODO" step stamps =:ROUTE_CANDIDATE: <inferred-project>= on any keeper whose inferred home differs from the current project; the router's candidate set is the local tasks carrying it.
+- Consequences: easier — precise (zero standing-backlog false positives), the inference happens once where context is richest, and the marker doubles as the router's "go" trigger. Harder — a paired edit to =process-inbox.org= Phase D ships coupled with the router.
+
+** DONE Source removal is a local todo.org edit on send; recovery via the reject flow
+- Owner / by-when: Craig / ratified 2026-06-21 (spec-response)
+- Context: the review left source-handling vague ("leave the source until the destination confirms by filing"), but there is no confirmation callback, so leaving it duplicates the task once the destination files. The keeper was filed into the *current* project this session and doesn't belong there.
+- Decision: On "go" we will remove the routed keeper from the *current* project's =todo.org= (a local single-file edit, not a cross-repo write) right after the =inbox-send=. If the destination rejects the handoff, =process-inbox='s reject-from-another-project flow returns it to the source's inbox, so the removal is reversible.
+- Consequences: easier — no duplication, the only deletion is from a file we own and are already committing, the reject path is the undo. Harder — a brief window exists where the task lives only as an in-flight inbox handoff (between send and the destination's filing); acceptable because the handoff file is durable and the reject path recovers a mis-route.
+
* Implementation phases
-** Phase 1 — Widened project discovery
-A discovery function returning every project with a =todo.org= that has a clean Open Work heading, reusing =tc--find-section=. Unit-tested against fixtures: =.ai/= project, plain-code-repo-with-todo, todo-without-Open-Work (excluded), ambiguous-Open-Work (excluded). Leaves the tree working — nothing calls it yet.
+** Phase 1 — Destination discovery (reuse inbox-send)
+Reuse =inbox-send.py='s =discover_projects= (a directory with =.ai/= AND =inbox/=) as the destination set — no new discovery code. Confirm the destination universe: if a real destination has a =todo.org= but no =.ai/+inbox/=, name it and bootstrap its inbox; otherwise the existing filter already covers it. Leaves the tree working.
-** Phase 2 — Atomic cross-project move helper
-Extend =todo-cleanup.el= (or sibling tool) with a "move this subtree into project X's Open Work" operation that inserts at the destination and removes the source as one step, stamping the provenance line. ERT coverage: successful move, missing-destination-heading refusal, source-removal-on-success, no-partial-move-on-failure.
+** Phase 2 — Candidate-set marking in process-inbox
+Extend =process-inbox.org='s "file as TODO" step (Phase D) to stamp =:ROUTE_CANDIDATE: <inferred-project>= on any keeper whose inferred home differs from the current project (decision D8). Sync the =.ai/= mirror. This is the paired workflow edit that lets the wrap-up router find candidates without scanning the standing backlog. (Replaces the superseded atomic-move helper.)
-** Phase 3 — Recommendation engine + candidate-set marking
-Infer destination from item content against the discovered list, with a confidence label. Pure function over (item, project-list) → (destination, confidence). Unit-tested: strong match (project named in item), weak match (topic-only → low-confidence), no match (stays put). Also settle the candidate-set marking (tag at file time vs CREATED-this-session inference) so the router considers only session-filed inbox keepers, never the standing backlog.
+** Phase 3 — Recommendation engine
+Infer destination from item content against the discovered list, with a confidence tier. Pure function =(item, project-list) → (destination, confidence)=. Unit-tested: strong match (destination project named or path present literally → high) , weak match (topic-word overlap only → low, still routed but labeled), no match (stays put, never surfaced), two-project tie (lowest-confidence / tie-break), empty project list (all stay put). The engine is shared by process-inbox's file-time marker (Phase 2) and the wrap-up router (Phase 4), so it lives where both can call it.
** Phase 4 — Wrap-up step wiring
-Add the router sub-step to =wrap-it-up.org= Step 3: surface the batch, the two options, apply-on-go via the Phase 2 helper. Per the D1/D5 decisions once settled. Sync the =.ai/= mirror.
+Add the optional router sub-step to =wrap-it-up.org= Step 3, after the inbox sanity check: surface the candidate batch (one line each: task, destination, delivery mode, confidence), the two options (go / skip). On "go", for each candidate, =inbox-send= a one-task handoff to the destination's =inbox/= and remove the keeper from the local =todo.org=. Empty candidate set = zero interaction (silent). Name the gate-vs-optional split in the prose (the sanity check gates; the router is optional). Sync the =.ai/= mirror.
** Phase 5 — Transcript routing (vNext, gated on the transcript decision)
Only after the transcript-scope decision resolves. File a recording into the destination =assets/= per =working-files.md=, batch go/skip mirroring the task router.
* Acceptance criteria
-- [ ] At wrap, an inbox item naming another project is surfaced with that project as the recommended destination.
-- [ ] "Go" moves every recommended item into its destination's Open Work section as a valid org task with a provenance line, and removes it from the source.
+- [ ] At wrap, a filed keeper naming another project is surfaced with that project as the recommended destination.
+- [ ] "Go" delivers every recommended item as a one-task =from-<source>= handoff into its destination's =inbox/= and removes it from the local =todo.org=.
- [ ] "Skip" leaves every item in place and the wrap completes cleanly.
-- [ ] A destination =todo.org= without a clean Open Work heading is surfaced and skipped, never corrupted.
-- [ ] A low-confidence recommendation is visibly labeled in the surfaced list.
-- [ ] A plain code repo with a =todo.org= (no =.ai/=) is a valid destination.
-- [ ] A failed move leaves both source and destination unchanged (no partial move).
+- [ ] An empty candidate set produces zero interaction (no prompt, no "0 items" line).
+- [ ] A weak (low-confidence) recommendation is visibly labeled in the surfaced list; a no-match item is never surfaced as a route.
+- [ ] A candidate whose destination has an =inbox/= but no =todo.org= still delivers (degrades gracefully).
+- [ ] A mis-routed handoff is recoverable via =process-inbox='s reject-from-another-project flow, returning it to the source's inbox.
+- [ ] The router considers only =:ROUTE_CANDIDATE:=-tagged keepers, never the standing backlog.
* Readiness dimensions
- Data model & ownership: items are org subtrees; the destination owns the moved task after the move (provenance note records origin). N/A for remote/cached state — all local files.
@@ -172,6 +187,14 @@ Only after the transcript-scope decision resolves. File a recording into the des
- *Two inbox-touching steps* (sanity check + router) risk reading as redundant. Dodge: the D1 decision states the gate-vs-optional split in the workflow prose.
- *Scope creep into transcripts* before the source-location question is answered would stall v1. Dodge: transcripts are explicitly vNext behind decision D4.
+* Review dispositions
+
+Everything in the 2026-06-21 review was accepted, with one modify:
+
+- *Modified — H1 source-handling.* The review proposed leaving the source keeper in place "until the destination confirms by filing." There is no confirmation callback, so leaving it would duplicate the task once the destination files. Resolved instead (decision D9) to remove the keeper from the *local* =todo.org= on send — a single-file edit in the project we already own and are committing, with =process-inbox='s reject flow as the undo for a mis-route. Keeps the no-foreign-write safety win without the duplication.
+
+Everything else accepted as written: H1 (inbox-route supersedes direct-move; D2/D3 superseded), H1a (one handoff per task), H1b (reuse =inbox-send= discovery; Phase 1), H2 (tag at file time; D8), M1 (confidence tiers defined in Phase 3 + acceptance), M2 (empty-set silence; acceptance), M3 (paired =process-inbox= edit; Phase 2), M4 (=cross-project.md= note adjusted to "the router uses the sanctioned inbox path").
+
* Review and iteration history
** 2026-06-13 Sat @ 01:23:13 -0500 — Claude Code (rulesets) — author
@@ -188,3 +211,8 @@ Only after the transcript-scope decision resolves. File a recording into the des
- What: spec-review pass. Rubric *Not ready*, two blocking findings. H1: the inbox-route alternative (inbox-send each routable keeper to the destination's inbox/, let its own process-inbox file it) supersedes the direct-move design — reshape D2, drop Phase 2 and D3's provenance burden. H2: pin the candidate-set marking to Option A (tag =:ROUTE_CANDIDATE:= at process-inbox file time). Four medium findings (M1 confidence tiers, M2 empty-set silence, M3 paired process-inbox edit phase, M4 cross-project.md note). Full review + drop-in implementation tasks in the review file.
- Why: Craig challenged D2 directly (why edit a foreign todo.org rather than use the sanctioned inbox-send path). The review confirmed it: inbox-send already emits the exact provenance D3 reinvents, process-inbox already files per-item with the destination's own gate, cross-project.md sanctions the inbox path, and a verified precondition reverses the spec's assumption — chime and yt-sync have inbox/ but no todo.org, so direct-move silently drops keepers headed there while inbox-route degrades gracefully.
- Artifacts: [[file:wrapup-routing-spec-review.org][review file]]. Next: spec-response to disposition H1/H2 (recommend accept both), which moves the rubric to Ready.
+
+** 2026-06-21 Sun @ 02:06:37 -0400 — Craig Jennings + Claude Code (rulesets) — responder
+- What: folded the spec-review in. Accepted H1 (inbox-route) and H2 (tag at file time); superseded D2 and D3; added D7 (deliver via =inbox-send=), D8 (=:ROUTE_CANDIDATE:= marker at file time), D9 (local source removal + reject-flow recovery). Rewrote Summary, Goals, Design mechanics, Implementation phases (dropped the atomic-move helper — Phase 2 is now the =process-inbox= marker edit), and Acceptance criteria for the inbox-route. One modify (D9) refines H1's vague source-handling. Cookie [9/9]; Status → Ready.
+- Why: Craig's inbox-route challenge held up under review — it reuses the sanctioned cross-project path, gets provenance and per-project filing for free, and degrades gracefully where direct-move drops the task. D9 closes the duplication gap the review left open.
+- Artifacts: review file deleted on this pass. Next: Phase 6 implementation-task breakdown into =todo.org= on the author's go.
diff --git a/todo.org b/todo.org
index d551484..08f4c61 100644
--- a/todo.org
+++ b/todo.org
@@ -189,13 +189,10 @@ Craig's answer (2026-06-16): spec it. Phase E reconciles with the "fix speedrun"
:CREATED: [2026-06-13 Sat]
:LAST_REVIEWED: 2026-06-15
:END:
-Optional wrap-up step that surfaces filed keepers belonging to another project, recommends a destination, and batch-moves them into that project's =todo.org= Open Work section (transcript filing deferred to vNext). All six decisions resolved (Reading B: the router acts on session-filed keepers, separate from the inbox gate and from defer-and-stage). Spec ready for review.
+Optional wrap-up step that surfaces filed keepers belonging to another project, recommends a destination, and routes each to that project's =inbox/= via =inbox-send= (the destination's own =process-inbox= files it; transcript filing deferred to vNext). Spec: [[file:docs/design/wrapup-routing-spec.org]] — Ready, [9/9] decisions. Source proposal: [[file:docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org]].
-Spec: [[file:docs/design/wrapup-routing-spec.org]]. Source proposal: [[file:docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org]] (archsetup handoff 2026-06-13). Next: =spec-review=.
-
-#+begin_src cj: comment
- I approved the spec in the spec document. please take it through the rest of the spec response process to implementation. bp
-#+end_src
+*** 2026-06-21 Sun @ 02:06:37 -0400 Spec-review + spec-response complete — Ready
+Craig's review challenge reshaped the design from a direct cross-repo =todo.org= move to =inbox-send= delivery into the destination's inbox (safer: reuses the sanctioned cross-project path, gets provenance + per-project filing for free, degrades gracefully where a destination has an =inbox/= but no =todo.org=). D2/D3 superseded; D7 (inbox-send delivery), D8 (=:ROUTE_CANDIDATE:= marker at file time), D9 (local source removal + reject-flow recovery) added. Spec-review file consumed and deleted. Next: Phase 6 implementation-task breakdown, then build (spec Phases 1-4).
** DONE [#C] Encourage org-roam KB contribution across workflows :feature:
CLOSED: [2026-06-20 Sat]