Merge resolution — Streams + Notes → Merge → Graph → Arc [Flow]
Source path: knowledge-base/diagrams/flows/tutor-resolution.md
# Merge resolution — Streams + Notes → Merge → Graph → Arc `[Flow]`
The **Merge** task closes the present-day layer. On the domain's cadence it folds **every** mature Stream and **every** ready Note into canon through **one group Merge** that synthesises a Reasoning Graph (opt-in) and continues the story with a new Arc. It is **autonomous** — always armed, no per-fold approval. The operator steers it *earlier and coarser*: designate a Stream's outcome in the Merges area **before** the window, flip the input switches, set the cadence, or merge early. Deterministic, in-pass, audit-critical, and reversible (branch + version).
```mermaid
flowchart TB
Streams(["open Streams<br/>(present-day belief, held by a perspective)"])
Notes(["ready Notes<br/>(the operator's authored input)"])
Streams -->|"streamMaturity()"| Mature
Mature{"mature?<br/>decisive (logit margin ≥ τ) ·<br/>committed · saturating"}
Mature -->|"no — contested/volatile/dormant"| Wait(["stay open — keep gathering evidence"])
Mature -->|"yes"| Gather
Notes -->|"unlocked, non-empty"| Gather
Designate(["operator PRE-MERGE designation<br/>(Merges area, before the window)"]) -.->|"selectedOutcomes<br/>overrides the engine lean"| Gather
Gather["**buildMergeProposal** — the in-pass group basis<br/>ALL mature Streams + ALL ready Notes"]
Gather --> Merge
subgraph Merge["Merge — the fold into canon (one pass, one merge, one arc)"]
direction TB
Fold["resolve every Stream's stance +<br/>fold every ready Note as note-inputs"]
Graph["**buildMergeGraph** → SavedReasoningGraph<br/>(OPT-IN: only when config.mergeGraph)"]
Fold --> Graph
end
Merge -->|"COMMIT_MERGE_ARC (atomic, pinned-basis revalidated)"| Arc["**new Arc** stamped basisMergeIds<br/>(Notes lock to the merge; basis change ⇒ clean abort, nothing stranded)"]
Arc -->|"Merge folded into a Thread"| Canon(["canon — the long-horizon layer"])
```
**Invariants**
- **Autonomous — no approval gate.** A Merge is always armed; the engine never leaves a standing directive and no Story asks the user to approve a fold. The `merge-applied` Story is a readout, not an action card. Control is the cadence, the Stream/Note/Graph switches, pre-merge outcome designation, and branch/version reversal.
- **One pass, one merge, one arc.** A single run gathers ALL mature Streams + ALL ready Notes (`buildMergeProposal`) and folds them into ONE Merge → ONE Arc. There is no two-tick propose/consume and no notes-only fallback.
- **Operator designation is respected, then cleared.** A pre-window outcome designation is a `staged` directive carrying `selectedOutcomes`; `collectMergeDesignations` reads it, `mergeFromStreams` applies it over the engine lean, and the pass RESERVES→FINALIZES it so it clears (no stale re-apply, no Watch-chain over-fire).
- **Maturity is deterministic, not a judgement call.** `streamMaturity` ripens a Stream on a decisive logit margin (`≥ STANCE_TAU_CLOSE`) OR a settled category (`committed` / `saturating`). Contested, volatile, developing, and dormant are **not** ripe.
- **Never strands.** `COMMIT_MERGE_ARC` revalidates the pinned Stream/Note basis; if it moved under the merge the pass aborts cleanly — Streams stay open, Notes stay unlocked, and the next cadence rebuilds and retries.
- **One Merge, one Graph (opt-in).** When `config.mergeGraph` is set, `mergeGraphId(mergeId)` is deterministic — a Merge owns exactly one Reasoning Graph, synthesised from its resolved outcomes + priors, which later grounds Positions ([projection-anatomy](../concepts/projection-anatomy.md)). Off by default (the CRG is the most expensive step).
- **`basisMergeIds` records continuity per branch.** The generated Arc stamps which merges extended continuity on *this* branch. A fork deep-copies the ledger (`forkLedger`) with fresh ids + origin back-links.
**Where it lives:** `src/lib/core/merging.ts` (maturity), `src/lib/core/program/merge.ts` (group basis · eligible streams/notes · designations), `src/lib/server/ops/handlers/program-merge.ts` (`runLoopMerge`, the autonomous fold), `src/lib/core/merges.ts` (`buildMergeGraph` · `forkLedger` · `basisMergeIds`), `src/lib/core/reducer/domain-reducer/slices/merge.ts` (`COMMIT_MERGE_ARC`). Task cadence: [program-loop](../concepts/program-loop.md); the three layers it moves belief between: [three-layers](../concepts/three-layers.md). Engine identifier stays `tutor`; surfaced as **Merge**.
Open on GitHubRaw Markdown source