@lmzhen/dsh-evolution-merge 0.30.23 → 0.30.25

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 (82) hide show
  1. package/lib/index.js +518 -182
  2. package/lib/types/approve-command.js +2 -1
  3. package/lib/types/authoring-flow.d.ts +5 -0
  4. package/lib/types/authoring-flow.js +8 -5
  5. package/lib/types/commands.d.ts +7 -0
  6. package/lib/types/commands.js +8 -1
  7. package/lib/types/commit-execute.d.ts +4 -0
  8. package/lib/types/commit-execute.js +8 -5
  9. package/lib/types/commit-receipt.js +3 -1
  10. package/lib/types/contracts.d.ts +20 -1
  11. package/lib/types/doctor.js +1 -1
  12. package/lib/types/domain/errors.js +5 -1
  13. package/lib/types/domain/merge-snapshot.d.ts +12 -3
  14. package/lib/types/domain/merge-snapshot.js +9 -4
  15. package/lib/types/domain/reconcile.js +1 -1
  16. package/lib/types/domain/row-v4.d.ts +4 -0
  17. package/lib/types/domain/row-v4.js +9 -9
  18. package/lib/types/domain/run-coordinator.d.ts +9 -0
  19. package/lib/types/domain/stage-aggregate.d.ts +6 -1
  20. package/lib/types/domain/stage-aggregate.js +16 -9
  21. package/lib/types/domain/validation-plan.js +5 -1
  22. package/lib/types/index.js +14 -2
  23. package/lib/types/kernel/adapter-v4.d.ts +12 -0
  24. package/lib/types/kernel/adapter-v4.js +54 -0
  25. package/lib/types/kernel/failure.d.ts +8 -0
  26. package/lib/types/kernel/failure.js +33 -0
  27. package/lib/types/kernel/index.d.ts +9 -0
  28. package/lib/types/kernel/index.js +9 -0
  29. package/lib/types/kernel/invariants.d.ts +4 -0
  30. package/lib/types/kernel/invariants.js +47 -0
  31. package/lib/types/kernel/ledger.d.ts +46 -0
  32. package/lib/types/kernel/ledger.js +62 -0
  33. package/lib/types/kernel/machine.d.ts +13 -0
  34. package/lib/types/kernel/machine.js +108 -0
  35. package/lib/types/kernel/model.d.ts +154 -0
  36. package/lib/types/kernel/model.js +5 -0
  37. package/lib/types/kernel/projection.d.ts +28 -0
  38. package/lib/types/kernel/projection.js +44 -0
  39. package/lib/types/kernel/recovery.d.ts +31 -0
  40. package/lib/types/kernel/recovery.js +19 -0
  41. package/lib/types/kernel/unit.d.ts +43 -0
  42. package/lib/types/kernel/unit.js +63 -0
  43. package/lib/types/merge-routes.js +9 -0
  44. package/lib/types/new-child-authoring.d.ts +1 -0
  45. package/lib/types/new-child-authoring.js +15 -7
  46. package/lib/types/pending-actions.d.ts +1 -1
  47. package/lib/types/pending-actions.js +4 -0
  48. package/lib/types/recovery-coordinator.d.ts +2 -0
  49. package/lib/types/recovery-coordinator.js +14 -4
  50. package/lib/types/recovery-projection.d.ts +4 -2
  51. package/lib/types/recovery-projection.js +3 -3
  52. package/lib/types/resume-command.js +2 -1
  53. package/lib/types/round2-outcome.js +26 -9
  54. package/lib/types/run-shell.d.ts +1 -0
  55. package/lib/types/run-shell.js +57 -15
  56. package/lib/types/runtime.d.ts +4 -1
  57. package/lib/types/runtime.js +34 -22
  58. package/lib/types/scan-command.d.ts +1 -0
  59. package/lib/types/scan-command.js +1 -0
  60. package/lib/types/scan-ports.js +3 -0
  61. package/lib/types/scan-run.d.ts +6 -1
  62. package/lib/types/scan-run.js +34 -18
  63. package/lib/types/surface/merge-view.js +12 -4
  64. package/lib/types/undo-operation.d.ts +2 -1
  65. package/lib/types/undo-operation.js +10 -4
  66. package/lib/types/wire-projection.js +7 -2
  67. package/lib/types/wire.d.ts +1 -0
  68. package/package.json +4 -4
  69. package/lib/types/adapters/model.d.ts +0 -31
  70. package/lib/types/adapters/model.js +0 -47
  71. package/lib/types/gate-cache.d.ts +0 -12
  72. package/lib/types/gate-cache.js +0 -36
  73. package/lib/types/gate-filter.d.ts +0 -31
  74. package/lib/types/gate-filter.js +0 -64
  75. package/lib/types/ladder.d.ts +0 -65
  76. package/lib/types/ladder.js +0 -162
  77. package/lib/types/propose.d.ts +0 -26
  78. package/lib/types/propose.js +0 -69
  79. package/lib/types/screening-port.d.ts +0 -47
  80. package/lib/types/screening-port.js +0 -93
  81. package/lib/types/screening.d.ts +0 -30
  82. package/lib/types/screening.js +0 -75
package/lib/index.js CHANGED
@@ -596,7 +596,11 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
596
596
  };
597
597
  if (event.type === "staged") return {
598
598
  ok: true,
599
- row: advance(row, at, { state: "fuse" }),
599
+ row: advance(row, at, {
600
+ state: "fuse",
601
+ ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
602
+ ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt }
603
+ }),
600
604
  version: expectedVersion + 1
601
605
  };
602
606
  if (event.type === "skipped") return {
@@ -631,7 +635,11 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
631
635
  case "approved":
632
636
  if (event.type === "staged") return {
633
637
  ok: true,
634
- row: advance(row, at, { state: "fuse" }),
638
+ row: advance(row, at, {
639
+ state: "fuse",
640
+ ...event.stagingRef === void 0 ? {} : { stagingRef: event.stagingRef },
641
+ ...event.stagingExpiresAt === void 0 ? {} : { stagingExpiresAt: event.stagingExpiresAt }
642
+ }),
635
643
  version: expectedVersion + 1
636
644
  };
637
645
  if (event.type === "committed") return {
@@ -648,7 +656,7 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
648
656
  }),
649
657
  version: expectedVersion + 1
650
658
  };
651
- if (event.type === "failed") return event.failure.retryable ? {
659
+ if (event.type === "failed") return {
652
660
  ok: true,
653
661
  row: advance(row, at, {
654
662
  state: "fuse",
@@ -656,15 +664,6 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
656
664
  attempts: row.attempts + 1
657
665
  }),
658
666
  version: expectedVersion + 1
659
- } : {
660
- ok: true,
661
- row: advance(row, at, {
662
- state: "skipped",
663
- reason: event.failure.code,
664
- failure: event.failure,
665
- attempts: row.attempts + 1
666
- }),
667
- version: expectedVersion + 1
668
667
  };
669
668
  if (event.type === "superseded") return {
670
669
  ok: true,
@@ -687,7 +686,7 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
687
686
  }),
688
687
  version: expectedVersion + 1
689
688
  };
