@lmzhen/dsh-evolution-merge 0.30.17 → 0.30.19

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 (187) hide show
  1. package/README.md +25 -7
  2. package/data/minhash_params.json +1063 -0
  3. package/lib/index.js +7308 -1800
  4. package/lib/types/adapters/channel.d.ts +1 -0
  5. package/lib/types/adapters/channel.js +15 -1
  6. package/lib/types/adapters/engine-screen.d.ts +1 -1
  7. package/lib/types/adapters/engine-screen.js +5 -2
  8. package/lib/types/adapters/library.js +4 -1
  9. package/lib/types/adapters/model-runner.d.ts +0 -5
  10. package/lib/types/adapters/model-runner.js +8 -2
  11. package/lib/types/adapters/model.d.ts +31 -0
  12. package/lib/types/adapters/model.js +47 -0
  13. package/lib/types/adapters/select.js +12 -8
  14. package/lib/types/approve-command.d.ts +14 -1
  15. package/lib/types/approve-command.js +72 -44
  16. package/lib/types/authoring-flow.d.ts +19 -1
  17. package/lib/types/authoring-flow.js +49 -15
  18. package/lib/types/channel-port.d.ts +3 -1
  19. package/lib/types/channel-port.js +8 -3
  20. package/lib/types/command-defs.d.ts +5 -0
  21. package/lib/types/command-defs.js +33 -0
  22. package/lib/types/command-surface.d.ts +5 -0
  23. package/lib/types/command-surface.js +11 -0
  24. package/lib/types/commands.d.ts +3 -4
  25. package/lib/types/commands.js +33 -72
  26. package/lib/types/commit-assembly.d.ts +16 -1
  27. package/lib/types/commit-assembly.js +72 -11
  28. package/lib/types/commit-execute.d.ts +14 -2
  29. package/lib/types/commit-execute.js +76 -14
  30. package/lib/types/commit-plan.d.ts +7 -1
  31. package/lib/types/commit-plan.js +4 -0
  32. package/lib/types/commit-receipt.d.ts +3 -0
  33. package/lib/types/commit-recovery.d.ts +7 -2
  34. package/lib/types/commit-recovery.js +73 -96
  35. package/lib/types/contracts.d.ts +44 -1
  36. package/lib/types/contracts.js +1 -1
  37. package/lib/types/coordinator.d.ts +3 -2
  38. package/lib/types/coordinator.js +4 -3
  39. package/lib/types/decision.js +1 -1
  40. package/lib/types/doctor.d.ts +18 -1
  41. package/lib/types/doctor.js +164 -10
  42. package/lib/types/domain/authoring-manifest.d.ts +26 -0
  43. package/lib/types/domain/authoring-manifest.js +43 -0
  44. package/lib/types/domain/claim.d.ts +4 -1
  45. package/lib/types/domain/claim.js +2 -2
  46. package/lib/types/domain/commit-journal-store.js +5 -1
  47. package/lib/types/domain/commit-journal.js +1 -1
  48. package/lib/types/domain/consistency.d.ts +22 -0
  49. package/lib/types/domain/consistency.js +50 -0
  50. package/lib/types/domain/engine-criteria.d.ts +10 -0
  51. package/lib/types/domain/engine-criteria.js +11 -0
  52. package/lib/types/domain/engine-input.d.ts +10 -0
  53. package/lib/types/domain/engine-input.js +26 -0
  54. package/lib/types/domain/errors.d.ts +15 -1
  55. package/lib/types/domain/errors.js +23 -4
  56. package/lib/types/domain/evidence-hash.d.ts +3 -0
  57. package/lib/types/domain/evidence-hash.js +14 -0
  58. package/lib/types/domain/failure-route.d.ts +14 -0
  59. package/lib/types/domain/failure-route.js +12 -0
  60. package/lib/types/domain/ledger.js +10 -1
  61. package/lib/types/domain/live-facts.d.ts +31 -0
  62. package/lib/types/domain/live-facts.js +54 -0
  63. package/lib/types/domain/merge-limits.d.ts +18 -0
  64. package/lib/types/domain/merge-limits.js +22 -0
  65. package/lib/types/domain/merge-report.d.ts +41 -0
  66. package/lib/types/domain/merge-report.js +2 -0
  67. package/lib/types/domain/merge-snapshot.d.ts +99 -0
  68. package/lib/types/domain/merge-snapshot.js +82 -0
  69. package/lib/types/domain/migrate-v3.d.ts +20 -0
  70. package/lib/types/domain/migrate-v3.js +68 -0
  71. package/lib/types/domain/pair-ledger.d.ts +34 -0
  72. package/lib/types/domain/pair-ledger.js +96 -0
  73. package/lib/types/domain/pre-1b-backup.d.ts +43 -0
  74. package/lib/types/domain/pre-1b-backup.js +140 -0
  75. package/lib/types/domain/provider-key.d.ts +2 -0
  76. package/lib/types/domain/provider-key.js +11 -0
  77. package/lib/types/domain/read-probe.d.ts +5 -0
  78. package/lib/types/domain/read-probe.js +11 -0
  79. package/lib/types/domain/reconcile.d.ts +47 -0
  80. package/lib/types/domain/reconcile.js +178 -0
  81. package/lib/types/domain/resolve-pair.d.ts +2 -9
  82. package/lib/types/domain/resolve-pair.js +2 -15
  83. package/lib/types/domain/row-tx.d.ts +27 -0
  84. package/lib/types/domain/row-tx.js +31 -0
  85. package/lib/types/domain/row-v3-bridge.d.ts +6 -0
  86. package/lib/types/domain/row-v3-bridge.js +47 -0
  87. package/lib/types/domain/row-v3.d.ts +113 -0
  88. package/lib/types/domain/row-v3.js +107 -0
  89. package/lib/types/domain/row-v4.d.ts +148 -0
  90. package/lib/types/domain/row-v4.js +159 -0
  91. package/lib/types/domain/row.d.ts +1 -0
  92. package/lib/types/domain/run-coordinator.d.ts +51 -0
  93. package/lib/types/domain/run-coordinator.js +45 -0
  94. package/lib/types/domain/schema4.d.ts +53 -0
  95. package/lib/types/domain/schema4.js +129 -0
  96. package/lib/types/domain/selection-policy.d.ts +14 -0
  97. package/lib/types/domain/selection-policy.js +6 -0
  98. package/lib/types/domain/skipped-expiry.d.ts +5 -0
  99. package/lib/types/domain/skipped-expiry.js +16 -0
  100. package/lib/types/domain/stage-aggregate.d.ts +96 -0
  101. package/lib/types/domain/stage-aggregate.js +293 -0
  102. package/lib/types/domain/staging-index.d.ts +15 -0
  103. package/lib/types/domain/staging-index.js +41 -0
  104. package/lib/types/domain/state-bootstrap.d.ts +43 -0
  105. package/lib/types/domain/state-bootstrap.js +267 -0
  106. package/lib/types/domain/state-v3.d.ts +4 -0
  107. package/lib/types/domain/state-v3.js +4 -0
  108. package/lib/types/domain/undo-journal.d.ts +19 -0
  109. package/lib/types/domain/undo-journal.js +20 -0
  110. package/lib/types/domain/undo-receipt.d.ts +18 -0
  111. package/lib/types/domain/undo-receipt.js +22 -0
  112. package/lib/types/domain/validation-plan.d.ts +65 -0
  113. package/lib/types/domain/validation-plan.js +49 -0
  114. package/lib/types/facts.d.ts +26 -0
  115. package/lib/types/facts.js +65 -0
  116. package/lib/types/gate-filter.d.ts +31 -0
  117. package/lib/types/gate-filter.js +64 -0
  118. package/lib/types/index.d.ts +7 -2
  119. package/lib/types/index.js +185 -82
  120. package/lib/types/ladder.js +1 -1
  121. package/lib/types/merge-command.d.ts +3 -1
  122. package/lib/types/merge-command.js +10 -6
  123. package/lib/types/merge-routes.d.ts +12 -7
  124. package/lib/types/merge-routes.js +87 -23
  125. package/lib/types/merge-tool.d.ts +1 -1
  126. package/lib/types/merge-tool.js +2 -2
  127. package/lib/types/metrics.d.ts +16 -0
  128. package/lib/types/metrics.js +27 -0
  129. package/lib/types/new-child-authoring.d.ts +5 -1
  130. package/lib/types/new-child-authoring.js +71 -13
  131. package/lib/types/params.d.ts +2 -4
  132. package/lib/types/params.js +13 -0
  133. package/lib/types/pending-actions.d.ts +2 -2
  134. package/lib/types/pending-actions.js +17 -17
  135. package/lib/types/prompts.d.ts +2 -4
  136. package/lib/types/prompts.js +21 -8
  137. package/lib/types/propose.d.ts +26 -0
  138. package/lib/types/propose.js +69 -0
  139. package/lib/types/read-commands.d.ts +4 -4
  140. package/lib/types/read-commands.js +19 -26
  141. package/lib/types/recovery-coordinator.d.ts +34 -0
  142. package/lib/types/recovery-coordinator.js +84 -0
  143. package/lib/types/recovery-projection.d.ts +31 -0
  144. package/lib/types/recovery-projection.js +96 -0
  145. package/lib/types/resume-command.d.ts +7 -1
  146. package/lib/types/resume-command.js +50 -14
  147. package/lib/types/revision.d.ts +17 -0
  148. package/lib/types/revision.js +39 -3
  149. package/lib/types/round2-outcome.d.ts +2 -1
  150. package/lib/types/round2-outcome.js +68 -12
  151. package/lib/types/run-shell.d.ts +16 -2
  152. package/lib/types/run-shell.js +49 -5
  153. package/lib/types/runtime.d.ts +4 -6
  154. package/lib/types/runtime.js +341 -86
  155. package/lib/types/scan-command.d.ts +1 -0
  156. package/lib/types/scan-command.js +2 -0
  157. package/lib/types/scan-ports.d.ts +21 -1
  158. package/lib/types/scan-ports.js +51 -23
  159. package/lib/types/scan-run.d.ts +17 -0
  160. package/lib/types/scan-run.js +221 -63
  161. package/lib/types/screening-port.d.ts +47 -0
  162. package/lib/types/screening-port.js +93 -0
  163. package/lib/types/screening.d.ts +30 -0
  164. package/lib/types/screening.js +75 -0
  165. package/lib/types/stage-tool-registry.d.ts +7 -0
  166. package/lib/types/stage-tool-registry.js +12 -1
  167. package/lib/types/state.d.ts +15 -1
  168. package/lib/types/state.js +39 -17
  169. package/lib/types/status.d.ts +8 -0
  170. package/lib/types/status.js +64 -0
  171. package/lib/types/surface/body.d.ts +4 -0
  172. package/lib/types/surface/body.js +16 -0
  173. package/lib/types/surface/merge-view.d.ts +14 -0
  174. package/lib/types/surface/merge-view.js +126 -0
  175. package/lib/types/target-claim.d.ts +1 -0
  176. package/lib/types/target-claim.js +63 -18
  177. package/lib/types/undo-operation.d.ts +24 -0
  178. package/lib/types/undo-operation.js +135 -0
  179. package/lib/types/unit-claim.d.ts +5 -0
  180. package/lib/types/unit-claim.js +15 -0
  181. package/lib/types/wire-projection.d.ts +36 -0
  182. package/lib/types/wire-projection.js +69 -0
  183. package/lib/types/wire.d.ts +273 -0
  184. package/lib/types/wire.js +3 -0
  185. package/package.json +10 -4
  186. package/lib/types/routes.d.ts +0 -70
  187. package/lib/types/routes.js +0 -78
