@lmzhen/dsh-evolution-merge 0.47.0 → 0.49.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 (78) hide show
  1. package/lib/index.js +224 -775
  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-migrate.d.ts +27 -0
  25. package/lib/types/domain/state-migrate.js +29 -0
  26. package/lib/types/domain/state.d.ts +1 -53
  27. package/lib/types/domain/state.js +0 -55
  28. package/lib/types/domain/undo-journal.d.ts +4 -4
  29. package/lib/types/domain/undo-journal.js +5 -7
  30. package/lib/types/index.d.ts +0 -1
  31. package/lib/types/index.js +8 -14
  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 +0 -2
  45. package/lib/types/state.js +0 -13
  46. package/lib/types/surface/merge-view.d.ts +1 -2
  47. package/lib/types/surface/merge-view.js +0 -1
  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 +10 -4
  52. package/scripts/merge-backup.mjs +31 -0
  53. package/scripts/merge-migrate.mjs +163 -0
  54. package/scripts/merge-restore.mjs +36 -0
  55. package/lib/types/commit-recovery.d.ts +0 -14
  56. package/lib/types/commit-recovery.js +0 -96
  57. package/lib/types/coordinator.d.ts +0 -77
  58. package/lib/types/coordinator.js +0 -137
  59. package/lib/types/domain/commit-journal-store.d.ts +0 -6
  60. package/lib/types/domain/commit-journal-store.js +0 -26
  61. package/lib/types/domain/commit-journal.d.ts +0 -16
  62. package/lib/types/domain/commit-journal.js +0 -26
  63. package/lib/types/domain/indexes.d.ts +0 -21
  64. package/lib/types/domain/indexes.js +0 -15
  65. package/lib/types/domain/merge-report.d.ts +0 -41
  66. package/lib/types/domain/merge-report.js +0 -2
  67. package/lib/types/domain/pair-index-store.d.ts +0 -6
  68. package/lib/types/domain/pair-index-store.js +0 -25
  69. package/lib/types/domain/pair-index.d.ts +0 -9
  70. package/lib/types/domain/pair-index.js +0 -28
  71. package/lib/types/domain/resolve-pair.d.ts +0 -4
  72. package/lib/types/domain/resolve-pair.js +0 -4
  73. package/lib/types/domain/row-tx.d.ts +0 -27
  74. package/lib/types/domain/row-tx.js +0 -31
  75. package/lib/types/domain/run-record.d.ts +0 -45
  76. package/lib/types/domain/run-record.js +0 -24
  77. package/lib/types/domain/state-v3.d.ts +0 -4
  78. package/lib/types/domain/state-v3.js +0 -4
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) {
@@ -1346,17 +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
1343
  async function rowsOf(io, home, queue) {
1361
1344
  const loaded = await loadMergeState(io, home);
1362
1345
  return queue === void 0 ? loaded.state.rows : loaded.state.rows.filter((row) => row.queue === queue);
@@ -1619,7 +1602,7 @@ function v3State(body) {
1619
1602
  rows
1620
1603
  };
1621
1604
  }
1622
- async function readJournal$1(io, home) {
1605
+ async function readJournal(io, home) {
1623
1606
  const raw = await readTextOrNull(io, migrationJournalPath(home));
1624
1607
  if (raw === null) return null;
1625
1608
  try {
@@ -1696,7 +1679,7 @@ async function readStateReadiness(io, home) {
1696
1679
  if (version === 3) {
1697
1680
  const state = v3State(body);
1698
1681
  if (state === null) return blocked("merge-state-needs-repair", "v3 state has an invalid row");
1699
- const journal = await readJournal$1(io, home);
1682
+ const journal = await readJournal(io, home);
1700
1683
  const report = await readReport(io, home);
1701
1684
  const marker = readMarker(body);
1702
1685
  if (body.migration !== void 0 && marker === null) return blocked("merge-state-needs-repair", "migration marker is invalid");
@@ -1755,7 +1738,7 @@ async function ensureMergeStateReady(io, home, fence) {
1755
1738
  outcome = blocked("merge-state-needs-repair", "v3 state has an invalid row");
1756
1739
  return current;
1757
1740
  }
1758
- const journal = await readJournal$1(io, home);
1741
+ const journal = await readJournal(io, home);
1759
1742
  if (journal !== null) {
1760
1743
  outcome = await completeJournal(io, home, journal, current);
1761
1744
  return current;
@@ -2000,15 +1983,6 @@ async function readStaging(io, home, runId) {
2000
1983
  return probeUnknown("staging manifest is not JSON: " + (error instanceof Error ? error.message : String(error)));
2001
1984
  }
2002
1985
  }
2003
- async function ensureStaging(io, home, input) {
2004
- const existing = await readStaging(io, home, input.runId);
2005
- if (existing.kind === "present") return {
2006
- ok: true,
2007
- path: stagingRoot(home, input.runId),
2008
- manifest: existing.value.manifest
2009
- };
2010
- return createStaging(io, home, input);
2011
- }
2012
1986
  async function setStagingTarget(io, home, stageKey, target, mode) {
2013
1987
  const view = await readStaging(io, home, stageKey);
2014
1988
  if (view.kind !== "present") return false;
@@ -2016,9 +1990,6 @@ async function setStagingTarget(io, home, stageKey, target, mode) {
2016
1990
  await io.writeText(join(stagingRoot(home, stageKey), "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
2017
1991
  return true;
2018
1992
  }
2019
- async function dropStaging(io, home, stageKey) {
2020
- await io.remove(stagingRoot(home, stageKey));
2021
- }
2022
1993
  /** Remove expired run-level staging trees; `keep` names runs a live row still points at. */
2023
1994
  async function cleanupExpiredStaging(io, home, now, keep = /* @__PURE__ */ new Set()) {
2024
1995
  let runs;
@@ -2175,6 +2146,7 @@ async function adoptLegacyProduction(io, home) {
2175
2146
  wrote
2176
2147
  };
2177
2148
  }
2149
+ /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
2178
2150
  async function createStageTree(io, home, input) {
2179
2151
  return await createStaging(io, home, input);
2180
2152
  }
@@ -2447,61 +2419,6 @@ async function readPairLedgerV4(io, home) {
2447
2419
  };
2448
2420
  }
2449
2421
  //#endregion
2450
- //#region lib/types/domain/pair-index.js
2451
- function parsePairIndex(raw) {
2452
- const index = /* @__PURE__ */ new Map();
2453
- if (raw === null) return index;
2454
- let parsed;
2455
- try {
2456
- parsed = JSON.parse(raw);
2457
- } catch {
2458
- return index;
2459
- }
2460
- const body = parsed;
2461
- if (!Array.isArray(body.entries)) return index;
2462
- for (const value of body.entries) {
2463
- const entry = value;
2464
- if (typeof entry.pairKey !== "string" || typeof entry.runId !== "string" || typeof entry.result !== "string") continue;
2465
- if (![
2466
- "merged",
2467
- "kept",
2468
- "failed",
2469
- "cancelled",
2470
- "undone"
2471
- ].includes(entry.result)) continue;
2472
- index.set(entry.pairKey, entry);
2473
- }
2474
- return index;
2475
- }
2476
- function serializePairIndex(index) {
2477
- return JSON.stringify({
2478
- schemaVersion: 1,
2479
- entries: [...index.values()]
2480
- }, null, 2);
2481
- }
2482
- //#endregion
2483
- //#region lib/types/domain/pair-index-store.js
2484
- /** Durable pairKey index at <home>/merge/pair-index.json. */
2485
- function pairIndexPath(home) {
2486
- return join(home, "merge", "pair-index.json");
2487
- }
2488
- async function loadPairIndex(io, home) {
2489
- let raw = null;
2490
- try {
2491
- raw = await io.readText(pairIndexPath(home));
2492
- } catch {
2493
- return /* @__PURE__ */ new Map();
2494
- }
2495
- return parsePairIndex(raw);
2496
- }
2497
- async function recordPair(io, home, entry) {
2498
- await transactIo(io, pairIndexPath(home), (current) => {
2499
- const index = parsePairIndex(current);
2500
- index.set(entry.pairKey, entry);
2501
- return serializePairIndex(index);
2502
- });
2503
- }
2504
- //#endregion
2505
2422
  //#region lib/types/domain/run-coordinator.js
2506
2423
  /** 1b-1 additive types for the merge run lifecycle; no writer is active in this phase. */
2507
2424
  function runMetaPath(home, runId) {
@@ -2908,19 +2825,18 @@ async function readMergeSnapshot(options) {
2908
2825
  });
2909
2826
  bodyRefs.push(...bodies);
2910
2827
  }
2911
- const pairIndex = await loadPairIndex(options.io, options.home);
2912
2828
  const runs = [];
2913
2829
  for (const record of options.runs) {
2914
2830
  const meta = await readRunMeta(options.io, options.home, record.id);
2915
- const pairs = [...pairIndex.values()].filter((item) => item.runId === record.id).map((item) => ({
2916
- pairKey: item.pairKey,
2917
- runId: item.runId,
2918
- result: item.result,
2919
- ...item.target === void 0 ? {} : { target: item.target }
2920
- }));
2921
2831
  const reportPath = mergeReportPath(options.home, record.id);
2922
2832
  const probe = await readMergeReport(options.io, options.home, record.id);
2923
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
+ }));
2924
2840
  runs.push({
2925
2841
  record,
2926
2842
  meta,
@@ -2992,8 +2908,9 @@ async function readMergeSnapshot(options) {
2992
2908
  })).digest("hex");
2993
2909
  const notes = [...options.notes ?? []];
2994
2910
  if (historyDegraded) notes.push("history archive port unavailable; receipts only");
2995
- 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")));
2996
- 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) + ")");
2997
2914
  return {
2998
2915
  wireVersion: 1,
2999
2916
  schemaVersion: 4,
@@ -3205,34 +3122,10 @@ var MergeUnitTransactions = class {
3205
3122
  await this.options.onPhase?.(phase);
3206
3123
  };
3207
3124
  try {
3208
- if (this.options.fence !== void 0) {
3209
- fenceLease = await this.options.fence.acquire();
3210
- lock = fenceLease.lock;
3211
- this.options.onLock?.("tree");
3212
- } else {
3213
- const fallbackLease = await new HomeAuthority({
3214
- home: this.options.home,
3215
- waitMs: this.options.waitMs ?? 5e3
3216
- }).lease("tree").catch(() => void 0);
3217
- if (fallbackLease === void 0) return {
3218
- ok: false,
3219
- code: "unit-fence-failed",
3220
- message: "tree lock unavailable"
3221
- };
3222
- fenceLease = {
3223
- lock: leaseAsTreeLock(fallbackLease),
3224
- release: async () => {
3225
- await fallbackLease.release();
3226
- }
3227
- };
3228
- lock = fenceLease.lock;
3229
- this.options.onLock?.("tree");
3230
- }
3231
- if (!await lock.assertHeld()) return {
3232
- ok: false,
3233
- code: "unit-fence-failed",
3234
- message: "tree fence lost"
3235
- };
3125
+ const acquired = await this.acquireTreeFence();
3126
+ fenceLease = acquired.fenceLease;
3127
+ lock = acquired.lock;
3128
+ this.options.onLock?.("tree");
3236
3129
  stage = "claim";
3237
3130
  claim = await this.options.claim.acquire(stored.plan.target, stored.plan.runId);
3238
3131
  this.options.onLock?.("claim");
@@ -3368,6 +3261,82 @@ var MergeUnitTransactions = class {
3368
3261
  await lease.release();
3369
3262
  }
3370
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
+ }
3371
3340
  async finalize(unitId) {
3372
3341
  return await this.withRecoveryFence(async () => this.finalizeUnlocked(unitId));
3373
3342
  }
@@ -3412,55 +3381,13 @@ var MergeUnitTransactions = class {
3412
3381
  code: "unit-rollback-blocked",
3413
3382
  message: "source is still live: " + liveSource.name
3414
3383
  };
3415
- let liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3416
- path: file.path,
3417
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3418
- })));
3419
- const supportMatches = () => liveSupport.every((file) => file.content === (snapshot.supportAfter.find((item) => item.path === file.path)?.content ?? null));
3420
- if (!supportMatches()) {
3421
- if (![
3422
- "support-pending",
3423
- "sources-archived",
3424
- "unit-written"
3425
- ].includes(journal.phase) || this.options.backend.restoreSupportFile === void 0) return {
3426
- ok: false,
3427
- code: "unit-rollback-blocked",
3428
- message: "support file live hash does not match the prepared support; manual recovery required"
3429
- };
3430
- for (const file of snapshot.supportAfter) await this.options.backend.restoreSupportFile(snapshot.target, file.path, file.content);
3431
- liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3432
- path: file.path,
3433
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3434
- })));
3435
- if (!supportMatches()) return {
3436
- ok: false,
3437
- code: "unit-rollback-blocked",
3438
- message: "support file could not be completed from the prepared snapshot"
3439
- };
3440
- }
3441
- const evidence = {
3442
- schemaVersion: 1,
3443
- unitId,
3444
- runId: snapshot.runId,
3445
- planDigest: snapshot.planDigest,
3446
- state: "complete",
3447
- target: {
3448
- before: snapshot.targetBefore,
3449
- after: await this.options.backend.readSkill(snapshot.target) ?? "",
3450
- supportDigest: createHash("sha256").update(JSON.stringify(snapshot.supportAfter)).digest("hex"),
3451
- created: !snapshot.targetExists
3452
- },
3453
- sources: snapshot.sourceBefore.map((source) => ({
3454
- name: source.name,
3455
- before: source.body,
3456
- after: null,
3457
- archived: checkpoints.some((item) => item.source === source.name),
3458
- absorbedInto: snapshot.target
3459
- })),
3460
- mutations: 0,
3461
- ...snapshot.validationPlan === void 0 ? {} : { validationPlan: snapshot.validationPlan },
3462
- 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
3463
3389
  };
