@lmzhen/dsh-evolution-merge 0.46.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/index.js +248 -1224
  2. package/lib/types/adapters/engine-screen.d.ts +0 -3
  3. package/lib/types/authoring-flow.d.ts +5 -0
  4. package/lib/types/authoring-flow.js +17 -10
  5. package/lib/types/commit-execute.js +1 -22
  6. package/lib/types/contracts.d.ts +0 -1
  7. package/lib/types/contracts.js +0 -1
  8. package/lib/types/doctor.d.ts +0 -1
  9. package/lib/types/doctor.js +2 -31
  10. package/lib/types/domain/consistency.d.ts +0 -1
  11. package/lib/types/domain/consistency.js +2 -11
  12. package/lib/types/domain/merge-limits.d.ts +0 -10
  13. package/lib/types/domain/merge-limits.js +0 -3
  14. package/lib/types/domain/merge-snapshot.js +4 -6
  15. package/lib/types/domain/pair-ledger.d.ts +0 -1
  16. package/lib/types/domain/row-v3.d.ts +0 -45
  17. package/lib/types/domain/row-v3.js +0 -68
  18. package/lib/types/domain/row.d.ts +2 -84
  19. package/lib/types/domain/row.js +1 -110
  20. package/lib/types/domain/run-coordinator.d.ts +0 -24
  21. package/lib/types/domain/run-coordinator.js +0 -1
  22. package/lib/types/domain/stage-aggregate.d.ts +0 -42
  23. package/lib/types/domain/stage-aggregate.js +1 -116
  24. package/lib/types/domain/state.d.ts +1 -53
  25. package/lib/types/domain/state.js +0 -55
  26. package/lib/types/domain/undo-journal.d.ts +4 -4
  27. package/lib/types/domain/undo-journal.js +5 -7
  28. package/lib/types/index.d.ts +0 -1
  29. package/lib/types/index.js +0 -6
  30. package/lib/types/merge-routes.d.ts +5 -21
  31. package/lib/types/merge-routes.js +53 -93
  32. package/lib/types/new-child-authoring.js +4 -10
  33. package/lib/types/prompts.d.ts +0 -1
  34. package/lib/types/prompts.js +0 -1
  35. package/lib/types/read-commands.d.ts +0 -1
  36. package/lib/types/read-commands.js +0 -4
  37. package/lib/types/recovery-projection.js +0 -3
  38. package/lib/types/round2-outcome.js +15 -37
  39. package/lib/types/scan-run.js +0 -3
  40. package/lib/types/skipped.d.ts +1 -2
  41. package/lib/types/skipped.js +0 -8
  42. package/lib/types/staging.d.ts +0 -2
  43. package/lib/types/staging.js +0 -9
  44. package/lib/types/state.d.ts +1 -39
  45. package/lib/types/state.js +2 -78
  46. package/lib/types/surface/merge-view.d.ts +1 -2
  47. package/lib/types/surface/merge-view.js +1 -2
  48. package/lib/types/undo-operation.js +0 -9
  49. package/lib/types/unit-protocol.d.ts +6 -0
  50. package/lib/types/unit-protocol.js +72 -102
  51. package/package.json +4 -4
  52. package/lib/types/commit-recovery.d.ts +0 -14
  53. package/lib/types/commit-recovery.js +0 -96
  54. package/lib/types/coordinator.d.ts +0 -77
  55. package/lib/types/coordinator.js +0 -137
  56. package/lib/types/domain/commit-journal-store.d.ts +0 -6
  57. package/lib/types/domain/commit-journal-store.js +0 -26
  58. package/lib/types/domain/commit-journal.d.ts +0 -16
  59. package/lib/types/domain/commit-journal.js +0 -26
  60. package/lib/types/domain/indexes.d.ts +0 -21
  61. package/lib/types/domain/indexes.js +0 -15
  62. package/lib/types/domain/merge-report.d.ts +0 -41
  63. package/lib/types/domain/merge-report.js +0 -2
  64. package/lib/types/domain/pair-index-store.d.ts +0 -6
  65. package/lib/types/domain/pair-index-store.js +0 -25
  66. package/lib/types/domain/pair-index.d.ts +0 -9
  67. package/lib/types/domain/pair-index.js +0 -28
  68. package/lib/types/domain/resolve-pair.d.ts +0 -4
  69. package/lib/types/domain/resolve-pair.js +0 -4
  70. package/lib/types/domain/row-tx.d.ts +0 -27
  71. package/lib/types/domain/row-tx.js +0 -31
  72. package/lib/types/domain/run-record.d.ts +0 -45
  73. package/lib/types/domain/run-record.js +0 -24
  74. package/lib/types/domain/state-v3.d.ts +0 -4
  75. package/lib/types/domain/state-v3.js +0 -4
  76. package/lib/types/facts.d.ts +0 -26
  77. package/lib/types/facts.js +0 -65
  78. package/lib/types/metrics.d.ts +0 -16
  79. package/lib/types/metrics.js +0 -27
  80. package/lib/types/wire-projection.d.ts +0 -36
  81. package/lib/types/wire-projection.js +0 -77
package/lib/index.js CHANGED
@@ -148,12 +148,6 @@ const MERGE_PAIR_STATES = [
148
148
  "fuse",
149
149
  "skipped"
150
150
  ];
151
- const MERGE_QUEUES = [
152
- "verify",
153
- "pending",
154
- "fuse",
155
- "skipped"
156
- ];
157
151
  /** User-facing queue of one data state. verify/fuse share retry, not two tabs. */