690
- if (event.type === "failed") return event.failure.retryable ? {
689
+ if (event.type === "failed") return {
691
690
  ok: true,
692
691
  row: advance(row, at, {
693
692
  state: "fuse",
@@ -695,15 +694,6 @@ function transitionV4(row, event, expectedVersion, at = (/* @__PURE__ */ new Dat
695
694
  attempts: row.attempts + 1
696
695
  }),
697
696
  version: expectedVersion + 1
698
- } : {
699
- ok: true,
700
- row: advance(row, at, {
701
- state: "skipped",
702
- reason: event.failure.code,
703
- failure: event.failure,
704
- attempts: row.attempts + 1
705
- }),
706
- version: expectedVersion + 1
707
697
  };
708
698
  if (event.type === "superseded") return {
709
699
  ok: true,
@@ -794,7 +784,14 @@ function v3ToV4(row) {
794
784
  target: row.target,
795
785
  decision: row.decision,
796
786
  ...row.sourceRevision === void 0 ? {} : { sourceRevision: row.sourceRevision },
797
- ...row.runId === void 0 ? {} : { runId: row.runId }
787
+ ...row.runId === void 0 ? {} : { runId: row.runId },
788
+ ...row.staging?.kind === "present" ? {
789
+ stagingRef: row.staging.ref,
790
+ stagingExpiresAt: row.staging.expiresAt
791
+ } : row.staging?.kind === "expired" && row.staging.ref !== void 0 ? {
792
+ stagingRef: row.staging.ref,
793
+ stagingExpiresAt: row.staging.expiresAt
794
+ } : {}
798
795
  };
799
796
  return {
800
797
  ...base,
@@ -870,6 +867,8 @@ function v4ToLegacy(row) {
870
867
  ...(row.state === "pending" || row.state === "approved" || row.state === "fuse") && row.sourceRevision !== void 0 ? { sourceRevision: row.sourceRevision } : {},
871
868
  ...(row.state === "pending" || row.state === "approved" || row.state === "fuse") && row.targetRevision !== void 0 ? { targetRevision: row.targetRevision } : {},
872
869
  ...row.runId === void 0 ? {} : { runId: row.runId },
870
+ ...row.state === "fuse" && row.stagingRef !== void 0 ? { stagingRef: row.stagingRef } : {},
871
+ ...row.state === "fuse" && row.stagingExpiresAt !== void 0 ? { stagingExpiresAt: row.stagingExpiresAt } : {},
873
872
  ...row.origin === void 0 ? {} : { origin: row.origin },
874
873
  ...row.attempts === void 0 ? {} : { attempts: row.attempts },
875
874
  createdAt: row.createdAt,
@@ -2311,9 +2310,13 @@ async function presentStage(io, home, entry) {
2311
2310
  state: "present"
2312
2311
  });
2313
2312
  }
2314
- async function dropStage(io, home, rowId, ref) {
2315
- await io.remove(join(home, "merge", "staging-index", encodeURIComponent(rowId) + ".json")).catch(() => {});
2316
- if (ref !== void 0 && ref !== "") await io.remove(join(home, "merge", "runs", ref)).catch(() => {});
2313
+ async function dropStage(io, home, rowId, ref, onError) {
2314
+ const index = join(home, "merge", "staging-index", encodeURIComponent(rowId) + ".json");
2315
+ await io.remove(index).catch((error) => onError?.(index, error));
2316
+ if (ref !== void 0 && ref !== "") {
2317
+ const tree = join(home, "merge", "runs", ref);
2318
+ await io.remove(tree).catch((error) => onError?.(tree, error));
2319
+ }
2317
2320
  }
2318
2321
  /** Bounded cleanup: reserved/present TTL, pending/verify stale index removal, terminal/absent rows, orphan trees. */
2319
2322
  async function cleanupStage(io, home, now, rows, limit = 100) {
@@ -2321,6 +2324,13 @@ async function cleanupStage(io, home, now, rows, limit = 100) {
2321
2324
  const expired = [];
2322
2325
  const kept = [];
2323
2326
  const orphans = [];
2327
+ const failures = [];
2328
+ const fail = (path, error) => {
2329
+ failures.push({
2330
+ path,
2331
+ message: error instanceof Error ? error.message : String(error)
2332
+ });
2333
+ };
2324
2334
  const refs = /* @__PURE__ */ new Set();
2325
2335
  const indexRoot = join(home, "merge", "staging-index");
2326
2336
  const names = await io.exists(indexRoot) ? await listOrEmpty(io, indexRoot) : [];
@@ -2338,7 +2348,7 @@ async function cleanupStage(io, home, now, rows, limit = 100) {
2338
2348
  if (entry.ref !== "") refs.add(entry.ref);
2339
2349
  const row = rows.get(entry.rowId);
2340
2350
  if (row === void 0 || row.state === "skipped" || row?.state === "pending" || row?.state === "verify") {
2341
- await dropStage(io, home, entry.rowId, entry.ref);
2351
+ await dropStage(io, home, entry.rowId, entry.ref, fail);
2342
2352
  removed.push(entry.rowId);
2343
2353
  continue;
2344
2354
  }
@@ -2353,11 +2363,13 @@ async function cleanupStage(io, home, now, rows, limit = 100) {
2353
2363
  createdAt: typeof entry.createdAt === "number" ? entry.createdAt : now,
2354
2364
  state: "expired"
2355
2365
  });
2356
- } catch {}
2366
+ } catch (error) {
2367
+ fail(join(indexRoot, name), error);
2368
+ }
2357
2369
  expired.push(entry.rowId);
2358
2370
  continue;
2359
2371
  }
2360
- await dropStage(io, home, entry.rowId, entry.ref);
2372
+ await dropStage(io, home, entry.rowId, entry.ref, fail);
2361
2373
  removed.push(entry.rowId);
2362
2374
  continue;
2363
2375
  }
@@ -2368,14 +2380,15 @@ async function cleanupStage(io, home, now, rows, limit = 100) {
2368
2380
  for (const run of runNames.slice(0, limit)) {
2369
2381
  const prefix = run + "/";
2370
2382
  if ([...refs].some((ref) => ref === run || ref.startsWith(prefix))) continue;
2371
- await io.remove(join(runRoot, run)).catch(() => {});
2383
+ await io.remove(join(runRoot, run)).catch((error) => fail(join(runRoot, run), error));
2372
2384
  orphans.push(run);
2373
2385
  }
2374
2386
  return {
2375
2387
  removed,
2376
2388
  expired,
2377
2389
  kept,
2378
- orphans
2390
+ orphans,
2391
+ failures
2379
2392
  };
2380
2393
  }
2381
2394
  /** Read all production v2 stage entries. */
@@ -2685,7 +2698,8 @@ async function writeCommitReceipt(io, home, receipt) {
2685
2698
  const path = commitReceiptPath(home, receipt.runId);
2686
2699
  await io.writeText(path, JSON.stringify(receipt, null, 2) + "\n");
2687
2700
  const parent = receipt.parentRunId ?? receipt.runId.split("/pairs/")[0] ?? receipt.runId;
2688
- await io.writeText(durableReceiptPath(home, parent, receipt.runId), JSON.stringify({
2701
+ const leaf = (receipt.runId + "-" + receipt.unitId).replace(/[^A-Za-z0-9._-]+/g, "-");
2702
+ await io.writeText(durableReceiptPath(home, parent, leaf), JSON.stringify({
2689
2703
  ...receipt,
2690
2704
  parentRunId: parent
2691
2705
  }, null, 2) + "\n");
@@ -2863,6 +2877,10 @@ const emptyCounts = {
2863
2877
  const queueOf$3 = (state) => state === "approved" || state === "fuse" ? "fuse" : state === "verify" ? "verify" : state === "pending" ? "pending" : "skipped";
2864
2878
  async function readMergeSnapshot(options) {
2865
2879
  const readAt = (options.now ?? (() => Date.now()))();
2880
+ const hostOnly = options.hostOnly === void 0 ? void 0 : await options.hostOnly().catch((error) => ({
2881
+ code: "E-323",
2882
+ detail: error instanceof Error ? error.message : String(error)
2883
+ }));
2866
2884
  const readiness = await readV4Readiness(options.io, options.home);
2867
2885
  if (!readiness.ok) return {
2868
2886
  wireVersion: 1,
@@ -2870,6 +2888,7 @@ async function readMergeSnapshot(options) {
2870
2888
  readAt,
2871
2889
  queueRevision: "blocked",
2872
2890
  consistency: "eventual",
2891
+ ...hostOnly === void 0 ? {} : { hostOnly },
2873
2892
  blocked: {
2874
2893
  code: readiness.code,
2875
2894
  message: readiness.message,
@@ -2908,6 +2927,7 @@ async function readMergeSnapshot(options) {
2908
2927
  for (const row of ledger?.rows ?? []) {
2909
2928
  const stage = stages.find((item) => item.rowId === row.id) ?? null;
2910
2929
  const queue = queueOf$3(row.state);
2930
+ const bSide = live !== void 0 && live.managed(row.b) ? "live" : "archive";
2911
2931
  const bodies = [{
2912
2932
  rowId: row.id,
2913
2933
  name: row.a,
@@ -2915,9 +2935,9 @@ async function readMergeSnapshot(options) {
2915
2935
  }, {
2916
2936
  rowId: row.id,
2917
2937
  name: row.b,
2918
- side: "archive"
2938
+ side: bSide
2919
2939
  }];
2920
- const expiredKind = persistedExpiry(row) ?? pendingExpiry(row, live);
2940
+ const expiredKind = row.state === "skipped" && row.expiredAt !== void 0 ? "skipped-ttl" : persistedExpiry(row) ?? pendingExpiry(row, live);
2921
2941
  rows.push({
2922
2942
  row,
2923
2943
  stage,
@@ -3020,6 +3040,7 @@ async function readMergeSnapshot(options) {
3020
3040
  readAt,
3021
3041
  queueRevision,
3022
3042
  consistency: "eventual",
3043
+ ...hostOnly === void 0 ? {} : { hostOnly },
3023
3044
  rows,
3024
3045
  stages,
3025
3046
  runs,
@@ -3345,7 +3366,13 @@ async function reconcileMergeState(options) {
3345
3366
  if (current === void 0 || current.state !== "approved") continue;
3346
3367
  const entry = byRow.get(row.id);
3347
3368
  if (usable.get(row.id) === true) {
3348
- if (tx.transition(row.id, { type: "staged" }, current.version).ok) {
3369
+ if (tx.transition(row.id, {
3370
+ type: "staged",
3371
+ ...entry === void 0 ? {} : {
3372
+ stagingRef: entry.ref,
3373
+ stagingExpiresAt: entry.expiresAt
3374
+ }
3375
+ }, current.version).ok) {
3349
3376
  actions.push({
3350
3377
  kind: "approved-fused",
3351
3378
  rowId: row.id,
@@ -3577,12 +3604,16 @@ function actions(row) {
3577
3604
  renderAction("reject", context, true, true),
3578
3605
  renderAction("discard", context, true, true)
3579
3606
  ];
3580
- if (row.row.state === "approved" || row.row.state === "fuse") return [
3581
- renderAction("retry-resume", context, true, true),
3582
- renderAction("discard", context, true, true),
3583
- renderAction("status", context)
3584
- ];
3607
+ if (row.row.state === "approved" || row.row.state === "fuse") {
3608
+ if (row.row.failure !== void 0 && row.row.failure.retryable === false) return [renderAction("pair", context, true, true), renderAction("discard", context, true, true)];
3609
+ return [
3610
+ renderAction("retry-resume", context, true, true),
3611
+ renderAction("discard", context, true, true),
3612
+ renderAction("status", context)
3613
+ ];
3614
+ }
3585
3615
  if (row.row.state === "skipped") return [renderAction("restore", context, true, true)];
3616
+ if (row.row.state === "verify") return [renderAction("retry-candidates", context)];
3586
3617
  return [renderAction("pair", context)];
3587
3618
  }
3588
3619
  function verifyState(row) {
@@ -3627,7 +3658,10 @@ function toRowFact$1(row) {
3627
3658
  b: state.b,
3628
3659
  ...target === void 0 ? {} : { target },
3629
3660
  ...summary === void 0 ? {} : { summary },
3630
- ...state.state === "skipped" ? { reason: state.reason } : {},
3661
+ ...state.state === "skipped" ? {
3662
+ reason: state.reason,
3663
+ reasonCode: state.reason
3664
+ } : {},
3631
3665
  ...state.origin === void 0 ? {} : { origin: state.origin },
3632
3666
  ...state.failure === void 0 ? {} : { failure: {
3633
3667
  stage: state.failure.stage,
@@ -3648,7 +3682,7 @@ function toRowFact$1(row) {
3648
3682
  }
3649
3683
  function runView(run, readAt, includeResultRef = false) {
3650
3684
  const record = run.record;
3651
- const state = run.meta?.phase === "undone" ? "undone" : run.meta?.approval === "pending" ? "awaiting-approval" : record.state;
3685
+ const state = run.meta?.phase === "undone" ? "undone" : run.meta?.stopReason === "interrupted" ? "interrupted" : run.meta?.approval === "pending" ? "awaiting-approval" : record.state;
3652
3686
  const stopReason = run.meta?.stopReason ?? (record.state === "cancelled" ? "cancelled" : record.state === "failed" ? "failed" : record.state === "succeeded" ? "completed" : void 0);
3653
3687
  return {
3654
3688
  wireVersion: 1,
@@ -3807,6 +3841,7 @@ function buildDoctorView(snapshot) {
3807
3841
  ...snapshot.runs[0] === void 0 ? {} : { latestRun: runView(snapshot.runs[0], snapshot.readAt) },
3808
3842
  staging: snapshot.doctor.staging,
3809
3843
  approval: snapshot.approval,
3844
+ ...snapshot.hostOnly === void 0 ? {} : { hostOnly: snapshot.hostOnly },
3810
3845
  ...snapshot.blocked === void 0 ? {} : { blocked: {
3811
3846
  code: snapshot.blocked.code,
3812
3847
  message: snapshot.blocked.message
@@ -4124,7 +4159,7 @@ async function mergeDoctor(io, home, configuredRoot) {
4124
4159
  key: "state",
4125
4160
  label: "合并状态",
4126
4161
  status: snapshot.blocked === void 0 || !hasState ? "ok" : "warn",
4127
- 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) : snapshot.blocked.code + ": " + snapshot.blocked.message
4162
+ 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
4128
4163
  });
4129
4164
  const recoveryRuns = /* @__PURE__ */ new Set();
4130
4165
  try {
@@ -4323,7 +4358,7 @@ async function mergeDoctor(io, home, configuredRoot) {
4323
4358
  //#endregion
4324
4359
  //#region lib/types/domain/errors.js
4325
4360
  function redact(text) {
4326
- return text.replace(/sk-[A-Za-z0-9_-]{16,}/g, "[redacted]").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/(api[_-]?key\s*[:=]\s*)\S+/gi, "$1[redacted]");
4361
+ return text.replace(/sk-[A-Za-z0-9_-]{16,}/g, "[redacted]").replace(/ghp_[A-Za-z0-9]{20,}/g, "[redacted]").replace(/github_pat_[A-Za-z0-9_]{20,}/g, "[redacted]").replace(/xox[baprs]-[A-Za-z0-9-]{10,}/g, "[redacted]").replace(/AKIA[0-9A-Z]{16}/g, "[redacted]").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/((?:api[_-]?key|token|password|secret)\s*[:=]\s*)\S+/gi, "$1[redacted]");
4327
4362
  }
4328
4363
  /** Build the typed failure every command/CLI/runtime branch returns. */
4329
4364
  function commandError(stage, code, hint, next, runId) {
@@ -4452,7 +4487,7 @@ async function projectRecoveredUnit(options) {
4452
4487
  command: "approve",
4453
4488
  startedAt: at,
4454
4489
  endedAt: at,
4455
- stopReason: "completed",
4490
+ stopReason: options.interrupted === true ? "interrupted" : "completed",
4456
4491
  approval: {
4457
4492
  platformWrite: false,
4458
4493
  mergeApproval: true
@@ -4526,17 +4561,21 @@ async function projectRecoveredUnit(options) {
4526
4561
  await writeRunMeta(options.io, options.home, {
4527
4562
  version: 1,
4528
4563
  runId: options.runId,
4564
+ ...meta?.pid === void 0 ? {} : { pid: meta.pid },
4529
4565
  phase: "settled",
4566
+ ...options.interrupted === true ? { stopReason: "interrupted" } : {},
4530
4567
  reportRef: reportPath,
4531
4568
  pairCount: 1,
4532
4569
  ...meta?.parentRunId === void 0 ? {} : { parentRunId: meta.parentRunId },
4570
+ ...meta?.stagingRef === void 0 ? {} : { stagingRef: meta.stagingRef },
4571
+ ...meta?.pairs === void 0 ? {} : { pairs: meta.pairs },
4533
4572
  createdAt: meta?.createdAt ?? new Date(at).toISOString(),
4534
4573
  updatedAt: new Date(at).toISOString()
4535
4574
  });
4536
4575
  await options.onPhase?.("settle");
4537
4576
  if (options.runs !== void 0) {
4538
4577
  const outcome = {
4539
- state: "succeeded",
4578
+ state: options.interrupted === true ? "interrupted" : "succeeded",
4540
4579
  resultRef: reportPath
4541
4580
  };
4542
4581
  if (!(options.runs.settleRecovered === void 0 ? false : await options.runs.settleRecovered(options.runId, outcome))) await options.runs.settle(options.runId, outcome);
@@ -4548,6 +4587,24 @@ async function projectRecoveredUnit(options) {
4548
4587
  };
4549
4588
  }
4550
4589
  //#endregion
4590
+ //#region lib/types/domain/undo-receipt.js
4591
+ /** Undo receipt: immutable per-pair settlement, separate from the commit receipt. */
4592
+ function undoReceiptPath(home, runId, pairKey) {
4593
+ return join(home, "merge", "undo-receipts", encodeURIComponent(runId), createHash("sha256").update(pairKey).digest("hex") + ".json");
4594
+ }
4595
+ async function readUndoReceipt(io, home, runId, pairKey) {
4596
+ const raw = await readTextOrNull(io, undoReceiptPath(home, runId, pairKey));
4597
+ if (raw === null) return null;
4598
+ try {
4599
+ return JSON.parse(raw);
4600
+ } catch {
4601
+ return null;
4602
+ }
4603
+ }
4604
+ async function writeUndoReceipt(io, home, receipt) {
4605
+ await io.writeText(undoReceiptPath(home, receipt.runId, receipt.pairKey), JSON.stringify(receipt, null, 2) + "\n");
4606
+ }
4607
+ //#endregion
4551
4608
  //#region lib/types/recovery-coordinator.js
4552
4609
  /** Batch 2c unit-journal recovery coordinator. UnitTransaction remains the only skill writer. */
4553
4610
  const branchOf = (phase) => {
@@ -4584,6 +4641,20 @@ async function recoverCommitUnits(options) {
4584
4641
  continue;
4585
4642
  }
4586
4643
  const evidence = await readUnitEvidence(options.io, options.home, journal.unitId);
4644
+ const receipt = (await readCommitReceipts(options.io, options.home)).find((item) => item.unitId === journal.unitId);
4645
+ const undoRunId = receipt?.parentRunId ?? journal.runId;
4646
+ if (receipt?.pairKey !== void 0 && await readUndoReceipt(options.io, options.home, undoRunId, receipt.pairKey) !== null) {
4647
+ reports.push({
4648
+ unitId: journal.unitId,
4649
+ runId: journal.runId,
4650
+ phase: journal.phase,
4651
+ branch: branchOf(journal.phase),
4652
+ action: "none",
4653
+ ok: true,
4654
+ code: "undo-recorded"
4655
+ });
4656
+ continue;
4657
+ }
4587
4658
  const branch = branchOf(journal.phase);
4588
4659
  if (journal.phase === "committed" || evidence !== null || journal.phase === "unit-written" || journal.phase === "sources-archived") {
4589
4660
  const result = await options.unit.finalize(journal.unitId);
@@ -4597,6 +4668,7 @@ async function recoverCommitUnits(options) {
4597
4668
  unitId: journal.unitId,
4598
4669
  evidence: result.evidence,
4599
4670
  now: options.now,
4671
+ ...branch === "D" ? { interrupted: true } : {},
4600
4672
  ...options.runs === void 0 ? {} : { runs: options.runs },
4601
4673
  ...options.onProjectionPhase === void 0 ? {} : { onPhase: options.onProjectionPhase }
4602
4674
  });
@@ -4615,7 +4687,8 @@ async function recoverCommitUnits(options) {
4615
4687
  action: "blocked",
4616
4688
  ok: false,
4617
4689
  code: "projection-failed",
4618
- message: projection.message
4690
+ message: projection.message,
4691
+ ...branch === "D" ? { state: "interrupted" } : {}
4619
4692
  });
4620
4693
  else reports.push({
4621
4694
  unitId: journal.unitId,
@@ -4624,7 +4697,8 @@ async function recoverCommitUnits(options) {
4624
4697
  branch,
4625
4698
  action: "finalize",
4626
4699
  ok: true,
4627
- evidenceSha256: evidenceSha256(result.evidence)
4700
+ evidenceSha256: evidenceSha256(result.evidence),
4701
+ ...branch === "D" ? { state: "interrupted" } : {}
4628
4702
  });
4629
4703
  continue;
4630
4704
  }
@@ -4636,7 +4710,8 @@ async function recoverCommitUnits(options) {
4636
4710
  action: "finalize",
4637
4711
  ok: false,
4638
4712
  code: result.code,
4639
- message: result.message
4713
+ message: result.message,
4714
+ ...branch === "D" ? { state: "interrupted" } : {}
4640
4715
  });
4641
4716
  continue;
4642
4717
  }
@@ -4838,8 +4913,31 @@ function actionRefs(row) {
4838
4913
  action("reject", true),
4839
4914
  action("discard", true)
4840
4915
  ];
4841
- if (row.state === "approved" || row.state === "fuse") return [action("discard", true), action("status")];
4916
+ if (row.state === "approved" || row.state === "fuse") {
4917
+ if (row.failure !== void 0 && row.failure.retryable === false) return [{
4918
+ id: "pair",
4919
+ command: "/evolution merge pair " + row.a + " " + row.b,
4920
+ confirm: true,
4921
+ enabled: true
4922
+ }, action("discard", true)];
4923
+ return [
4924
+ {
4925
+ id: "retry-resume",
4926
+ command: "/evolution merge resume --retry " + row.id,
4927
+ confirm: true,
4928
+ enabled: true
4929
+ },
4930
+ action("discard", true),
4931
+ action("status")
4932
+ ];
4933
+ }
4842
4934
  if (row.state === "skipped") return [action("restore", true)];
4935
+ if (row.state === "verify") return [{
4936
+ id: "retry-candidates",
4937
+ command: "/evolution merge candidates --retry " + row.id,
4938
+ confirm: false,
4939
+ enabled: true
4940
+ }];
4843
4941
  return [action("pair")];
4844
4942
  }
4845
4943
  function toRowFact(row, actions = actionRefs(row)) {
@@ -4945,6 +5043,11 @@ function makeMergeRoutes(deps, _audit) {
4945
5043
  code: "merge-state-blocked",
4946
5044
  readiness
4947
5045
  });
5046
+ const hostOnly = (snapshot) => snapshot.hostOnly === void 0 ? null : json(503, {
5047
+ wireVersion: 1,
5048
+ code: "E-323",
5049
+ message: snapshot.hostOnly.detail ?? "host data plane unavailable"
5050
+ });
4948
5051
  const notReady = async (source) => {
4949
5052
  if (source.readiness === void 0) return null;
4950
5053
  const readiness = await source.readiness();
@@ -4974,6 +5077,8 @@ function makeMergeRoutes(deps, _audit) {
4974
5077
  code: "merge-state-blocked",
4975
5078
  readiness: snapshot.blocked.readiness
4976
5079
  });
5080
+ const capability = hostOnly(snapshot);
5081
+ if (capability !== null) return capability;
4977
5082
  return json(200, buildFacts$1(snapshot));
4978
5083
  }
4979
5084
  const readiness = await notReady(source);
@@ -5009,6 +5114,8 @@ function makeMergeRoutes(deps, _audit) {
5009
5114
  code: "merge-state-blocked",
5010
5115
  readiness: snapshot.blocked.readiness
5011
5116
  });
5117
+ const capability = hostOnly(snapshot);
5118
+ if (capability !== null) return capability;
5012
5119
  try {
5013
5120
  return json(200, buildQueuePage(snapshot, queue, expiredOnly, query.get("cursor") ?? void 0, Number.isFinite(limit) ? limit : 50));
5014
5121
  } catch (error) {
@@ -5261,8 +5368,26 @@ function parseMergeCommand(argv) {
5261
5368
  };
5262
5369
  }
5263
5370
  }
5371
+ /** Machine-readable three-verb table: merge undo / skill undo / skill restore. */
5372
+ const MERGE_LIFECYCLE_VERBS = [
5373
+ {
5374
+ verb: "merge undo",
5375
+ appliesTo: "一次 merge run(按 run 反序撤销 unit)",
5376
+ excludes: ["skill undo", "skill restore"]
5377
+ },
5378
+ {
5379
+ verb: "skill undo",
5380
+ appliesTo: "单个技能的一次 patch",
5381
+ excludes: ["merge undo"]
5382
+ },
5383
+ {
5384
+ verb: "skill restore",
5385
+ appliesTo: "从 .archive 恢复整棵技能树",
5386
+ excludes: ["merge undo"]
5387
+ }
5388
+ ];
5264
5389
  function mergeUsage() {
5265
- return "usage: " + MERGE_COMMAND_DEFS.filter((def) => def.id !== "merge.doctor" && def.usage !== false).map((def) => def.fallback).join(" | ") + "\ndefaults: mergeApproval=false, mergeSkippedTtlDays=0, mergeRunTimeoutMinutes=0, mergePairTimeoutMinutes=15, mergeModelsRoot=\"\"";
5390
+ return "usage: " + MERGE_COMMAND_DEFS.filter((def) => def.id !== "merge.doctor" && def.usage !== false).map((def) => def.fallback).join(" | ") + "\ndefaults: mergeApproval=false, mergeSkippedTtlDays=0, mergeRunTimeoutMinutes=0, mergePairTimeoutMinutes=15, mergeModelsRoot=\"\"\nlifecycle: " + MERGE_LIFECYCLE_VERBS.map((item) => item.verb + "=" + item.appliesTo + ";互斥 " + item.excludes.join("/")).join(" | ");
5266
5391
  }
5267
5392
  //#endregion
5268
5393
  //#region lib/types/presentation-budget.js
@@ -5492,6 +5617,36 @@ function failureOf(stage, error) {
5492
5617
  retryFrom: stage === "decide" ? "agent1" : "agent2"
5493
5618
  };
5494
5619
  }
5620
+ function pairCriteria(outcome) {
5621
+ if (outcome === "merged") return {
5622
+ decision: "merge",
5623
+ validation: "required",
5624
+ commit: "required",
5625
+ archive: "sources",
5626
+ evidence: "receipt"
5627
+ };
5628
+ if (outcome === "kept") return {
5629
+ decision: "keep",
5630
+ validation: "report-only",
5631
+ commit: "never",
5632
+ archive: "none",
5633
+ evidence: "none"
5634
+ };
5635
+ if (outcome === "pending") return {
5636
+ decision: "merge",
5637
+ validation: "required",
5638
+ commit: "if-approved",
5639
+ archive: "none",
5640
+ evidence: "none"
5641
+ };
5642
+ return {
5643
+ decision: "merge",
5644
+ validation: "required",
5645
+ commit: "never",
5646
+ archive: "none",
5647
+ evidence: "none"
5648
+ };
5649
+ }
5495
5650
  function pairOf(row, candidate, outcome, extra = {}) {
5496
5651
  return {
5497
5652
  id: row.id,
@@ -5503,6 +5658,7 @@ function pairOf(row, candidate, outcome, extra = {}) {
5503
5658
  ...candidate.gate === void 0 ? {} : { gate: candidate.gate },
5504
5659
  outcome,
5505
5660
  archived: [],
5661
+ criteria: pairCriteria(outcome),
5506
5662
  ...extra
5507
5663
  };
5508
5664
  }
@@ -5699,7 +5855,11 @@ async function runScan(options) {
5699
5855
  totals.failed += 1;
5700
5856
  continue;
5701
5857
  }
5702
- const mergeDecision = options.cards !== void 0 ? withDecisionRevisions(round1.decision, options.cards, row.a, row.b) : round1.decision;
5858
+ const decided = options.model === void 0 || round1.decision.model !== void 0 ? round1.decision : {
5859
+ ...round1.decision,
5860
+ model: options.model
5861
+ };
5862
+ const mergeDecision = options.cards !== void 0 ? withDecisionRevisions(decided, options.cards, row.a, row.b) : decided;
5703
5863
  if (options.approval) {
5704
5864
  const event = {
5705
5865
  type: "verify-passed",
@@ -5764,7 +5924,13 @@ async function runScan(options) {
5764
5924
  const fused = await pairLedger(options.io, options.home).transact((tx) => {
5765
5925
  const current = tx.rows().find((item) => item.id === row.id);
5766
5926
  if (current === void 0) return null;
5767
- const moved = tx.transition(row.id, { type: "staged" }, current.version);
5927
+ const moved = tx.transition(row.id, {
5928
+ type: "staged",
5929
+ ...stageEntry === void 0 ? {} : {
5930
+ stagingRef: stageEntry.ref,
5931
+ stagingExpiresAt: stageEntry.expiresAt
5932
+ }
5933
+ }, current.version);
5768
5934
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
5769
5935
  });
5770
5936
  if (fused === null) {
@@ -5778,20 +5944,15 @@ async function runScan(options) {
5778
5944
  row = fused;
5779
5945
  if (stageEntry !== void 0) await presentStage(options.io, options.home, stageEntry);
5780
5946
  if (isAborted(options.signal)) {
5781
- const projected = await pairLedger(options.io, options.home).transact((tx) => {
5947
+ await pairLedger(options.io, options.home).transact((tx) => {
5782
5948
  const current = tx.rows().find((item) => item.id === row.id);
5783
- if (current === void 0) return null;
5784
- const moved = tx.transition(row.id, {
5785
- type: "skipped",
5786
- reason: "cancelled"
5787
- }, current.version);
5788
- return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
5949
+ return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
5789
5950
  });
5790
- if (projected !== null) row = projected;
5951
+ await dropStage(options.io, options.home, row.id, row.stagingRef);
5791
5952
  stopReason = "cancelled";
5792
5953
  remaining.push(...candidates.slice(index).map((candidate) => ({
5793
5954
  pairKey: pairKey(candidate.a, candidate.b),
5794
- queue: "fuse"
5955
+ queue: "verify"
5795
5956
  })));
5796
5957
  break;
5797
5958
  }
@@ -5817,6 +5978,8 @@ async function runScan(options) {
5817
5978
  pairs.push({
5818
5979
  ...pairOf(row, candidate, "merged", {
5819
5980
  decision: mergeDecision,
5981
+ ...round2.manifestFiles === void 0 ? {} : { manifestFiles: round2.manifestFiles },
5982
+ ...round2.validationIssues === void 0 ? {} : { validation: { issues: round2.validationIssues } },
5820
5983
  ...round2.unitId === void 0 ? {} : { unitId: round2.unitId },
5821
5984
  ...round2.target === void 0 ? {} : { target: round2.target },
5822
5985
  ...round2.usage === void 0 ? {} : { usage: round2.usage },
@@ -5829,8 +5992,8 @@ async function runScan(options) {
5829
5992
  continue;
5830
5993
  }
5831
5994
  const failed = round2.failure ?? failureOf("author", "round2 failed");
5832
- if (failed.code === "merge-over-limit" || failed.code === "limit" || failed.code === "merge-validation") {
5833
- const reason = failed.code === "merge-validation" ? "validation-failed" : "merge-over-limit";
5995
+ if (failed.code === "merge-over-limit" || failed.code === "limit") {
5996
+ const reason = "merge-over-limit";
5834
5997
  if (await pairLedger(options.io, options.home).transact((tx) => {
5835
5998
  const current = tx.rows().find((item) => item.id === row.id);
5836
5999
  return current === void 0 ? false : tx.transition(row.id, {
@@ -5842,33 +6005,35 @@ async function runScan(options) {
5842
6005
  })) await dropStage(options.io, options.home, row.id);
5843
6006
  pairs.push(pairOf(row, candidate, "failed", {
5844
6007
  decision: mergeDecision,
5845
- failure: failed
6008
+ failure: failed,
6009
+ ...round2.manifestFiles === void 0 ? {} : { manifestFiles: round2.manifestFiles },
6010
+ ...round2.criteria === void 0 ? {} : { criteria: round2.criteria },
6011
+ ...round2.validationIssues === void 0 ? {} : { validation: { issues: round2.validationIssues } }
5846
6012
  }));
5847
6013
  totals.failed += 1;
5848
6014
  continue;
5849
6015
  }
5850
6016
  if (failed.code === "target-changed" || failed.code === "target-taken" || failed.code === "source-changed" || failed.code === "support-changed" || failed.code === "source-gone") {
5851
6017
  const code = failed.code === "target-changed" ? "target-taken" : failed.code === "support-changed" ? "source-changed" : failed.code;
6018
+ const terminalFailure = {
6019
+ ...failed,
6020
+ code,
6021
+ retryable: false,
6022
+ retryFrom: "none"
6023
+ };
5852
6024
  await pairLedger(options.io, options.home).transact((tx) => {
5853
6025
  const current = tx.rows().find((item) => item.id === row.id);
5854
6026
  if (current === void 0) return false;
5855
6027
  if (code === "source-gone") return tx.remove(row.id, "superseded", void 0, current.version).ok;
5856
6028
  return tx.transition(row.id, {
5857
- type: "skipped",
5858
- reason: code,
5859
- origin: "system",
5860
- skipSource: "system"
6029
+ type: "failed",
6030
+ failure: terminalFailure
5861
6031
  }, current.version).ok;
5862
6032
  });
5863
6033
  await dropStage(options.io, options.home, row.id);
5864
6034
  pairs.push(pairOf(row, candidate, "failed", {
5865
6035
  decision: mergeDecision,
5866
- failure: {
5867
- ...failed,
5868
- code,
5869
- retryable: false,
5870
- retryFrom: "none"
5871
- }
6036
+ failure: terminalFailure
5872
6037
  }));
5873
6038
  totals.failed += 1;
5874
6039
  continue;
@@ -5884,13 +6049,24 @@ async function runScan(options) {
5884
6049
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
5885
6050
  });
5886
6051
  if (projected === null && terminal) {
5887
- await pairLedger(options.io, options.home).transact((tx) => {
6052
+ if (await pairLedger(options.io, options.home).transact((tx) => {
5888
6053
  const current = tx.rows().find((item) => item.id === row.id);
5889
6054
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
5890
- });
6055
+ })) {
6056
+ await dropStage(options.io, options.home, row.id, row.stagingRef);
6057
+ await recordPair(options.io, options.home, {
6058
+ pairKey: row.pairKey,
6059
+ runId: row.runId ?? "merge",
6060
+ result: "failed",
6061
+ ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
6062
+ });
6063
+ }
5891
6064
  pairs.push(pairOf(row, candidate, "failed", {
5892
6065
  decision: mergeDecision,
5893
- failure: failed
6066
+ failure: failed,
6067
+ ...round2.manifestFiles === void 0 ? {} : { manifestFiles: round2.manifestFiles },
6068
+ ...round2.criteria === void 0 ? {} : { criteria: round2.criteria },
6069
+ ...round2.validationIssues === void 0 ? {} : { validation: { issues: round2.validationIssues } }
5894
6070
  }));
5895
6071
  totals.failed += 1;
5896
6072
  continue;
@@ -5906,7 +6082,10 @@ async function runScan(options) {
5906
6082
  row = projected;
5907
6083
  pairs.push(pairOf(row, candidate, "failed", {
5908
6084
  decision: mergeDecision,
5909
- failure: failed
6085
+ failure: failed,
6086
+ ...round2.manifestFiles === void 0 ? {} : { manifestFiles: round2.manifestFiles },
6087
+ ...round2.criteria === void 0 ? {} : { criteria: round2.criteria },
6088
+ ...round2.validationIssues === void 0 ? {} : { validation: { issues: round2.validationIssues } }
5910
6089
  }));
5911
6090
  totals.failed += 1;
5912
6091
  }
@@ -5978,6 +6157,8 @@ async function runScan(options) {
5978
6157
  }
5979
6158
  //#endregion
5980
6159
  //#region lib/types/run-shell.js
6160
+ /** Bounded wait for a detached body before the run is reported as timed out. */
6161
+ const DETACHED_DRAIN_MS = 3e4;
5981
6162
  function emptyDraft(reason) {
5982
6163
  return {
5983
6164
  stopReason: "failed",
@@ -6018,8 +6199,14 @@ async function withMergeRun(options) {
6018
6199
  await writeRunMeta(options.io, options.home, {
6019
6200
  version: 1,
6020
6201
  runId: handle.id,
6202
+ pid: process.pid,
6021
6203
  ...options.parentRunId === void 0 ? {} : { parentRunId: options.parentRunId },
6022
6204
  ...options.pairKey === void 0 ? {} : { pairKey: options.pairKey },
6205
+ ...options.stagingRef === void 0 ? {} : { stagingRef: options.stagingRef },
6206
+ ...options.pairKey === void 0 ? {} : { pairs: [{
6207
+ pairKey: options.pairKey,
6208
+ state: "pending"
6209
+ }] },
6023
6210
  pairCount: options.pairCount ?? (options.pairKey === void 0 ? 0 : 1),
6024
6211
  phase: "running",
6025
6212
  approval: options.approval.mergeApproval ? "pending" : "approved",
@@ -6035,26 +6222,50 @@ async function withMergeRun(options) {
6035
6222
  handle.signal.addEventListener("abort", onAbort, { once: true });
6036
6223
  let timer;
6037
6224
  const timeoutMinutes = options.timeoutMinutes;
6038
- const timeout = timeoutMinutes === void 0 || timeoutMinutes <= 0 ? void 0 : new Promise((_, reject) => {
6039
- timer = setTimeout(() => {
6040
- runController.abort();
6041
- reject(/* @__PURE__ */ new Error("merge run timed out after " + String(timeoutMinutes) + " minutes"));
6042
- }, timeoutMinutes * 6e4);
6043
- });
6225
+ const bodyOutcome = options.body({
6226
+ runId: handle.id,
6227
+ startedAt,
6228
+ signal: runController.signal,
6229
+ stage
6230
+ }).then((value) => ({
6231
+ kind: "draft",
6232
+ value
6233
+ })).catch((error) => ({
6234
+ kind: "error",
6235
+ error
6236
+ }));
6237
+ let outcome;
6238
+ let timedOut = false;
6044
6239
  try {
6045
- draft = timeout === void 0 ? await options.body({
6046
- runId: handle.id,
6047
- startedAt,
6048
- signal: runController.signal,
6049
- stage
6050
- }) : await Promise.race([options.body({
6051
- runId: handle.id,
6052
- startedAt,
6053
- signal: runController.signal,
6054
- stage
6055
- }), timeout]);
6056
- } catch (error) {
6057
- draft = emptyDraft("run body failed: " + (error instanceof Error ? error.message : String(error)));
6240
+ if (timeoutMinutes === void 0 || timeoutMinutes <= 0) outcome = await bodyOutcome;
6241
+ else {
6242
+ const timeout = new Promise((resolve) => {
6243
+ timer = setTimeout(() => {
6244
+ runController.abort();
6245
+ resolve({ kind: "timeout" });
6246
+ }, timeoutMinutes * 6e4);
6247
+ });
6248
+ const raced = await Promise.race([bodyOutcome, timeout]);
6249
+ if (raced.kind !== "timeout") outcome = raced;
6250
+ else {
6251
+ timedOut = true;
6252
+ let drainTimer;
6253
+ const drained = await Promise.race([bodyOutcome, new Promise((resolve) => {
6254
+ drainTimer = setTimeout(() => resolve({ kind: "drain-timeout" }), DETACHED_DRAIN_MS);
6255
+ })]);
6256
+ if (drainTimer !== void 0) clearTimeout(drainTimer);
6257
+ outcome = drained.kind === "drain-timeout" ? {
6258
+ kind: "error",
6259
+ error: /* @__PURE__ */ new Error("timeout: body did not stop within 30s drain window")
6260
+ } : drained;
6261
+ }
6262
+ }
6263
+ draft = outcome.kind === "draft" ? outcome.value : emptyDraft("run body failed: " + (outcome.error instanceof Error ? outcome.error.message : String(outcome.error)));
6264
+ if (timedOut) draft = {
6265
+ ...draft,
6266
+ stopReason: "timeout",
6267
+ notes: [...draft.notes, "merge run timeout reached; body aborted"]
6268
+ };
6058
6269
  } finally {
6059
6270
  if (timer !== void 0) clearTimeout(timer);
6060
6271
  handle.signal.removeEventListener("abort", onAbort);
@@ -6070,9 +6281,13 @@ async function withMergeRun(options) {
6070
6281
  approval: options.approval,
6071
6282
  scope: draft.scope,
6072
6283
  pairs: draft.pairs,
6284
+ processedPairs: draft.pairs,
6073
6285
  totals: draft.totals,
6074
6286
  notes: draft.notes,
6075
- ...draft.remaining === void 0 ? {} : { remaining: draft.remaining },
6287
+ ...draft.remaining === void 0 ? {} : {
6288
+ remaining: draft.remaining,
6289
+ remainingPairs: draft.remaining
6290
+ },
6076
6291
  ...draft.reportOnly === void 0 ? {} : { reportOnly: draft.reportOnly },
6077
6292
  ...draft.validationFailures === void 0 ? {} : { validationFailures: draft.validationFailures },
6078
6293
  ...draft.archived === void 0 ? {} : { archived: draft.archived },
@@ -6117,11 +6332,21 @@ async function withMergeRun(options) {
6117
6332
  ...runState === "failed" ? { failure: "merge " + report.stopReason } : {}
6118
6333
  });
6119
6334
  const awaiting = options.approval.mergeApproval && (options.command === "scan" || options.command === "pair") && report.stopReason === "completed" && report.totals.pending > 0;
6335
+ const finalPairs = draft.pairs.length > 0 ? draft.pairs.map((pair) => ({
6336
+ pairKey: pair.pairKey,
6337
+ state: pair.outcome === "merged" ? "fused" : pair.outcome === "pending" ? "pending" : "skipped"
6338
+ })) : options.pairKey === void 0 ? [] : [{
6339
+ pairKey: options.pairKey,
6340
+ state: "pending"
6341
+ }];
6120
6342
  await writeRunMeta(options.io, options.home, {
6121
6343
  version: 1,
6122
6344
  runId: handle.id,
6345
+ pid: process.pid,
6123
6346
  ...options.parentRunId === void 0 ? {} : { parentRunId: options.parentRunId },
6124
6347
  ...options.pairKey === void 0 ? {} : { pairKey: options.pairKey },
6348
+ ...options.stagingRef === void 0 ? {} : { stagingRef: options.stagingRef },
6349
+ ...finalPairs.length === 0 ? {} : { pairs: finalPairs },
6125
6350
  pairCount: options.pairCount ?? (options.pairKey === void 0 ? 0 : 1),
6126
6351
  phase: awaiting ? "awaiting-approval" : "settled",
6127
6352
  approval: awaiting ? "pending" : "approved",
@@ -6184,7 +6409,8 @@ async function runScanCommand(options) {
6184
6409
  ...options.timeoutMinutes === void 0 ? {} : { timeoutMinutes: options.timeoutMinutes },
6185
6410
  ...options.notes === void 0 ? {} : { notes: options.notes },
6186
6411
  ...options.isOwnerActive === void 0 ? {} : { isOwnerActive: options.isOwnerActive },
6187
- ...options.cards === void 0 ? {} : { cards: options.cards }
6412
+ ...options.cards === void 0 ? {} : { cards: options.cards },
6413
+ ...options.model === void 0 ? {} : { model: options.model }
6188
6414
  });
6189
6415
  }
6190
6416
  });
@@ -6204,6 +6430,11 @@ async function act(io, home, now, id, type) {
6204
6430
  reason: "not-pending",
6205
6431
  message: "row is not pending: " + row.state
6206
6432
  };
6433
+ if (row.expired !== void 0) return {
6434
+ ok: false,
6435
+ reason: "expired",
6436
+ message: "pending row expired (" + row.expired + "): only discard or re-pair"
6437
+ };
6207
6438
  const event = type === "approve" ? {
6208
6439
  type: "approved",
6209
6440
  runId: row.runId ?? "approve:" + id
@@ -6253,6 +6484,11 @@ function casExpiredHook(code) {
6253
6484
  //#endregion
6254
6485
  //#region lib/types/round2-outcome.js
6255
6486
  async function recordRound2Outcome(io, home, now, row, outcome, deferProjection = false) {
6487
+ const reportExtra = {
6488
+ ...outcome.manifestFiles === void 0 ? {} : { manifestFiles: outcome.manifestFiles },
6489
+ ...outcome.criteria === void 0 ? {} : { criteria: outcome.criteria },
6490
+ ...outcome.validationIssues === void 0 ? {} : { validation: { issues: outcome.validationIssues } }
6491
+ };
6256
6492
  if (outcome.kind === "merged") {
6257
6493
  const unitRef = {
6258
6494
  unitId: outcome.unitId ?? "unknown",
@@ -6289,8 +6525,10 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6289
6525
  ...row.decision === void 0 ? {} : { decision: row.decision },
6290
6526
  outcome: "merged",
6291
6527
  archived: outcome.archived ?? [],
6528
+ ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
6292
6529
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
6293
- ...outcome.target === void 0 ? {} : { target: outcome.target }
6530
+ ...outcome.target === void 0 ? {} : { target: outcome.target },
6531
+ ...reportExtra
6294
6532
  }
6295
6533
  };
6296
6534
  await finalize();
@@ -6305,6 +6543,8 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6305
6543
  ...row.decision === void 0 ? {} : { decision: row.decision },
6306
6544
  outcome: "merged",
6307
6545
  archived: outcome.archived ?? [],
6546
+ ...outcome.lengths === void 0 ? {} : { lengths: outcome.lengths },
6547
+ ...reportExtra,
6308
6548
  ...outcome.unitId === void 0 ? {} : { unitId: outcome.unitId },
6309
6549
  ...outcome.target === void 0 ? {} : { target: outcome.target },
6310
6550
  ...outcome.validationPlan === void 0 ? {} : { validationPlan: outcome.validationPlan }
@@ -6318,8 +6558,8 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6318
6558
  retryable: true,
6319
6559
  retryFrom: "agent2"
6320
6560
  };
6321
- if (failure.code === "merge-over-limit" || failure.code === "limit" || failure.code === "merge-validation") {
6322
- const reason = failure.code === "merge-validation" ? "validation-failed" : "merge-over-limit";
6561
+ if (failure.code === "merge-over-limit" || failure.code === "limit") {
6562
+ const reason = "merge-over-limit";
6323
6563
  if (await pairLedger(io, home).transact((tx) => {
6324
6564
  const current = tx.rows().find((item) => item.id === row.id);
6325
6565
  return current === void 0 ? false : tx.transition(row.id, {
@@ -6340,20 +6580,25 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6340
6580
  ...row.decision === void 0 ? {} : { decision: row.decision },
6341
6581
  outcome: "failed",
6342
6582
  archived: [],
6343
- failure
6583
+ failure,
6584
+ ...reportExtra
6344
6585
  }
6345
6586
  };
6346
6587
  }
6347
6588
  const cas = casExpiredHook(failure.code);
6348
6589
  if (cas !== null) {
6590
+ const terminalFailure = {
6591
+ ...failure,
6592
+ code: cas.reason,
6593
+ retryable: false,
6594
+ retryFrom: "none"
6595
+ };
6349
6596
  await pairLedger(io, home).transact((tx) => {
6350
6597
  const current = tx.rows().find((item) => item.id === row.id);
6351
6598
  if (current === void 0) return false;
6352
6599
  return tx.transition(row.id, {
6353
- type: "skipped",
6354
- reason: cas.reason,
6355
- origin: "system",
6356
- skipSource: "system"
6600
+ type: "failed",
6601
+ failure: terminalFailure
6357
6602
  }, current.version).ok;
6358
6603
  });
6359
6604
  await dropStage(io, home, row.id);
@@ -6368,24 +6613,30 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6368
6613
  ...row.decision === void 0 ? {} : { decision: row.decision },
6369
6614
  outcome: "failed",
6370
6615
  archived: [],
6371
- failure: {
6372
- ...failure,
6373
- code: cas.reason,
6374
- retryable: false,
6375
- retryFrom: "none"
6376
- }
6616
+ failure: terminalFailure,
6617
+ ...reportExtra
6377
6618
  }
6378
6619
  };
6379
6620
  }
6380
- if (failure.code === "commit-failed" || failure.code === "validation-failed") await pairLedger(io, home).transact((tx) => {
6381
- const current = tx.rows().find((item) => item.id === row.id);
6382
- return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
6383
- });
6621
+ if (failure.code === "commit-failed" || failure.code === "validation-failed") {
6622
+ if (await pairLedger(io, home).transact((tx) => {
6623
+ const current = tx.rows().find((item) => item.id === row.id);
6624
+ return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
6625
+ })) {
6626
+ await dropStage(io, home, row.id, row.stagingRef);
6627
+ await recordPair(io, home, {
6628
+ pairKey: row.pairKey,
6629
+ runId: row.runId ?? "merge",
6630
+ result: "failed",
6631
+ ...row.decision?.target === void 0 ? {} : { target: row.decision.target }
6632
+ });
6633
+ }
6634
+ }
6384
6635
  if (failure.code === "source-gone") {
6385
- await pairLedger(io, home).transact((tx) => {
6636
+ if (await pairLedger(io, home).transact((tx) => {
6386
6637
  const current = tx.rows().find((item) => item.id === row.id);
6387
6638
  return current === void 0 ? false : tx.remove(row.id, "superseded", void 0, current.version).ok;
6388
- });
6639
+ })) await dropStage(io, home, row.id, row.stagingRef);
6389
6640
  return {
6390
6641
  merged: false,
6391
6642
  pair: {
@@ -6422,7 +6673,8 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6422
6673
  ...failure,
6423
6674
  code: "row-changed",
6424
6675
  message: "row changed before failure record"
6425
- }
6676
+ },
6677
+ ...reportExtra
6426
6678
  }
6427
6679
  };
6428
6680
  await recordPair(io, home, {
@@ -6442,7 +6694,8 @@ async function recordRound2Outcome(io, home, now, row, outcome, deferProjection
6442
6694
  ...row.decision === void 0 ? {} : { decision: row.decision },
6443
6695
  outcome: "failed",
6444
6696
  archived: [],
6445
- failure
6697
+ failure,
6698
+ ...reportExtra
6446
6699
  }
6447
6700
  };
6448
6701
  }
@@ -6470,7 +6723,11 @@ async function runApproveCommand(options) {
6470
6723
  const fused = await pairLedger(options.io, options.home).transact((tx) => {
6471
6724
  const current = tx.rows().find((item) => item.id === row.id);
6472
6725
  if (current === void 0) return null;
6473
- const moved = tx.transition(row.id, { type: "staged" }, current.version);
6726
+ const moved = tx.transition(row.id, {
6727
+ type: "staged",
6728
+ stagingRef: staged.entry.ref,
6729
+ stagingExpiresAt: staged.entry.expiresAt
6730
+ }, current.version);
6474
6731
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
6475
6732
  });
6476
6733
  if (fused === null) return failure("approve", "row-changed", "行在暂存完成前被并发修改:" + row.id, "运行 /evolution merge status " + row.id + " 后重试", row.runId);
@@ -6487,6 +6744,7 @@ async function runApproveCommand(options) {
6487
6744
  mergeApproval: true
6488
6745
  },
6489
6746
  pairKey: row.pairKey,
6747
+ ...row.stagingRef === void 0 ? {} : { stagingRef: row.stagingRef },
6490
6748
  parentRunId,
6491
6749
  now: options.now,
6492
6750
  ...options.detached === true ? { detached: true } : {},
@@ -6598,7 +6856,11 @@ async function runResumeCommand(options) {
6598
6856
  const fused = await pairLedger(options.io, options.home).transact((tx) => {
6599
6857
  const current = tx.rows().find((item) => item.id === selected.id);
6600
6858
  if (current === void 0) return null;
6601
- const moved = tx.transition(selected.id, { type: "staged" }, current.version);
6859
+ const moved = tx.transition(selected.id, {
6860
+ type: "staged",
6861
+ stagingRef: staged.entry.ref,
6862
+ stagingExpiresAt: staged.entry.expiresAt
6863
+ }, current.version);
6602
6864
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
6603
6865
  });
6604
6866
  if (fused === null) return failure("resume", "row-changed", "行在暂存完成前被并发修改:" + selected.id, "运行 /evolution merge status " + selected.id + " 后重试", selected.runId);
@@ -6615,6 +6877,7 @@ async function runResumeCommand(options) {
6615
6877
  mergeApproval: options.approval
6616
6878
  },
6617
6879
  pairKey: row.pairKey,
6880
+ ...row.stagingRef === void 0 ? {} : { stagingRef: row.stagingRef },
6618
6881
  parentRunId,
6619
6882
  now: options.now,
6620
6883
  ...options.detached === true ? { detached: true } : {},
@@ -6710,24 +6973,6 @@ async function writeUndoJournal(io, home, journal) {
6710
6973
  await transactIo(io, undoJournalPath(home, journal.runId), () => JSON.stringify(journal, null, 2) + "\n");
6711
6974
  }
6712
6975
  //#endregion
6713
- //#region lib/types/domain/undo-receipt.js
6714
- /** Undo receipt: immutable per-pair settlement, separate from the commit receipt. */
6715
- function undoReceiptPath(home, runId, pairKey) {
6716
- return join(home, "merge", "undo-receipts", encodeURIComponent(runId), createHash("sha256").update(pairKey).digest("hex") + ".json");
6717
- }
6718
- async function readUndoReceipt(io, home, runId, pairKey) {
6719
- const raw = await readTextOrNull(io, undoReceiptPath(home, runId, pairKey));
6720
- if (raw === null) return null;
6721
- try {
6722
- return JSON.parse(raw);
6723
- } catch {
6724
- return null;
6725
- }
6726
- }
6727
- async function writeUndoReceipt(io, home, receipt) {
6728
- await io.writeText(undoReceiptPath(home, receipt.runId, receipt.pairKey), JSON.stringify(receipt, null, 2) + "\n");
6729
- }
6730
- //#endregion
6731
6976
  //#region lib/types/undo-operation.js
6732
6977
  function digest$1(value) {
6733
6978
  return value === null ? null : contentHash(value);
@@ -6749,11 +6994,16 @@ function sortReceipts(receipts) {
6749
6994
  }
6750
6995
  async function preflight(io, home, port, receipt) {
6751
6996
  const evidence = await readUnitEvidence(io, home, receipt.unitId);
6752
- if (evidence === null) return {
6997
+ if (evidence === null) return receipt.evidenceSha256 === void 0 ? {
6753
6998
  ok: false,
6754
6999
  code: "undo-unavailable-legacy-receipt",
6755
7000
  message: "commit evidence is missing; undo unavailable for legacy receipt",
6756
7001
  undone: []
7002
+ } : {
7003
+ ok: false,
7004
+ code: "undo-evidence-expired",
7005
+ message: "commit evidence aged out of the retention window; undo unavailable (fail-closed)",
7006
+ undone: []
6757
7007
  };
6758
7008
  const expectedSha = receipt.evidenceSha256 ?? evidenceSha256(evidence);
6759
7009
  if (evidenceSha256(evidence) !== expectedSha) return {
@@ -6814,8 +7064,9 @@ async function settleRun(io, home, runId, pairKey, allPairs, at) {
6814
7064
  });
6815
7065
  }
6816
7066
  async function runUndoOperation(options) {
6817
- const allReceipts = (await readCommitReceipts(options.io, options.home)).filter((receipt) => (receipt.parentRunId ?? receipt.runId) === options.runId);
6818
- const allPairKeys = allReceipts.map((receipt) => receipt.pairKey ?? receipt.unitId);
7067
+ const runReceipts = (await readCommitReceipts(options.io, options.home)).filter((receipt) => (receipt.parentRunId ?? receipt.runId) === options.runId);
7068
+ const allPairKeys = runReceipts.map((receipt) => receipt.pairKey ?? receipt.unitId);
7069
+ const allReceipts = runReceipts.filter((receipt) => options.target === void 0 || receipt.target === options.target);
6819
7070
  const receipts = options.pairKey === void 0 ? allReceipts : allReceipts.filter((receipt) => receipt.pairKey === options.pairKey);
6820
7071
  if (receipts.length === 0) return {
6821
7072
  ok: false,
@@ -7712,6 +7963,7 @@ async function commitStagedRun(io, home, runId, port, options) {
7712
7963
  };
7713
7964
  let result;
7714
7965
  let evidenceSha256$1;
7966
+ let warning;
7715
7967
  const transaction = options.unit ?? port.unit ?? legacyUnitAdapter(port, assembled.plan, options, runId);
7716
7968
  if (transaction === void 0) return {
7717
7969
  ok: false,
@@ -7747,7 +7999,7 @@ async function commitStagedRun(io, home, runId, port, options) {
7747
7999
  message: prepared.message,
7748
8000
  plan: assembled.plan
7749
8001
  };
7750
- const committed = await transaction.commit(prepared.prepared);
8002
+ const committed = await transaction.commit(prepared.prepared, options.signal === void 0 ? void 0 : { signal: options.signal });
7751
8003
  if (!committed.ok) {
7752
8004
  try {
7753
8005
  await writeJournal(io, home, {
@@ -7772,11 +8024,7 @@ async function commitStagedRun(io, home, runId, port, options) {
7772
8024
  const finalize = transaction.finalize;
7773
8025
  if (finalize !== void 0) {
7774
8026
  const finalized = await finalize.call(transaction, unitId);
7775
- if (!finalized.ok) return {
7776
- ok: false,
7777
- message: "unit committed but finalize failed: " + finalized.message,
7778
- plan: assembled.plan
7779
- };
8027
+ if (!finalized.ok) warning = "commit landed but finalize failed: " + finalized.message;
7780
8028
  }
7781
8029
  evidenceSha256$1 = evidenceSha256(committed.evidence);
7782
8030
  result = {
@@ -7820,7 +8068,7 @@ async function commitStagedRun(io, home, runId, port, options) {
7820
8068
  runId,
7821
8069
  ...evidenceSha256$1 === void 0 ? {} : { evidenceSha256: evidenceSha256$1 },
7822
8070
  ...options.validationPlan === void 0 ? {} : { validationPlan: options.validationPlan },
7823
- parentRunId: runId.split("/pairs/")[0] ?? runId,
8071
+ ...options.parentRunId === void 0 ? {} : { parentRunId: options.parentRunId },
7824
8072
  unitId: result.unitId,
7825
8073
  target: assembled.plan.target,
7826
8074
  create: !assembled.plan.targetExists,
@@ -7838,14 +8086,15 @@ async function commitStagedRun(io, home, runId, port, options) {
7838
8086
  ok: true,
7839
8087
  plan: assembled.plan,
7840
8088
  result,
7841
- receipt
8089
+ receipt,
8090
+ ...warning === void 0 ? {} : { warning }
7842
8091
  };
7843
8092
  } catch (error) {
7844
8093
  return {
7845
8094
  ok: true,
7846
8095
  plan: assembled.plan,
7847
8096
  result,
7848
- warning: "commit landed but receipt write failed: " + (error instanceof Error ? error.message : String(error))
8097
+ warning: (warning === void 0 ? "" : warning + "; ") + "commit landed but receipt write failed: " + (error instanceof Error ? error.message : String(error))
7849
8098
  };
7850
8099
  }
7851
8100
  }
@@ -7971,7 +8220,8 @@ function buildValidationPlan(input) {
7971
8220
  const reverseRefs = files.map((file) => file.path).filter((path) => !refs.has(path) && manifest.has(path)).sort();
7972
8221
  const orphanFiles = [...new Set(input.existingSupport ?? [])].filter((path) => !staged.has(path)).sort();
7973
8222
  const byPath = /* @__PURE__ */ new Map();
7974
- for (const file of input.supportSources ?? []) {
8223
+ const supportSources = input.supportSources ?? [];
8224
+ for (const file of supportSources) {
7975
8225
  const entry = byPath.get(file.path) ?? {
7976
8226
  contents: /* @__PURE__ */ new Set(),
7977
8227
  sources: []
@@ -8202,7 +8452,8 @@ async function runAuthoring(options) {
8202
8452
  message: "manifest file missing from staged tree: " + missing
8203
8453
  };
8204
8454
  const limitsSnapshot = limitsSnapshotOf(options.limits ?? DEFAULT_SKILL_LIMITS);
8205
- const supportSources = options.cards.flatMap((card) => (card.files ?? []).map((file) => ({
8455
+ const participants = new Set(options.sources ?? []);
8456
+ const supportSources = options.cards.filter((card) => participants.has(card.name)).flatMap((card) => (card.files ?? []).map((file) => ({
8206
8457
  path: file.path,
8207
8458
  content: file.content,
8208
8459
  source: card.name
@@ -8224,28 +8475,31 @@ async function runAuthoring(options) {
8224
8475
  existingSupport: (options.cards.find((card) => card.name === options.target)?.files ?? []).map((file) => file.path)
8225
8476
  });
8226
8477
  const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
8478
+ const lengths = {
8479
+ bodyBytes: Buffer.byteLength(tree.value.body, "utf8"),
8480
+ fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, "utf8"), 0),
8481
+ totalBytes: Buffer.byteLength(tree.value.body, "utf8") + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, "utf8"), 0)
8482
+ };
8227
8483
  if (hasBlockingFinding(findings)) return {
8228
8484
  ok: false,
8229
8485
  code: findings.some((finding) => finding.rule === "limit") ? "merge-over-limit" : "merge-validation",
8230
8486
  message: "staged tree failed the rule gate",
8231
8487
  findings,
8232
- lengths: {
8233
- bodyBytes: Buffer.byteLength(tree.value.body, "utf8"),
8234
- fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, "utf8"), 0),
8235
- totalBytes: Buffer.byteLength(tree.value.body, "utf8") + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, "utf8"), 0)
8236
- }
8488
+ lengths
8237
8489
  };
8238
8490
  const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
8239
8491
  origin: "foreground",
8240
8492
  summary: options.summary,
8241
8493
  now: options.now,
8494
+ ...options.signal === void 0 ? {} : { signal: options.signal },
8242
8495
  ...options.isBlocked === void 0 ? {} : { isBlocked: options.isBlocked },
8243
8496
  ...options.unit === void 0 ? {} : { unit: options.unit },
8244
- ...options.limits === void 0 ? {} : { limits: options.limits },
8497
+ limits: limitsSnapshot.skillLimits,
8245
8498
  validationPlan: {
8246
8499
  planVersion: validationPlan.planVersion,
8247
8500
  stageDigest: validationPlan.stageDigest,
8248
- revision: validationPlan.revision
8501
+ revision: validationPlan.revision,
8502
+ manifestFiles: validationPlan.manifestFiles
8249
8503
  },
8250
8504
  ...options.revisions?.targetRevision === void 0 ? {} : { targetRevision: options.revisions.targetRevision },
8251
8505
  ...options.revisions?.sourceRevisions === void 0 ? {} : { sourceRevisions: options.revisions.sourceRevisions },
@@ -8255,7 +8509,8 @@ async function runAuthoring(options) {
8255
8509
  ok: true,
8256
8510
  tree: tree.value,
8257
8511
  validationPlan,
8258
- commit
8512
+ commit,
8513
+ lengths
8259
8514
  };
8260
8515
  return {
8261
8516
  ok: false,
@@ -8391,6 +8646,7 @@ function createChannelScanPorts(options) {
8391
8646
  reuseStaging: true,
8392
8647
  target: decision.target,
8393
8648
  mode: decision.mode,
8649
+ sources: [row.a, row.b],
8394
8650
  cards: options.cards,
8395
8651
  commitPort: options.commitPort,
8396
8652
  ...options.unit === void 0 ? {} : { unit: options.unit },
@@ -8400,6 +8656,7 @@ function createChannelScanPorts(options) {
8400
8656
  ...options.limits === void 0 ? {} : { limits: options.limits },
8401
8657
  promptVersion: MERGE_PROMPT_VERSION,
8402
8658
  promptHash: promptHashOf(buildRound2Prompt(row, decision)),
8659
+ signal,
8403
8660
  revisions: {
8404
8661
  ...decision.targetRevisionRef === void 0 ? {} : { targetRevision: decision.targetRevisionRef },
8405
8662
  ...decision.sourceRevisions === void 0 ? {} : { sourceRevisions: decision.sourceRevisions },
@@ -8430,6 +8687,7 @@ function createChannelScanPorts(options) {
8430
8687
  ...receipt?.unitId === void 0 ? {} : { unitId: receipt.unitId },
8431
8688
  ...receipt?.evidenceSha256 === void 0 ? {} : { evidenceSha256: receipt.evidenceSha256 },
8432
8689
  ...receipt?.validationPlan === void 0 ? {} : { validationPlan: receipt.validationPlan },
8690
+ ...authored.lengths === void 0 ? {} : { lengths: authored.lengths },
8433
8691
  ...usage === void 0 ? {} : { usage },
8434
8692
  archived: receipt?.archived ?? [],
8435
8693
  target: {
@@ -8632,7 +8890,7 @@ async function runNewChildAuthoring(options, row, decision, signal) {
8632
8890
  })),
8633
8891
  manifestFiles,
8634
8892
  existingSupport: (options.cards.find((card) => card.name === decision.target)?.files ?? []).map((file) => file.path),
8635
- supportSources: options.cards.flatMap((card) => (card.files ?? []).map((file) => ({
8893
+ supportSources: options.cards.filter((card) => card.name === row.a || card.name === row.b).flatMap((card) => (card.files ?? []).map((file) => ({
8636
8894
  path: file.path,
8637
8895
  content: file.content,
8638
8896
  source: card.name
@@ -8646,6 +8904,12 @@ async function runNewChildAuthoring(options, row, decision, signal) {
8646
8904
  totalBytes: Buffer.byteLength(tree.value.body, "utf8") + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, "utf8"), 0)
8647
8905
  };
8648
8906
  const limit = findings.find((finding) => finding.rule === "limit");
8907
+ const plan = {
8908
+ planVersion: validationPlan.planVersion,
8909
+ stageDigest: validationPlan.stageDigest,
8910
+ revision: validationPlan.revision,
8911
+ manifestFiles: validationPlan.manifestFiles
8912
+ };
8649
8913
  return limit === void 0 ? {
8650
8914
  kind: "failed",
8651
8915
  failure: {
@@ -8654,6 +8918,16 @@ async function runNewChildAuthoring(options, row, decision, signal) {
8654
8918
  message: "staged tree failed the rule gate",
8655
8919
  retryable: false,
8656
8920
  retryFrom: "none"
8921
+ },
8922
+ validationPlan: plan,
8923
+ manifestFiles: validationPlan.manifestFiles,
8924
+ validationIssues: findings,
8925
+ criteria: {
8926
+ decision: "merge",
8927
+ validation: "required",
8928
+ commit: "never",
8929
+ archive: "none",
8930
+ evidence: "none"
8657
8931
  }
8658
8932
  } : {
8659
8933
  kind: "failed",
@@ -8664,16 +8938,29 @@ async function runNewChildAuthoring(options, row, decision, signal) {
8664
8938
  retryable: false,
8665
8939
  retryFrom: "none",
8666
8940
  lengths
8941
+ },
8942
+ validationPlan: plan,
8943
+ manifestFiles: validationPlan.manifestFiles,
8944
+ validationIssues: findings,
8945
+ criteria: {
8946
+ decision: "merge",
8947
+ validation: "required",
8948
+ commit: "never",
8949
+ archive: "none",
8950
+ evidence: "none"
8667
8951
  }
8668
8952
  };
8669
8953
  }
8954
+ const parentRunId = typeof options.parentRunId === "function" ? options.parentRunId(options.runId) : options.parentRunId;
8670
8955
  const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
8671
8956
  origin: "foreground",
8672
8957
  summary: "merge " + row.a + " + " + row.b,
8673
8958
  now: options.now,
8959
+ signal,
8674
8960
  ...options.isBlocked === void 0 ? {} : { isBlocked: options.isBlocked },
8675
8961
  ...options.unit === void 0 ? {} : { unit: options.unit },
8676
8962
  ...options.limits === void 0 ? {} : { limits: options.limits },
8963
+ ...parentRunId === void 0 ? {} : { parentRunId },
8677
8964
  validationPlan: {
8678
8965
  planVersion: validationPlan.planVersion,
8679
8966
  stageDigest: validationPlan.stageDigest,
@@ -8707,6 +8994,22 @@ async function runNewChildAuthoring(options, row, decision, signal) {
8707
8994
  ...receipt?.unitId === void 0 ? {} : { unitId: receipt.unitId },
8708
8995
  archived: receipt?.archived ?? [],
8709
8996
  lengths,
8997
+ validationPlan: {
8998
+ planVersion: validationPlan.planVersion,
8999
+ stageDigest: validationPlan.stageDigest,
9000
+ revision: validationPlan.revision,
9001
+ manifestFiles: validationPlan.manifestFiles
9002
+ },
9003
+ ...parentRunId === void 0 ? {} : { parentRunId },
9004
+ manifestFiles: validationPlan.manifestFiles,
9005
+ validationIssues: findings,
9006
+ criteria: {
9007
+ decision: "merge",
9008
+ validation: "required",
9009
+ commit: "required",
9010
+ archive: "sources",
9011
+ evidence: "receipt"
9012
+ },
8710
9013
  target: {
8711
9014
  requested: decision.target,
8712
9015
  actual: commit.plan.target,
@@ -8834,6 +9137,7 @@ function createMergeRuntime(deps) {
8834
9137
  registry: deps.registry,
8835
9138
  platformWrite: deps.platformWrite,
8836
9139
  now: deps.now,
9140
+ ...deps.model === void 0 ? {} : { model: deps.model },
8837
9141
  ...deps.isOwnerActive === void 0 ? {} : { isOwnerActive: deps.isOwnerActive },
8838
9142
  ...timeout === void 0 ? {} : { timeoutMinutes: timeout }
8839
9143
  };
@@ -8853,11 +9157,20 @@ function createMergeRuntime(deps) {
8853
9157
  if (command.verb === "pair") {
8854
9158
  const signal = new AbortController().signal;
8855
9159
  if (command.a === command.b) return fail("pair", "pair-same-source", "merge pair 需要两个不同技能源:" + command.a, "重新指定两个不同的 family-managed 技能");
8856
- if ([command.a, command.b].some((name) => name.includes("/") || name.includes(":"))) return fail("pair", "target-invalid", "技能名不能包含 provider/name 或 provider:name 语法:" + command.a + " / " + command.b, "使用裸技能名重新 pair");
9160
+ if ([command.a, command.b].some((name) => name.includes("/") || name.includes(":"))) return {
9161
+ ok: false,
9162
+ text: "merge: 技能名不能包含 provider/name 或 provider:name 语法:" + command.a + " / " + command.b + " (provider-conflict)\nusage: /evolution merge pair <a> <b>"
9163
+ };
8857
9164
  if (agentless) return fail("pair", "e-305", errorText("e-305-this-invocation-carries-no", { a1: "pair" }), "在 GUI 或 CLI 的会话里运行 /evolution merge pair " + command.a + " " + command.b);
8858
9165
  const pairKeyValue = pairKey(command.a, command.b);
8859
9166
  const inFlight = inFlightPairRun(await readRunMetas(deps.io, deps.home), pairKeyValue);
8860
- if (inFlight !== void 0 && (deps.isOwnerActive === void 0 || await deps.isOwnerActive(inFlight.runId) === true)) return fail("pair", "merge-pair-in-flight", "同一 pair 已在运行 " + inFlight.runId, "等待该 run 结束,或运行 /evolution merge status " + inFlight.runId, inFlight.runId);
9167
+ let ownerActive = false;
9168
+ if (inFlight !== void 0) if (deps.isOwnerActive !== void 0) ownerActive = await deps.isOwnerActive(inFlight.runId);
9169
+ else ownerActive = inFlight.pid === void 0 ? true : isProcessAlive(inFlight.pid);
9170
+ if (inFlight !== void 0 && ownerActive) return {
9171
+ ok: true,
9172
+ text: "merge pair 已在运行:" + inFlight.runId + ";作用域:family-managed 技能对,1 对,无 per-run 上限"
9173
+ };
8861
9174
  const cards = await deps.cards(signal);
8862
9175
  const cardA = cards.find((card) => card.name === command.a);
8863
9176
  const cardB = cards.find((card) => card.name === command.b);
@@ -8869,12 +9182,14 @@ function createMergeRuntime(deps) {
8869
9182
  b: command.b
8870
9183
  }];
8871
9184
  const existing = (await loadMergeState(deps.io, deps.home)).state.rows.find((row) => row.pairKey === pairKey(command.a, command.b));
8872
- if (existing?.state === "skipped") {
8873
- if (!await removeRuntimeRow(deps.io, deps.home, existing.id, "superseded")) return fail("pair", "row-changed", "清理 skipped 行时发现并发修改:" + existing.id, "运行 /evolution merge status 后重试");
9185
+ let cleared = false;
9186
+ if (existing?.state === "skipped" || existing?.state === "fuse" && existing.failure?.retryable === false) {
9187
+ if (!await removeRuntimeRow(deps.io, deps.home, existing.id, "superseded")) return fail("pair", "row-changed", "清理可替换行时发现并发修改:" + existing.id, "运行 /evolution merge status 后重试");
9188
+ cleared = true;
8874
9189
  }
8875
9190
  if (existing?.state === "verify" && existing.runId !== void 0 && await deps.isOwnerActive?.(existing.runId) === true) return fail("pair", "merge-pair-in-flight", "同一 pair 已在运行 " + existing.runId, "等待该 run 结束,或运行 /evolution merge status " + existing.runId, existing.runId);
8876
9191
  if (existing?.state === "pending") return fail("pair", "pair-pending", "该 pair 已在待批准队列:" + existing.id, "先运行 /evolution merge approve/reject/discard " + existing.id, existing.runId);
8877
- if (existing?.state === "approved" || existing?.state === "fuse") return fail("pair", "pair-fuse", "该 pair 已在待合并队列,可复用决策重跑", "运行 /evolution merge resume --retry " + (existing.runId ?? existing.id), existing.runId);
9192
+ if (!cleared && (existing?.state === "approved" || existing?.state === "fuse")) return fail("pair", "pair-fuse", "该 pair 已在待合并队列,可复用决策重跑", "运行 /evolution merge resume --retry " + (existing.runId ?? existing.id), existing.runId);
8878
9193
  const forced = command.force === true ? forcedDecisionFor(command.a, command.b) : void 0;
8879
9194
  const approval = deps.params.mergeApproval;
8880
9195
  const result = await runScanCommand({
@@ -8914,7 +9229,7 @@ function createMergeRuntime(deps) {
8914
9229
  if (!result.ok) return failRun("pair", result.message, "修复后重新发起 pair;run 报告见 /evolution merge report", result);
8915
9230
  if ("accepted" in result) return {
8916
9231
  ok: true,
8917
- text: "merge pair 已启动:" + result.runId
9232
+ text: "merge pair 已启动:" + result.runId + ";作用域:family-managed 技能对,1 对,无 per-run 上限"
8918
9233
  };
8919
9234
  if (!("report" in result)) return fail("pair", "report-missing", "merge pair 已结束但没有写报告", "重新运行 pair,或运行 /evolution merge runs 查看运行");
8920
9235
  return {
@@ -8980,7 +9295,7 @@ function createMergeRuntime(deps) {
8980
9295
  if (!result.ok) return failRun("scan", result.message, "修复后重新 scan;run 报告见 /evolution merge report", result);
8981
9296
  if ("accepted" in result) return {
8982
9297
  ok: true,
8983
- text: "merge scan 已启动:" + result.runId
9298
+ text: "merge scan 已启动:" + result.runId + ";作用域:family-managed 技能库,候选来源 engine screening,无 per-run 上限"
8984
9299
  };
8985
9300
  if (!("report" in result)) return fail("scan", "report-missing", "merge scan 已结束但没有写报告", "重新运行 scan,或运行 /evolution merge runs 查看运行");
8986
9301
  return {
@@ -9031,7 +9346,7 @@ function createMergeRuntime(deps) {
9031
9346
  if (!result.ok) return failRun("candidates", result.message, "修复后重试 candidates --retry", result);
9032
9347
  if ("accepted" in result) return {
9033
9348
  ok: true,
9034
- text: "candidates --retry 已启动:" + result.runId
9349
+ text: "candidates --retry 已启动:" + result.runId + ";作用域:单条 verify 行重跑,无 per-run 上限"
9035
9350
  };
9036
9351
  if (!("report" in result)) return fail("candidates", "report-missing", "candidates --retry 已结束但没有写报告", "运行 /evolution merge runs 查看运行");
9037
9352
  return {
@@ -9131,10 +9446,9 @@ function createMergeRuntime(deps) {
9131
9446
  };
9132
9447
  }
9133
9448
  if (command.verb === "undo") {
9134
- const receipt = await readCommitReceipt(deps.io, deps.home, command.run, command.target);
9135
- if (receipt === null) return fail("undo", "receipt-not-found", "该 run 没有提交回执:" + command.run, "只有已提交的 run 可 undo;运行 /evolution merge runs 查看");
9136
- if (receipt.target !== command.target) return fail("undo", "receipt-target-mismatch", "回执目标是 " + receipt.target + ",不是 " + command.target, "按回执目标重新运行 undo");
9137
- const undoRunId = receipt.parentRunId ?? receipt.runId;
9449
+ const receipts = (await readCommitReceipts(deps.io, deps.home)).filter((receipt) => (receipt.parentRunId ?? receipt.runId) === command.run && receipt.target === command.target);
9450
+ if (receipts.length === 0) return fail("undo", "receipt-not-found", "该 run 没有目标回执:" + command.run + " / " + command.target, "只有已提交的 run 可 undo;运行 /evolution merge runs 查看");
9451
+ const undoRunId = receipts[0].parentRunId ?? receipts[0].runId;
9138
9452
  if (deps.registry.runs("merge").some((run) => run.id === undoRunId && run.supersededAt !== void 0)) return fail("undo", "run-superseded", "该 run 已因技能树 restore 失效,不能再 undo:" + undoRunId, "运行 /evolution merge runs 查看失效标记");
9139
9453
  const unit = unitFor(deps);
9140
9454
  const result = await runUndoOperation({
@@ -9143,7 +9457,7 @@ function createMergeRuntime(deps) {
9143
9457
  runId: undoRunId,
9144
9458
  port: skillLibraryUndoPort(deps.skills),
9145
9459
  now: deps.now,
9146
- ...receipt.pairKey === void 0 ? {} : { pairKey: receipt.pairKey },
9460
+ target: command.target,
9147
9461
  ...unit === void 0 ? {} : { finalizeUnit: (id) => unit.finalize.call(unit, id) }
9148
9462
  });
9149
9463
  return result.ok ? {
@@ -9241,6 +9555,7 @@ function createMergeRuntime(deps) {
9241
9555
  cards,
9242
9556
  channel: deps.channel,
9243
9557
  parent,
9558
+ parentRunId: (runId) => runId,
9244
9559
  allow: deps.allow,
9245
9560
  ...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
9246
9561
  timeoutMs: deps.params.mergePairTimeoutMinutes * 6e4,
@@ -9255,7 +9570,7 @@ function createMergeRuntime(deps) {
9255
9570
  if (!result.ok) return failRun("resume", result.message, "修复后重新 resume;run 报告见 /evolution merge report", result);
9256
9571
  if ("accepted" in result) return {
9257
9572
  ok: true,
9258
- text: "merge resume 已启动:" + result.runId
9573
+ text: "merge resume 已启动:" + result.runId + ";作用域:单条待合并行重跑 agent2,无 per-run 上限"
9259
9574
  };
9260
9575
  if (!("report" in result)) return fail("resume", "report-missing", "merge resume 已结束但没有写报告", "重新运行 resume,或运行 /evolution merge runs 查看运行");
9261
9576
  return {
@@ -9381,6 +9696,7 @@ function createMergeRuntime(deps) {
9381
9696
  cards,
9382
9697
  channel: deps.channel,
9383
9698
  parent,
9699
+ parentRunId: (runId) => runId,
9384
9700
  allow: deps.allow,
9385
9701
  ...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
9386
9702
  timeoutMs: deps.params.mergePairTimeoutMinutes * 6e4,
@@ -9396,7 +9712,7 @@ function createMergeRuntime(deps) {
9396
9712
  if (!result.ok) return failRun("approve", result.message, "修复后重新 approve;run 报告见 /evolution merge report", result);
9397
9713
  if ("accepted" in result) return {
9398
9714
  ok: true,
9399
- text: "merge approve 已启动:" + result.runId
9715
+ text: "merge approve 已启动:" + result.runId + ";作用域:单条 pending 行批准后重跑 agent2,无 per-run 上限"
9400
9716
  };
9401
9717
  if (!("report" in result)) return fail("approve", "report-missing", "merge approve 已结束但没有写报告", "重新运行 approve,或运行 /evolution merge runs 查看运行");
9402
9718
  return {
@@ -10185,6 +10501,7 @@ function apply(ctx, rawConfig = {}) {
10185
10501
  if (v4.ok) {
10186
10502
  const cleanup = await cleanupStage(io, home, Date.now(), new Map(v4.state.rows.map((row) => [row.id, { state: row.state }])));
10187
10503
  if (cleanup.expired.length > 0 || cleanup.removed.length > 0) ctx.logger.info("merge stage cleanup: " + String(cleanup.expired.length) + " expired, " + String(cleanup.removed.length) + " removed, " + String(cleanup.orphans.length) + " orphan(s)");
10504
+ if (cleanup.failures.length > 0) ctx.logger.warn("merge stage cleanup failed for " + String(cleanup.failures.length) + " path(s): " + cleanup.failures.map((item) => item.path + "=" + item.message).join("; "));
10188
10505
  const library = v4.state.rows.some((row) => row.state === "pending") ? await cards().catch(() => []) : [];
10189
10506
  const live = library.length === 0 ? void 0 : liveRowFactsOf(library);
10190
10507
  const reconciled = await reconcileMergeState({
@@ -10215,7 +10532,8 @@ function apply(ctx, rawConfig = {}) {
10215
10532
  }).catch((error) => {
10216
10533
  ctx.logger.warn("merge state readiness failed: " + (error instanceof Error ? error.message : String(error)));
10217
10534
  });
10218
- const channel = subagentChannelAdapter(createSubagentChannel(ctx));
10535
+ const platformChannel = createSubagentChannel(ctx);
10536
+ const channel = subagentChannelAdapter(platformChannel);
10219
10537
  const stage = new StageToolRegistry();
10220
10538
  const engineInput = resolveEngineInput({ modelsRoot: params.mergeModelsRoot });
10221
10539
  const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
@@ -10279,6 +10597,23 @@ function apply(ctx, rawConfig = {}) {
10279
10597
  runs: runs.runs("merge"),
10280
10598
  approval: params.mergeApproval,
10281
10599
  platformWrite,
10600
+ hostOnly: async () => {
10601
+ try {
10602
+ const state = await platformChannel.prepare({
10603
+ kind: "reviewer",
10604
+ allow: ["skill"]
10605
+ });
10606
+ return state.kind === "unavailable" ? {
10607
+ code: "E-323",
10608
+ detail: state.reason
10609
+ } : void 0;
10610
+ } catch (error) {
10611
+ return {
10612
+ code: "E-323",
10613
+ detail: error instanceof Error ? error.message : String(error)
10614
+ };
10615
+ }
10616
+ },
10282
10617
  live: async () => liveRowFactsOf(await readSkillCards(skills)),
10283
10618
  history: async () => {
10284
10619
  const items = /* @__PURE__ */ new Map();
@@ -10367,6 +10702,7 @@ function apply(ctx, rawConfig = {}) {
10367
10702
  now: () => Date.now(),
10368
10703
  params,
10369
10704
  platformWrite,
10705
+ ...defaultModel === void 0 ? {} : { model: defaultModel.currentSelection().model },
10370
10706
  channel,
10371
10707
  parent: void 0,
10372
10708
  allow: ["merge_stage"],
@@ -10415,4 +10751,4 @@ function apply(ctx, rawConfig = {}) {
10415
10751
  });
10416
10752
  }
10417
10753
  //#endregion
10418
- export { Config, MERGE_DOCTOR_SCHEMA_VERSION, 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, 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, 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 };
10754
+ 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, 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 };