3390
+ const evidence = await this.evidenceFor(unitId, snapshot, journal, await this.options.backend.readSkill(snapshot.target) ?? "");
3464
3391
  if (receipt?.evidenceSha256 !== void 0 && unitEvidenceDigest(evidence) !== receipt.evidenceSha256) return {
3465
3392
  ok: false,
3466
3393
  code: "unit-rollback-blocked",
@@ -3522,55 +3449,13 @@ var MergeUnitTransactions = class {
3522
3449
  code: "unit-rollback-blocked",
3523
3450
  message: "source is still live: " + liveSource.name
3524
3451
  };
3525
- let liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3526
- path: file.path,
3527
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3528
- })));
3529
- const supportMatches = () => liveSupport.every((file) => file.content === (snapshot.supportAfter.find((item) => item.path === file.path)?.content ?? null));
3530
- if (!supportMatches()) {
3531
- if (![
3532
- "support-pending",
3533
- "sources-archived",
3534
- "unit-written"
3535
- ].includes(journal.phase) || this.options.backend.restoreSupportFile === void 0) return {
3536
- ok: false,
3537
- code: "unit-rollback-blocked",
3538
- message: "support file live hash does not match the prepared support; manual recovery required"
3539
- };
3540
- for (const file of snapshot.supportAfter) await this.options.backend.restoreSupportFile(snapshot.target, file.path, file.content);
3541
- liveSupport = await Promise.all(snapshot.supportAfter.map(async (file) => ({
3542
- path: file.path,
3543
- content: await this.options.backend.readSupportFile(snapshot.target, file.path)
3544
- })));
3545
- if (!supportMatches()) return {
3546
- ok: false,
3547
- code: "unit-rollback-blocked",
3548
- message: "support file could not be completed from the prepared snapshot"
3549
- };
3550
- }
3551
- const evidence = {
3552
- schemaVersion: 1,
3553
- unitId,
3554
- runId: snapshot.runId,
3555
- planDigest: snapshot.planDigest,
3556
- state: "complete",
3557
- target: {
3558
- before: snapshot.targetBefore,
3559
- after: targetAfter,
3560
- supportDigest: createHash("sha256").update(JSON.stringify(snapshot.supportAfter)).digest("hex"),
3561
- created: !snapshot.targetExists
3562
- },
3563
- sources: snapshot.sourceBefore.map((source) => ({
3564
- name: source.name,
3565
- before: source.body,
3566
- after: null,
3567
- archived: checkpoints.some((item) => item.source === source.name),
3568
- absorbedInto: snapshot.target
3569
- })),
3570
- mutations: 0,
3571
- ...snapshot.validationPlan === void 0 ? {} : { validationPlan: snapshot.validationPlan },
3572
- 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
3573
3457
  };