@@ -0,0 +1,33 @@
1
+ /** Merge CommandDef data; parser/help/surface/fallback live in evolution-commands. */
2
+ import { readFileSync } from 'node:fs';
3
+ const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
4
+ export const MERGE_PACKAGE_VERSION = manifest.version ?? '0.30.18';
5
+ export const MERGE_ALIAS_DEF = { id: 'merge.alias', words: ['merge'], summaryKey: 'merge.alias', helpKey: 'merge.alias.help', flags: [], positional: [], readOnly: false, needsRun: false, fallback: '/merge', deprecatedAt: '0.31' };
6
+ const pos = (name, kind, required = true) => ({ name, kind, required });
7
+ const flag = (name, kind, valueName, required = false) => ({ name, kind, ...(valueName === undefined ? {} : { valueName }), ...(required ? { required: true } : {}) });
8
+ const action = (id, command, confirm = false) => ({ id, command, confirm });
9
+ const cmd = (id, words, flags, positional, readOnly, needsRun, rowActions, fallback) => ({ id, words, summaryKey: id, helpKey: id + '.help', flags, positional, readOnly, needsRun, rowActions, fallback });
10
+ export const MERGE_COMMAND_DEFS = [
11
+ ({ ...({ ...cmd('merge.doctor', ['doctor'], [], [], false, false, [], '/evolution merge doctor'), usage: false }), surface: false }),
12
+ ({ ...cmd('merge.reconcile', ['reconcile'], [], [], false, false, [], '/evolution merge reconcile'), surface: false }),
13
+ cmd('merge.scan', ['scan'], [], [], false, true, [], '/evolution merge scan'),
14
+ cmd('merge.pair', ['pair'], [], [pos('a', 'skill'), pos('b', 'skill')], false, true, [action('pair', '/evolution merge pair {a} {b}', true)], '/evolution merge pair <a> <b>'),
15
+ cmd('merge.pair.force', ['pair'], [flag('force', 'boolean')], [pos('a', 'skill'), pos('b', 'skill')], false, true, [action('pair', '/evolution merge pair {a} {b}', true)], '/evolution merge pair --force <a> <b>'),
16
+ cmd('merge.status', ['status'], [], [pos('id', 'id', false)], true, false, [action('status', '/evolution merge status {runId}')], '/evolution merge status'),
17
+ cmd('merge.report', ['report'], [], [pos('id', 'id', false)], true, false, [action('report', '/evolution merge report {runId}')], '/evolution merge report'),
18
+ cmd('merge.cancel', ['cancel'], [], [pos('id', 'id', false)], false, false, [action('cancel', '/evolution merge cancel {runId}')], '/evolution merge cancel <id>'),
19
+ cmd('merge.runs', ['runs'], [], [], true, false, [], '/evolution merge runs'),
20
+ cmd('merge.pending', ['pending'], [], [], true, false, [], '/evolution merge pending'),
21
+ cmd('merge.pending.expired', ['pending'], [flag('expired', 'boolean')], [], true, false, [], '/evolution merge pending --expired'),
22
+ cmd('merge.approve', ['approve'], [], [pos('id', 'id')], false, true, [action('approve', '/evolution merge approve {id}')], '/evolution merge approve <id>'),
23
+ cmd('merge.reject', ['reject'], [], [pos('id', 'id')], false, false, [action('reject', '/evolution merge reject {id}', true)], '/evolution merge reject <id>'),
24
+ cmd('merge.skipped', ['skipped'], [], [], true, false, [], '/evolution merge skipped'),
25
+ cmd('merge.skipped.restore', ['skipped'], [flag('restore', 'value', 'id')], [], false, false, [action('restore', '/evolution merge skipped --restore {id}', true)], '/evolution merge skipped --restore <id>'),
26
+ cmd('merge.candidates', ['candidates'], [], [], true, false, [], '/evolution merge candidates'),
27
+ cmd('merge.candidates.retry', ['candidates'], [flag('retry', 'boolean')], [pos('id', 'id', false)], false, true, [action('retry-candidates', '/evolution merge candidates --retry {id}')], '/evolution merge candidates --retry <id>'),
28
+ cmd('merge.resume', ['resume'], [flag('clear', 'value', 'id')], [pos('id', 'id', false)], true, false, [action('retry-resume', '/evolution merge resume --retry {id}')], '/evolution merge resume'),
29
+ cmd('merge.resume.retry', ['resume'], [flag('retry', 'boolean'), flag('clear', 'value', 'id')], [pos('id', 'id', false)], false, true, [action('retry-resume', '/evolution merge resume --retry {id}')], '/evolution merge resume --retry <id>'),
30
+ cmd('merge.discard', ['discard'], [], [pos('id', 'id')], false, false, [action('discard', '/evolution merge discard {id}', true)], '/evolution merge discard <id>'),
31
+ cmd('merge.undo', ['undo'], [flag('run', 'value', 'id', true)], [pos('target', 'target')], false, false, [], '/evolution merge undo <target> --run <id>'),
32
+ ];
33
+ //# sourceMappingURL=command-defs.js.map
@@ -0,0 +1,5 @@
1
+ /** Generated merge CommandSurface from command-defs; no handwritten table. */
2
+ import type { CommandSpec, CommandSurface } from './wire.ts';
3
+ export declare const MERGE_COMMANDS: readonly CommandSpec[];
4
+ export declare const MERGE_COMMAND_SURFACE: CommandSurface;
5
+ //# sourceMappingURL=command-surface.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { buildSurface } from '@lmzhen/dsh-evolution-commands';
2
+ import { MERGE_COMMAND_DEFS } from "./command-defs.js";
3
+ const generated = buildSurface(MERGE_COMMAND_DEFS, '/evolution merge');
4
+ export const MERGE_COMMANDS = generated.commands.map(command => ({
5
+ id: command.id, words: command.words, flags: command.flags, positional: command.positional,
6
+ summary: command.id, readOnly: command.readOnly, needsRun: command.needsRun, defaults: command.defaults,
7
+ rowActions: command.rowActions.map(action => ({ id: action.id, command: action.command, confirm: action.confirm })),
8
+ fallback: command.fallback,
9
+ }));
10
+ export const MERGE_COMMAND_SURFACE = { wireVersion: 1, prefix: '/evolution merge', commands: MERGE_COMMANDS, fallback: generated.fallback.map(item => ({ id: item.id, command: item.command })) };
11
+ //# sourceMappingURL=command-surface.js.map
@@ -1,7 +1,3 @@
1
- /**
2
- * Pure command-surface parser for the /evolution merge prefix. The command layer only
3
- * validates shape; run/state effects are injected by the host shell.
4
- */
5
1
  export type MergeCliCommand = {
6
2
  readonly verb: 'scan';
7
3
  } | {
@@ -22,12 +18,15 @@ export type MergeCliCommand = {
22
18
  readonly verb: 'runs';
23
19
  } | {
24
20
  readonly verb: 'doctor';
21
+ } | {
22
+ readonly verb: 'reconcile';
25
23
  } | {
26
24
  readonly verb: 'pending';
27
25
  readonly expired?: true;
28
26
  } | {
29
27
  readonly verb: 'candidates';
30
28
  readonly retry?: true;
29
+ readonly id?: string;
31
30
  } | {
32
31
  readonly verb: 'approve';
33
32
  readonly id: string;
@@ -1,79 +1,40 @@
1
- const USAGE = 'usage: /evolution merge scan | pair [--force] <a> <b> | status [id] | report [id] | cancel [id] | runs | doctor | pending [--expired] | candidates [--retry] | approve <id> | reject <id> | resume [id] [--retry] [--clear <id>] | skipped [--restore <id>] | discard <id> | undo <target> --run <id>\ndefaults: mergeApproval=false, mergeSkippedTtlDays=0, mergeRunTimeoutMinutes=0, mergePairTimeoutMinutes=15, mergeModelsRoot=""';
2
- function flags(args, allowed) {
3
- const values = new Map();
4
- for (let index = 0; index < args.length; index += 1) {
5
- const token = args[index];
6
- if (!token.startsWith('--') || token === '--force' || token === '--expired' || token === '--retry')
7
- continue;
8
- const name = token.slice(2);
9
- if (!allowed.includes(name))
10
- return { values, error: 'unknown flag: --' + name };
11
- const value = args[index + 1];
12
- if (value === undefined || value.startsWith('--'))
13
- return { values, error: 'missing value for --' + name };
14
- values.set(name, value);
15
- index += 1;
16
- }
17
- return { values };
18
- }
19
- function switches(args) {
20
- return new Set(args.filter(token => token === '--force' || token === '--expired' || token === '--retry').map(token => token.slice(2)));
21
- }
22
- function positional(args) {
23
- const filtered = args.filter(token => token !== '--force' && token !== '--expired' && token !== '--retry');
24
- return filtered.filter((token, index) => !token.startsWith('--') && !(index > 0 && filtered[index - 1].startsWith('--')));
25
- }
1
+ /** Merge command adapter over the family command catalog. No parser/usage table here. */
2
+ import { parseCommand } from '@lmzhen/dsh-evolution-commands';
3
+ import { MERGE_COMMAND_DEFS } from "./command-defs.js";
4
+ function text(value) { return typeof value === 'string' ? value : undefined; }
26
5
  export function parseMergeCommand(argv) {
27
- const [verb, ...rest] = argv;
28
- if (verb === undefined || verb === 'help' || verb === '--help' || verb === '-h')
6
+ const parsed = parseCommand(MERGE_COMMAND_DEFS, argv);
7
+ if (!parsed.ok)
8
+ return parsed;
9
+ if (parsed.help === true)
29
10
  return { ok: true, command: { verb: 'scan' }, help: true };
30
- const pos = positional(rest);
31
- const marked = switches(rest);
32
- switch (verb) {
33
- case 'scan': return pos.length === 0 ? { ok: true, command: { verb: 'scan' } } : { ok: false, code: 'usage', message: USAGE };
34
- case 'pair': return pos.length === 2 ? { ok: true, command: { verb: 'pair', a: pos[0], b: pos[1], ...(marked.has('force') ? { force: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
35
- case 'status': return pos.length <= 1 ? { ok: true, command: { verb: 'status', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
36
- case 'report': return pos.length <= 1 ? { ok: true, command: { verb: 'report', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
37
- case 'cancel': return pos.length <= 1 ? { ok: true, command: { verb: 'cancel', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
38
- case 'runs': return pos.length === 0 ? { ok: true, command: { verb: 'runs' } } : { ok: false, code: 'usage', message: USAGE };
39
- case 'doctor': return pos.length === 0 ? { ok: true, command: { verb: 'doctor' } } : { ok: false, code: 'usage', message: USAGE };
40
- case 'pending': return pos.length === 0 ? { ok: true, command: { verb: 'pending', ...(marked.has('expired') ? { expired: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
41
- case 'candidates': return pos.length === 0 ? { ok: true, command: { verb: 'candidates', ...(marked.has('retry') ? { retry: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
42
- case 'approve': return pos.length === 1 ? { ok: true, command: { verb: 'approve', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
43
- case 'reject': return pos.length === 1 ? { ok: true, command: { verb: 'reject', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
44
- case 'resume': {
45
- const parsed = flags(rest, ['clear']);
46
- if (parsed.error !== undefined)
47
- return { ok: false, code: 'usage', message: parsed.error };
48
- const clear = parsed.values.get('clear');
49
- if (pos.length > 1)
50
- return { ok: false, code: 'usage', message: USAGE };
51
- return { ok: true, command: { verb: 'resume', ...(pos[0] === undefined ? {} : { id: pos[0] }), ...(clear === undefined ? {} : { clear }), ...(marked.has('retry') ? { retry: true } : {}) } };
52
- }
53
- case 'skipped': {
54
- const parsed = flags(rest, ['restore']);
55
- if (parsed.error !== undefined)
56
- return { ok: false, code: 'usage', message: parsed.error };
57
- if (pos.length > 0)
58
- return { ok: false, code: 'usage', message: USAGE };
59
- const restore = parsed.values.get('restore');
60
- return { ok: true, command: { verb: 'skipped', ...(restore === undefined ? {} : { restore }) } };
61
- }
62
- case 'discard': return pos.length === 1 ? { ok: true, command: { verb: 'discard', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
63
- case 'undo': {
64
- const parsed = flags(rest, ['run']);
65
- if (parsed.error !== undefined)
66
- return { ok: false, code: 'usage', message: parsed.error };
67
- const target = pos[0];
68
- const run = parsed.values.get('run');
69
- if (target === undefined || run === undefined || pos.length !== 1)
70
- return { ok: false, code: 'usage', message: USAGE };
71
- return { ok: true, command: { verb: 'undo', target, run } };
72
- }
73
- default: return { ok: false, code: 'usage', message: 'unknown merge command: ' + verb + '\n' + USAGE };
11
+ const v = parsed.values;
12
+ switch (parsed.id) {
13
+ case 'merge.scan': return { ok: true, command: { verb: 'scan' } };
14
+ case 'merge.doctor': return { ok: true, command: { verb: 'doctor' } };
15
+ case 'merge.reconcile': return { ok: true, command: { verb: 'reconcile' } };
16
+ case 'merge.pair': return { ok: true, command: { verb: 'pair', a: text(v.a) ?? '', b: text(v.b) ?? '' } };
17
+ case 'merge.pair.force': return { ok: true, command: { verb: 'pair', a: text(v.a) ?? '', b: text(v.b) ?? '', force: true } };
18
+ case 'merge.status': return { ok: true, command: { verb: 'status', ...(text(v.id) === undefined ? {} : { id: text(v.id) }) } };
19
+ case 'merge.report': return { ok: true, command: { verb: 'report', ...(text(v.id) === undefined ? {} : { id: text(v.id) }) } };
20
+ case 'merge.cancel': return { ok: true, command: { verb: 'cancel', ...(text(v.id) === undefined ? {} : { id: text(v.id) }) } };
21
+ case 'merge.runs': return { ok: true, command: { verb: 'runs' } };
22
+ case 'merge.pending': return { ok: true, command: { verb: 'pending' } };
23
+ case 'merge.pending.expired': return { ok: true, command: { verb: 'pending', expired: true } };
24
+ case 'merge.approve': return { ok: true, command: { verb: 'approve', id: text(v.id) ?? '' } };
25
+ case 'merge.reject': return { ok: true, command: { verb: 'reject', id: text(v.id) ?? '' } };
26
+ case 'merge.skipped': return { ok: true, command: { verb: 'skipped' } };
27
+ case 'merge.skipped.restore': return { ok: true, command: { verb: 'skipped', restore: text(v.restore) ?? '' } };
28
+ case 'merge.candidates': return { ok: true, command: { verb: 'candidates' } };
29
+ case 'merge.candidates.retry': return { ok: true, command: { verb: 'candidates', retry: true, ...(text(v.id) === undefined ? {} : { id: text(v.id) }) } };
30
+ case 'merge.resume': return { ok: true, command: { verb: 'resume', ...(text(v.id) === undefined ? {} : { id: text(v.id) }), ...(text(v.clear) === undefined ? {} : { clear: text(v.clear) }) } };
31
+ case 'merge.resume.retry': return { ok: true, command: { verb: 'resume', retry: true, ...(text(v.id) === undefined ? {} : { id: text(v.id) }), ...(text(v.clear) === undefined ? {} : { clear: text(v.clear) }) } };
32
+ case 'merge.discard': return { ok: true, command: { verb: 'discard', id: text(v.id) ?? '' } };
33
+ case 'merge.undo': return { ok: true, command: { verb: 'undo', target: text(v.target) ?? '', run: text(v.run) ?? '' } };
34
+ default: return { ok: false, code: 'usage', message: 'unknown merge command: ' + parsed.id };
74
35
  }
75
36
  }
76
37
  export function mergeUsage() {
77
- return USAGE;
38
+ 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=""';
78
39
  }
79
40
  //# sourceMappingURL=commands.js.map
@@ -3,24 +3,39 @@
3
3
  *
4
4
  * Source bodies come from the LIVE tree (the plan's CAS baseline), the target body from
5
5
  * the merged tree. This function is pure except for the injected reads; it never commits.
6
+ * It also re-checks the round-1 decision anchors against the live tree BEFORE the commit
7
+ * port is called, so a source/target change is refused with a typed cause rather than a
8
+ * generic commit message.
6
9
  */
7
- import type { CommitMergePlan, WriteOrigin } from '@lmzhen/dsh-evolution-core';
10
+ import type { CommitMergePlan, WriteOrigin, SkillLimits } from '@lmzhen/dsh-evolution-core';
11
+ import { type RevisionFile } from '@lmzhen/dsh-evolution-core';
8
12
  import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
13
+ import type { RevisionRef } from './contracts.ts';
9
14
  export interface CommitAssemblyPort {
10
15
  /** Current SKILL.md body, or null when the skill is not live. */
11
16
  readSkill(name: string): Promise<string | null>;
17
+ /** Every support file of a live skill; absent means the port cannot enumerate them. */
18
+ readSupportFiles?(name: string): Promise<readonly RevisionFile[]>;
12
19
  /** Whether the name is already occupied by an archived (tombstoned) skill. */
13
20
  isArchived?(name: string): Promise<boolean>;
21
+ /** Whether a directory exists for the name (including a SKILL.md-less ghost). */
22
+ isNameOccupied?(name: string): Promise<boolean>;
14
23
  }
15
24
  export interface CommitAssemblyOptions {
16
25
  readonly origin: WriteOrigin;
17
26
  readonly summary: string;
27
+ readonly limits?: SkillLimits;
28
+ /** Round-1 decision anchors re-checked here (the store repeats the check under its lock). */
29
+ readonly targetRevision?: RevisionRef;
30
+ readonly sourceRevisions?: Readonly<Record<string, RevisionRef>>;
31
+ readonly supportDigest?: string;
18
32
  }
19
33
  export type CommitAssemblyResult = {
20
34
  readonly ok: true;
21
35
  readonly plan: CommitMergePlan;
22
36
  } | {
23
37
  readonly ok: false;
38
+ readonly code?: 'target-changed' | 'source-changed' | 'support-changed';
24
39
  readonly message: string;
25
40
  };
26
41
  export declare function assembleStagedCommit(io: EvolutionIoLike, home: string, runId: string, port: CommitAssemblyPort, options: CommitAssemblyOptions): Promise<CommitAssemblyResult>;
@@ -1,7 +1,55 @@
1
- import { isPresent } from '@lmzhen/dsh-evolution-core';
1
+ import { isPresent, revisionAnchorOf, revisionFingerprint, revisionSupportDigest } from '@lmzhen/dsh-evolution-core';
2
2
  import { readStaging } from "./staging.js";
3
3
  import { readStagedTree } from "./staged-tree.js";
4
4
  import { buildCommitPlan } from "./commit-plan.js";
5
+ function sameAnchor(expected, live) {
6
+ if (expected.kind === 'absent')
7
+ return live.kind === 'absent';
8
+ return live.kind === 'present' && expected.sha256 === live.sha256;
9
+ }
10
+ async function liveRevision(port, name, body) {
11
+ if (port.readSupportFiles === undefined)
12
+ return { verified: false };
13
+ let files;
14
+ try {
15
+ files = body === null ? [] : await port.readSupportFiles(name);
16
+ }
17
+ catch {
18
+ return { verified: false };
19
+ }
20
+ return { verified: true, anchor: revisionAnchorOf(name, body, files), fingerprint: body === null ? null : revisionFingerprint(name, body, files) };
21
+ }
22
+ /** Why the staging tree no longer matches the decision, or null when it still does. */
23
+ async function decisionRevisionFailure(port, requested, targetCurrent, sources, options) {
24
+ if (options.targetRevision === undefined && options.sourceRevisions === undefined && options.supportDigest === undefined)
25
+ return null;
26
+ const target = await liveRevision(port, requested, targetCurrent);
27
+ if (!target.verified)
28
+ return null;
29
+ if (options.targetRevision !== undefined && !sameAnchor(options.targetRevision, target.anchor)) {
30
+ return { code: 'target-changed', message: '目标技能「' + requested + '」的内容在决策之后被改过;拒绝提交(未写入任何内容)。' };
31
+ }
32
+ const fingerprints = target.fingerprint === null ? [] : [target.fingerprint];
33
+ for (const source of sources) {
34
+ // The staging manifest lists the target among its sources; the digest is over the
35
+ // participating skills (target + absorbed sources), so the target appears once.
36
+ if (source.name === requested)
37
+ continue;
38
+ const live = await liveRevision(port, source.name, source.body);
39
+ if (!live.verified)
40
+ continue;
41
+ const expected = options.sourceRevisions?.[source.name];
42
+ if (expected !== undefined && !sameAnchor(expected, live.anchor)) {
43
+ return { code: 'source-changed', message: '来源技能「' + source.name + '」的内容在决策之后被改过;拒绝提交(未写入任何内容)。' };
44
+ }
45
+ if (live.fingerprint !== null)
46
+ fingerprints.push(live.fingerprint);
47
+ }
48
+ if (options.supportDigest !== undefined && revisionSupportDigest(fingerprints) !== options.supportDigest) {
49
+ return { code: 'support-changed', message: '参与合并的技能支撑文件在决策之后被改过;拒绝提交(未写入任何内容)。' };
50
+ }
51
+ return null;
52
+ }
5
53
  export async function assembleStagedCommit(io, home, runId, port, options) {
6
54
  const staging = await readStaging(io, home, runId);
7
55
  if (!isPresent(staging))
@@ -10,15 +58,31 @@ export async function assembleStagedCommit(io, home, runId, port, options) {
10
58
  if (!isPresent(tree))
11
59
  return { ok: false, message: 'merged tree is not readable for run ' + runId };
12
60
  const requested = staging.value.manifest.target;
61
+ const requestedCurrent = await port.readSkill(requested);
13
62
  let target = requested;
14
- let targetCurrent = await port.readSkill(requested);
63
+ let targetCurrent = requestedCurrent;
64
+ if (staging.value.manifest.mode !== 'create-umbrella' && targetCurrent === null) {
65
+ return { ok: false, message: 'target is gone; refusing to re-create it: ' + requested };
66
+ }
67
+ const sources = [];
68
+ for (const name of staging.value.manifest.sources) {
69
+ const body = await port.readSkill(name);
70
+ if (body === null)
71
+ return { ok: false, message: 'source skill is not live: ' + name };
72
+ sources.push({ name, body });
73
+ }
74
+ const revisionFailure = await decisionRevisionFailure(port, requested, requestedCurrent, sources, options);
75
+ if (revisionFailure !== null)
76
+ return { ok: false, code: revisionFailure.code, message: revisionFailure.message };
15
77
  const requestedArchived = targetCurrent === null && await port.isArchived?.(requested) === true;
16
- if (staging.value.manifest.mode === 'create-umbrella' && (targetCurrent !== null || requestedArchived)) {
78
+ const requestedOccupied = targetCurrent === null && await port.isNameOccupied?.(requested) === true;
79
+ if (staging.value.manifest.mode === 'create-umbrella' && (targetCurrent !== null || requestedArchived || requestedOccupied)) {
17
80
  target = requested;
18
81
  for (let suffix = 2; suffix <= 99; suffix += 1) {
19
82
  const candidate = requested + '-v' + String(suffix);
20
83
  if (await port.readSkill(candidate) === null
21
- && await port.isArchived?.(candidate) !== true) {
84
+ && await port.isArchived?.(candidate) !== true
85
+ && await port.isNameOccupied?.(candidate) !== true) {
22
86
  target = candidate;
23
87
  targetCurrent = null;
24
88
  break;
@@ -27,13 +91,6 @@ export async function assembleStagedCommit(io, home, runId, port, options) {
27
91
  if (target === requested)
28
92
  return { ok: false, message: 'no free umbrella target for ' + requested };
29
93
  }
30
- const sources = [];
31
- for (const name of staging.value.manifest.sources) {
32
- const body = await port.readSkill(name);
33
- if (body === null)
34
- return { ok: false, message: 'source skill is not live: ' + name };
35
- sources.push({ name, body });
36
- }
37
94
  return buildCommitPlan({
38
95
  target,
39
96
  create: targetCurrent === null,
@@ -43,6 +100,10 @@ export async function assembleStagedCommit(io, home, runId, port, options) {
43
100
  sourceBodies: sources,
44
101
  origin: options.origin,
45
102
  summary: options.summary,
103
+ ...(options.limits === undefined ? {} : { limits: options.limits }),
104
+ ...(options.targetRevision === undefined ? {} : { targetRevision: options.targetRevision }),
105
+ ...(options.sourceRevisions === undefined ? {} : { sourceRevisions: options.sourceRevisions }),
106
+ ...(options.supportDigest === undefined ? {} : { supportDigest: options.supportDigest }),
46
107
  });
47
108
  }
48
109
  //# sourceMappingURL=commit-assembly.js.map
@@ -3,11 +3,14 @@
3
3
  * receipt. A failed commit returns without a receipt; a landed commit with a failed receipt
4
4
  * stays successful and carries a warning.
5
5
  */
6
- import type { CommitMergePlan, CommitMergeResult, EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
6
+ import type { CommitMergePlan, CommitMergeResult, EvolutionIoLike, SkillLimits } from '@lmzhen/dsh-evolution-core';
7
7
  import { type CommitAssemblyPort } from './commit-assembly.ts';
8
8
  import { type CommitReceipt } from './commit-receipt.ts';
9
+ import type { UnitTransaction } from '@lmzhen/dsh-evolution-core';
9
10
  export interface CommitPort extends CommitAssemblyPort {
10
- commitMerge(plan: CommitMergePlan): Promise<CommitMergeResult>;
11
+ /** Transitional G2: when set, commit-execute prefers this over commitMerge. */
12
+ unit?: UnitTransaction;
13
+ commitMerge?(plan: CommitMergePlan): Promise<CommitMergeResult>;
11
14
  readSupportFiles?(name: string): Promise<readonly {
12
15
  readonly path: string;
13
16
  readonly content: string;
@@ -17,6 +20,14 @@ export interface CommitStagedOptions {
17
20
  readonly origin: CommitMergePlan['origin'];
18
21
  readonly summary: string;
19
22
  readonly now: () => number;
23
+ readonly isBlocked?: (name: string) => boolean | Promise<boolean>;
24
+ readonly limits?: SkillLimits;
25
+ readonly validationPlan?: import('./contracts.ts').MergeValidationPlanRef;
26
+ readonly targetRevision?: import('./contracts.ts').RevisionRef;
27
+ readonly sourceRevisions?: Readonly<Record<string, import('./contracts.ts').RevisionRef>>;
28
+ readonly supportDigest?: string;
29
+ /** G2 transitional: when present, commit through core UnitTransaction. */
30
+ readonly unit?: UnitTransaction;
20
31
  }
21
32
  export type CommitStagedResult = {
22
33
  readonly ok: true;
@@ -28,6 +39,7 @@ export type CommitStagedResult = {
28
39
  readonly ok: false;
29
40
  readonly message: string;
30
41
  readonly plan?: CommitMergePlan;
42
+ readonly code?: CommitMergeResult['code'];
31
43
  };
32
44
  export declare function commitStagedRun(io: EvolutionIoLike, home: string, runId: string, port: CommitPort, options: CommitStagedOptions): Promise<CommitStagedResult>;
33
45
  //# sourceMappingURL=commit-execute.d.ts.map
@@ -2,23 +2,59 @@ import { assembleStagedCommit } from "./commit-assembly.js";
2
2
  import { writeCommitReceipt } from "./commit-receipt.js";
3
3
  import { pairKey } from "./contracts.js";
4
4
  import { writeJournal } from "./domain/commit-journal-store.js";
5
- import { snapshotPath, writeCommitSnapshot } from "./commit-snapshot.js";
5
+ import { evidenceSha256 as digestEvidence } from "./domain/evidence-hash.js";
6
+ import { snapshotPath } from "./commit-snapshot.js";
6
7
  export async function commitStagedRun(io, home, runId, port, options) {
7
- const assembled = await assembleStagedCommit(io, home, runId, port, { origin: options.origin, summary: options.summary });
8
+ const assembled = await assembleStagedCommit(io, home, runId, port, {
9
+ origin: options.origin,
10
+ summary: options.summary,
11
+ ...(options.limits === undefined ? {} : { limits: options.limits }),
12
+ ...(options.targetRevision === undefined ? {} : { targetRevision: options.targetRevision }),
13
+ ...(options.sourceRevisions === undefined ? {} : { sourceRevisions: options.sourceRevisions }),
14
+ ...(options.supportDigest === undefined ? {} : { supportDigest: options.supportDigest }),
15
+ });
8
16
  if (!assembled.ok)
9
- return { ok: false, message: assembled.message };
10
- try {
11
- await writeCommitSnapshot(io, home, runId, assembled.plan, options.now(), port);
12
- }
13
- catch (error) {
14
- return { ok: false, message: 'commit snapshot failed; refusing to write: ' + (error instanceof Error ? error.message : String(error)), plan: assembled.plan };
17
+ return { ok: false, message: assembled.message, ...(assembled.code === undefined ? {} : { code: assembled.code }) };
18
+ if (await options.isBlocked?.(assembled.plan.target) === true) {
19
+ return { ok: false, message: 'target is blocked by curator: ' + assembled.plan.target, plan: assembled.plan };
15
20
  }
16
- const journal = { runId, target: assembled.plan.target, create: !assembled.plan.targetExists, sources: assembled.plan.sources, phase: 'sources-archived', updatedAt: new Date(options.now()).toISOString() };
17
- try {
18
- await writeJournal(io, home, journal);
21
+ const journal = { runId, target: assembled.plan.target, create: !assembled.plan.targetExists, sources: assembled.plan.sources, phase: 'prepared', updatedAt: new Date(options.now()).toISOString() };
22
+ let result;
23
+ let evidenceSha256;
24
+ const transaction = options.unit ?? port.unit ?? legacyUnitAdapter(port, assembled.plan, options, runId);
25
+ if (transaction === undefined)
26
+ return { ok: false, message: 'commit port missing: UnitTransaction or commitMerge required', plan: assembled.plan };
27
+ {
28
+ const first = assembled.plan.sources[0] ?? assembled.plan.target;
29
+ const unitPlan = { runId, pairKey: pairKey(assembled.plan.target, first), target: assembled.plan.target, targetExists: assembled.plan.targetExists, sources: assembled.plan.sources, body: assembled.plan.body, supportFiles: assembled.plan.supportFiles, expectedTargetBody: assembled.plan.expectedTargetBody, expectedSourceBodies: assembled.plan.expectedSourceBodies, origin: options.origin, summary: options.summary, ...(options.limits === undefined ? {} : { skillLimits: options.limits }), ...(options.validationPlan === undefined ? {} : { validationPlan: options.validationPlan }), ...(options.targetRevision === undefined ? {} : { targetRevision: options.targetRevision }), ...(options.sourceRevisions === undefined ? {} : { sourceRevisions: options.sourceRevisions }), ...(options.supportDigest === undefined ? {} : { supportDigest: options.supportDigest }) };
30
+ try {
31
+ await writeJournal(io, home, journal);
32
+ }
33
+ catch { /* compat mirror; unit journal is authority */ }
34
+ const prepared = await transaction.prepare(unitPlan);
35
+ if (!prepared.ok)
36
+ return { ok: false, message: prepared.message, plan: assembled.plan };
37
+ const committed = await transaction.commit(prepared.prepared);
38
+ if (!committed.ok) {
39
+ try {
40
+ await writeJournal(io, home, { ...journal, phase: 'failed', updatedAt: new Date(options.now()).toISOString() });
41
+ }
42
+ catch { }
43
+ ;
44
+ return { ok: false, message: committed.message, plan: assembled.plan, ...(committed.code === 'target-changed' || committed.code === 'source-changed' || committed.code === 'support-changed' ? { code: committed.code } : {}) };
45
+ }
46
+ const unitId = committed.evidence.unitId;
47
+ if (committed.evidence.unitId !== prepared.prepared.unitId && !prepared.prepared.unitId.startsWith('legacy:'))
48
+ return { ok: false, message: 'unit evidence does not match prepared unitId', plan: assembled.plan };
49
+ const finalize = transaction.finalize;
50
+ if (finalize !== undefined) {
51
+ const finalized = await finalize.call(transaction, unitId);
52
+ if (!finalized.ok)
53
+ return { ok: false, message: 'unit committed but finalize failed: ' + finalized.message, plan: assembled.plan };
54
+ }
55
+ evidenceSha256 = digestEvidence(committed.evidence);
56
+ result = { ok: true, message: 'merged', unitId, archived: committed.evidence.sources.filter(source => source.archived).map(source => source.name), mutations: committed.evidence.mutations };
19
57
  }
20
- catch { /* journal is best-effort; receipt remains the commit record */ }
21
- const result = await port.commitMerge(assembled.plan);
22
58
  if (!result.ok) {
23
59
  try {
24
60
  await writeJournal(io, home, { ...journal, phase: 'failed', updatedAt: new Date(options.now()).toISOString() });
@@ -26,7 +62,7 @@ export async function commitStagedRun(io, home, runId, port, options) {
26
62
  catch { }
27
63
  return { ok: false, message: result.message, plan: assembled.plan };
28
64
  }
29
- const committed = { ...journal, phase: 'committed', updatedAt: new Date(options.now()).toISOString() };
65
+ const committed = { ...journal, phase: 'committed', updatedAt: new Date(options.now()).toISOString(), ...(result.unitId === undefined ? {} : { unitId: result.unitId }) };
30
66
  try {
31
67
  await writeJournal(io, home, committed);
32
68
  }
@@ -35,6 +71,8 @@ export async function commitStagedRun(io, home, runId, port, options) {
35
71
  return { ok: true, plan: assembled.plan, result, warning: 'commit landed without a unitId; receipt not written' };
36
72
  const receipt = {
37
73
  runId,
74
+ ...(evidenceSha256 === undefined ? {} : { evidenceSha256 }),
75
+ ...(options.validationPlan === undefined ? {} : { validationPlan: options.validationPlan }),
38
76
  parentRunId: runId.split('/pairs/')[0] ?? runId,
39
77
  unitId: result.unitId,
40
78
  target: assembled.plan.target,
@@ -61,4 +99,28 @@ export async function commitStagedRun(io, home, runId, port, options) {
61
99
  };
62
100
  }
63
101
  }
102
+ function legacyUnitAdapter(port, plan, options, runId) {
103
+ if (port.commitMerge === undefined)
104
+ return undefined;
105
+ let evidence;
106
+ const unitId = 'legacy:' + plan.target + ':' + String(options.now());
107
+ return {
108
+ async prepare() { return { ok: true, prepared: { unitId, planDigest: unitId } }; },
109
+ async commit() {
110
+ if (port.commitMerge === undefined)
111
+ return { ok: false, code: 'unit-commit-failed', message: 'commitMerge missing' };
112
+ const result = await port.commitMerge(plan);
113
+ if (!result.ok)
114
+ return { ok: false, code: result.code ?? 'unit-commit-failed', message: result.message };
115
+ const actualUnitId = result.unitId ?? unitId;
116
+ evidence = { schemaVersion: 1, unitId: actualUnitId, runId, planDigest: actualUnitId, state: 'complete', target: { before: plan.expectedTargetBody, after: plan.body, supportDigest: '', created: !plan.targetExists }, sources: plan.expectedSourceBodies.map(source => ({ name: source.name, before: source.body, after: null, archived: (result.archived ?? []).includes(source.name), absorbedInto: plan.target })), mutations: result.mutations ?? 0, at: options.now() };
117
+ return { ok: true, evidence };
118
+ },
119
+ async verify() { return evidence ?? null; },
120
+ async rollback() { return { ok: false, code: 'unit-committed', message: 'legacy commit cannot roll back' }; },
121
+ async finalize() { return evidence === undefined ? { ok: false, code: 'unit-plan-missing', message: 'legacy unit has no evidence' } : { ok: true, evidence }; },
122
+ async rollForward() { return evidence === undefined ? { ok: false, code: 'unit-plan-missing', message: 'legacy unit has no evidence' } : { ok: true, evidence }; },
123
+ async cancel() { return { ok: false, phase: 'none' }; },
124
+ };
125
+ }
64
126
  //# sourceMappingURL=commit-execute.js.map
@@ -5,7 +5,8 @@
5
5
  * This module only turns those bytes into a CommitMergePlan; it never reads the live tree and
6
6
  * never decides whether a merge is allowed (validateStagedTree does that).
7
7
  */
8
- import type { CommitMergePlan, WriteOrigin } from '@lmzhen/dsh-evolution-core';
8
+ import type { CommitMergePlan, WriteOrigin, SkillLimits } from '@lmzhen/dsh-evolution-core';
9
+ import type { RevisionRef } from './contracts.ts';
9
10
  export type ResolveTargetResult = {
10
11
  readonly ok: true;
11
12
  readonly name: string;
@@ -33,6 +34,11 @@ export interface BuildCommitPlanInput {
33
34
  }[];
34
35
  readonly origin: WriteOrigin;
35
36
  readonly summary: string;
37
+ readonly limits?: SkillLimits;
38
+ /** Round-1 anchors; carried on the plan so every committer (unit or legacy port) re-checks them. */
39
+ readonly targetRevision?: RevisionRef;
40
+ readonly sourceRevisions?: Readonly<Record<string, RevisionRef>>;
41
+ readonly supportDigest?: string;
36
42
  }
37
43
  export type BuildCommitPlanResult = {
38
44
  readonly ok: true;
@@ -48,8 +48,12 @@ export function buildCommitPlan(input) {
48
48
  supportFiles: input.mergedFiles,
49
49
  expectedTargetBody: input.targetCurrent,
50
50
  expectedSourceBodies: sources,
51
+ ...(input.targetRevision === undefined ? {} : { targetRevision: input.targetRevision }),
52
+ ...(input.sourceRevisions === undefined ? {} : { sourceRevisions: input.sourceRevisions }),
53
+ ...(input.supportDigest === undefined ? {} : { supportDigest: input.supportDigest }),
51
54
  origin: input.origin,
52
55
  summary: input.summary,
56
+ ...(input.limits === undefined ? {} : { limits: input.limits }),
53
57
  },
54
58
  };
55
59
  }
@@ -1,8 +1,11 @@
1
1
  import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
+ import type { MergeValidationPlanRef } from './contracts.ts';
2
3
  export interface CommitReceipt {
3
4
  readonly runId: string;
4
5
  readonly parentRunId?: string;
5
6
  readonly unitId: string;
7
+ readonly evidenceSha256?: string;
8
+ readonly validationPlan?: MergeValidationPlanRef;
6
9
  readonly target: string;
7
10
  readonly create: boolean;
8
11
  readonly sources: readonly string[];
@@ -2,9 +2,14 @@ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
2
  import type { CommitJournal } from './domain/commit-journal.ts';
3
3
  export interface CommitRecoveryReport {
4
4
  readonly runId: string;
5
- readonly phase: CommitJournal['phase'];
5
+ readonly phase: CommitJournal['phase'] | 'unknown';
6
6
  readonly action: string;
7
7
  readonly ok: boolean;
8
+ readonly target?: string;
9
+ readonly sources?: readonly string[];
10
+ readonly journalPath?: string;
11
+ readonly snapshotPath?: string;
12
+ readonly unitId?: string;
8
13
  }
9
- export declare function recoverCommitJournals(io: EvolutionIoLike, home: string, now: number, skillRoot: string, restoreSource?: (name: string) => Promise<boolean>, isActive?: (runId: string) => boolean): Promise<readonly CommitRecoveryReport[]>;
14
+ export declare function recoverCommitJournals(io: EvolutionIoLike, home: string, now: number, isActive?: (runId: string) => boolean): Promise<readonly CommitRecoveryReport[]>;
10
15
  //# sourceMappingURL=commit-recovery.d.ts.map