158
152
  function queueOf(state) {
159
153
  switch (state) {
@@ -1273,7 +1267,7 @@ function parseV4State(raw) {
1273
1267
  * The one merge-state ledger: <home>/merge/merge-state.json.
1274
1268
  *
1275
1269
  * Reads are pure projections (no write, no reconciliation side effect). Every write goes
1276
- * through mutateMergeState and a single transactIo, so there is no second file and no
1270
+ * through pairLedger and a single transactIo, so there is no second file and no
1277
1271
  * incremental writer. A row's queue is always derived from its state on read; a hand-edited
1278
1272
  * mismatch is repaired by the next write, never by a GET.
1279
1273
  */
@@ -1346,107 +1340,6 @@ async function loadMergeState(io, home) {
1346
1340
  ...parsed.note === void 0 ? {} : { note: parsed.note }
1347
1341
  };
1348
1342
  }
1349
- /** Schema probe for doctor; the only other reader of mergeStatePath. */
1350
- async function loadStateSchema(io, home) {
1351
- try {
1352
- const raw = await io.readText(mergeStatePath(home));
1353
- if (raw === null) return null;
1354
- const parsed = JSON.parse(raw);
1355
- return typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : null;
1356
- } catch {
1357
- return null;
1358
- }
1359
- }
1360
- /** G3-5 runtime write API: only createVerify/createSkipped/transition. v2 writes are rejected. */
1361
- async function transactRows(_io, _home, _fn) {
1362
- throw new Error("legacy-writer-removed");
1363
- }
1364
- /** The ONE write point. The callback returns the whole row set, not a delta. */
1365
- async function mutateMergeState(_io, _home, _now, _fn) {
1366
- throw new Error("legacy-writer-removed");
1367
- }
1368
- async function upsertRow(io, home, now, row, expect = "any", cas) {
1369
- return await mutateMergeState(io, home, now, (rows) => {
1370
- const index = rows.findIndex((item) => item.pairKey === row.pairKey);
1371
- const current = index < 0 ? void 0 : rows[index];
1372
- if (cas !== void 0 && (cas === null && current !== void 0 || cas !== null && current?.updatedAt !== cas)) return {
1373
- rows,
1374
- result: {
1375
- ok: false,
1376
- current: current ?? row
1377
- }
1378
- };
1379
- if (current !== void 0 && expect !== "any" && (expect === "absent" || queueOf(current.state) !== expect)) return {
1380
- rows,
1381
- result: {
1382
- ok: false,
1383
- current
1384
- }
1385
- };
1386
- if (current === void 0 && expect !== "any" && expect !== "absent" && expect !== "verify") return {
1387
- rows,
1388
- result: {
1389
- ok: false,
1390
- current: row
1391
- }
1392
- };
1393
- const normalized = {
1394
- ...row,
1395
- queue: queueOf(row.state)
1396
- };
1397
- const next = [...rows];
1398
- if (current === void 0) next.push(normalized);
1399
- else next[index] = {
1400
- ...normalized,
1401
- createdAt: current.createdAt
1402
- };
1403
- return {
1404
- rows: next,
1405
- result: { ok: true }
1406
- };
1407
- });
1408
- }
1409
- async function moveRow(io, home, now, id, patch) {
1410
- return await mutateMergeState(io, home, now, (rows) => {
1411
- const index = rows.findIndex((item) => item.id === id);
1412
- if (index < 0) return {
1413
- rows,
1414
- result: void 0
1415
- };
1416
- const current = rows[index];
1417
- const state = patch.state ?? current.state;
1418
- const next = {
1419
- ...current,
1420
- state,
1421
- queue: queueOf(state),
1422
- updatedAt: new Date(now()).toISOString(),
1423
- ...patch.failure === void 0 ? {} : { failure: patch.failure },
1424
- ...patch.runId === void 0 ? {} : { runId: patch.runId },
1425
- ...patch.unitId === void 0 ? {} : { unitId: patch.unitId },
1426
- ...patch.origin === void 0 ? {} : { origin: patch.origin },
1427
- ...patch.attempts === void 0 ? {} : { attempts: patch.attempts }
1428
- };
1429
- const copy = [...rows];
1430
- copy[index] = next;
1431
- return {
1432
- rows: copy,
1433
- result: next
1434
- };
1435
- });
1436
- }
1437
- /** G3-5 v3-aware drop: only terminal events. */
1438
- async function dropRowV3(_io, _home, _id, _event, _unitId = "unknown") {
1439
- throw new Error("legacy-writer-removed");
1440
- }
1441
- async function dropRow(io, home, now, id) {
1442
- return await mutateMergeState(io, home, now, (rows) => {
1443
- const next = rows.filter((item) => item.id !== id);
1444
- return {
1445
- rows: next,
1446
- result: next.length !== rows.length
1447
- };
1448
- });
1449
- }
1450
1343
  async function rowsOf(io, home, queue) {
1451
1344
  const loaded = await loadMergeState(io, home);
1452
1345
  return queue === void 0 ? loaded.state.rows : loaded.state.rows.filter((row) => row.queue === queue);
@@ -1709,7 +1602,7 @@ function v3State(body) {
1709
1602
  rows
1710
1603
  };
1711
1604
  }
1712
- async function readJournal$1(io, home) {
1605
+ async function readJournal(io, home) {
1713
1606
  const raw = await readTextOrNull(io, migrationJournalPath(home));
1714
1607
  if (raw === null) return null;
1715
1608
  try {
@@ -1786,7 +1679,7 @@ async function readStateReadiness(io, home) {
1786
1679
  if (version === 3) {
1787
1680
  const state = v3State(body);
1788
1681
  if (state === null) return blocked("merge-state-needs-repair", "v3 state has an invalid row");
1789
- const journal = await readJournal$1(io, home);
1682
+ const journal = await readJournal(io, home);
1790
1683
  const report = await readReport(io, home);
1791
1684
  const marker = readMarker(body);
1792
1685
  if (body.migration !== void 0 && marker === null) return blocked("merge-state-needs-repair", "migration marker is invalid");
@@ -1845,7 +1738,7 @@ async function ensureMergeStateReady(io, home, fence) {
1845
1738
  outcome = blocked("merge-state-needs-repair", "v3 state has an invalid row");
1846
1739
  return current;
1847
1740
  }
1848
- const journal = await readJournal$1(io, home);
1741
+ const journal = await readJournal(io, home);
1849
1742
  if (journal !== null) {
1850
1743
  outcome = await completeJournal(io, home, journal, current);
1851
1744
  return current;
@@ -2090,15 +1983,6 @@ async function readStaging(io, home, runId) {
2090
1983
  return probeUnknown("staging manifest is not JSON: " + (error instanceof Error ? error.message : String(error)));
2091
1984
  }
2092
1985
  }
2093
- async function ensureStaging(io, home, input) {
2094
- const existing = await readStaging(io, home, input.runId);
2095
- if (existing.kind === "present") return {
2096
- ok: true,
2097
- path: stagingRoot(home, input.runId),
2098
- manifest: existing.value.manifest
2099
- };
2100
- return createStaging(io, home, input);
2101
- }
2102
1986
  async function setStagingTarget(io, home, stageKey, target, mode) {
2103
1987
  const view = await readStaging(io, home, stageKey);
2104
1988
  if (view.kind !== "present") return false;
@@ -2106,9 +1990,6 @@ async function setStagingTarget(io, home, stageKey, target, mode) {
2106
1990
  await io.writeText(join(stagingRoot(home, stageKey), "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
2107
1991
  return true;
2108
1992
  }
2109
- async function dropStaging(io, home, stageKey) {
2110
- await io.remove(stagingRoot(home, stageKey));
2111
- }
2112
1993
  /** Remove expired run-level staging trees; `keep` names runs a live row still points at. */
2113
1994
  async function cleanupExpiredStaging(io, home, now, keep = /* @__PURE__ */ new Set()) {
2114
1995
  let runs;
@@ -2265,6 +2146,7 @@ async function adoptLegacyProduction(io, home) {
2265
2146
  wrote
2266
2147
  };
2267
2148
  }
2149
+ /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
2268
2150
  async function createStageTree(io, home, input) {
2269
2151
  return await createStaging(io, home, input);
2270
2152
  }
@@ -2537,61 +2419,6 @@ async function readPairLedgerV4(io, home) {
2537
2419
  };
2538
2420
  }
2539
2421
  //#endregion
2540
- //#region lib/types/domain/pair-index.js
2541
- function parsePairIndex(raw) {
2542
- const index = /* @__PURE__ */ new Map();
2543
- if (raw === null) return index;
2544
- let parsed;
2545
- try {
2546
- parsed = JSON.parse(raw);
2547
- } catch {
2548
- return index;
2549
- }
2550
- const body = parsed;
2551
- if (!Array.isArray(body.entries)) return index;
2552
- for (const value of body.entries) {
2553
- const entry = value;
2554
- if (typeof entry.pairKey !== "string" || typeof entry.runId !== "string" || typeof entry.result !== "string") continue;
2555
- if (![
2556
- "merged",
2557
- "kept",
2558
- "failed",
2559
- "cancelled",
2560
- "undone"
2561
- ].includes(entry.result)) continue;
2562
- index.set(entry.pairKey, entry);
2563
- }
2564
- return index;
2565
- }
2566
- function serializePairIndex(index) {
2567
- return JSON.stringify({
2568
- schemaVersion: 1,
2569
- entries: [...index.values()]
2570
- }, null, 2);
2571
- }
2572
- //#endregion
2573
- //#region lib/types/domain/pair-index-store.js
2574
- /** Durable pairKey index at <home>/merge/pair-index.json. */
2575
- function pairIndexPath(home) {
2576
- return join(home, "merge", "pair-index.json");
2577
- }
2578
- async function loadPairIndex(io, home) {
2579
- let raw = null;
2580
- try {
2581
- raw = await io.readText(pairIndexPath(home));
2582
- } catch {
2583
- return /* @__PURE__ */ new Map();
2584
- }
2585
- return parsePairIndex(raw);
2586
- }
2587
- async function recordPair(io, home, entry) {
2588
- await transactIo(io, pairIndexPath(home), (current) => {
2589
- const index = parsePairIndex(current);
2590
- index.set(entry.pairKey, entry);
2591
- return serializePairIndex(index);
2592
- });
2593
- }
2594
- //#endregion
2595
2422
  //#region lib/types/domain/run-coordinator.js
2596
2423
  /** 1b-1 additive types for the merge run lifecycle; no writer is active in this phase. */
2597
2424
  function runMetaPath(home, runId) {
@@ -2924,7 +2751,7 @@ const emptyCounts = {
2924
2751
  },
2925
2752
  expired: 0
2926
2753
  };
2927
- const queueOf$3 = (state) => state === "approved" || state === "fuse" ? "fuse" : state === "verify" ? "verify" : state === "pending" ? "pending" : "skipped";
2754
+ const queueOf$1 = (state) => state === "approved" || state === "fuse" ? "fuse" : state === "verify" ? "verify" : state === "pending" ? "pending" : "skipped";
2928
2755
  async function readMergeSnapshot(options) {
2929
2756
  const readAt = (options.now ?? (() => Date.now()))();
2930
2757
  const hostOnly = options.hostOnly === void 0 ? void 0 : await options.hostOnly().catch((error) => ({
@@ -2976,7 +2803,7 @@ async function readMergeSnapshot(options) {
2976
2803
  const live = options.live !== void 0 && (ledger?.rows ?? []).some((row) => row.state === "pending" && row.expired === void 0 || row.state === "verify" || row.state === "fuse" || row.state === "approved") ? await options.live().catch(() => void 0) : void 0;
2977
2804
  for (const row of ledger?.rows ?? []) {
2978
2805
  const stage = stages.find((item) => item.rowId === row.id) ?? null;
2979
- const queue = queueOf$3(row.state);
2806
+ const queue = queueOf$1(row.state);
2980
2807
  const bSide = live !== void 0 && live.managed(row.b) ? "live" : "archive";
2981
2808
  const bodies = [{
2982
2809
  rowId: row.id,
@@ -2998,19 +2825,18 @@ async function readMergeSnapshot(options) {
2998
2825
  });
2999
2826
  bodyRefs.push(...bodies);
3000
2827
  }
3001
- const pairIndex = await loadPairIndex(options.io, options.home);
3002
2828
  const runs = [];
3003
2829
  for (const record of options.runs) {
3004
2830
  const meta = await readRunMeta(options.io, options.home, record.id);
3005
- const pairs = [...pairIndex.values()].filter((item) => item.runId === record.id).map((item) => ({
3006
- pairKey: item.pairKey,
3007
- runId: item.runId,
3008
- result: item.result,
3009
- ...item.target === void 0 ? {} : { target: item.target }
3010
- }));
3011
2831
  const reportPath = mergeReportPath(options.home, record.id);
3012
2832
  const probe = await readMergeReport(options.io, options.home, record.id);
3013
2833
  const report = probe.kind === "present" ? probe.value : null;
2834
+ const pairs = (report?.pairs ?? []).map((pair) => ({
2835
+ pairKey: pair.pairKey,
2836
+ runId: record.id,
2837
+ result: pair.outcome,
2838
+ ...pair.target === void 0 ? {} : { target: pair.target.requested }
2839
+ }));
3014
2840
  runs.push({
3015
2841
  record,
3016
2842
  meta,
@@ -3082,8 +2908,9 @@ async function readMergeSnapshot(options) {
3082
2908
  })).digest("hex");
3083
2909
  const notes = [...options.notes ?? []];
3084
2910
  if (historyDegraded) notes.push("history archive port unavailable; receipts only");
3085
- const ledgerConflicts = [...pairIndex.values()].filter((entry) => (entry.result === "merged" || entry.result === "undone") && rows.some((item) => item.row.pairKey === entry.pairKey && (item.row.state === "verify" || item.row.state === "pending" || item.row.state === "approved" || item.row.state === "fuse")));
3086
- if (ledgerConflicts.length > 0) notes.push("ledger-conflict: pair-index says completed but the ledger still has it active (" + String(ledgerConflicts.length) + ")");
2911
+ const activePairKeys = new Set(rows.filter((item) => item.row.state === "verify" || item.row.state === "pending" || item.row.state === "approved" || item.row.state === "fuse").map((item) => item.row.pairKey));
2912
+ const ledgerConflicts = runs.flatMap((run) => run.pairs).filter((entry) => (entry.result === "merged" || entry.result === "undone") && activePairKeys.has(entry.pairKey));
2913
+ if (ledgerConflicts.length > 0) notes.push("ledger-conflict: a run says completed but the ledger still has it active (" + String(ledgerConflicts.length) + ")");
3087
2914
  return {
3088
2915
  wireVersion: 1,
3089
2916
  schemaVersion: 4,
@@ -3295,34 +3122,10 @@ var MergeUnitTransactions = class {
3295
3122
  await this.options.onPhase?.(phase);
3296
3123
  };
3297
3124
  try {
3298
- if (this.options.fence !== void 0) {
3299
- fenceLease = await this.options.fence.acquire();
3300
- lock = fenceLease.lock;
3301
- this.options.onLock?.("tree");
3302
- } else {
3303
- const fallbackLease = await new HomeAuthority({
3304
- home: this.options.home,
3305
- waitMs: this.options.waitMs ?? 5e3
3306
- }).lease("tree").catch(() => void 0);
3307
- if (fallbackLease === void 0) return {
3308
- ok: false,
3309
- code: "unit-fence-failed",
3310
- message: "tree lock unavailable"
3311
- };
3312
- fenceLease = {
3313
- lock: leaseAsTreeLock(fallbackLease),
3314
- release: async () => {
3315
- await fallbackLease.release();
3316
- }
3317
- };
3318
- lock = fenceLease.lock;
3319
- this.options.onLock?.("tree");
3320
- }
3321
- if (!await lock.assertHeld()) return {
3322
- ok: false,
3323
- code: "unit-fence-failed",
3324
- message: "tree fence lost"
3325
- };
3125
+ const acquired = await this.acquireTreeFence();
3126
+ fenceLease = acquired.fenceLease;
3127
+ lock = acquired.lock;
3128
+ this.options.onLock?.("tree");
3326
3129
  stage = "claim";
3327
3130
  claim = await this.options.claim.acquire(stored.plan.target, stored.plan.runId);
3328
3131
  this.options.onLock?.("claim");
@@ -3458,6 +3261,82 @@ var MergeUnitTransactions = class {
3458
3261
  await lease.release();
3459
3262
  }
3460
3263
  }
3264
+ /** Acquire the one tree fence (or a HomeAuthority fallback) and prove it is held. */
3265
+ async acquireTreeFence() {
3266
+ let fenceLease;
3267
+ if (this.options.fence !== void 0) fenceLease = await this.options.fence.acquire();
3268
+ else {
3269
+ const fallbackLease = await new HomeAuthority({
3270
+ home: this.options.home,
3271
+ waitMs: this.options.waitMs ?? 5e3
3272
+ }).lease("tree").catch(() => void 0);
3273
+ if (fallbackLease === void 0) throw new Error("tree lock unavailable");
3274
+ fenceLease = {
3275
+ lock: leaseAsTreeLock(fallbackLease),
3276
+ release: async () => {
3277
+ await fallbackLease.release();
3278
+ }
3279
+ };
3280
+ }
3281
+ if (!await fenceLease.lock.assertHeld()) throw new Error("tree fence lost");
3282
+ return {
3283
+ fenceLease,
3284
+ lock: fenceLease.lock
3285
+ };
3286
+ }
3287
+ /** Finish the idempotent support writes from the prepared snapshot, or name the drift. */
3288
+ async completeSupport(snapshot, journal) {
3289
+ let liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3290
+ path: file.path,
3291
+ content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3292
+ })));
3293
+ const supportMatches = () => liveSupport.every((file) => file.content === (snapshot.supportAfter.find((item) => item.path === file.path)?.content ?? null));
3294
+ if (supportMatches()) return { ok: true };
3295
+ if (![
3296
+ "support-pending",
3297
+ "sources-archived",
3298
+ "unit-written"
3299
+ ].includes(journal.phase) || this.options.backend.restoreSupportFile === void 0) return {
3300
+ ok: false,
3301
+ message: "support file live hash does not match the prepared support; manual recovery required"
3302
+ };
3303
+ for (const file of snapshot.supportAfter) await this.options.backend.restoreSupportFile(snapshot.target, file.path, file.content);
3304
+ liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3305
+ path: file.path,
3306
+ content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3307
+ })));
3308
+ return supportMatches() ? { ok: true } : {
3309
+ ok: false,
3310
+ message: "support file could not be completed from the prepared snapshot"
3311
+ };
3312
+ }
3313
+ /** The complete evidence record both settlement paths write. */
3314
+ async evidenceFor(unitId, snapshot, journal, targetAfter) {
3315
+ const checkpoints = journal.checkpoints ?? [];
3316
+ return {
3317
+ schemaVersion: 1,
3318
+ unitId,
3319
+ runId: snapshot.runId,
3320
+ planDigest: snapshot.planDigest,
3321
+ state: "complete",
3322
+ target: {
3323
+ before: snapshot.targetBefore,
3324
+ after: targetAfter,
3325
+ supportDigest: createHash("sha256").update(JSON.stringify(snapshot.supportAfter)).digest("hex"),
3326
+ created: !snapshot.targetExists
3327
+ },
3328
+ sources: snapshot.sourceBefore.map((source) => ({
3329
+ name: source.name,
3330
+ before: source.body,
3331
+ after: null,
3332
+ archived: checkpoints.some((item) => item.source === source.name),
3333
+ absorbedInto: snapshot.target
3334
+ })),
3335
+ mutations: 0,
3336
+ ...snapshot.validationPlan === void 0 ? {} : { validationPlan: snapshot.validationPlan },
3337
+ at: this.now()
3338
+ };
3339
+ }
3461
3340
  async finalize(unitId) {
3462
3341
  return await this.withRecoveryFence(async () => this.finalizeUnlocked(unitId));
3463
3342
  }
@@ -3502,55 +3381,13 @@ var MergeUnitTransactions = class {
3502
3381
  code: "unit-rollback-blocked",
3503
3382
  message: "source is still live: " + liveSource.name
3504
3383
  };
3505
- let liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3506
- path: file.path,
3507
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3508
- })));
3509
- const supportMatches = () => liveSupport.every((file) => file.content === (snapshot.supportAfter.find((item) => item.path === file.path)?.content ?? null));
3510
- if (!supportMatches()) {
3511
- if (![
3512
- "support-pending",
3513
- "sources-archived",
3514
- "unit-written"
3515
- ].includes(journal.phase) || this.options.backend.restoreSupportFile === void 0) return {
3516
- ok: false,
3517
- code: "unit-rollback-blocked",
3518
- message: "support file live hash does not match the prepared support; manual recovery required"
3519
- };
3520
- for (const file of snapshot.supportAfter) await this.options.backend.restoreSupportFile(snapshot.target, file.path, file.content);
3521
- liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3522
- path: file.path,
3523
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3524
- })));
3525
- if (!supportMatches()) return {
3526
- ok: false,
3527
- code: "unit-rollback-blocked",
3528
- message: "support file could not be completed from the prepared snapshot"
3529
- };
3530
- }
3531
- const evidence = {
3532
- schemaVersion: 1,
3533
- unitId,
3534
- runId: snapshot.runId,
3535
- planDigest: snapshot.planDigest,
3536
- state: "complete",
3537
- target: {
3538
- before: snapshot.targetBefore,
3539
- after: await this.options.backend.readSkill(snapshot.target) ?? "",
3540
- supportDigest: createHash("sha256").update(JSON.stringify(snapshot.supportAfter)).digest("hex"),
3541
- created: !snapshot.targetExists
3542
- },
3543
- sources: snapshot.sourceBefore.map((source) => ({
3544
- name: source.name,
3545
- before: source.body,
3546
- after: null,
3547
- archived: checkpoints.some((item) => item.source === source.name),
3548
- absorbedInto: snapshot.target
3549
- })),
3550
- mutations: 0,
3551
- ...snapshot.validationPlan === void 0 ? {} : { validationPlan: snapshot.validationPlan },
3552
- at: this.now()
3384
+ const support = await this.completeSupport(snapshot, journal);
3385
+ if (!support.ok) return {
3386
+ ok: false,
3387
+ code: "unit-rollback-blocked",
3388
+ message: support.message
3553
3389
  };
3390
+ const evidence = await this.evidenceFor(unitId, snapshot, journal, await this.options.backend.readSkill(snapshot.target) ?? "");
3554
3391
  if (receipt?.evidenceSha256 !== void 0 && unitEvidenceDigest(evidence) !== receipt.evidenceSha256) return {
3555
3392
  ok: false,
3556
3393
  code: "unit-rollback-blocked",
@@ -3612,55 +3449,13 @@ var MergeUnitTransactions = class {
3612
3449
  code: "unit-rollback-blocked",
3613
3450
  message: "source is still live: " + liveSource.name
3614
3451
  };
3615
- let liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3616
- path: file.path,
3617
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3618
- })));
3619
- const supportMatches = () => liveSupport.every((file) => file.content === (snapshot.supportAfter.find((item) => item.path === file.path)?.content ?? null));
3620
- if (!supportMatches()) {
3621
- if (![
3622
- "support-pending",
3623
- "sources-archived",
3624
- "unit-written"
3625
- ].includes(journal.phase) || this.options.backend.restoreSupportFile === void 0) return {
3626
- ok: false,
3627
- code: "unit-rollback-blocked",
3628
- message: "support file live hash does not match the prepared support; manual recovery required"
3629
- };
3630
- for (const file of snapshot.supportAfter) await this.options.backend.restoreSupportFile(snapshot.target, file.path, file.content);
3631
- liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3632
- path: file.path,
3633
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3634
- })));
3635
- if (!supportMatches()) return {
3636
- ok: false,
3637
- code: "unit-rollback-blocked",
3638
- message: "support file could not be completed from the prepared snapshot"
3639
- };
3640
- }
3641
- const evidence = {
3642
- schemaVersion: 1,
3643
- unitId,
3644
- runId: snapshot.runId,
3645
- planDigest: snapshot.planDigest,
3646
- state: "complete",
3647
- target: {
3648
- before: snapshot.targetBefore,
3649
- after: targetAfter,
3650
- supportDigest: createHash("sha256").update(JSON.stringify(snapshot.supportAfter)).digest("hex"),
3651
- created: !snapshot.targetExists
3652
- },
3653
- sources: snapshot.sourceBefore.map((source) => ({
3654
- name: source.name,
3655
- before: source.body,
3656
- after: null,
3657
- archived: checkpoints.some((item) => item.source === source.name),
3658
- absorbedInto: snapshot.target
3659
- })),
3660
- mutations: 0,
3661
- ...snapshot.validationPlan === void 0 ? {} : { validationPlan: snapshot.validationPlan },
3662
- at: this.now()
3452
+ const support = await this.completeSupport(snapshot, journal);
3453
+ if (!support.ok) return {
3454
+ ok: false,
3455
+ code: "unit-rollback-blocked",
3456
+ message: support.message
3663
3457
  };
3458
+ const evidence = await this.evidenceFor(unitId, snapshot, journal, targetAfter);
3664
3459
  await writeUnitEvidence(this.options.io, this.options.home, evidence);
3665
3460
  await writeUnitJournal(this.options.io, this.options.home, {
3666
3461
  ...journal,
@@ -3692,33 +3487,10 @@ var MergeUnitTransactions = class {
3692
3487
  let lock;
3693
3488
  let claim;
3694
3489
  try {
3695
- if (this.options.fence !== void 0) {
3696
- fenceLease = await this.options.fence.acquire();
3697
- lock = fenceLease.lock;
3698
- } else {
3699
- const fallbackLease = await new HomeAuthority({
3700
- home: this.options.home,
3701
- waitMs: this.options.waitMs ?? 5e3
3702
- }).lease("tree").catch(() => void 0);
3703
- if (fallbackLease === void 0) return {
3704
- ok: false,
3705
- code: "unit-fence-failed",
3706
- message: "tree lock unavailable"
3707
- };
3708
- fenceLease = {
3709
- lock: leaseAsTreeLock(fallbackLease),
3710
- release: async () => {
3711
- await fallbackLease.release();
3712
- }
3713
- };
3714
- lock = fenceLease.lock;
3715
- }
3490
+ const acquired = await this.acquireTreeFence();
3491
+ fenceLease = acquired.fenceLease;
3492
+ lock = acquired.lock;
3716
3493
  this.options.onLock?.("tree");
3717
- if (!await lock.assertHeld()) return {
3718
- ok: false,
3719
- code: "unit-fence-failed",
3720
- message: "tree fence lost"
3721
- };
3722
3494
  claim = await this.options.claim.acquire(snapshot.target, snapshot.runId);
3723
3495
  this.options.onLock?.("claim");
3724
3496
  const currentTarget = await this.options.backend.readSkill(snapshot.target);
@@ -4438,7 +4210,7 @@ function verifyState(row) {
4438
4210
  if (row.row.verifySubState === "screened") return "running";
4439
4211
  return "queued";
4440
4212
  }
4441
- function toRowFact$1(row) {
4213
+ function toRowFact(row) {
4442
4214
  const state = row.row;
4443
4215
  const target = state.state === "pending" || state.state === "approved" || state.state === "fuse" ? {
4444
4216
  requested: state.target,
@@ -4548,7 +4320,7 @@ function runView(run, readAt, includeResultRef = false) {
4548
4320
  ]
4549
4321
  };
4550
4322
  }
4551
- function buildFacts$1(snapshot) {
4323
+ function buildFacts(snapshot) {
4552
4324
  const ordered = [...snapshot.runs].sort((a, b) => b.record.startedAt - a.record.startedAt);
4553
4325
  const active = ordered.find((run) => run.record.state === "running");
4554
4326
  const last = ordered.find((run) => run.record.state !== "running");
@@ -4597,7 +4369,7 @@ function buildQueuePage(snapshot, queue, expiredOnly, cursor, limit = 50) {
4597
4369
  queue,
4598
4370
  expiredOnly,
4599
4371
  counts: counts(snapshot.rows),
4600
- rows: page.map(toRowFact$1),
4372
+ rows: page.map(toRowFact),
4601
4373
  ...next === void 0 ? {} : { nextCursor: next },
4602
4374
  notes: snapshot.notes
4603
4375
  };
@@ -4638,7 +4410,7 @@ function buildRunDetail(snapshot, runId) {
4638
4410
  pairKey: pair.pairKey,
4639
4411
  a: pair.pairKey.split("\0")[0] ?? "",
4640
4412
  b: pair.pairKey.split("\0")[1] ?? "",
4641
- outcome: pair.result === "kept" ? "kept" : pair.result === "failed" ? "failed" : pair.result === "cancelled" ? "cancelled" : "merged",
4413
+ outcome: pair.result === "kept" ? "kept" : pair.result === "failed" ? "failed" : pair.result === "cancelled" ? "cancelled" : pair.result === "undone" ? "undone" : "merged",
4642
4414
  ...pair.target === void 0 ? {} : { target: { requested: pair.target } }
4643
4415
  })),
4644
4416
  pairsTruncated: false
@@ -4758,78 +4530,6 @@ const ALLOWED = [
4758
4530
  evidence: "none",
4759
4531
  activeRuns: 0
4760
4532
  },
4761
- {
4762
- row: "fuse",
4763
- run: "none",
4764
- staging: "none",
4765
- journal: "none",
4766
- evidence: "none",
4767
- activeRuns: 0,
4768
- recovery: true
4769
- },
4770
- {
4771
- row: "fuse",
4772
- run: "failed",
4773
- staging: "none",
4774
- journal: "none",
4775
- evidence: "none",
4776
- activeRuns: 0,
4777
- recovery: true
4778
- },
4779
- {
4780
- row: "fuse",
4781
- run: "orphan",
4782
- staging: "none",
4783
- journal: "none",
4784
- evidence: "none",
4785
- activeRuns: 0,
4786
- recovery: true
4787
- },
4788
- {
4789
- row: "fuse",
4790
- run: "running",
4791
- staging: "none",
4792
- journal: "none",
4793
- evidence: "none",
4794
- activeRuns: 1,
4795
- recovery: true
4796
- },
4797
- {
4798
- row: "fuse",
4799
- run: "none",
4800
- staging: "missing",
4801
- journal: "none",
4802
- evidence: "none",
4803
- activeRuns: 0,
4804
- recovery: true
4805
- },
4806
- {
4807
- row: "fuse",
4808
- run: "failed",
4809
- staging: "missing",
4810
- journal: "none",
4811
- evidence: "none",
4812
- activeRuns: 0,
4813
- recovery: true
4814
- },
4815
- {
4816
- row: "fuse",
4817
- run: "orphan",
4818
- staging: "missing",
4819
- journal: "none",
4820
- evidence: "none",
4821
- activeRuns: 0,
4822
- recovery: true
4823
- },
4824
- {
4825
- row: "fuse",
4826
- run: "running",
4827
- staging: "missing",
4828
- journal: "none",
4829
- evidence: "none",
4830
- activeRuns: 1,
4831
- recovery: true
4832
- },
4833
4533
  {
4834
4534
  row: "fuse",
4835
4535
  run: "running",
@@ -4864,7 +4564,7 @@ const ALLOWED = [
4864
4564
  }
4865
4565
  ];
4866
4566
  function same(a, b) {
4867
- return a.row === b.row && a.run === b.run && a.staging === b.staging && a.journal === b.journal && a.evidence === b.evidence && a.activeRuns === b.activeRuns && (b.recovery ?? false) === (a.recovery ?? false);
4567
+ return a.row === b.row && a.run === b.run && a.staging === b.staging && a.journal === b.journal && a.evidence === b.evidence && a.activeRuns === b.activeRuns;
4868
4568
  }
4869
4569
  function validateArtifacts(state) {
4870
4570
  if (state.activeRuns > 1) return {
@@ -4884,7 +4584,7 @@ function validateArtifacts(state) {
4884
4584
  ok: false,
4885
4585
  reason: "skipped-running"
4886
4586
  };
4887
- if (state.row === "fuse" && state.staging === "missing" && state.recovery !== true) return {
4587
+ if (state.row === "fuse" && state.staging === "missing") return {
4888
4588
  ok: false,
4889
4589
  reason: "fuse-lost-staging"
4890
4590
  };
@@ -4902,35 +4602,12 @@ function validateArtifacts(state) {
4902
4602
  };
4903
4603
  }
4904
4604
  //#endregion
4905
- //#region lib/types/domain/commit-journal-store.js
4906
- /** Durable commit journal: <home>/merge/journals/<run>.json. */
4907
- function journalPath(home, runId) {
4908
- return join(home, "merge", "journals", runId.replace(/[^A-Za-z0-9_-]+/g, "_") + ".json");
4909
- }
4910
- async function writeJournal(io, home, journal) {
4911
- const path = journalPath(home, journal.runId);
4912
- const content = JSON.stringify(journal, null, 2);
4913
- if (io.transact !== void 0) await io.transact(path, () => content);
4914
- else await io.writeText(path, content);
4915
- return path;
4916
- }
4917
- async function readJournal(io, home, runId) {
4918
- const raw = await io.readText(journalPath(home, runId));
4919
- if (raw === null) return null;
4920
- try {
4921
- return JSON.parse(raw);
4922
- } catch {
4923
- return null;
4924
- }
4925
- }
4926
- //#endregion
4927
4605
  //#region lib/types/doctor.js
4928
4606
  /**
4929
4607
  * Merge doctor rows: state, pending, gate cache and staging are each probed read-only.
4930
4608
  * A missing file is an empty feature, not a failure; an unreadable file is a warning the
4931
4609
  * operator can act on. This module never repairs or writes.
4932
4610
  */
4933
- const MERGE_DOCTOR_SCHEMA_VERSION = 3;
4934
4611
  /** Compose the one-line global-doctor fact from buildDoctorView + the last reconcile report. */
4935
4612
  function mergeDoctorFact(view, report) {
4936
4613
  const counts = view.counts;
@@ -4979,13 +4656,6 @@ async function mergeDoctor(io, home, configuredRoot) {
4979
4656
  status: snapshot.blocked === void 0 || !hasState ? "ok" : "warn",
4980
4657
  detail: snapshot.blocked === void 0 ? "pairs " + String(snapshot.rows.length) + ";verify " + String(counts.verify) + " / pending " + String(counts.pending) + " / fuse " + String(counts.fuse) + " / skipped " + String(counts.skipped) + " / expired " + String(snapshot.doctor.counts.expired) : snapshot.blocked.code + ": " + snapshot.blocked.message
4981
4658
  });
4982
- const recoveryRuns = /* @__PURE__ */ new Set();
4983
- try {
4984
- const raw = await io.readText(join(home, "merge", "recovery.json"));
4985
- if (raw !== null) {
4986
- for (const report of JSON.parse(raw)) if (report.ok === true && report.runId !== void 0) recoveryRuns.add(report.runId);
4987
- }
4988
- } catch {}
4989
4659
  const invalid = [];
4990
4660
  if (snapshot.blocked !== void 0) invalid.push({
4991
4661
  id: "state",
@@ -4998,15 +4668,14 @@ async function mergeDoctor(io, home, configuredRoot) {
4998
4668
  const run = runId === void 0 ? "none" : row.state === "pending" ? "awaiting-approval" : "running";
4999
4669
  const staging = item.stage === null ? row.state === "fuse" ? "missing" : "none" : item.stage.state === "present" || item.stage.state === "reserved" ? "kept" : row.state === "fuse" ? "missing" : "none";
5000
4670
  const receipt = runId === void 0 ? null : await readCommitReceipt(io, home, runId);
5001
- const phase = receipt !== null ? await readUnitJournal(io, home, receipt.unitId) : runId === void 0 ? null : await readJournal(io, home, runId);
4671
+ const phase = receipt === null ? null : await readUnitJournal(io, home, receipt.unitId);
5002
4672
  const result = validateArtifacts({
5003
4673
  row: artifactRow,
5004
4674
  run,
5005
4675
  staging,
5006
4676
  journal: phase === null ? "none" : phase.phase === "prepared" || phase.phase === "target-pending" ? "prepared" : phase.phase === "target-written" || phase.phase === "sources-archived" || phase.phase === "support-pending" || phase.phase === "source-archiving" ? "committing" : phase.phase === "committed" ? "committed" : phase.phase === "rolled-back" ? "rolled-back" : "failed",
5007
4677
  evidence: receipt === null ? "none" : "receipt",
5008
- activeRuns: run === "running" || run === "awaiting-approval" ? 1 : 0,
5009
- ...runId !== void 0 && recoveryRuns.has(runId) ? { recovery: true } : {}
4678
+ activeRuns: run === "running" || run === "awaiting-approval" ? 1 : 0
5010
4679
  });
5011
4680
  if (!result.ok) invalid.push({
5012
4681
  id: row.id,
@@ -5149,26 +4818,6 @@ async function mergeDoctor(io, home, configuredRoot) {
5149
4818
  status: "ok",
5150
4819
  detail: stagingRoot(home, "<runId>")
5151
4820
  });
5152
- try {
5153
- const raw = await io.readText(join(home, "merge", "recovery.json"));
5154
- if (raw !== null) {
5155
- const reports = JSON.parse(raw);
5156
- const failed = reports.filter((report) => report.ok !== true);
5157
- rows.push({
5158
- key: "recovery",
5159
- label: "提交恢复",
5160
- status: failed.length === 0 ? "ok" : "warn",
5161
- detail: failed.length === 0 ? String(reports.length) + " 条恢复报告,无待处理" : String(failed.length) + " 条待处理:" + failed.map((report) => (report.runId ?? "?") + "=" + (report.action ?? "?")).join(", ")
5162
- });
5163
- }
5164
- } catch {
5165
- rows.push({
5166
- key: "recovery",
5167
- label: "提交恢复",
5168
- status: "unknown",
5169
- detail: "无法读取 merge/recovery.json"
5170
- });
5171
- }
5172
4821
  return rows;
5173
4822
  }
5174
4823
  //#endregion
@@ -5365,13 +5014,6 @@ async function projectRecoveredUnit(options) {
5365
5014
  await options.onPhase?.("stage-drop");
5366
5015
  await dropStage(options.io, options.home, stageRowId);
5367
5016
  }
5368
- await options.onPhase?.("pair-index");
5369
- await recordPair(options.io, options.home, {
5370
- pairKey,
5371
- runId: options.runId,
5372
- result: "merged",
5373
- target
5374
- });
5375
5017
  await options.onPhase?.("run-meta");
5376
5018
  const meta = await readRunMeta(options.io, options.home, options.runId);
5377
5019
  await writeRunMeta(options.io, options.home, {
@@ -5603,258 +5245,32 @@ async function recoverCommitUnits(options) {
5603
5245
  branch,
5604
5246
  action: "rollForward",
5605
5247
  ok: false,
5606
- code: result.code,
5607
- message: result.message
5608
- });
5609
- continue;
5610
- }
5611
- reports.push({
5612
- unitId: journal.unitId,
5613
- runId: journal.runId,
5614
- phase: journal.phase,
5615
- branch,
5616
- action: "blocked",
5617
- ok: false,
5618
- code: "unit-recovery-blocked"
5619
- });
5620
- }
5621
- return reports;
5622
- }
5623
- //#endregion
5624
- //#region lib/types/facts.js
5625
- const queueOf$2 = (row) => row.state === "approved" ? "fuse" : row.queue;
5626
- const isExpired = (row) => row.expired !== void 0;
5627
- function countsOf(rows) {
5628
- const active = {
5629
- verify: 0,
5630
- pending: 0,
5631
- fuse: 0,
5632
- skipped: 0
5633
- };
5634
- let expired = 0;
5635
- for (const row of rows) {
5636
- if (isExpired(row)) {
5637
- expired += 1;
5638
- continue;
5639
- }
5640
- active[queueOf$2(row)] += 1;
5641
- }
5642
- return {
5643
- active,
5644
- expired
5645
- };
5646
- }
5647
- function toRunView(record, readAt, meta = null) {
5648
- const state = record.state;
5649
- return {
5650
- wireVersion: 1,
5651
- readAt,
5652
- id: record.id,
5653
- state,
5654
- command: record.command ?? "scan",
5655
- ...record.stage === void 0 ? {} : { stage: record.stage },
5656
- progress: record.progress === void 0 ? null : {
5657
- done: record.progress.done,
5658
- total: record.progress.total
5659
- },
5660
- startedAt: record.startedAt,
5661
- ...record.endedAt === void 0 ? {} : { endedAt: record.endedAt },
5662
- ...record.failure === void 0 ? {} : { failure: {
5663
- stage: "settle",
5664
- code: "run-failed",
5665
- retryable: false,
5666
- retryFrom: "none",
5667
- detail: record.failure
5668
- } },
5669
- ...record.parentRunId === void 0 ? {} : { parentRunId: record.parentRunId },
5670
- actions: [
5671
- {
5672
- id: "status",
5673
- command: "/evolution merge status " + record.id,
5674
- confirm: false,
5675
- enabled: true
5676
- },
5677
- {
5678
- id: "report",
5679
- command: "/evolution merge report " + record.id,
5680
- confirm: false,
5681
- enabled: record.resultRef !== void 0
5682
- },
5683
- {
5684
- id: "cancel",
5685
- command: "/evolution merge cancel " + record.id,
5686
- confirm: false,
5687
- enabled: state === "running"
5688
- }
5689
- ]
5690
- };
5691
- }
5692
- function buildFacts(input) {
5693
- const ordered = [...input.runs].sort((a, b) => b.startedAt - a.startedAt);
5694
- const active = ordered.find((run) => run.state === "running");
5695
- const last = ordered.find((run) => run.state !== "running");
5696
- const notes = input.notes ?? [];
5697
- return {
5698
- wireVersion: 1,
5699
- readAt: input.readAt,
5700
- approval: { mergeApproval: input.approval },
5701
- counts: countsOf(input.rows),
5702
- active: active === void 0 ? null : toRunView(active, input.readAt, input.runMeta?.get(active.id) ?? null),
5703
- last: last === void 0 ? null : toRunView(last, input.readAt, input.runMeta?.get(last.id) ?? null),
5704
- startCommands: {
5705
- scan: "/evolution merge scan",
5706
- pair: "/evolution merge pair <a> <b>"
5707
- },
5708
- fallback: MERGE_COMMAND_SURFACE.fallback,
5709
- notes,
5710
- degraded: notes.length > 0
5711
- };
5712
- }
5713
- //#endregion
5714
- //#region lib/types/wire-projection.js
5715
- const queueOf$1 = (row) => row.state === "approved" ? "fuse" : row.queue;
5716
- const expiredOf = (row) => row.expired !== void 0;
5717
- /** Host-owned row actions; client only renders/copies them. */
5718
- function actionRefs(row) {
5719
- const base = "/evolution merge ";
5720
- const disabled = row.expired === void 0 ? void 0 : "expired";
5721
- const action = (id, confirm = false, enabled = true) => ({
5722
- id,
5723
- command: base + id + " " + row.id,
5724
- confirm,
5725
- enabled,
5726
- ...enabled ? {} : { disabledReason: disabled ?? "invalid" }
5727
- });
5728
- if (row.state === "pending" && row.expired !== void 0) return [{
5729
- id: "discard",
5730
- command: "/evolution merge discard " + row.id,
5731
- confirm: true,
5732
- enabled: true
5733
- }, {
5734
- id: "pair",
5735
- command: "/evolution merge pair " + row.a + " " + row.b,
5736
- confirm: true,
5737
- enabled: true
5738
- }];
5739
- if (row.state === "pending") return [
5740
- action("approve"),
5741
- action("reject", true),
5742
- action("discard", true)
5743
- ];
5744
- if (row.state === "approved" || row.state === "fuse") {
5745
- if (row.failure !== void 0 && row.failure.retryable === false) return [{
5746
- id: "pair",
5747
- command: "/evolution merge pair " + row.a + " " + row.b,
5748
- confirm: true,
5749
- enabled: true
5750
- }, action("discard", true)];
5751
- return [
5752
- {
5753
- id: "retry-resume",
5754
- command: "/evolution merge resume --retry " + row.id,
5755
- confirm: true,
5756
- enabled: true
5757
- },
5758
- action("discard", true),
5759
- action("status")
5760
- ];
5761
- }
5762
- if (row.state === "skipped") return [action("restore", true)];
5763
- if (row.state === "verify") return [{
5764
- id: "retry-candidates",
5765
- command: "/evolution merge candidates --retry " + row.id,
5766
- confirm: false,
5767
- enabled: true
5768
- }];
5769
- return [action("pair")];
5770
- }
5771
- function toRowFact(row, actions = actionRefs(row)) {
5772
- return {
5773
- id: row.id,
5774
- state: row.state,
5775
- queue: queueOf$1(row),
5776
- ...row.state === "verify" ? { verifySubState: "queued" } : {},
5777
- a: row.a,
5778
- b: row.b,
5779
- ...row.target === void 0 ? {} : { target: {
5780
- requested: row.target.requested,
5781
- ...row.target.actual === void 0 ? {} : { actual: row.target.actual },
5782
- ...row.target.created === void 0 ? {} : { created: row.target.created }
5783
- } },
5784
- ...row.state === "pending" ? { summary: {
5785
- a: null,
5786
- b: null,
5787
- rationale: row.decision?.rationale ?? null
5788
- } } : {},
5789
- ...row.reason === void 0 ? {} : { reason: row.reason },
5790
- ...row.state === "skipped" ? { origin: row.origin ?? "system" } : {},
5791
- ...row.failure === void 0 ? {} : { failure: {
5792
- stage: row.failure.stage === "screening" ? "screening" : row.failure.stage === "decide" ? "decide" : row.failure.stage === "author" ? "author" : row.failure.stage === "validate" ? "validate" : "commit",
5793
- code: row.failure.code,
5794
- retryable: row.failure.retryable,
5795
- retryFrom: row.failure.retryFrom
5796
- } },
5797
- ...row.stagingRef === void 0 ? {} : { staging: {
5798
- state: "kept",
5799
- ...row.stagingExpiresAt === void 0 ? {} : { expiresAt: row.stagingExpiresAt }
5800
- } },
5801
- ...row.expired === void 0 ? {} : { expired: {
5802
- kind: row.expired,
5803
- at: Date.parse(row.updatedAt)
5804
- } },
5805
- ...row.attempts === void 0 ? {} : { attempts: row.attempts },
5806
- ...row.decision?.confidence === void 0 ? {} : { confidence: row.decision.confidence },
5807
- ...row.state === "skipped" && row.stagingExpiresAt !== void 0 ? { expiresAt: row.stagingExpiresAt } : {},
5808
- createdAt: Date.parse(row.createdAt),
5809
- updatedAt: Date.parse(row.updatedAt),
5810
- actions
5811
- };
5812
- }
5813
- function toQueuePage(rows, queue, expiredOnly, readAt, limit = 50) {
5814
- const filtered = rows.filter((row) => queueOf$1(row) === queue && (expiredOnly ? expiredOf(row) : !expiredOf(row)));
5815
- const page = filtered.slice(0, Math.max(0, Math.min(200, limit)));
5816
- return {
5817
- wireVersion: 1,
5818
- readAt,
5819
- queue,
5820
- expiredOnly,
5821
- counts: countsOf(rows),
5822
- rows: page.map((row) => toRowFact(row)),
5823
- ...filtered.length > page.length ? { nextCursor: String(page.length) } : {},
5824
- notes: []
5825
- };
5826
- }
5827
- function toBodyView(name, side, text, readAt) {
5828
- return {
5829
- wireVersion: 1,
5830
- readAt,
5831
- name,
5832
- side,
5833
- text,
5834
- markdown: true,
5835
- truncated: false,
5836
- totalBytes: Buffer.byteLength(text, "utf8")
5837
- };
5838
- }
5839
- function toHistoryView(items, readAt) {
5840
- return {
5841
- wireVersion: 1,
5842
- readAt,
5843
- items: items.map((item) => {
5844
- const parsed = item.archivedAt === null ? null : typeof item.archivedAt === "number" ? item.archivedAt : Date.parse(item.archivedAt);
5845
- return {
5846
- name: item.name,
5847
- kind: item.kind,
5848
- reason: item.reason,
5849
- lastVersion: item.lastVersion,
5850
- archivedAt: parsed === null || Number.isFinite(parsed) ? parsed : null,
5851
- ...item.sourceRunId === void 0 ? {} : { sourceRunId: item.sourceRunId }
5852
- };
5853
- })
5854
- };
5248
+ code: result.code,
5249
+ message: result.message
5250
+ });
5251
+ continue;
5252
+ }
5253
+ reports.push({
5254
+ unitId: journal.unitId,
5255
+ runId: journal.runId,
5256
+ phase: journal.phase,
5257
+ branch,
5258
+ action: "blocked",
5259
+ ok: false,
5260
+ code: "unit-recovery-blocked"
5261
+ });
5262
+ }
5263
+ return reports;
5855
5264
  }
5856
5265
  //#endregion
5857
5266
  //#region lib/types/merge-routes.js
5267
+ /**
5268
+ * Four read-only merge routes: view / rows / body / history.
5269
+ * The host loads one snapshot; these handlers only project wire envelopes.
5270
+ * There is no second (legacy rows/runs/history) source: the production constructor has
5271
+ * always provided the snapshot, and the legacy branches were the only consumers of
5272
+ * facts.ts / wire-projection.ts (W11 D2b).
5273
+ */
5858
5274
  const MERGE_PATHS = HOST_ROUTES.merge;
5859
5275
  const MERGE_BASE = MERGE_PATHS.view.slice(0, -4);
5860
5276
  const MERGE_ROUTES = {
@@ -5870,21 +5286,16 @@ function makeMergeRoutes(deps, _audit) {
5870
5286
  code: "E-325",
5871
5287
  message: "merge service is not mounted"
5872
5288
  });
5873
- const blocked = (readiness) => json(503, {
5289
+ const blocked = (snapshot) => snapshot.blocked === void 0 ? null : json(503, {
5874
5290
  wireVersion: 1,
5875
5291
  code: "merge-state-blocked",
5876
- readiness
5292
+ readiness: snapshot.blocked.readiness
5877
5293
  });
5878
5294
  const hostOnly = (snapshot) => snapshot.hostOnly === void 0 ? null : json(503, {
5879
5295
  wireVersion: 1,
5880
5296
  code: "E-323",
5881
5297
  message: snapshot.hostOnly.detail ?? "host data plane unavailable"
5882
5298
  });
5883
- const notReady = async (source) => {
5884
- if (source.readiness === void 0) return null;
5885
- const readiness = await source.readiness();
5886
- return readiness.ok ? null : blocked(readiness);
5887
- };
5888
5299
  const bad = (message) => json(400, {
5889
5300
  wireVersion: 1,
5890
5301
  code: "merge-bad-query",
@@ -5892,6 +5303,11 @@ function makeMergeRoutes(deps, _audit) {
5892
5303
  });
5893
5304
  const get = (request) => request.method === "GET" ? null : methodNotAllowed(["GET"]);
5894
5305
  const url = (request) => new URL(request.url, "http://local");
5306
+ const snapshotOf = async () => {
5307
+ const source = deps.getSource();
5308
+ return source === void 0 ? null : await source.snapshot();
5309
+ };
5310
+ const unavailable = (snapshot) => snapshot === null ? absent() : blocked(snapshot) ?? hostOnly(snapshot);
5895
5311
  return [
5896
5312
  {
5897
5313
  path: MERGE_ROUTES.view,
@@ -5900,28 +5316,10 @@ function makeMergeRoutes(deps, _audit) {
5900
5316
  fetch: async (request) => {
5901
5317
  const denied = get(request);
5902
5318
  if (denied !== null) return denied;
5903
- const source = deps.getSource();
5904
- if (source === void 0) return absent();
5905
- if (source.snapshot !== void 0) {
5906
- const snapshot = await source.snapshot();
5907
- if (snapshot.blocked !== void 0) return json(503, {
5908
- wireVersion: 1,
5909
- code: "merge-state-blocked",
5910
- readiness: snapshot.blocked.readiness
5911
- });
5912
- const capability = hostOnly(snapshot);
5913
- if (capability !== null) return capability;
5914
- return json(200, buildFacts$1(snapshot));
5915
- }
5916
- const readiness = await notReady(source);
5917
- if (readiness !== null) return readiness;
5918
- const readAt = Date.now();
5919
- return json(200, buildFacts({
5920
- rows: await source.rows?.() ?? [],
5921
- runs: await source.runs?.(void 0, 20) ?? [],
5922
- approval: source.approval?.() === true,
5923
- readAt
5924
- }));
5319
+ const snapshot = await snapshotOf();
5320
+ const notReady = unavailable(snapshot);
5321
+ if (notReady !== null) return notReady;
5322
+ return json(200, buildFacts(snapshot));
5925
5323
  }
5926
5324
  },
5927
5325
  {
@@ -5931,37 +5329,20 @@ function makeMergeRoutes(deps, _audit) {
5931
5329
  fetch: async (request) => {
5932
5330
  const denied = get(request);
5933
5331
  if (denied !== null) return denied;
5934
- const source = deps.getSource();
5935
- if (source === void 0) return absent();
5936
5332
  const query = url(request).searchParams;
5937
5333
  const queue = query.get("queue") ?? "verify";
5938
5334
  if (queue !== "verify" && queue !== "pending" && queue !== "fuse" && queue !== "skipped") return bad("queue must be verify/pending/fuse/skipped");
5939
5335
  const expiredOnly = query.get("expired") === "1";
5940
5336
  if (expiredOnly && queue !== "pending") return bad("expired is only allowed for pending");
5941
5337
  const limit = Number.parseInt(query.get("limit") ?? "50", 10);
5942
- if (source.snapshot !== void 0) {
5943
- const snapshot = await source.snapshot();
5944
- if (snapshot.blocked !== void 0) return json(503, {
5945
- wireVersion: 1,
5946
- code: "merge-state-blocked",
5947
- readiness: snapshot.blocked.readiness
5948
- });
5949
- const capability = hostOnly(snapshot);
5950
- if (capability !== null) return capability;
5951
- try {
5952
- return json(200, buildQueuePage(snapshot, queue, expiredOnly, query.get("cursor") ?? void 0, Number.isFinite(limit) ? limit : 50));
5953
- } catch (error) {
5954
- return bad(error instanceof Error ? error.message : "queue cursor is stale");
5955
- }
5338
+ const snapshot = await snapshotOf();
5339
+ const notReady = unavailable(snapshot);
5340
+ if (notReady !== null) return notReady;
5341
+ try {
5342
+ return json(200, buildQueuePage(snapshot, queue, expiredOnly, query.get("cursor") ?? void 0, Number.isFinite(limit) ? limit : 50));
5343
+ } catch (error) {
5344
+ return bad(error instanceof Error ? error.message : "queue cursor is stale");
5956
5345
  }
5957
- const readiness = await notReady(source);
5958
- if (readiness !== null) return readiness;
5959
- const offset = Number.parseInt(query.get("cursor") ?? "0", 10);
5960
- const page = toQueuePage(await source.rows?.() ?? [], queue, expiredOnly, Date.now(), Number.isFinite(limit) ? limit : 50);
5961
- return json(200, Number.isFinite(offset) && offset > 0 ? {
5962
- ...page,
5963
- rows: page.rows.slice(offset)
5964
- } : page);
5965
5346
  }
5966
5347
  },
5967
5348
  {
@@ -5982,39 +5363,25 @@ function makeMergeRoutes(deps, _audit) {
5982
5363
  code: "merge-body-not-found",
5983
5364
  message: "merge body requires id or name"
5984
5365
  });
5985
- if (source.snapshot !== void 0) {
5986
- const snapshot = await source.snapshot();
5987
- if (snapshot.blocked !== void 0) return json(503, {
5988
- wireVersion: 1,
5989
- code: "merge-state-blocked",
5990
- readiness: snapshot.blocked.readiness
5991
- });
5992
- const sideFilter = side === "a" ? "live" : side === "b" ? "archive" : side === "live" || side === "archive" ? side : void 0;
5993
- const refs = snapshot.bodyRefs.filter((ref) => (id === null || id === "" || ref.rowId === id) && (name === null || name === "" || ref.name === name) && (sideFilter === void 0 || ref.side === sideFilter));
5994
- if (refs.length === 0) return json(404, {
5995
- wireVersion: 1,
5996
- code: "merge-body-not-found",
5997
- message: "merge body not found"
5998
- });
5999
- if (refs.length > 1) return bad("merge body is ambiguous; pass id, name and side");
6000
- const row = snapshot.rows.find((item) => item.row.id === refs[0].rowId);
6001
- if (row?.stage?.state === "lost" || row?.stage?.state === "expired") return json(404, {
6002
- wireVersion: 1,
6003
- code: "merge-body-not-found",
6004
- message: "merge stage is lost"
6005
- });
6006
- const ref = refs[0];
6007
- return json(200, buildBodyView(snapshot, ref, (source.bodyRef === void 0 ? null : await source.bodyRef(ref)) ?? ""));
6008
- }
6009
- const readiness = await notReady(source);
6010
- if (readiness !== null) return readiness;
6011
- if (id === null || id === "" || side !== "a" && side !== "b") return bad("id and side=a|b are required");
6012
- const content = await source.body?.(id, side) ?? null;
6013
- return content === null ? json(200, {
5366
+ const snapshot = await source.snapshot();
5367
+ const notReady = blocked(snapshot);
5368
+ if (notReady !== null) return notReady;
5369
+ const sideFilter = side === "a" ? "live" : side === "b" ? "archive" : side === "live" || side === "archive" ? side : void 0;
5370
+ const refs = snapshot.bodyRefs.filter((ref) => (id === null || id === "" || ref.rowId === id) && (name === null || name === "" || ref.name === name) && (sideFilter === void 0 || ref.side === sideFilter));
5371
+ if (refs.length === 0) return json(404, {
5372
+ wireVersion: 1,
5373
+ code: "merge-body-not-found",
5374
+ message: "merge body not found"
5375
+ });
5376
+ if (refs.length > 1) return bad("merge body is ambiguous; pass id, name and side");
5377
+ const row = snapshot.rows.find((item) => item.row.id === refs[0].rowId);
5378
+ if (row?.stage?.state === "lost" || row?.stage?.state === "expired") return json(404, {
6014
5379
  wireVersion: 1,
6015
- code: "merge-body-missing",
6016
- message: "merge body not found: " + id
6017
- }) : json(200, toBodyView(id, side === "a" ? "live" : "archive", content, Date.now()));
5380
+ code: "merge-body-not-found",
5381
+ message: "merge stage is lost"
5382
+ });
5383
+ const ref = refs[0];
5384
+ return json(200, buildBodyView(snapshot, ref, (source.bodyRef === void 0 ? null : await source.bodyRef(ref)) ?? ""));
6018
5385
  }
6019
5386
  },
6020
5387
  {
@@ -6024,20 +5391,11 @@ function makeMergeRoutes(deps, _audit) {
6024
5391
  fetch: async (request) => {
6025
5392
  const denied = get(request);
6026
5393
  if (denied !== null) return denied;
6027
- const source = deps.getSource();
6028
- if (source === void 0) return absent();
6029
- if (source.snapshot !== void 0) {
6030
- const snapshot = await source.snapshot();
6031
- if (snapshot.blocked !== void 0) return json(503, {
6032
- wireVersion: 1,
6033
- code: "merge-state-blocked",
6034
- readiness: snapshot.blocked.readiness
6035
- });
6036
- return json(200, buildHistoryView(snapshot));
6037
- }
6038
- const readiness = await notReady(source);
6039
- if (readiness !== null) return readiness;
6040
- return json(200, toHistoryView(await source.history?.() ?? [], Date.now()));
5394
+ const snapshot = await snapshotOf();
5395
+ if (snapshot === null) return absent();
5396
+ const notReady = blocked(snapshot);
5397
+ if (notReady !== null) return notReady;
5398
+ return json(200, buildHistoryView(snapshot));
6041
5399
  }
6042
5400
  },
6043
5401
  {
@@ -6351,13 +5709,13 @@ async function handleReadOnlyMergeCommand(command, io, home, cards, runs, approv
6351
5709
  ...live === void 0 ? {} : { live }
6352
5710
  });
6353
5711
  if (snapshot.blocked !== void 0) throw new Error(snapshot.blocked.code + ": " + snapshot.blocked.message);
6354
- const rows = snapshot.rows.map(toRowFact$1);
5712
+ const rows = snapshot.rows.map(toRowFact);
6355
5713
  if (command.verb === "status") {
6356
5714
  if (command.id !== void 0) {
6357
5715
  const row = rows.find((item) => item.id === command.id);
6358
5716
  return row === void 0 ? "merge row not found: " + command.id : formatSnapshotStatus([row]);
6359
5717
  }
6360
- return formatFacts(buildFacts$1(snapshot));
5718
+ return formatFacts(buildFacts(snapshot));
6361
5719
  }
6362
5720
  if (command.verb === "pending") {
6363
5721
  const live = command.expired === true ? rows.filter((row) => row.expired !== void 0) : rows.filter((row) => row.queue === "pending" && row.expired === void 0);
@@ -6370,9 +5728,6 @@ async function handleReadOnlyMergeCommand(command, io, home, cards, runs, approv
6370
5728
  }
6371
5729
  return formatSnapshotQueue(rows.filter((row) => row.queue === "skipped"), "skipped");
6372
5730
  }
6373
- function mergeCommandHelp() {
6374
- return mergeUsage();
6375
- }
6376
5731
  //#endregion
6377
5732
  //#region lib/types/merge-command.js
6378
5733
  function createReadOnlyMergeService(io, home) {
@@ -6426,10 +5781,6 @@ function createMergeCommand(service, version = MERGE_PACKAGE_VERSION) {
6426
5781
  }
6427
5782
  //#endregion
6428
5783
  //#region lib/types/skipped.js
6429
- /**
6430
- * Skipped filtering: a pair the operator marked as not-worth-merging stays out of automatic
6431
- * screening until mergeSkippedTtlDays expires. Explicit /merge pair ignores this set.
6432
- */
6433
5784
  function skippedPairKeys(rows, ttlDays, now) {
6434
5785
  const keys = /* @__PURE__ */ new Set();
6435
5786
  for (const row of rows) {
@@ -6443,9 +5794,6 @@ function skippedPairKeys(rows, ttlDays, now) {
6443
5794
  }
6444
5795
  return keys;
6445
5796
  }
6446
- function isSkippedPair(keys, a, b) {
6447
- return keys.has(pairKey(a, b));
6448
- }
6449
5797
  //#endregion
6450
5798
  //#region lib/types/domain/engine-criteria.js
6451
5799
  function criteriaFrom(level, verdict, action) {
@@ -6698,11 +6046,6 @@ async function runScan(options) {
6698
6046
  continue;
6699
6047
  }
6700
6048
  row = projected;
6701
- await recordPair(options.io, options.home, {
6702
- pairKey: row.pairKey,
6703
- runId: row.runId ?? "merge",
6704
- result: "kept"
6705
- });
6706
6049
  pairs.push(pairOf(row, candidate, "kept", {
6707
6050
  ...round1.decision === void 0 ? {} : { decision: round1.decision },
6708
6051
  ...round1.usage === void 0 ? {} : { usage: round1.usage }
@@ -6912,15 +6255,7 @@ async function runScan(options) {
6912
6255
  if (await pairLedger(options.io, options.home).transact((tx) => {
6913
6256
  const current = tx.rows().find((item) => item.id === row.id);
6914
6257
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
6915
- })) {
6916
- await dropStage(options.io, options.home, row.id, row.stagingRef);
6917
- await recordPair(options.io, options.home, {
6918
- pairKey: row.pairKey,
6919
- runId: row.runId ?? "merge",
6920
- result: "failed",
6921
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
6922
- });
6923
- }
6258
+ })) await dropStage(options.io, options.home, row.id, row.stagingRef);
6924
6259
  pairs.push(pairOf(row, candidate, "failed", {
6925
6260
  decision: mergeDecision,
6926
6261
  failure: failed,
@@ -7422,12 +6757,22 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7422
6757
  ...outcome.criteria === void 0 ? {} : { criteria: outcome.criteria },
7423
6758
  ...outcome.validationIssues === void 0 ? {} : { validation: { issues: outcome.validationIssues } }
7424
6759
  };
6760
+ const pair = (outcomeName, extra = {}, withDecision = true) => ({
6761
+ id: row.id,
6762
+ pairKey: row.pairKey,
6763
+ a: row.a,
6764
+ b: row.b,
6765
+ queue: row.queue,
6766
+ ...withDecision && row.decision !== void 0 ? { decision: row.decision } : {},
6767
+ outcome: outcomeName,
6768
+ archived: [],
6769
+ ...extra
6770
+ });
7425
6771
  if (outcome.kind === "merged") {
7426
6772
  const unitRef = {
7427
6773
  unitId: outcome.unitId ?? "unknown",
7428
6774
  evidenceSha256: outcome.evidenceSha256 ?? "unknown"
7429
6775
  };
7430
- const target = outcome.target?.actual ?? outcome.target?.requested ?? row.decision?.target;
7431
6776
  const finalize = async () => {
7432
6777
  if (!(await pairLedger(io, home, fence).transact((tx) => {
7433
6778
  const current = tx.rows().find((item) => item.id === row.id);
@@ -7438,50 +6783,28 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7438
6783
  } : tx.remove(row.id, "committed", unitRef, current.version);
7439
6784
  })).ok) throw new Error("row changed before commit record: " + row.id);
7440
6785
  await dropStage(io, home, row.id);
7441
- await recordPair(io, home, {
7442
- pairKey: row.pairKey,
7443
- runId: row.runId ?? "merge",
7444
- result: "merged",
7445
- ...target === void 0 ? {} : { target },
7446
- ...outcome.unitId === void 0 ? {} : { reportRef: outcome.unitId }
7447
- });
7448
6786
  };
7449
6787
  if (deferProjection) return {
7450
6788
  merged: true,
7451
6789
  finalize,
7452
- pair: {
7453
- id: row.id,
7454
- pairKey: row.pairKey,
7455
- a: row.a,
7456
- b: row.b,
7457
- queue: row.queue,
7458
- ...row.decision === void 0 ? {} : { decision: row.decision },
7459
- outcome: "merged",
7460
- archived: outcome.archived ?? [],
6790
+ pair: pair("merged", {
7461
6791
  ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
7462
6792
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
7463
6793
  ...outcome.target === void 0 ? {} : { target: outcome.target },
7464
6794
  ...reportExtra
7465
- }
6795
+ })
7466
6796
  };
7467
6797
  await finalize();
7468
6798
  return {
7469
6799
  merged: true,
7470
- pair: {
7471
- id: row.id,
7472
- pairKey: row.pairKey,
7473
- a: row.a,
7474
- b: row.b,
7475
- queue: row.queue,
7476
- ...row.decision === void 0 ? {} : { decision: row.decision },
7477
- outcome: "merged",
7478
- archived: outcome.archived ?? [],
6800
+ pair: pair("merged", {
6801
+ ...outcome.archived === void 0 ? {} : { archived: outcome.archived },
7479
6802
  ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
7480
6803
  ...reportExtra,
7481
6804
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
7482
6805
  ...outcome.target === void 0 ? {} : { target: outcome.target },
7483
6806
  ...outcome.validationPlan === void 0 ? {} : { validationPlan: outcome.validationPlan }
7484
- }
6807
+ })
7485
6808
  };
7486
6809
  }
7487
6810
  const failure = outcome.failure ?? {
@@ -7504,18 +6827,10 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7504
6827
  })) await dropStage(io, home, row.id);
7505
6828
  return {
7506
6829
  merged: false,
7507
- pair: {
7508
- id: row.id,
7509
- pairKey: row.pairKey,
7510
- a: row.a,
7511
- b: row.b,
7512
- queue: row.queue,
7513
- ...row.decision === void 0 ? {} : { decision: row.decision },
7514
- outcome: "failed",
7515
- archived: [],
6830
+ pair: pair("failed", {
7516
6831
  failure,
7517
6832
  ...reportExtra
7518
- }
6833
+ })
7519
6834
  };
7520
6835
  }
7521
6836
  const cas = casExpiredHook(failure.code);
@@ -7537,33 +6852,17 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7537
6852
  await dropStage(io, home, row.id);
7538
6853
  return {
7539
6854
  merged: false,
7540
- pair: {
7541
- id: row.id,
7542
- pairKey: row.pairKey,
7543
- a: row.a,
7544
- b: row.b,
7545
- queue: row.queue,
7546
- ...row.decision === void 0 ? {} : { decision: row.decision },
7547
- outcome: "failed",
7548
- archived: [],
6855
+ pair: pair("failed", {
7549
6856
  failure: terminalFailure,
7550
6857
  ...reportExtra
7551
- }
6858
+ })
7552
6859
  };
7553
6860
  }
7554
6861
  if (failure.code === "commit-failed" || failure.code === "validation-failed") {
7555
6862
  if (await pairLedger(io, home, fence).transact((tx) => {
7556
6863
  const current = tx.rows().find((item) => item.id === row.id);
7557
6864
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
7558
- })) {
7559
- await dropStage(io, home, row.id, row.stagingRef);
7560
- await recordPair(io, home, {
7561
- pairKey: row.pairKey,
7562
- runId: row.runId ?? "merge",
7563
- result: "failed",
7564
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
7565
- });
7566
- }
6865
+ })) await dropStage(io, home, row.id, row.stagingRef);
7567
6866
  }
7568
6867
  if (failure.code === "source-gone") {
7569
6868
  if (await pairLedger(io, home, fence).transact((tx) => {
@@ -7572,16 +6871,7 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7572
6871
  })) await dropStage(io, home, row.id, row.stagingRef);
7573
6872
  return {
7574
6873
  merged: false,
7575
- pair: {
7576
- id: row.id,
7577
- pairKey: row.pairKey,
7578
- a: row.a,
7579
- b: row.b,
7580
- queue: row.queue,
7581
- outcome: "failed",
7582
- archived: [],
7583
- failure
7584
- }
6874
+ pair: pair("failed", { failure }, false)
7585
6875
  };
7586
6876
  }
7587
6877
  if (await pairLedger(io, home, fence).transact((tx) => {
@@ -7594,42 +6884,21 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7594
6884
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
7595
6885
  }) === null) return {
7596
6886
  merged: false,
7597
- pair: {
7598
- id: row.id,
7599
- pairKey: row.pairKey,
7600
- a: row.a,
7601
- b: row.b,
7602
- queue: row.queue,
7603
- outcome: "failed",
7604
- archived: [],
6887
+ pair: pair("failed", {
7605
6888
  failure: {
7606
6889
  ...failure,
7607
6890
  code: "row-changed",
7608
6891
  message: "row changed before failure record"
7609
6892
  },
7610
6893
  ...reportExtra
7611
- }
6894
+ }, false)
7612
6895
  };
7613
- await recordPair(io, home, {
7614
- pairKey: row.pairKey,
7615
- runId: row.runId ?? "merge",
7616
- result: "failed",
7617
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
7618
- });
7619
6896
  return {
7620
6897
  merged: false,
7621
- pair: {
7622
- id: row.id,
7623
- pairKey: row.pairKey,
7624
- a: row.a,
7625
- b: row.b,
7626
- queue: row.queue,
7627
- ...row.decision === void 0 ? {} : { decision: row.decision },
7628
- outcome: "failed",
7629
- archived: [],
6898
+ pair: pair("failed", {
7630
6899
  failure,
7631
6900
  ...reportExtra
7632
- }
6901
+ })
7633
6902
  };
7634
6903
  }
7635
6904
  //#endregion
@@ -8078,14 +7347,6 @@ async function runUndoSteps(options) {
8078
7347
  };
8079
7348
  }
8080
7349
  if (await readUndoReceipt(options.io, options.home, options.runId, pairKey) !== null) {
8081
- await markPhase("pair-index");
8082
- await options.onPhase?.("pair-index", pairKey);
8083
- await recordPair(options.io, options.home, {
8084
- pairKey,
8085
- runId: options.runId,
8086
- result: "undone",
8087
- target: receipt.target
8088
- });
8089
7350
  await markPhase("run-meta");
8090
7351
  await options.onPhase?.("run-meta", pairKey);
8091
7352
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
@@ -8112,14 +7373,6 @@ async function runUndoSteps(options) {
8112
7373
  restoredSourceHashes: Object.fromEntries(checked.evidence.sources.map((source) => [source.name, contentHash(source.before)])),
8113
7374
  at: options.now()
8114
7375
  });
8115
- await markPhase("pair-index");
8116
- await options.onPhase?.("pair-index", pairKey);
8117
- await recordPair(options.io, options.home, {
8118
- pairKey,
8119
- runId: options.runId,
8120
- result: "undone",
8121
- target: receipt.target
8122
- });
8123
7376
  await markPhase("run-meta");
8124
7377
  await options.onPhase?.("run-meta", pairKey);
8125
7378
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
@@ -8179,13 +7432,6 @@ async function runUndoSteps(options) {
8179
7432
  restoredSourceHashes: Object.fromEntries(checked.evidence.sources.map((source) => [source.name, contentHash(source.before)])),
8180
7433
  at: options.now()
8181
7434
  });
8182
- await options.onPhase?.("pair-index", pairKey);
8183
- await recordPair(options.io, options.home, {
8184
- pairKey,
8185
- runId: options.runId,
8186
- result: "undone",
8187
- target: receipt.target
8188
- });
8189
7435
  await options.onPhase?.("run-meta", pairKey);
8190
7436
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
8191
7437
  undone.push(pairKey);
@@ -8416,9 +7662,6 @@ function buildRound2Prompt(row, decision, mode = "continue") {
8416
7662
  ...body
8417
7663
  ] : [...head, ...body]).join("\n");
8418
7664
  }
8419
- function buildStandbyPrompt() {
8420
- return STANDBY_PROMPT;
8421
- }
8422
7665
  //#endregion
8423
7666
  //#region lib/types/channel-port.js
8424
7667
  async function runRound1OverChannel(options, candidate, signal) {
@@ -8926,14 +8169,6 @@ async function commitStagedRun(io, home, runId, port, options) {
8926
8169
  message: "target is blocked by curator: " + assembled.plan.target,
8927
8170
  plan: assembled.plan
8928
8171
  };
8929
- const journal = {
8930
- runId,
8931
- target: assembled.plan.target,
8932
- create: !assembled.plan.targetExists,
8933
- sources: assembled.plan.sources,
8934
- phase: "prepared",
8935
- updatedAt: new Date(options.now()).toISOString()
8936
- };
8937
8172
  let result;
8938
8173
  let evidenceSha256$1;
8939
8174
  let warning;
@@ -8963,9 +8198,6 @@ async function commitStagedRun(io, home, runId, port, options) {
8963
8198
  ...options.sourceRevisions === void 0 ? {} : { sourceRevisions: options.sourceRevisions },
8964
8199
  ...options.supportDigest === void 0 ? {} : { supportDigest: options.supportDigest }
8965
8200
  };
8966
- try {
8967
- await writeJournal(io, home, journal);
8968
- } catch {}
8969
8201
  const prepared = await transaction.prepare(unitPlan);
8970
8202
  if (!prepared.ok) return {
8971
8203
  ok: false,
@@ -8973,21 +8205,12 @@ async function commitStagedRun(io, home, runId, port, options) {
8973
8205
  plan: assembled.plan
8974
8206
  };
8975
8207
  const committed = await transaction.commit(prepared.prepared, options.signal === void 0 ? void 0 : { signal: options.signal });
8976
- if (!committed.ok) {
8977
- try {
8978
- await writeJournal(io, home, {
8979
- ...journal,
8980
- phase: "failed",
8981
- updatedAt: new Date(options.now()).toISOString()
8982
- });
8983
- } catch {}
8984
- return {
8985
- ok: false,
8986
- message: committed.message,
8987
- plan: assembled.plan,
8988
- ...committed.code === "target-changed" || committed.code === "source-changed" || committed.code === "support-changed" ? { code: committed.code } : {}
8989
- };
8990
- }
8208
+ if (!committed.ok) return {
8209
+ ok: false,
8210
+ message: committed.message,
8211
+ plan: assembled.plan,
8212
+ ...committed.code === "target-changed" || committed.code === "source-changed" || committed.code === "support-changed" ? { code: committed.code } : {}
8213
+ };
8991
8214
  const unitId = committed.evidence.unitId;
8992
8215
  if (committed.evidence.unitId !== prepared.prepared.unitId && !prepared.prepared.unitId.startsWith("legacy:")) return {
8993
8216
  ok: false,
@@ -9008,29 +8231,11 @@ async function commitStagedRun(io, home, runId, port, options) {
9008
8231
  mutations: committed.evidence.mutations
9009
8232
  };
9010
8233
  }
9011
- if (!result.ok) {
9012
- try {
9013
- await writeJournal(io, home, {
9014
- ...journal,
9015
- phase: "failed",
9016
- updatedAt: new Date(options.now()).toISOString()
9017
- });
9018
- } catch {}
9019
- return {
9020
- ok: false,
9021
- message: result.message,
9022
- plan: assembled.plan
9023
- };
9024
- }
9025
- const committed = {
9026
- ...journal,
9027
- phase: "committed",
9028
- updatedAt: new Date(options.now()).toISOString(),
9029
- ...result.unitId === void 0 ? {} : { unitId: result.unitId }
8234
+ if (!result.ok) return {
8235
+ ok: false,
8236
+ message: result.message,
8237
+ plan: assembled.plan
9030
8238
  };
9031
- try {
9032
- await writeJournal(io, home, committed);
9033
- } catch {}
9034
8239
  if (result.unitId === void 0) return {
9035
8240
  ok: true,
9036
8241
  plan: assembled.plan,
@@ -9312,32 +8517,38 @@ function missingManifestFile(files, stagedPaths) {
9312
8517
  }
9313
8518
  //#endregion
9314
8519
  //#region lib/types/authoring-flow.js
9315
- async function runAuthoring(options) {
9316
- if (options.target.includes("/") || options.target.includes(":")) return {
9317
- ok: false,
8520
+ /** The target facts both authoring entries may refuse on before writing anything. */
8521
+ async function targetAdmission(target, targetCard, createMode, isBlocked) {
8522
+ if (target.includes("/") || target.includes(":")) return {
9318
8523
  code: "target-invalid",
9319
- message: "target must be a bare skill name: " + options.target
8524
+ message: "target must be a bare skill name: " + target
9320
8525
  };
9321
- const targetCard = options.cards.find((card) => card.name === options.target);
9322
- const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
9323
- const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
9324
- const createMode = mode === "create-umbrella";
9325
8526
  if (!createMode && targetCard !== void 0) {
9326
8527
  if (targetCard.managed !== true) return {
9327
- ok: false,
9328
8528
  code: "target-not-managed",
9329
- message: "target is not family-managed: " + options.target
8529
+ message: "target is not family-managed: " + target
9330
8530
  };
9331
8531
  if (targetCard.protectionUnknown === true || targetCard.protectedBy != null) return {
9332
- ok: false,
9333
8532
  code: "target-protected",
9334
- message: "target is protected (" + (targetCard.protectedBy ?? "unknown") + "): " + options.target
8533
+ message: "target is protected (" + (targetCard.protectedBy ?? "unknown") + "): " + target
9335
8534
  };
9336
8535
  }
9337
- if (!createMode && await options.isBlocked?.(options.target) === true) return {
9338
- ok: false,
8536
+ if (!createMode && await isBlocked?.(target) === true) return {
9339
8537
  code: "target-blocked",
9340
- message: "target is blocked by curator: " + options.target
8538
+ message: "target is blocked by curator: " + target
8539
+ };
8540
+ return null;
8541
+ }
8542
+ async function runAuthoring(options) {
8543
+ const targetCard = options.cards.find((card) => card.name === options.target);
8544
+ const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
8545
+ const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
8546
+ const createMode = mode === "create-umbrella";
8547
+ const targetRefusal = await targetAdmission(options.target, targetCard, createMode, options.isBlocked);
8548
+ if (targetRefusal !== null) return {
8549
+ ok: false,
8550
+ code: targetRefusal.code,
8551
+ message: targetRefusal.message
9341
8552
  };
9342
8553
  const stageKey = options.stageKey ?? options.runId;
9343
8554
  if (options.reuseStaging === true) {
@@ -9659,46 +8870,15 @@ const STALE_CAUSES = new Set([
9659
8870
  "target-gone"
9660
8871
  ]);
9661
8872
  async function runNewChildAuthoring(options, row, decision, signal) {
9662
- if (decision.target.includes("/") || decision.target.includes(":")) return {
9663
- kind: "failed",
9664
- failure: {
9665
- stage: "author",
9666
- code: "target-invalid",
9667
- message: "target must be a bare skill name: " + decision.target,
9668
- retryable: false,
9669
- retryFrom: "none"
9670
- }
9671
- };
9672
8873
  const targetCard = options.cards.find((card) => card.name === decision.target);
9673
8874
  const createMode = decision.mode === "create-umbrella";
9674
- if (!createMode && targetCard !== void 0) {
9675
- if (targetCard.managed !== true) return {
9676
- kind: "failed",
9677
- failure: {
9678
- stage: "author",
9679
- code: "target-not-managed",
9680
- message: "target is not family-managed: " + decision.target,
9681
- retryable: false,
9682
- retryFrom: "none"
9683
- }
9684
- };
9685
- if (targetCard.protectionUnknown === true || targetCard.protectedBy != null) return {
9686
- kind: "failed",
9687
- failure: {
9688
- stage: "author",
9689
- code: "target-protected",
9690
- message: "target is protected: " + decision.target,
9691
- retryable: false,
9692
- retryFrom: "none"
9693
- }
9694
- };
9695
- }
9696
- if (!createMode && await options.isBlocked?.(decision.target) === true) return {
8875
+ const targetRefusal = await targetAdmission(decision.target, targetCard, createMode, options.isBlocked);
8876
+ if (targetRefusal !== null) return {
9697
8877
  kind: "failed",
9698
8878
  failure: {
9699
8879
  stage: "author",
9700
- code: "target-blocked",
9701
- message: "target is blocked by curator: " + decision.target,
8880
+ code: targetRefusal.code,
8881
+ message: targetRefusal.message,
9702
8882
  retryable: false,
9703
8883
  retryFrom: "none"
9704
8884
  }
@@ -11161,143 +10341,6 @@ async function supersedeMergeRuns(registry) {
11161
10341
  return ids.length;
11162
10342
  }
11163
10343
  //#endregion
11164
- //#region lib/types/coordinator.js
11165
- /**
11166
- * Pure merge transition table. No io, no run shell, no subagents.
11167
- *
11168
- * Every transition returns the whole next row (or a drop), so the caller can persist it
11169
- * through one state transaction. Invalid transitions are refused by name;
11170
- * a caller never "repairs" a row by guessing which state the operator meant.
11171
- */
11172
- function invalid(event, state) {
11173
- return {
11174
- ok: false,
11175
- code: "invalid-transition",
11176
- message: "cannot apply " + event.type + " to " + (state ?? "no row")
11177
- };
11178
- }
11179
- /** Drop fields that must not survive a transition; optional fields stay absent. */
11180
- function base(row, at) {
11181
- return {
11182
- id: row.id,
11183
- pairKey: row.pairKey,
11184
- a: row.a,
11185
- b: row.b,
11186
- state: row.state,
11187
- queue: row.queue,
11188
- createdAt: row.createdAt,
11189
- updatedAt: at,
11190
- ...row.runId === void 0 ? {} : { runId: row.runId },
11191
- ...row.unitId === void 0 ? {} : { unitId: row.unitId },
11192
- ...row.origin === void 0 ? {} : { origin: row.origin },
11193
- ...row.decision === void 0 ? {} : { decision: row.decision },
11194
- ...row.sourceRevision === void 0 ? {} : { sourceRevision: row.sourceRevision },
11195
- ...row.targetRevision === void 0 ? {} : { targetRevision: row.targetRevision },
11196
- ...row.attempts === void 0 ? {} : { attempts: row.attempts },
11197
- ...row.stagingRef === void 0 ? {} : { stagingRef: row.stagingRef },
11198
- ...row.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: row.stagingExpiresAt },
11199
- ...row.expired === void 0 ? {} : { expired: row.expired },
11200
- ...row.reason === void 0 ? {} : { reason: row.reason },
11201
- ...row.target === void 0 ? {} : { target: row.target }
11202
- };
11203
- }
11204
- function next(row, at, state, patch = {}) {
11205
- return {
11206
- ok: true,
11207
- row: {
11208
- ...base(row, at),
11209
- state,
11210
- queue: queueOf(state),
11211
- ...patch
11212
- }
11213
- };
11214
- }
11215
- /** Legacy v2 projection only; runtime writes must use transitionV3/RowTx. */
11216
- function transitionLegacyForProjection(current, event) {
11217
- if (event.type === "candidate") {
11218
- if (current !== void 0) return {
11219
- ok: true,
11220
- row: current
11221
- };
11222
- const key = pairKey(event.a, event.b);
11223
- return {
11224
- ok: true,
11225
- row: {
11226
- id: rowIdOf(key),
11227
- pairKey: key,
11228
- a: event.a,
11229
- b: event.b,
11230
- state: "verify",
11231
- queue: "verify",
11232
- createdAt: event.at,
11233
- updatedAt: event.at
11234
- }
11235
- };
11236
- }
11237
- if (event.type === "source-gone") return {
11238
- ok: true,
11239
- row: null
11240
- };
11241
- if (event.type === "gate-keep") {
11242
- if (current !== void 0) return {
11243
- ok: true,
11244
- row: current
11245
- };
11246
- const key = pairKey(event.a, event.b);
11247
- return {
11248
- ok: true,
11249
- row: {
11250
- id: rowIdOf(key),
11251
- pairKey: key,
11252
- a: event.a,
11253
- b: event.b,
11254
- state: "skipped",
11255
- queue: "skipped",
11256
- origin: "agent",
11257
- createdAt: event.at,
11258
- updatedAt: event.at
11259
- }
11260
- };
11261
- }
11262
- if (current === void 0) return invalid(event, void 0);
11263
- switch (event.type) {
11264
- case "round1-keep": return current.state === "verify" ? next(current, event.at, "skipped", { origin: "agent" }) : invalid(event, current.state);
11265
- case "round1-merge":
11266
- if (current.state !== "verify") return invalid(event, current.state);
11267
- return event.approval === "on" ? next(current, event.at, "pending", {
11268
- ...event.decision === void 0 ? {} : { decision: event.decision },
11269
- ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
11270
- ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt },
11271
- ...event.sourceRevision === void 0 ? {} : { sourceRevision: event.sourceRevision },
11272
- ...event.targetRevision === void 0 ? {} : { targetRevision: event.targetRevision }
11273
- }) : next(current, event.at, "approved", {
11274
- ...event.decision === void 0 ? {} : { decision: event.decision },
11275
- ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
11276
- ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt },
11277
- ...event.sourceRevision === void 0 ? {} : { sourceRevision: event.sourceRevision },
11278
- ...event.targetRevision === void 0 ? {} : { targetRevision: event.targetRevision }
11279
- });
11280
- case "round1-fail": return current.state === "verify" ? next(current, event.at, "verify", {
11281
- failure: event.failure,
11282
- attempts: (current.attempts ?? 0) + 1
11283
- }) : invalid(event, current.state);
11284
- case "approve": return current.state === "pending" ? next(current, event.at, "approved", event.runId === void 0 ? {} : { runId: event.runId }) : invalid(event, current.state);
11285
- case "reject": return current.state === "pending" ? next(current, event.at, "skipped", { origin: "human" }) : invalid(event, current.state);
11286
- case "round2-cancel": return current.state === "fuse" || current.state === "approved" ? next(current, event.at, "approved") : invalid(event, current.state);
11287
- case "round2-fail": return current.state === "fuse" || current.state === "approved" ? next(current, event.at, "approved", {
11288
- failure: event.failure,
11289
- attempts: (current.attempts ?? 0) + 1
11290
- }) : invalid(event, current.state);
11291
- case "round2-success": return current.state === "fuse" || current.state === "approved" ? {
11292
- ok: true,
11293
- row: null
11294
- } : invalid(event, current.state);
11295
- case "target-taken":
11296
- case "source-changed": return current.state === "pending" || current.state === "approved" || current.state === "fuse" ? next(current, event.at, current.state, { expired: event.type }) : invalid(event, current.state);
11297
- case "restore": return current.state === "skipped" ? next(current, event.at, "verify") : invalid(event, current.state);
11298
- }
11299
- }
11300
- //#endregion
11301
10344
  //#region lib/types/pair-command.js
11302
10345
  async function runPairCommand(options) {
11303
10346
  return await runScanCommand({
@@ -11674,25 +10717,6 @@ function apply(ctx, rawConfig = {}) {
11674
10717
  },
11675
10718
  async bodyRef(ref) {
11676
10719
  return await resolveBody(io, home, ref, async () => ref.side === "archive" ? await skills.readArchived(ref.name) : await skills.read(ref.name));
11677
- },
11678
- approval() {
11679
- return getParams().mergeApproval;
11680
- },
11681
- readiness() {
11682
- return ready;
11683
- },
11684
- commands() {
11685
- return [
11686
- "merge scan",
11687
- "merge doctor",
11688
- "merge status",
11689
- "merge pending",
11690
- "merge skipped",
11691
- "merge report",
11692
- "merge runs",
11693
- "merge cancel",
11694
- "merge reconcile"
11695
- ];
11696
10720
  }
11697
10721
  };
11698
10722
  doctorFact = async () => {
@@ -11779,4 +10803,4 @@ function apply(ctx, rawConfig = {}) {
11779
10803
  });
11780
10804
  }
11781
10805
  //#endregion
11782
- export { Config, MERGE_DOCTOR_SCHEMA_VERSION, MERGE_LIFECYCLE_VERBS, MERGE_MAX_ATTEMPTS, MERGE_PAIR_STATES, MERGE_PARAM_DEFAULTS, MERGE_PROMPT_VERSION, MERGE_QUEUES, MERGE_ROUTES, MERGE_STAGE_TOOL_NAME, MERGE_STATE_VERSION, STAGING_SCHEMA_VERSION, STANDBY_PROMPT, StageToolRegistry, apply, applyStageTool, approvePending, assembleStagedCommit, buildCommitPlan, buildRound1Prompt, buildRound2Prompt, buildStandbyPrompt, busyTarget, canRetry, cleanupExpiredStaging, commitReceiptPath, commitStagedRun, committedPairs, configureMergePresentation, copyStagedFile, countRows, createChannelScanPorts, createEngineRunner, createEngineScreening, createMergeCommand, createMergeRuntime, createMergeStageTool, createNewChildRound2, createReadOnlyMergeService, createStageForCards, createStaging, dropRow, dropRowV3, dropStaging, durableReceiptDir, durableReceiptPath, emptyMergeState, engineConfigFromRoot, ensureStaging, forcedDecisionFor, formatFacts, formatQueue, formatRunDetail, formatRunsView, formatSnapshotQueue, formatSnapshotStatus, formatStatus, handleReadOnlyMergeCommand, hasBlockingFinding, inject, isSkippedPair, listStagedFiles, liveRowFactsOf, loadMergeState, loadStateSchema, makeMergeRoutes, manifestFilesOf, mergeCommandHelp, mergeDoctor, mergeDoctorAvailability, mergeDoctorFact, mergeReportDigestPath, mergeReportPath, mergeRunIdsToSupersede, mergeStatePath, mergeUsage, missingManifestFile, moveRow, mutateMergeState, pairKey, parseAuthoringManifest, parseDecision, parseMergeCommand, pendingExpiry, persistedExpiry, promptHashOf, queueOf, readCommitReceipt, readCommitReceipts, readMergeReport, readStagedFile, readStagedTree, readStaging, recordRound2Outcome, rejectPending, resolveMergeParams, resolveTargetName, retryMergeReport, rewriteFrontmatterName, rowIdOf, rowsOf, runApproveCommand, runAuthoring, runMergeCommand, runNewChildAuthoring, runPairCommand, runResumeCommand, runRound1OverChannel, runScan, runScanCommand, setStagingTarget, skillLibraryPort, skillLibraryUndoPort, skippedPairKeys, snapshotCards, sourceBlock, stageKeyOf, stageOf, stagingRoot, subagentChannelAdapter, supersedeMergeRuns, transactRows, transitionLegacyForProjection, undoMergeReceipt, unitFor, upsertRow, validateStagedTree, withMergeRun, writeCommitReceipt, writeMergeReport, writeStagedFile };
10806
+ export { Config, MERGE_LIFECYCLE_VERBS, MERGE_MAX_ATTEMPTS, MERGE_PAIR_STATES, MERGE_PARAM_DEFAULTS, MERGE_PROMPT_VERSION, MERGE_ROUTES, MERGE_STAGE_TOOL_NAME, MERGE_STATE_VERSION, STAGING_SCHEMA_VERSION, STANDBY_PROMPT, StageToolRegistry, apply, applyStageTool, approvePending, assembleStagedCommit, buildCommitPlan, buildRound1Prompt, buildRound2Prompt, busyTarget, canRetry, cleanupExpiredStaging, commitReceiptPath, commitStagedRun, committedPairs, configureMergePresentation, copyStagedFile, countRows, createChannelScanPorts, createEngineRunner, createEngineScreening, createMergeCommand, createMergeRuntime, createMergeStageTool, createNewChildRound2, createReadOnlyMergeService, createStageForCards, createStaging, durableReceiptDir, durableReceiptPath, emptyMergeState, engineConfigFromRoot, forcedDecisionFor, formatFacts, formatQueue, formatRunDetail, formatRunsView, formatSnapshotQueue, formatSnapshotStatus, formatStatus, handleReadOnlyMergeCommand, hasBlockingFinding, inject, listStagedFiles, liveRowFactsOf, loadMergeState, makeMergeRoutes, manifestFilesOf, mergeDoctor, mergeDoctorAvailability, mergeDoctorFact, mergeReportDigestPath, mergeReportPath, mergeRunIdsToSupersede, mergeStatePath, mergeUsage, missingManifestFile, pairKey, parseAuthoringManifest, parseDecision, parseMergeCommand, pendingExpiry, persistedExpiry, promptHashOf, queueOf, readCommitReceipt, readCommitReceipts, readMergeReport, readStagedFile, readStagedTree, readStaging, recordRound2Outcome, rejectPending, resolveMergeParams, resolveTargetName, retryMergeReport, rewriteFrontmatterName, rowIdOf, rowsOf, runApproveCommand, runAuthoring, runMergeCommand, runNewChildAuthoring, runPairCommand, runResumeCommand, runRound1OverChannel, runScan, runScanCommand, setStagingTarget, skillLibraryPort, skillLibraryUndoPort, skippedPairKeys, snapshotCards, sourceBlock, stageKeyOf, stageOf, stagingRoot, subagentChannelAdapter, supersedeMergeRuns, targetAdmission, undoMergeReceipt, unitFor, validateStagedTree, withMergeRun, writeCommitReceipt, writeMergeReport, writeStagedFile };