3458
+ const evidence = await this.evidenceFor(unitId, snapshot, journal, targetAfter);
3574
3459
  await writeUnitEvidence(this.options.io, this.options.home, evidence);
3575
3460
  await writeUnitJournal(this.options.io, this.options.home, {
3576
3461
  ...journal,
@@ -3602,33 +3487,10 @@ var MergeUnitTransactions = class {
3602
3487
  let lock;
3603
3488
  let claim;
3604
3489
  try {
3605
- if (this.options.fence !== void 0) {
3606
- fenceLease = await this.options.fence.acquire();
3607
- lock = fenceLease.lock;
3608
- } else {
3609
- const fallbackLease = await new HomeAuthority({
3610
- home: this.options.home,
3611
- waitMs: this.options.waitMs ?? 5e3
3612
- }).lease("tree").catch(() => void 0);
3613
- if (fallbackLease === void 0) return {
3614
- ok: false,
3615
- code: "unit-fence-failed",
3616
- message: "tree lock unavailable"
3617
- };
3618
- fenceLease = {
3619
- lock: leaseAsTreeLock(fallbackLease),
3620
- release: async () => {
3621
- await fallbackLease.release();
3622
- }
3623
- };
3624
- lock = fenceLease.lock;
3625
- }
3490
+ const acquired = await this.acquireTreeFence();
3491
+ fenceLease = acquired.fenceLease;
3492
+ lock = acquired.lock;
3626
3493
  this.options.onLock?.("tree");
3627
- if (!await lock.assertHeld()) return {
3628
- ok: false,
3629
- code: "unit-fence-failed",
3630
- message: "tree fence lost"
3631
- };
3632
3494
  claim = await this.options.claim.acquire(snapshot.target, snapshot.runId);
3633
3495
  this.options.onLock?.("claim");
3634
3496
  const currentTarget = await this.options.backend.readSkill(snapshot.target);
@@ -4668,78 +4530,6 @@ const ALLOWED = [
4668
4530
  evidence: "none",
4669
4531
  activeRuns: 0
4670
4532
  },
4671
- {
4672
- row: "fuse",
4673
- run: "none",
4674
- staging: "none",
4675
- journal: "none",
4676
- evidence: "none",
4677
- activeRuns: 0,
4678
- recovery: true
4679
- },
4680
- {
4681
- row: "fuse",
4682
- run: "failed",
4683
- staging: "none",
4684
- journal: "none",
4685
- evidence: "none",
4686
- activeRuns: 0,
4687
- recovery: true
4688
- },
4689
- {
4690
- row: "fuse",
4691
- run: "orphan",
4692
- staging: "none",
4693
- journal: "none",
4694
- evidence: "none",
4695
- activeRuns: 0,
4696
- recovery: true
4697
- },
4698
- {
4699
- row: "fuse",
4700
- run: "running",
4701
- staging: "none",
4702
- journal: "none",
4703
- evidence: "none",
4704
- activeRuns: 1,
4705
- recovery: true
4706
- },
4707
- {
4708
- row: "fuse",
4709
- run: "none",
4710
- staging: "missing",
4711
- journal: "none",
4712
- evidence: "none",
4713
- activeRuns: 0,
4714
- recovery: true
4715
- },
4716
- {
4717
- row: "fuse",
4718
- run: "failed",
4719
- staging: "missing",
4720
- journal: "none",
4721
- evidence: "none",
4722
- activeRuns: 0,
4723
- recovery: true
4724
- },
4725
- {
4726
- row: "fuse",
4727
- run: "orphan",
4728
- staging: "missing",
4729
- journal: "none",
4730
- evidence: "none",
4731
- activeRuns: 0,
4732
- recovery: true
4733
- },
4734
- {
4735
- row: "fuse",
4736
- run: "running",
4737
- staging: "missing",
4738
- journal: "none",
4739
- evidence: "none",
4740
- activeRuns: 1,
4741
- recovery: true
4742
- },
4743
4533
  {
4744
4534
  row: "fuse",
4745
4535
  run: "running",
@@ -4774,7 +4564,7 @@ const ALLOWED = [
4774
4564
  }
4775
4565
  ];
4776
4566
  function same(a, b) {
4777
- 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;
4778
4568
  }
4779
4569
  function validateArtifacts(state) {
4780
4570
  if (state.activeRuns > 1) return {
@@ -4794,7 +4584,7 @@ function validateArtifacts(state) {
4794
4584
  ok: false,
4795
4585
  reason: "skipped-running"
4796
4586
  };
4797
- if (state.row === "fuse" && state.staging === "missing" && state.recovery !== true) return {
4587
+ if (state.row === "fuse" && state.staging === "missing") return {
4798
4588
  ok: false,
4799
4589
  reason: "fuse-lost-staging"
4800
4590
  };
@@ -4812,35 +4602,12 @@ function validateArtifacts(state) {
4812
4602
  };
4813
4603
  }
4814
4604
  //#endregion
4815
- //#region lib/types/domain/commit-journal-store.js
4816
- /** Durable commit journal: <home>/merge/journals/<run>.json. */
4817
- function journalPath(home, runId) {
4818
- return join(home, "merge", "journals", runId.replace(/[^A-Za-z0-9_-]+/g, "_") + ".json");
4819
- }
4820
- async function writeJournal(io, home, journal) {
4821
- const path = journalPath(home, journal.runId);
4822
- const content = JSON.stringify(journal, null, 2);
4823
- if (io.transact !== void 0) await io.transact(path, () => content);
4824
- else await io.writeText(path, content);
4825
- return path;
4826
- }
4827
- async function readJournal(io, home, runId) {
4828
- const raw = await io.readText(journalPath(home, runId));
4829
- if (raw === null) return null;
4830
- try {
4831
- return JSON.parse(raw);
4832
- } catch {
4833
- return null;
4834
- }
4835
- }
4836
- //#endregion
4837
4605
  //#region lib/types/doctor.js
4838
4606
  /**
4839
4607
  * Merge doctor rows: state, pending, gate cache and staging are each probed read-only.
4840
4608
  * A missing file is an empty feature, not a failure; an unreadable file is a warning the
4841
4609
  * operator can act on. This module never repairs or writes.
4842
4610
  */
4843
- const MERGE_DOCTOR_SCHEMA_VERSION = 3;
4844
4611
  /** Compose the one-line global-doctor fact from buildDoctorView + the last reconcile report. */
4845
4612
  function mergeDoctorFact(view, report) {
4846
4613
  const counts = view.counts;
@@ -4889,13 +4656,6 @@ async function mergeDoctor(io, home, configuredRoot) {
4889
4656
  status: snapshot.blocked === void 0 || !hasState ? "ok" : "warn",
4890
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
4891
4658
  });
4892
- const recoveryRuns = /* @__PURE__ */ new Set();
4893
- try {
4894
- const raw = await io.readText(join(home, "merge", "recovery.json"));
4895
- if (raw !== null) {
4896
- for (const report of JSON.parse(raw)) if (report.ok === true && report.runId !== void 0) recoveryRuns.add(report.runId);
4897
- }
4898
- } catch {}
4899
4659
  const invalid = [];
4900
4660
  if (snapshot.blocked !== void 0) invalid.push({
4901
4661
  id: "state",
@@ -4908,15 +4668,14 @@ async function mergeDoctor(io, home, configuredRoot) {
4908
4668
  const run = runId === void 0 ? "none" : row.state === "pending" ? "awaiting-approval" : "running";
4909
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";
4910
4670
  const receipt = runId === void 0 ? null : await readCommitReceipt(io, home, runId);
4911
- 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);
4912
4672
  const result = validateArtifacts({
4913
4673
  row: artifactRow,
4914
4674
  run,
4915
4675
  staging,
4916
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",
4917
4677
  evidence: receipt === null ? "none" : "receipt",
4918
- activeRuns: run === "running" || run === "awaiting-approval" ? 1 : 0,
4919
- ...runId !== void 0 && recoveryRuns.has(runId) ? { recovery: true } : {}
4678
+ activeRuns: run === "running" || run === "awaiting-approval" ? 1 : 0
4920
4679
  });
4921
4680
  if (!result.ok) invalid.push({
4922
4681
  id: row.id,
@@ -5059,26 +4818,6 @@ async function mergeDoctor(io, home, configuredRoot) {
5059
4818
  status: "ok",
5060
4819
  detail: stagingRoot(home, "<runId>")
5061
4820
  });
5062
- try {
5063
- const raw = await io.readText(join(home, "merge", "recovery.json"));
5064
- if (raw !== null) {
5065
- const reports = JSON.parse(raw);
5066
- const failed = reports.filter((report) => report.ok !== true);
5067
- rows.push({
5068
- key: "recovery",
5069
- label: "提交恢复",
5070
- status: failed.length === 0 ? "ok" : "warn",
5071
- detail: failed.length === 0 ? String(reports.length) + " 条恢复报告,无待处理" : String(failed.length) + " 条待处理:" + failed.map((report) => (report.runId ?? "?") + "=" + (report.action ?? "?")).join(", ")
5072
- });
5073
- }
5074
- } catch {
5075
- rows.push({
5076
- key: "recovery",
5077
- label: "提交恢复",
5078
- status: "unknown",
5079
- detail: "无法读取 merge/recovery.json"
5080
- });
5081
- }
5082
4821
  return rows;
5083
4822
  }
5084
4823
  //#endregion
@@ -5135,6 +4874,48 @@ async function resolveBody(io, home, ref, rowBody) {
5135
4874
  return await rowBody();
5136
4875
  }
5137
4876
  //#endregion
4877
+ //#region lib/types/domain/state-migrate.js
4878
+ /**
4879
+ * Migrate v1/v2/v3 merge state to v4. v4 is a no-op; forward-only/needs-repair are reported
4880
+ * before attempting anything, so a newer state is never overwritten.
4881
+ * @param io - the evolution IO seam.
4882
+ * @param home - the evolution home.
4883
+ * @param fence - optional home write fence used by the host.
4884
+ * @returns the migration outcome.
4885
+ */
4886
+ async function migrateMergeStateToV4(io, home, fence) {
4887
+ const v4 = await readV4Readiness(io, home);
4888
+ if (v4.ok) return {
4889
+ ok: true,
4890
+ migrated: false
4891
+ };
4892
+ if (v4.code === "merge-state-forward-only" || v4.code === "merge-state-needs-repair") return {
4893
+ ok: false,
4894
+ code: v4.code,
4895
+ message: v4.message
4896
+ };
4897
+ const v3 = await ensureMergeStateReady(io, home, fence);
4898
+ if (!v3.ok) return {
4899
+ ok: false,
4900
+ code: v3.code,
4901
+ message: v3.message
4902
+ };
4903
+ const normalized = await normalizePairLedger(io, home);
4904
+ if (!normalized.ok) return {
4905
+ ok: false,
4906
+ code: "merge-state-needs-repair",
4907
+ message: normalized.message ?? "v3->v4 normalize failed"
4908
+ };
4909
+ return normalized.backupRef === void 0 ? {
4910
+ ok: true,
4911
+ migrated: normalized.migrated
4912
+ } : {
4913
+ ok: true,
4914
+ migrated: normalized.migrated,
4915
+ backupRef: normalized.backupRef
4916
+ };
4917
+ }
4918
+ //#endregion
5138
4919
  //#region lib/types/domain/evidence-hash.js
5139
4920
  /** Canonical evidence JSON hash shared with core unit receipts. */
5140
4921
  function canonical(value) {
@@ -5275,13 +5056,6 @@ async function projectRecoveredUnit(options) {
5275
5056
  await options.onPhase?.("stage-drop");
5276
5057
  await dropStage(options.io, options.home, stageRowId);
5277
5058
  }
5278
- await options.onPhase?.("pair-index");
5279
- await recordPair(options.io, options.home, {
5280
- pairKey,
5281
- runId: options.runId,
5282
- result: "merged",
5283
- target
5284
- });
5285
5059
  await options.onPhase?.("run-meta");
5286
5060
  const meta = await readRunMeta(options.io, options.home, options.runId);
5287
5061
  await writeRunMeta(options.io, options.home, {
@@ -5996,9 +5770,6 @@ async function handleReadOnlyMergeCommand(command, io, home, cards, runs, approv
5996
5770
  }
5997
5771
  return formatSnapshotQueue(rows.filter((row) => row.queue === "skipped"), "skipped");
5998
5772
  }
5999
- function mergeCommandHelp() {
6000
- return mergeUsage();
6001
- }
6002
5773
  //#endregion
6003
5774
  //#region lib/types/merge-command.js
6004
5775
  function createReadOnlyMergeService(io, home) {
@@ -6052,10 +5823,6 @@ function createMergeCommand(service, version = MERGE_PACKAGE_VERSION) {
6052
5823
  }
6053
5824
  //#endregion
6054
5825
  //#region lib/types/skipped.js
6055
- /**
6056
- * Skipped filtering: a pair the operator marked as not-worth-merging stays out of automatic
6057
- * screening until mergeSkippedTtlDays expires. Explicit /merge pair ignores this set.
6058
- */
6059
5826
  function skippedPairKeys(rows, ttlDays, now) {
6060
5827
  const keys = /* @__PURE__ */ new Set();
6061
5828
  for (const row of rows) {
@@ -6069,9 +5836,6 @@ function skippedPairKeys(rows, ttlDays, now) {
6069
5836
  }
6070
5837
  return keys;
6071
5838
  }
6072
- function isSkippedPair(keys, a, b) {
6073
- return keys.has(pairKey(a, b));
6074
- }
6075
5839
  //#endregion
6076
5840
  //#region lib/types/domain/engine-criteria.js
6077
5841
  function criteriaFrom(level, verdict, action) {
@@ -6324,11 +6088,6 @@ async function runScan(options) {
6324
6088
  continue;
6325
6089
  }
6326
6090
  row = projected;
6327
- await recordPair(options.io, options.home, {
6328
- pairKey: row.pairKey,
6329
- runId: row.runId ?? "merge",
6330
- result: "kept"
6331
- });
6332
6091
  pairs.push(pairOf(row, candidate, "kept", {
6333
6092
  ...round1.decision === void 0 ? {} : { decision: round1.decision },
6334
6093
  ...round1.usage === void 0 ? {} : { usage: round1.usage }
@@ -6538,15 +6297,7 @@ async function runScan(options) {
6538
6297
  if (await pairLedger(options.io, options.home).transact((tx) => {
6539
6298
  const current = tx.rows().find((item) => item.id === row.id);
6540
6299
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
6541
- })) {
6542
- await dropStage(options.io, options.home, row.id, row.stagingRef);
6543
- await recordPair(options.io, options.home, {
6544
- pairKey: row.pairKey,
6545
- runId: row.runId ?? "merge",
6546
- result: "failed",
6547
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
6548
- });
6549
- }
6300
+ })) await dropStage(options.io, options.home, row.id, row.stagingRef);
6550
6301
  pairs.push(pairOf(row, candidate, "failed", {
6551
6302
  decision: mergeDecision,
6552
6303
  failure: failed,
@@ -7048,12 +6799,22 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7048
6799
  ...outcome.criteria === void 0 ? {} : { criteria: outcome.criteria },
7049
6800
  ...outcome.validationIssues === void 0 ? {} : { validation: { issues: outcome.validationIssues } }
7050
6801
  };
6802
+ const pair = (outcomeName, extra = {}, withDecision = true) => ({
6803
+ id: row.id,
6804
+ pairKey: row.pairKey,
6805
+ a: row.a,
6806
+ b: row.b,
6807
+ queue: row.queue,
6808
+ ...withDecision && row.decision !== void 0 ? { decision: row.decision } : {},
6809
+ outcome: outcomeName,
6810
+ archived: [],
6811
+ ...extra
6812
+ });
7051
6813
  if (outcome.kind === "merged") {
7052
6814
  const unitRef = {
7053
6815
  unitId: outcome.unitId ?? "unknown",
7054
6816
  evidenceSha256: outcome.evidenceSha256 ?? "unknown"
7055
6817
  };
7056
- const target = outcome.target?.actual ?? outcome.target?.requested ?? row.decision?.target;
7057
6818
  const finalize = async () => {
7058
6819
  if (!(await pairLedger(io, home, fence).transact((tx) => {
7059
6820
  const current = tx.rows().find((item) => item.id === row.id);
@@ -7064,50 +6825,28 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7064
6825
  } : tx.remove(row.id, "committed", unitRef, current.version);
7065
6826
  })).ok) throw new Error("row changed before commit record: " + row.id);
7066
6827
  await dropStage(io, home, row.id);
7067
- await recordPair(io, home, {
7068
- pairKey: row.pairKey,
7069
- runId: row.runId ?? "merge",
7070
- result: "merged",
7071
- ...target === void 0 ? {} : { target },
7072
- ...outcome.unitId === void 0 ? {} : { reportRef: outcome.unitId }
7073
- });
7074
6828
  };
7075
6829
  if (deferProjection) return {
7076
6830
  merged: true,
7077
6831
  finalize,
7078
- pair: {
7079
- id: row.id,
7080
- pairKey: row.pairKey,
7081
- a: row.a,
7082
- b: row.b,
7083
- queue: row.queue,
7084
- ...row.decision === void 0 ? {} : { decision: row.decision },
7085
- outcome: "merged",
7086
- archived: outcome.archived ?? [],
6832
+ pair: pair("merged", {
7087
6833
  ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
7088
6834
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
7089
6835
  ...outcome.target === void 0 ? {} : { target: outcome.target },
7090
6836
  ...reportExtra
7091
- }
6837
+ })
7092
6838
  };
7093
6839
  await finalize();
7094
6840
  return {
7095
6841
  merged: true,
7096
- pair: {
7097
- id: row.id,
7098
- pairKey: row.pairKey,
7099
- a: row.a,
7100
- b: row.b,
7101
- queue: row.queue,
7102
- ...row.decision === void 0 ? {} : { decision: row.decision },
7103
- outcome: "merged",
7104
- archived: outcome.archived ?? [],
6842
+ pair: pair("merged", {
6843
+ ...outcome.archived === void 0 ? {} : { archived: outcome.archived },
7105
6844
  ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
7106
6845
  ...reportExtra,
7107
6846
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
7108
6847
  ...outcome.target === void 0 ? {} : { target: outcome.target },
7109
6848
  ...outcome.validationPlan === void 0 ? {} : { validationPlan: outcome.validationPlan }
7110
- }
6849
+ })
7111
6850
  };
7112
6851
  }
7113
6852
  const failure = outcome.failure ?? {
@@ -7130,18 +6869,10 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7130
6869
  })) await dropStage(io, home, row.id);
7131
6870
  return {
7132
6871
  merged: false,
7133
- pair: {
7134
- id: row.id,
7135
- pairKey: row.pairKey,
7136
- a: row.a,
7137
- b: row.b,
7138
- queue: row.queue,
7139
- ...row.decision === void 0 ? {} : { decision: row.decision },
7140
- outcome: "failed",
7141
- archived: [],
6872
+ pair: pair("failed", {
7142
6873
  failure,
7143
6874
  ...reportExtra
7144
- }
6875
+ })
7145
6876
  };
7146
6877
  }
7147
6878
  const cas = casExpiredHook(failure.code);
@@ -7163,33 +6894,17 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7163
6894
  await dropStage(io, home, row.id);
7164
6895
  return {
7165
6896
  merged: false,
7166
- pair: {
7167
- id: row.id,
7168
- pairKey: row.pairKey,
7169
- a: row.a,
7170
- b: row.b,
7171
- queue: row.queue,
7172
- ...row.decision === void 0 ? {} : { decision: row.decision },
7173
- outcome: "failed",
7174
- archived: [],
6897
+ pair: pair("failed", {
7175
6898
  failure: terminalFailure,
7176
6899
  ...reportExtra
7177
- }
6900
+ })
7178
6901
  };
7179
6902
  }
7180
6903
  if (failure.code === "commit-failed" || failure.code === "validation-failed") {
7181
6904
  if (await pairLedger(io, home, fence).transact((tx) => {
7182
6905
  const current = tx.rows().find((item) => item.id === row.id);
7183
6906
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
7184
- })) {
7185
- await dropStage(io, home, row.id, row.stagingRef);
7186
- await recordPair(io, home, {
7187
- pairKey: row.pairKey,
7188
- runId: row.runId ?? "merge",
7189
- result: "failed",
7190
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
7191
- });
7192
- }
6907
+ })) await dropStage(io, home, row.id, row.stagingRef);
7193
6908
  }
7194
6909
  if (failure.code === "source-gone") {
7195
6910
  if (await pairLedger(io, home, fence).transact((tx) => {
@@ -7198,16 +6913,7 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7198
6913
  })) await dropStage(io, home, row.id, row.stagingRef);
7199
6914
  return {
7200
6915
  merged: false,
7201
- pair: {
7202
- id: row.id,
7203
- pairKey: row.pairKey,
7204
- a: row.a,
7205
- b: row.b,
7206
- queue: row.queue,
7207
- outcome: "failed",
7208
- archived: [],
7209
- failure
7210
- }
6916
+ pair: pair("failed", { failure }, false)
7211
6917
  };
7212
6918
  }
7213
6919
  if (await pairLedger(io, home, fence).transact((tx) => {
@@ -7220,42 +6926,21 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
7220
6926
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
7221
6927
  }) === null) return {
7222
6928
  merged: false,
7223
- pair: {
7224
- id: row.id,
7225
- pairKey: row.pairKey,
7226
- a: row.a,
7227
- b: row.b,
7228
- queue: row.queue,
7229
- outcome: "failed",
7230
- archived: [],
6929
+ pair: pair("failed", {
7231
6930
  failure: {
7232
6931
  ...failure,
7233
6932
  code: "row-changed",
7234
6933
  message: "row changed before failure record"
7235
6934
  },
7236
6935
  ...reportExtra
7237
- }
6936
+ }, false)
7238
6937
  };
7239
- await recordPair(io, home, {
7240
- pairKey: row.pairKey,
7241
- runId: row.runId ?? "merge",
7242
- result: "failed",
7243
- ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
7244
- });
7245
6938
  return {
7246
6939
  merged: false,
7247
- pair: {
7248
- id: row.id,
7249
- pairKey: row.pairKey,
7250
- a: row.a,
7251
- b: row.b,
7252
- queue: row.queue,
7253
- ...row.decision === void 0 ? {} : { decision: row.decision },
7254
- outcome: "failed",
7255
- archived: [],
6940
+ pair: pair("failed", {
7256
6941
  failure,
7257
6942
  ...reportExtra
7258
- }
6943
+ })
7259
6944
  };
7260
6945
  }
7261
6946
  //#endregion
@@ -7704,14 +7389,6 @@ async function runUndoSteps(options) {
7704
7389
  };
7705
7390
  }
7706
7391
  if (await readUndoReceipt(options.io, options.home, options.runId, pairKey) !== null) {
7707
- await markPhase("pair-index");
7708
- await options.onPhase?.("pair-index", pairKey);
7709
- await recordPair(options.io, options.home, {
7710
- pairKey,
7711
- runId: options.runId,
7712
- result: "undone",
7713
- target: receipt.target
7714
- });
7715
7392
  await markPhase("run-meta");
7716
7393
  await options.onPhase?.("run-meta", pairKey);
7717
7394
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
@@ -7738,14 +7415,6 @@ async function runUndoSteps(options) {
7738
7415
  restoredSourceHashes: Object.fromEntries(checked.evidence.sources.map((source) => [source.name, contentHash(source.before)])),
7739
7416
  at: options.now()
7740
7417
  });
7741
- await markPhase("pair-index");
7742
- await options.onPhase?.("pair-index", pairKey);
7743
- await recordPair(options.io, options.home, {
7744
- pairKey,
7745
- runId: options.runId,
7746
- result: "undone",
7747
- target: receipt.target
7748
- });
7749
7418
  await markPhase("run-meta");
7750
7419
  await options.onPhase?.("run-meta", pairKey);
7751
7420
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
@@ -7805,13 +7474,6 @@ async function runUndoSteps(options) {
7805
7474
  restoredSourceHashes: Object.fromEntries(checked.evidence.sources.map((source) => [source.name, contentHash(source.before)])),
7806
7475
  at: options.now()
7807
7476
  });
7808
- await options.onPhase?.("pair-index", pairKey);
7809
- await recordPair(options.io, options.home, {
7810
- pairKey,
7811
- runId: options.runId,
7812
- result: "undone",
7813
- target: receipt.target
7814
- });
7815
7477
  await options.onPhase?.("run-meta", pairKey);
7816
7478
  await settleRun(options.io, options.home, options.runId, pairKey, allPairKeys, options.now());
7817
7479
  undone.push(pairKey);
@@ -8042,9 +7704,6 @@ function buildRound2Prompt(row, decision, mode = "continue") {
8042
7704
  ...body
8043
7705
  ] : [...head, ...body]).join("\n");
8044
7706
  }
8045
- function buildStandbyPrompt() {
8046
- return STANDBY_PROMPT;
8047
- }
8048
7707
  //#endregion
8049
7708
  //#region lib/types/channel-port.js
8050
7709
  async function runRound1OverChannel(options, candidate, signal) {
@@ -8552,14 +8211,6 @@ async function commitStagedRun(io, home, runId, port, options) {
8552
8211
  message: "target is blocked by curator: " + assembled.plan.target,
8553
8212
  plan: assembled.plan
8554
8213
  };
8555
- const journal = {
8556
- runId,
8557
- target: assembled.plan.target,
8558
- create: !assembled.plan.targetExists,
8559
- sources: assembled.plan.sources,
8560
- phase: "prepared",
8561
- updatedAt: new Date(options.now()).toISOString()
8562
- };
8563
8214
  let result;
8564
8215
  let evidenceSha256$1;
8565
8216
  let warning;
@@ -8589,9 +8240,6 @@ async function commitStagedRun(io, home, runId, port, options) {
8589
8240
  ...options.sourceRevisions === void 0 ? {} : { sourceRevisions: options.sourceRevisions },
8590
8241
  ...options.supportDigest === void 0 ? {} : { supportDigest: options.supportDigest }
8591
8242
  };
8592
- try {
8593
- await writeJournal(io, home, journal);
8594
- } catch {}
8595
8243
  const prepared = await transaction.prepare(unitPlan);
8596
8244
  if (!prepared.ok) return {
8597
8245
  ok: false,
@@ -8599,21 +8247,12 @@ async function commitStagedRun(io, home, runId, port, options) {
8599
8247
  plan: assembled.plan
8600
8248
  };
8601
8249
  const committed = await transaction.commit(prepared.prepared, options.signal === void 0 ? void 0 : { signal: options.signal });
8602
- if (!committed.ok) {
8603
- try {
8604
- await writeJournal(io, home, {
8605
- ...journal,
8606
- phase: "failed",
8607
- updatedAt: new Date(options.now()).toISOString()
8608
- });
8609
- } catch {}
8610
- return {
8611
- ok: false,
8612
- message: committed.message,
8613
- plan: assembled.plan,
8614
- ...committed.code === "target-changed" || committed.code === "source-changed" || committed.code === "support-changed" ? { code: committed.code } : {}
8615
- };
8616
- }
8250
+ if (!committed.ok) return {
8251
+ ok: false,
8252
+ message: committed.message,
8253
+ plan: assembled.plan,
8254
+ ...committed.code === "target-changed" || committed.code === "source-changed" || committed.code === "support-changed" ? { code: committed.code } : {}
8255
+ };
8617
8256
  const unitId = committed.evidence.unitId;
8618
8257
  if (committed.evidence.unitId !== prepared.prepared.unitId && !prepared.prepared.unitId.startsWith("legacy:")) return {
8619
8258
  ok: false,
@@ -8634,29 +8273,11 @@ async function commitStagedRun(io, home, runId, port, options) {
8634
8273
  mutations: committed.evidence.mutations
8635
8274
  };
8636
8275
  }
8637
- if (!result.ok) {
8638
- try {
8639
- await writeJournal(io, home, {
8640
- ...journal,
8641
- phase: "failed",
8642
- updatedAt: new Date(options.now()).toISOString()
8643
- });
8644
- } catch {}
8645
- return {
8646
- ok: false,
8647
- message: result.message,
8648
- plan: assembled.plan
8649
- };
8650
- }
8651
- const committed = {
8652
- ...journal,
8653
- phase: "committed",
8654
- updatedAt: new Date(options.now()).toISOString(),
8655
- ...result.unitId === void 0 ? {} : { unitId: result.unitId }
8276
+ if (!result.ok) return {
8277
+ ok: false,
8278
+ message: result.message,
8279
+ plan: assembled.plan
8656
8280
  };
8657
- try {
8658
- await writeJournal(io, home, committed);
8659
- } catch {}
8660
8281
  if (result.unitId === void 0) return {
8661
8282
  ok: true,
8662
8283
  plan: assembled.plan,
@@ -8938,32 +8559,38 @@ function missingManifestFile(files, stagedPaths) {
8938
8559
  }
8939
8560
  //#endregion
8940
8561
  //#region lib/types/authoring-flow.js
8941
- async function runAuthoring(options) {
8942
- if (options.target.includes("/") || options.target.includes(":")) return {
8943
- ok: false,
8562
+ /** The target facts both authoring entries may refuse on before writing anything. */
8563
+ async function targetAdmission(target, targetCard, createMode, isBlocked) {
8564
+ if (target.includes("/") || target.includes(":")) return {
8944
8565
  code: "target-invalid",
8945
- message: "target must be a bare skill name: " + options.target
8566
+ message: "target must be a bare skill name: " + target
8946
8567
  };
8947
- const targetCard = options.cards.find((card) => card.name === options.target);
8948
- const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
8949
- const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
8950
- const createMode = mode === "create-umbrella";
8951
8568
  if (!createMode && targetCard !== void 0) {
8952
8569
  if (targetCard.managed !== true) return {
8953
- ok: false,
8954
8570
  code: "target-not-managed",
8955
- message: "target is not family-managed: " + options.target
8571
+ message: "target is not family-managed: " + target
8956
8572
  };
8957
8573
  if (targetCard.protectionUnknown === true || targetCard.protectedBy != null) return {
8958
- ok: false,
8959
8574
  code: "target-protected",
8960
- message: "target is protected (" + (targetCard.protectedBy ?? "unknown") + "): " + options.target
8575
+ message: "target is protected (" + (targetCard.protectedBy ?? "unknown") + "): " + target
8961
8576
  };
8962
8577
  }
8963
- if (!createMode && await options.isBlocked?.(options.target) === true) return {
8964
- ok: false,
8578
+ if (!createMode && await isBlocked?.(target) === true) return {
8965
8579
  code: "target-blocked",
8966
- message: "target is blocked by curator: " + options.target
8580
+ message: "target is blocked by curator: " + target
8581
+ };
8582
+ return null;
8583
+ }
8584
+ async function runAuthoring(options) {
8585
+ const targetCard = options.cards.find((card) => card.name === options.target);
8586
+ const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
8587
+ const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
8588
+ const createMode = mode === "create-umbrella";
8589
+ const targetRefusal = await targetAdmission(options.target, targetCard, createMode, options.isBlocked);
8590
+ if (targetRefusal !== null) return {
8591
+ ok: false,
8592
+ code: targetRefusal.code,
8593
+ message: targetRefusal.message
8967
8594
  };
8968
8595
  const stageKey = options.stageKey ?? options.runId;
8969
8596
  if (options.reuseStaging === true) {
@@ -9285,46 +8912,15 @@ const STALE_CAUSES = new Set([
9285
8912
  "target-gone"
9286
8913
  ]);
9287
8914
  async function runNewChildAuthoring(options, row, decision, signal) {
9288
- if (decision.target.includes("/") || decision.target.includes(":")) return {
9289
- kind: "failed",
9290
- failure: {
9291
- stage: "author",
9292
- code: "target-invalid",
9293
- message: "target must be a bare skill name: " + decision.target,
9294
- retryable: false,
9295
- retryFrom: "none"
9296
- }
9297
- };
9298
8915
  const targetCard = options.cards.find((card) => card.name === decision.target);
9299
8916
  const createMode = decision.mode === "create-umbrella";
9300
- if (!createMode && targetCard !== void 0) {
9301
- if (targetCard.managed !== true) return {
9302
- kind: "failed",
9303
- failure: {
9304
- stage: "author",
9305
- code: "target-not-managed",
9306
- message: "target is not family-managed: " + decision.target,
9307
- retryable: false,
9308
- retryFrom: "none"
9309
- }
9310
- };
9311
- if (targetCard.protectionUnknown === true || targetCard.protectedBy != null) return {
9312
- kind: "failed",
9313
- failure: {
9314
- stage: "author",
9315
- code: "target-protected",
9316
- message: "target is protected: " + decision.target,
9317
- retryable: false,
9318
- retryFrom: "none"
9319
- }
9320
- };
9321
- }
9322
- if (!createMode && await options.isBlocked?.(decision.target) === true) return {
8917
+ const targetRefusal = await targetAdmission(decision.target, targetCard, createMode, options.isBlocked);
8918
+ if (targetRefusal !== null) return {
9323
8919
  kind: "failed",
9324
8920
  failure: {
9325
8921
  stage: "author",
9326
- code: "target-blocked",
9327
- message: "target is blocked by curator: " + decision.target,
8922
+ code: targetRefusal.code,
8923
+ message: targetRefusal.message,
9328
8924
  retryable: false,
9329
8925
  retryFrom: "none"
9330
8926
  }
@@ -10787,143 +10383,6 @@ async function supersedeMergeRuns(registry) {
10787
10383
  return ids.length;
10788
10384
  }
10789
10385
  //#endregion
10790
- //#region lib/types/coordinator.js
10791
- /**
10792
- * Pure merge transition table. No io, no run shell, no subagents.
10793
- *
10794
- * Every transition returns the whole next row (or a drop), so the caller can persist it
10795
- * through one state transaction. Invalid transitions are refused by name;
10796
- * a caller never "repairs" a row by guessing which state the operator meant.
10797
- */
10798
- function invalid(event, state) {
10799
- return {
10800
- ok: false,
10801
- code: "invalid-transition",
10802
- message: "cannot apply " + event.type + " to " + (state ?? "no row")
10803
- };
10804
- }
10805
- /** Drop fields that must not survive a transition; optional fields stay absent. */
10806
- function base(row, at) {
10807
- return {
10808
- id: row.id,
10809
- pairKey: row.pairKey,
10810
- a: row.a,
10811
- b: row.b,
10812
- state: row.state,
10813
- queue: row.queue,
10814
- createdAt: row.createdAt,
10815
- updatedAt: at,
10816
- ...row.runId === void 0 ? {} : { runId: row.runId },
10817
- ...row.unitId === void 0 ? {} : { unitId: row.unitId },
10818
- ...row.origin === void 0 ? {} : { origin: row.origin },
10819
- ...row.decision === void 0 ? {} : { decision: row.decision },
10820
- ...row.sourceRevision === void 0 ? {} : { sourceRevision: row.sourceRevision },
10821
- ...row.targetRevision === void 0 ? {} : { targetRevision: row.targetRevision },
10822
- ...row.attempts === void 0 ? {} : { attempts: row.attempts },
10823
- ...row.stagingRef === void 0 ? {} : { stagingRef: row.stagingRef },
10824
- ...row.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: row.stagingExpiresAt },
10825
- ...row.expired === void 0 ? {} : { expired: row.expired },
10826
- ...row.reason === void 0 ? {} : { reason: row.reason },
10827
- ...row.target === void 0 ? {} : { target: row.target }
10828
- };
10829
- }
10830
- function next(row, at, state, patch = {}) {
10831
- return {
10832
- ok: true,
10833
- row: {
10834
- ...base(row, at),
10835
- state,
10836
- queue: queueOf(state),
10837
- ...patch
10838
- }
10839
- };
10840
- }
10841
- /** Legacy v2 projection only; runtime writes must use transitionV3/RowTx. */
10842
- function transitionLegacyForProjection(current, event) {
10843
- if (event.type === "candidate") {
10844
- if (current !== void 0) return {
10845
- ok: true,
10846
- row: current
10847
- };
10848
- const key = pairKey(event.a, event.b);
10849
- return {
10850
- ok: true,
10851
- row: {
10852
- id: rowIdOf(key),
10853
- pairKey: key,
10854
- a: event.a,
10855
- b: event.b,
10856
- state: "verify",
10857
- queue: "verify",
10858
- createdAt: event.at,
10859
- updatedAt: event.at
10860
- }
10861
- };
10862
- }
10863
- if (event.type === "source-gone") return {
10864
- ok: true,
10865
- row: null
10866
- };
10867
- if (event.type === "gate-keep") {
10868
- if (current !== void 0) return {
10869
- ok: true,
10870
- row: current
10871
- };
10872
- const key = pairKey(event.a, event.b);
10873
- return {
10874
- ok: true,
10875
- row: {
10876
- id: rowIdOf(key),
10877
- pairKey: key,
10878
- a: event.a,
10879
- b: event.b,
10880
- state: "skipped",
10881
- queue: "skipped",
10882
- origin: "agent",
10883
- createdAt: event.at,
10884
- updatedAt: event.at
10885
- }
10886
- };
10887
- }
10888
- if (current === void 0) return invalid(event, void 0);
10889
- switch (event.type) {
10890
- case "round1-keep": return current.state === "verify" ? next(current, event.at, "skipped", { origin: "agent" }) : invalid(event, current.state);
10891
- case "round1-merge":
10892
- if (current.state !== "verify") return invalid(event, current.state);
10893
- return event.approval === "on" ? next(current, event.at, "pending", {
10894
- ...event.decision === void 0 ? {} : { decision: event.decision },
10895
- ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
10896
- ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt },
10897
- ...event.sourceRevision === void 0 ? {} : { sourceRevision: event.sourceRevision },
10898
- ...event.targetRevision === void 0 ? {} : { targetRevision: event.targetRevision }
10899
- }) : next(current, event.at, "approved", {
10900
- ...event.decision === void 0 ? {} : { decision: event.decision },
10901
- ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
10902
- ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt },
10903
- ...event.sourceRevision === void 0 ? {} : { sourceRevision: event.sourceRevision },
10904
- ...event.targetRevision === void 0 ? {} : { targetRevision: event.targetRevision }
10905
- });
10906
- case "round1-fail": return current.state === "verify" ? next(current, event.at, "verify", {
10907
- failure: event.failure,
10908
- attempts: (current.attempts ?? 0) + 1
10909
- }) : invalid(event, current.state);
10910
- case "approve": return current.state === "pending" ? next(current, event.at, "approved", event.runId === void 0 ? {} : { runId: event.runId }) : invalid(event, current.state);
10911
- case "reject": return current.state === "pending" ? next(current, event.at, "skipped", { origin: "human" }) : invalid(event, current.state);
10912
- case "round2-cancel": return current.state === "fuse" || current.state === "approved" ? next(current, event.at, "approved") : invalid(event, current.state);
10913
- case "round2-fail": return current.state === "fuse" || current.state === "approved" ? next(current, event.at, "approved", {
10914
- failure: event.failure,
10915
- attempts: (current.attempts ?? 0) + 1
10916
- }) : invalid(event, current.state);
10917
- case "round2-success": return current.state === "fuse" || current.state === "approved" ? {
10918
- ok: true,
10919
- row: null
10920
- } : invalid(event, current.state);
10921
- case "target-taken":
10922
- 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);
10923
- case "restore": return current.state === "skipped" ? next(current, event.at, "verify") : invalid(event, current.state);
10924
- }
10925
- }
10926
- //#endregion
10927
10386
  //#region lib/types/pair-command.js
10928
10387
  async function runPairCommand(options) {
10929
10388
  return await runScanCommand({
@@ -11044,37 +10503,27 @@ function apply(ctx, rawConfig = {}) {
11044
10503
  if (registry === void 0) return;
11045
10504
  const io = evolutionIoAdapter(() => registry.provider());
11046
10505
  const home = evolutionHome();
10506
+ const homeFence = ctx.get("evolutionMutationAuthority")?.homeFence;
11047
10507
  const ready = (async () => {
11048
- const v4 = await readV4Readiness(io, home);
11049
- if (v4.ok) return {
10508
+ const migration = await migrateMergeStateToV4(io, home, homeFence);
10509
+ if (!migration.ok) return migration;
10510
+ const loaded = await loadMergeState(io, home);
10511
+ return {
11050
10512
  ok: true,
11051
10513
  schema: 3,
11052
- migrated: false,
10514
+ migrated: migration.migrated,
11053
10515
  state: {
11054
10516
  schemaVersion: 3,
11055
- rows: (await loadMergeState(io, home)).state.rows.map(legacyToV3).filter((row) => row !== null)
10517
+ rows: loaded.state.rows.map(legacyToV3).filter((row) => row !== null)
11056
10518
  },
11057
10519
  quarantine: [],
11058
10520
  report: null
11059
10521
  };
11060
- const state = await ensureMergeStateReady(io, home, ctx.get("evolutionMutationAuthority")?.homeFence);
11061
- if (!state.ok) return v4.code === "merge-state-forward-only" || v4.code === "merge-state-needs-repair" ? {
11062
- ok: false,
11063
- code: v4.code,
11064
- message: v4.message
11065
- } : state;
11066
- const normalized = await normalizePairLedger(io, home);
11067
- return normalized.ok ? state : {
11068
- ok: false,
11069
- code: "merge-state-needs-repair",
11070
- message: normalized.message ?? "v4 normalize failed"
11071
- };
11072
10522
  })().catch((error) => ({
11073
10523
  ok: false,
11074
10524
  code: "merge-state-migration-blocked",
11075
10525
  message: error instanceof Error ? error.message : String(error)
11076
10526
  }));
11077
- const homeFence = ctx.get("evolutionMutationAuthority")?.homeFence;
11078
10527
  const runs = newRunRegistry({
11079
10528
  io,
11080
10529
  home,
@@ -11386,4 +10835,4 @@ function apply(ctx, rawConfig = {}) {
11386
10835
  });
11387
10836
  }
11388
10837
  //#endregion
11389
- 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, 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, 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, transitionLegacyForProjection, undoMergeReceipt, unitFor, validateStagedTree, withMergeRun, writeCommitReceipt, writeMergeReport, writeStagedFile };
10838
+ 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 };