@@ 540,13 540,60 @@ schema, and it is also the safe order: a crash can leave a row with no branch
fully recoverable, since the row holds every field — but never a branch whose
metadata is lost.
+**Spaces order the other way round: repository first, then the row**, with the
+directory removed if the insert fails. Git is authoritative and a space's owner
+and name are recoverable from its path, whereas a row without a repository is a
+phantom that lists correctly and 404s on every read — and, with no delete for it,
+is unrepairable.
+
| Crash between | Symptom | Repair |
|---|---|---|
-| row insert and branch write | `open` row, no branch | reconciler deletes the row; the agent re-proposes (it holds the content) |
+| row insert and branch write | `open` row, no branch, **older than the grace window** | delete the row; the agent re-proposes (it holds the content) |
| — | orphan `proposals/*` ref with no row | delete the ref: it is unreferenced and its content is unrecoverable anyway |
-| merge commit and row update | merged ref, row still `open` | reconciler marks merged (the ref is truth for *merged-ness*) |
+| merge commit and row update | row still `open`, branch merged into the approved head **and tip ≠ its recorded base** | mark merged (the ref is truth for *merged-ness*) |
| merge and reindex | stale index | per-space index rev stamp ≠ approved head → reindex |
+The two emphasised guards are not fussiness. Without them **this table eats live
+data**, and both were found by implementing it:
+
+- **The grace window is mandatory.** Row-first ordering is *forced* by the schema
+ (the branch name derives from the serial), so **every** proposal passes through
+ "row exists, branch does not". An ungraced reconciler on a timer would delete
+ an agent's work at random, and never once in a test. A row younger than the
+ window (5 minutes) is in flight, not abandoned.
+- **Merged-ness needs more than ancestry.** A proposal branch is cut *at* the
+ approved head, so between creating it and the agent's first commit its tip is
+ trivially an ancestor of that head — and the plain ancestry rule would mark a
+ contentless proposal merged, permanently, since `open → merged` is terminal.
+ Comparing the tip against the proposal's recorded `base_rev` distinguishes
+ "never written to" from "actually merged".
+
+**Ordering within a pass matters too:** list proposal *refs* for every space
+before reading any proposal *row*. The other order makes a concurrently-opened
+proposal look like "branch with no row" and deletes a live agent's branch; this
+order makes it look like "row with no branch", which the grace window covers.
+
+**Marking merged cannot fully satisfy the schema, and invents two values.** The
+`merged` state requires `approval` and `merged_rev`, and neither is recoverable
+from a ref — the same argument that killed rebuilding a row from a ref. So:
+
+- `approval` is recorded as `policy`. Of the two available lies it is the safe
+ one: `human` would launder unreviewed content as blessed, whereas `policy`
+ merely over-reports into the digest, where a human sees it again. A sound
+ inference exists — a merge touching any path that fails `Policy.AutoMerges`
+ cannot have been policy-authorized — and is worth adding once auto-merge
+ evaluation exists in Phase 3.
+- `merged_rev` is recorded as the current approved head, not the merge commit.
+ They coincide in the common crash-right-after-merge case and diverge if the
+ branch moved on. Finding the true merge commit needs a history walk for "the
+ commit whose second parent is this tip", which `gitx` does not expose.
+
+**Two states are deliberately not repaired**, and are reported rather than
+touched: a merged or rejected row whose branch still exists (not a half-finished
+write — branch cleanup is separate), and a space row whose repository is missing
+(the table covers proposals and the index; a missing repo is an operator
+problem, not a torn write).
+
A **reconciler runs at startup and periodically**: scan `proposals/*` refs and
each space's approved head, compare against rows and index rev stamps, repair
divergence. Deleting an unreferenced ref or a contentless row is safe precisely