@lmzhen/dsh-evolution-merge 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/lib/index.js +175 -758
  2. package/lib/types/adapters/engine-screen.d.ts +0 -3
  3. package/lib/types/authoring-flow.d.ts +5 -0
  4. package/lib/types/authoring-flow.js +17 -10
  5. package/lib/types/commit-execute.js +1 -22
  6. package/lib/types/contracts.d.ts +0 -1
  7. package/lib/types/contracts.js +0 -1
  8. package/lib/types/doctor.d.ts +0 -1
  9. package/lib/types/doctor.js +2 -31
  10. package/lib/types/domain/consistency.d.ts +0 -1
  11. package/lib/types/domain/consistency.js +2 -11
  12. package/lib/types/domain/merge-limits.d.ts +0 -10
  13. package/lib/types/domain/merge-limits.js +0 -3
  14. package/lib/types/domain/merge-snapshot.js +4 -6
  15. package/lib/types/domain/pair-ledger.d.ts +0 -1
  16. package/lib/types/domain/row-v3.d.ts +0 -45
  17. package/lib/types/domain/row-v3.js +0 -68
  18. package/lib/types/domain/row.d.ts +2 -84
  19. package/lib/types/domain/row.js +1 -110
  20. package/lib/types/domain/run-coordinator.d.ts +0 -24
  21. package/lib/types/domain/run-coordinator.js +0 -1
  22. package/lib/types/domain/stage-aggregate.d.ts +0 -42
  23. package/lib/types/domain/stage-aggregate.js +1 -116
  24. package/lib/types/domain/state.d.ts +1 -53
  25. package/lib/types/domain/state.js +0 -55
  26. package/lib/types/domain/undo-journal.d.ts +4 -4
  27. package/lib/types/domain/undo-journal.js +5 -7
  28. package/lib/types/index.d.ts +0 -1
  29. package/lib/types/index.js +0 -1
  30. package/lib/types/new-child-authoring.js +4 -10
  31. package/lib/types/prompts.d.ts +0 -1
  32. package/lib/types/prompts.js +0 -1
  33. package/lib/types/read-commands.d.ts +0 -1
  34. package/lib/types/read-commands.js +0 -4
  35. package/lib/types/recovery-projection.js +0 -3
  36. package/lib/types/round2-outcome.js +15 -37
  37. package/lib/types/scan-run.js +0 -3
  38. package/lib/types/skipped.d.ts +1 -2
  39. package/lib/types/skipped.js +0 -8
  40. package/lib/types/staging.d.ts +0 -2
  41. package/lib/types/staging.js +0 -9
  42. package/lib/types/state.d.ts +0 -2
  43. package/lib/types/state.js +0 -13
  44. package/lib/types/surface/merge-view.d.ts +1 -2
  45. package/lib/types/surface/merge-view.js +0 -1
  46. package/lib/types/undo-operation.js +0 -9
  47. package/lib/types/unit-protocol.d.ts +6 -0
  48. package/lib/types/unit-protocol.js +72 -102
  49. package/package.json +4 -4
  50. package/lib/types/commit-recovery.d.ts +0 -14
  51. package/lib/types/commit-recovery.js +0 -96
  52. package/lib/types/coordinator.d.ts +0 -77
  53. package/lib/types/coordinator.js +0 -137
  54. package/lib/types/domain/commit-journal-store.d.ts +0 -6
  55. package/lib/types/domain/commit-journal-store.js +0 -26
  56. package/lib/types/domain/commit-journal.d.ts +0 -16
  57. package/lib/types/domain/commit-journal.js +0 -26
  58. package/lib/types/domain/indexes.d.ts +0 -21
  59. package/lib/types/domain/indexes.js +0 -15
  60. package/lib/types/domain/merge-report.d.ts +0 -41
  61. package/lib/types/domain/merge-report.js +0 -2
  62. package/lib/types/domain/pair-index-store.d.ts +0 -6
  63. package/lib/types/domain/pair-index-store.js +0 -25
  64. package/lib/types/domain/pair-index.d.ts +0 -9
  65. package/lib/types/domain/pair-index.js +0 -28
  66. package/lib/types/domain/resolve-pair.d.ts +0 -4
  67. package/lib/types/domain/resolve-pair.js +0 -4
  68. package/lib/types/domain/row-tx.d.ts +0 -27
  69. package/lib/types/domain/row-tx.js +0 -31
  70. package/lib/types/domain/run-record.d.ts +0 -45
  71. package/lib/types/domain/run-record.js +0 -24
  72. package/lib/types/domain/state-v3.d.ts +0 -4
  73. package/lib/types/domain/state-v3.js +0 -4
@@ -1,111 +1,2 @@
1
- /** Pure v2 row transitions for G1 cutover. No IO. */
2
- import { pairKey, rowIdOf } from "../contracts.js";
3
- import { queueOf, transition as stateTransition } from "./state.js";
4
- function base(row, at) {
5
- return {
6
- id: row.id, pairKey: row.pairKey, a: row.a, b: row.b,
7
- state: row.state, queue: row.queue, createdAt: row.createdAt, updatedAt: at,
8
- ...(row.runId === undefined ? {} : { runId: row.runId }),
9
- ...(row.unitId === undefined ? {} : { unitId: row.unitId }),
10
- ...(row.origin === undefined ? {} : { origin: row.origin }),
11
- ...(row.decision === undefined ? {} : { decision: row.decision }),
12
- ...(row.sourceRevision === undefined ? {} : { sourceRevision: row.sourceRevision }),
13
- ...(row.targetRevision === undefined ? {} : { targetRevision: row.targetRevision }),
14
- ...(row.attempts === undefined ? {} : { attempts: row.attempts }),
15
- ...(row.stagingRef === undefined ? {} : { stagingRef: row.stagingRef }),
16
- ...(row.stagingExpiresAt === undefined ? {} : { stagingExpiresAt: row.stagingExpiresAt }),
17
- ...(row.expired === undefined ? {} : { expired: row.expired }),
18
- ...(row.target === undefined ? {} : { target: row.target }),
19
- ...(row.reason === undefined ? {} : { reason: row.reason }),
20
- };
21
- }
22
- function next(row, at, state, patch = {}) {
23
- return { ok: true, row: { ...base(row, at), state, queue: queueOf(state), ...patch } };
24
- }
25
- function invalid(event, state) {
26
- return { ok: false, code: 'invalid-transition', message: 'cannot apply ' + event.type + ' to ' + (state ?? 'no row') };
27
- }
28
- export function transitionRow(current, event) {
29
- if (event.type === 'candidate') {
30
- if (current !== undefined)
31
- return { ok: true, row: current };
32
- const key = pairKey(event.a, event.b);
33
- return { ok: true, row: { id: rowIdOf(key), pairKey: key, a: event.a, b: event.b, state: 'verify', queue: 'verify', createdAt: event.at, updatedAt: event.at } };
34
- }
35
- if (event.type === 'source-gone')
36
- return { ok: true, row: null };
37
- if (event.type === 'gate-keep') {
38
- if (current !== undefined)
39
- return { ok: true, row: current };
40
- const key = pairKey(event.a, event.b);
41
- return { ok: true, row: { id: rowIdOf(key), pairKey: key, a: event.a, b: event.b, state: 'skipped', queue: 'skipped', origin: 'agent', createdAt: event.at, updatedAt: event.at } };
42
- }
43
- if (current === undefined)
44
- return invalid(event, undefined);
45
- if (event.type === 'discard') {
46
- return current.state === 'pending' || current.state === 'approved' || current.state === 'fuse'
47
- ? { ok: true, row: null }
48
- : invalid(event, current.state);
49
- }
50
- if (event.type === 'restore') {
51
- return current.state === 'skipped' ? next(current, event.at, 'verify') : invalid(event, current.state);
52
- }
53
- if (event.type === 'target-taken' || event.type === 'source-changed') {
54
- return current.state === 'pending' || current.state === 'approved' || current.state === 'fuse'
55
- ? next(current, event.at, current.state, { expired: event.type })
56
- : invalid(event, current.state);
57
- }
58
- if (event.type === 'staging-expired') {
59
- return current.state === 'fuse' || current.state === 'approved'
60
- ? next(current, event.at, 'approved')
61
- : invalid(event, current.state);
62
- }
63
- if (event.type === 'round2-cancel' || event.type === 'timeout' || event.type === 'cancel') {
64
- return current.state === 'fuse' ? next(current, event.at, 'approved') : { ok: true, row: current };
65
- }
66
- const mapped = event.type === 'round2-success'
67
- ? { type: 'round2-merged' }
68
- : event.type === 'round2-fail'
69
- ? { type: 'round2-fail' }
70
- : event.type === 'round1-merge'
71
- ? { type: 'round1-merge', approval: event.approval === 'on' }
72
- : event.type === 'round1-start'
73
- ? { type: 'round1-start' }
74
- : event.type === 'round2-start'
75
- ? { type: 'round2-start' }
76
- : event.type === 'round1-keep'
77
- ? { type: 'round1-keep' }
78
- : event.type === 'round1-fail'
79
- ? { type: 'round1-fail' }
80
- : event.type === 'approve'
81
- ? { type: 'approve' }
82
- : { type: 'reject' };
83
- const moved = stateTransition(current.state, mapped);
84
- if (!moved.ok)
85
- return invalid(event, current.state);
86
- if (moved.state === null)
87
- return { ok: true, row: null };
88
- const patch = {};
89
- if (event.type === 'round1-merge') {
90
- if (event.decision !== undefined)
91
- patch.decision = event.decision;
92
- if (event.stagingRef !== undefined)
93
- patch.stagingRef = event.stagingRef;
94
- if (event.stagingExpiresAt !== undefined)
95
- patch.stagingExpiresAt = event.stagingExpiresAt;
96
- }
97
- if (event.type === 'approve' && event.runId !== undefined)
98
- patch.runId = event.runId;
99
- if (event.type === 'round1-fail' || event.type === 'round2-fail') {
100
- patch.failure = event.failure;
101
- patch.attempts = (current.attempts ?? 0) + 1;
102
- }
103
- if (event.type === 'round1-keep')
104
- patch.origin = 'agent';
105
- if (event.type === 'reject')
106
- patch.origin = 'human';
107
- if (event.type === 'round2-success' && event.unitId !== undefined)
108
- patch.unitId = event.unitId;
109
- return next(current, event.at, moved.state, patch);
110
- }
1
+ export {};
111
2
  //# sourceMappingURL=row.js.map
@@ -28,29 +28,6 @@ export interface MergeRunMeta {
28
28
  readonly createdAt: string;
29
29
  readonly updatedAt: string;
30
30
  }
31
- export interface PairIndexEntry {
32
- readonly version: 1;
33
- readonly runId: string;
34
- readonly pairKey: string;
35
- readonly a: string;
36
- readonly b: string;
37
- readonly target: string;
38
- readonly targetRevision?: string;
39
- readonly sourceRevisions?: Readonly<Record<string, string>>;
40
- readonly state: 'pending' | 'approved' | 'fused' | 'skipped';
41
- readonly updatedAt: string;
42
- }
43
- export interface RunCoordinatorRead {
44
- readonly runId: string;
45
- readonly meta: MergeRunMeta | null;
46
- readonly pairs: readonly PairIndexEntry[];
47
- readonly consistency: 'eventual';
48
- }
49
- export interface RunCoordinator {
50
- begin(meta: MergeRunMeta): Promise<void>;
51
- recordPair(entry: PairIndexEntry): Promise<void>;
52
- read(runId: string): Promise<RunCoordinatorRead>;
53
- }
54
31
  export declare function runMetaPath(home: string, runId: string): string;
55
32
  export declare function writeRunMeta(io: EvolutionIoLike, home: string, meta: MergeRunMeta, fence?: {
56
33
  run<T>(body: () => Promise<T>): Promise<T>;
@@ -60,5 +37,4 @@ export declare function readRunMeta(io: EvolutionIoLike, home: string, runId: st
60
37
  export declare function readRunMetas(io: EvolutionIoLike, home: string): Promise<readonly MergeRunMeta[]>;
61
38
  /** The run currently working on this pair, if any (pairKey is written at run begin). */
62
39
  export declare function inFlightPairRun(metas: readonly MergeRunMeta[], pairKeyValue: string): MergeRunMeta | undefined;
63
- export { recordPair } from './pair-index-store.ts';
64
40
  //# sourceMappingURL=run-coordinator.d.ts.map
@@ -74,5 +74,4 @@ export async function readRunMetas(io, home) {
74
74
  export function inFlightPairRun(metas, pairKeyValue) {
75
75
  return metas.find(meta => meta.phase === 'running' && meta.pairKey === pairKeyValue);
76
76
  }
77
- export { recordPair } from "./pair-index-store.js";
78
77
  //# sourceMappingURL=run-coordinator.js.map
@@ -1,12 +1,6 @@
1
1
  import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
2
  import { type StagingEntry } from './staging-index.ts';
3
3
  import { readStaging, type CreateStagingInput, type CreateStagingResult } from '../staging.ts';
4
- /**
5
- * Old production staging writers (grep baseline at Batch 1b-1). Their production
6
- * path is merge/staging-index + runs/<runId>/pairs/<hash>. adoptLegacy must never
7
- * call any of these; it only writes merge/.shadow/staging-index.
8
- */
9
- export declare const LEGACY_STAGING_WRITER_FILES: readonly ["src/scan-ports.ts", "src/scan-run.ts", "src/staging.ts", "src/new-child-authoring.ts", "src/domain/staging-index.ts", "src/state.ts", "src/domain/state-bootstrap.ts"];
10
4
  export type StageState = 'reserved' | 'present' | 'expired' | 'lost';
11
5
  export interface StageEntry {
12
6
  readonly version: 1;
@@ -33,48 +27,12 @@ export interface ShadowAdoptResult {
33
27
  readonly shadowPath: string;
34
28
  readonly wrote: boolean;
35
29
  }
36
- export interface StageAggregateRead {
37
- readonly entries: readonly StageEntry[];
38
- readonly shadowPath: string;
39
- }
40
- export declare function shadowStagingIndexPath(home: string, rowId: string): string;
41
- export declare function shadowStagingRoot(home: string): string;
42
- /** Read-only adoption into the shadow index; never writes production staging-index. */
43
- export declare function adoptLegacy(io: EvolutionIoLike, home: string): Promise<ShadowAdoptResult>;
44
- export declare function readShadowStageAggregate(io: EvolutionIoLike, home: string, rowId?: string): Promise<StageAggregateRead>;
45
- /** 1b-2b execution order; kept here so 1b-2a tests can pin the contract. */
46
- export declare const STAGE_WRITE_ORDER: readonly ["index:reserved", "tree", "index:present"];
47
- export type StageAdmission = {
48
- readonly ok: true;
49
- } | {
50
- readonly ok: false;
51
- readonly message: string;
52
- };
53
- export declare function admitStage(rowState: string): StageAdmission;
54
- export type ApprovedReservedPlan = {
55
- readonly kind: 'transition-fuse';
56
- readonly rowId: string;
57
- readonly ref: string;
58
- readonly expiresAt: number;
59
- } | {
60
- readonly kind: 'write-present';
61
- readonly rowId: string;
62
- readonly ref: string;
63
- } | {
64
- readonly kind: 'drop-orphan';
65
- readonly rowId: string;
66
- } | {
67
- readonly kind: 'none';
68
- };
69
- export declare function planApprovedReserved(rowState: string, entry: StageEntry | null): ApprovedReservedPlan;
70
30
  /** Production adoption for 1b-2b normalize; writes the existing staging-index, not shadow. */
71
31
  export declare function adoptLegacyProduction(io: EvolutionIoLike, home: string): Promise<ShadowAdoptResult>;
72
32
  /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
73
- export declare function ensureStageTree(io: EvolutionIoLike, home: string, input: CreateStagingInput): Promise<CreateStagingResult>;
74
33
  export declare function createStageTree(io: EvolutionIoLike, home: string, input: CreateStagingInput): Promise<CreateStagingResult>;
75
34
  export declare function readStageTree(io: EvolutionIoLike, home: string, runId: string): Promise<Awaited<ReturnType<typeof readStaging>>>;
76
35
  export declare function setStageTreeTarget(io: EvolutionIoLike, home: string, stageKey: string, target: string, mode?: 'merge-into-existing' | 'create-umbrella' | 'keep'): Promise<boolean>;
77
- export declare function dropStageTree(io: EvolutionIoLike, home: string, stageKey: string): Promise<void>;
78
36
  export declare function writeStageIndex(io: EvolutionIoLike, home: string, entry: StagingEntry): Promise<void>;
79
37
  export declare function readStageIndex(io: EvolutionIoLike, home: string, rowId: string, now?: number): Promise<StagingEntry | null>;
80
38
  export declare function readStageEntry(io: EvolutionIoLike, home: string, rowId: string): Promise<StageEntry | null>;
@@ -3,27 +3,7 @@ import { readTextOrNull, listOrEmpty } from "./read-probe.js";
3
3
  import { isAbsolute, join } from 'node:path';
4
4
  import { readPairLedger } from "./pair-ledger.js";
5
5
  import { readStagingIndex, writeStagingIndex } from "./staging-index.js";
6
- import { createStaging, dropStaging, ensureStaging, readStaging, setStagingTarget } from "../staging.js";
7
- /**
8
- * Old production staging writers (grep baseline at Batch 1b-1). Their production
9
- * path is merge/staging-index + runs/<runId>/pairs/<hash>. adoptLegacy must never
10
- * call any of these; it only writes merge/.shadow/staging-index.
11
- */
12
- export const LEGACY_STAGING_WRITER_FILES = [
13
- 'src/scan-ports.ts',
14
- 'src/scan-run.ts',
15
- 'src/staging.ts',
16
- 'src/new-child-authoring.ts',
17
- 'src/domain/staging-index.ts',
18
- 'src/state.ts',
19
- 'src/domain/state-bootstrap.ts',
20
- ];
21
- export function shadowStagingIndexPath(home, rowId) {
22
- return join(home, 'merge', '.shadow', 'staging-index', encodeURIComponent(rowId) + '.json');
23
- }
24
- export function shadowStagingRoot(home) {
25
- return join(home, 'merge', '.shadow', 'staging-index');
26
- }
6
+ import { createStaging, readStaging, setStagingTarget } from "../staging.js";
27
7
  function stagingTreePath(home, ref) {
28
8
  return join(home, 'merge', 'runs', ref);
29
9
  }
@@ -44,99 +24,6 @@ function markerStaging(migration) {
44
24
  }
45
25
  return facts;
46
26
  }
47
- /** Read-only adoption into the shadow index; never writes production staging-index. */
48
- export async function adoptLegacy(io, home) {
49
- const ledger = await readPairLedger(io, home);
50
- const entries = new Map();
51
- const anomalies = [];
52
- const now = Date.now();
53
- for (const fact of markerStaging(ledger.migration)) {
54
- const present = fact.ref !== '' && await io.exists(stagingTreePath(home, fact.ref));
55
- entries.set(fact.rowId, { version: 1, rowId: fact.rowId, pairKey: fact.pairKey, ...(fact.runId === undefined ? {} : { runId: fact.runId }), ref: fact.ref, state: present ? 'present' : 'lost', createdAt: fact.createdAt, expiresAt: fact.expiresAt, updatedAt: now });
56
- }
57
- for (const row of ledger.rows) {
58
- if (row.state === 'fuse' && !entries.has(row.id)) {
59
- if (row.staging.kind === 'present')
60
- entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref, state: 'present', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
61
- else if (row.staging.kind === 'expired')
62
- entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref ?? '', state: 'expired', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
63
- continue;
64
- }
65
- if (row.state === 'pending') {
66
- if (await readStagingIndex(io, home, row.id, now) !== null)
67
- anomalies.push('pending-staging-index:' + row.id);
68
- continue;
69
- }
70
- if (row.state === 'approved' && !entries.has(row.id)) {
71
- const old = await readStagingIndex(io, home, row.id, now);
72
- if (old !== null)
73
- entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: old.ref, state: 'reserved', createdAt: old.createdAt, expiresAt: old.expiresAt, updatedAt: now });
74
- }
75
- }
76
- const adopted = [...entries.values()].sort((a, b) => a.rowId.localeCompare(b.rowId));
77
- let wrote = false;
78
- for (const entry of adopted) {
79
- const path = shadowStagingIndexPath(home, entry.rowId);
80
- const text = JSON.stringify(entry, null, 2) + '\n';
81
- const current = await readTextOrNull(io, path);
82
- if (current !== null) {
83
- try {
84
- const previous = JSON.parse(current);
85
- if (previous.rowId === entry.rowId && previous.state === entry.state && previous.ref === entry.ref && previous.expiresAt === entry.expiresAt)
86
- continue;
87
- }
88
- catch { /* malformed shadow entry is replaced below */ }
89
- }
90
- if (io.transact !== undefined)
91
- await io.transact(path, () => text);
92
- else
93
- await io.writeText(path, text);
94
- wrote = true;
95
- }
96
- return { entries: adopted, anomalies, shadowPath: shadowStagingRoot(home), wrote };
97
- }
98
- export async function readShadowStageAggregate(io, home, rowId) {
99
- if (rowId !== undefined) {
100
- const raw = await readTextOrNull(io, shadowStagingIndexPath(home, rowId));
101
- if (raw === null)
102
- return { entries: [], shadowPath: shadowStagingRoot(home) };
103
- try {
104
- return { entries: [JSON.parse(raw)], shadowPath: shadowStagingRoot(home) };
105
- }
106
- catch {
107
- return { entries: [], shadowPath: shadowStagingRoot(home) };
108
- }
109
- }
110
- const listed = await io.exists(shadowStagingRoot(home)) ? await io.list(shadowStagingRoot(home)) : [];
111
- const entries = [];
112
- for (const name of listed) {
113
- const raw = await readTextOrNull(io, join(shadowStagingRoot(home), name));
114
- if (raw === null)
115
- continue;
116
- try {
117
- entries.push(JSON.parse(raw));
118
- }
119
- catch { /* ignore malformed shadow */ }
120
- }
121
- entries.sort((a, b) => a.rowId.localeCompare(b.rowId));
122
- return { entries, shadowPath: shadowStagingRoot(home) };
123
- }
124
- /** 1b-2b execution order; kept here so 1b-2a tests can pin the contract. */
125
- export const STAGE_WRITE_ORDER = ['index:reserved', 'tree', 'index:present'];
126
- export function admitStage(rowState) {
127
- return rowState === 'approved' || rowState === 'fuse' ? { ok: true } : { ok: false, message: 'stage requires approved or fuse: ' + rowState };
128
- }
129
- export function planApprovedReserved(rowState, entry) {
130
- if (entry === null)
131
- return rowState === 'approved' || rowState === 'fuse' ? { kind: 'drop-orphan', rowId: '' } : { kind: 'none' };
132
- if (rowState === 'approved' && entry.state === 'reserved')
133
- return { kind: 'transition-fuse', rowId: entry.rowId, ref: entry.ref, expiresAt: entry.expiresAt };
134
- if (rowState === 'approved' && entry.state === 'present')
135
- return { kind: 'transition-fuse', rowId: entry.rowId, ref: entry.ref, expiresAt: entry.expiresAt };
136
- if (rowState === 'fuse' && entry.state === 'reserved')
137
- return { kind: 'write-present', rowId: entry.rowId, ref: entry.ref };
138
- return { kind: 'none' };
139
- }
140
27
  /** Production adoption for 1b-2b normalize; writes the existing staging-index, not shadow. */
141
28
  export async function adoptLegacyProduction(io, home) {
142
29
  const ledger = await readPairLedger(io, home);
@@ -178,11 +65,9 @@ export async function adoptLegacyProduction(io, home) {
178
65
  return { entries: adopted, anomalies, shadowPath: join(home, 'merge', 'staging-index'), wrote };
179
66
  }
180
67
  /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
181
- export async function ensureStageTree(io, home, input) { return await ensureStaging(io, home, input); }
182
68
  export async function createStageTree(io, home, input) { return await createStaging(io, home, input); }
183
69
  export async function readStageTree(io, home, runId) { return await readStaging(io, home, runId); }
184
70
  export async function setStageTreeTarget(io, home, stageKey, target, mode) { return await setStagingTarget(io, home, stageKey, target, mode); }
185
- export async function dropStageTree(io, home, stageKey) { await dropStaging(io, home, stageKey); }
186
71
  export async function writeStageIndex(io, home, entry) { await writeStagingIndex(io, home, entry); }
187
72
  export async function readStageIndex(io, home, rowId, now = Date.now()) { return await readStagingIndex(io, home, rowId, now); }
188
73
  export async function readStageEntry(io, home, rowId) {
@@ -1,57 +1,5 @@
1
- /** Pure merge decision state machine. No IO, no host imports. */
1
+ /** Merge state vocabulary shared by the row types and the queue projection. */
2
2
  export type MergeState = 'verify' | 'pending' | 'approved' | 'fuse' | 'skipped';
3
3
  export type MergeQueue = 'verify' | 'pending' | 'fuse' | 'skipped';
4
- export type MergeEvent = {
5
- readonly type: 'candidate';
6
- } | {
7
- readonly type: 'round1-start';
8
- } | {
9
- readonly type: 'round1-keep';
10
- } | {
11
- readonly type: 'round1-merge';
12
- readonly approval: boolean;
13
- } | {
14
- readonly type: 'round1-fail';
15
- } | {
16
- readonly type: 'approve';
17
- } | {
18
- readonly type: 'reject';
19
- } | {
20
- readonly type: 'round2-start';
21
- } | {
22
- readonly type: 'round2-fail';
23
- } | {
24
- readonly type: 'round2-merged';
25
- } | {
26
- readonly type: 'round2-cancel';
27
- } | {
28
- readonly type: 'staging-expired';
29
- } | {
30
- readonly type: 'source-gone';
31
- } | {
32
- readonly type: 'target-taken';
33
- } | {
34
- readonly type: 'source-changed';
35
- } | {
36
- readonly type: 'cancel';
37
- } | {
38
- readonly type: 'timeout';
39
- };
40
- export type TransitionResult = {
41
- readonly ok: true;
42
- readonly state: MergeState;
43
- } | {
44
- readonly ok: true;
45
- readonly state: null;
46
- readonly removed: true;
47
- } | {
48
- readonly ok: false;
49
- readonly code: 'invalid-transition';
50
- readonly message: string;
51
- };
52
4
  export declare function queueOf(state: MergeState): MergeQueue;
53
- export declare function isTerminal(state: MergeState): boolean;
54
- /** Single transition point; callers must not mutate fields outside this function. */
55
- export declare function transition(state: MergeState, event: MergeEvent): TransitionResult;
56
- export declare function allowedEvents(state: MergeState): readonly MergeEvent['type'][];
57
5
  //# sourceMappingURL=state.d.ts.map
@@ -7,59 +7,4 @@ export function queueOf(state) {
7
7
  case 'skipped': return 'skipped';
8
8
  }
9
9
  }
10
- export function isTerminal(state) {
11
- return state === 'skipped';
12
- }
13
- function fail(state, event) {
14
- return { ok: false, code: 'invalid-transition', message: 'cannot apply ' + event.type + ' to ' + state };
15
- }
16
- /** Single transition point; callers must not mutate fields outside this function. */
17
- export function transition(state, event) {
18
- switch (event.type) {
19
- case 'candidate':
20
- return state === 'verify' ? { ok: true, state: 'verify' } : fail(state, event);
21
- case 'round1-start':
22
- return state === 'verify' ? { ok: true, state: 'verify' } : fail(state, event);
23
- case 'round1-keep':
24
- return state === 'verify' ? { ok: true, state: 'skipped' } : fail(state, event);
25
- case 'round1-merge':
26
- return state === 'verify'
27
- ? { ok: true, state: event.approval ? 'pending' : 'approved' }
28
- : fail(state, event);
29
- case 'round1-fail':
30
- return state === 'verify' ? { ok: true, state: 'verify' } : fail(state, event);
31
- case 'approve':
32
- return state === 'pending' ? { ok: true, state: 'approved' } : fail(state, event);
33
- case 'reject':
34
- return state === 'pending' || state === 'approved' || state === 'fuse'
35
- ? { ok: true, state: 'skipped' }
36
- : fail(state, event);
37
- case 'round2-start':
38
- return state === 'approved' ? { ok: true, state: 'fuse' } : fail(state, event);
39
- case 'round2-fail':
40
- case 'round2-cancel':
41
- return state === 'fuse' || state === 'approved' ? { ok: true, state: 'approved' } : fail(state, event);
42
- case 'round2-merged':
43
- return state === 'fuse' || state === 'approved' ? { ok: true, state: null, removed: true } : fail(state, event);
44
- case 'staging-expired':
45
- return state === 'fuse' ? { ok: true, state: 'approved' } : fail(state, event);
46
- case 'source-gone':
47
- return { ok: true, state: null, removed: true };
48
- case 'target-taken':
49
- case 'source-changed':
50
- return state === 'pending' ? { ok: true, state: 'pending' } : fail(state, event);
51
- case 'cancel':
52
- case 'timeout':
53
- return state === 'verify' || state === 'approved' || state === 'fuse' ? { ok: true, state } : fail(state, event);
54
- }
55
- }
56
- export function allowedEvents(state) {
57
- switch (state) {
58
- case 'verify': return ['candidate', 'round1-start', 'round1-keep', 'round1-merge', 'round1-fail', 'source-gone', 'cancel', 'timeout'];
59
- case 'pending': return ['approve', 'reject', 'target-taken', 'source-changed', 'source-gone'];
60
- case 'approved': return ['round2-start', 'reject', 'staging-expired', 'source-gone', 'cancel', 'timeout'];
61
- case 'fuse': return ['round2-fail', 'round2-merged', 'round2-cancel', 'staging-expired', 'reject', 'source-gone', 'cancel', 'timeout'];
62
- case 'skipped': return [];
63
- }
64
- }
65
10
  //# sourceMappingURL=state.js.map
@@ -1,6 +1,6 @@
1
1
  import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
2
  import type { JournalProtocol } from '@lmzhen/dsh-evolution-core';
3
- export type UndoPairPhase = 'pending' | 'restore-target' | 'restore-source' | 'receipt' | 'pair-index' | 'run-meta' | 'done';
3
+ export type UndoPairPhase = 'pending' | 'restore-target' | 'restore-source' | 'receipt' | 'run-meta' | 'done';
4
4
  export interface UndoPairState {
5
5
  readonly pairKey: string;
6
6
  readonly unitId: string;
@@ -15,9 +15,9 @@ export interface UndoJournal {
15
15
  readonly updatedAt: string;
16
16
  }
17
17
  /**
18
- * The undo journal's protocol (W9-commit-protocol §3.1): `pair-index` and `run-meta` are explicit
19
- * phases, because "the receipt was written but the pair index was not" is exactly the state a
20
- * restart has to recognise.
18
+ * The undo journal's protocol (W9-commit-protocol §3.1): `receipt` and `run-meta` are explicit
19
+ * phases, because "the receipt was written but the run metadata was not" is exactly the state a
20
+ * restart has to recognise. The pair-index ledger is gone in 0.48, so it has no phase.
21
21
  */
22
22
  export declare const UNDO_PAIR_PROTOCOL: JournalProtocol<UndoPairPhase>;
23
23
  export declare function undoJournalPath(home: string, runId: string): string;
@@ -3,18 +3,17 @@ import { readTextOrNull } from "./read-probe.js";
3
3
  import { join } from 'node:path';
4
4
  import { transactIo } from '@lmzhen/dsh-evolution-core';
5
5
  /**
6
- * The undo journal's protocol (W9-commit-protocol §3.1): `pair-index` and `run-meta` are explicit
7
- * phases, because "the receipt was written but the pair index was not" is exactly the state a
8
- * restart has to recognise.
6
+ * The undo journal's protocol (W9-commit-protocol §3.1): `receipt` and `run-meta` are explicit
7
+ * phases, because "the receipt was written but the run metadata was not" is exactly the state a
8
+ * restart has to recognise. The pair-index ledger is gone in 0.48, so it has no phase.
9
9
  */
10
10
  export const UNDO_PAIR_PROTOCOL = {
11
- phases: ['pending', 'restore-target', 'restore-source', 'receipt', 'pair-index', 'run-meta', 'done'],
11
+ phases: ['pending', 'restore-target', 'restore-source', 'receipt', 'run-meta', 'done'],
12
12
  next: {
13
13
  pending: ['restore-target', 'receipt', 'done'],
14
14
  'restore-target': ['restore-source', 'receipt'],
15
15
  'restore-source': ['receipt'],
16
- receipt: ['pair-index'],
17
- 'pair-index': ['run-meta'],
16
+ receipt: ['run-meta'],
18
17
  'run-meta': ['done'],
19
18
  done: [],
20
19
  },
@@ -25,7 +24,6 @@ export const UNDO_PAIR_PROTOCOL = {
25
24
  case 'restore-target':
26
25
  case 'restore-source':
27
26
  case 'receipt':
28
- case 'pair-index':
29
27
  case 'run-meta': return 'continue';
30
28
  default: return 'refuse';
31
29
  }
@@ -38,7 +38,6 @@ export declare function configureMergePresentation(ctx: MergePresentationContext
38
38
  export * from './contracts.ts';
39
39
  export * from './skill-card.ts';
40
40
  export * from './state.ts';
41
- export * from './coordinator.ts';
42
41
  export * from './report.ts';
43
42
  export * from './staging.ts';
44
43
  export * from './staging-files.ts';
@@ -85,7 +85,6 @@ export function configureMergePresentation(ctx) {
85
85
  export * from "./contracts.js";
86
86
  export * from "./skill-card.js";
87
87
  export * from "./state.js";
88
- export * from "./coordinator.js";
89
88
  export * from "./report.js";
90
89
  export * from "./staging.js";
91
90
  export * from "./staging-files.js";
@@ -10,21 +10,15 @@ import { buildValidationPlan } from "./domain/validation-plan.js";
10
10
  import { commitStagedRun } from "./commit-execute.js";
11
11
  import { manifestFilesOf, missingManifestFile, parseAuthoringManifest } from "./domain/authoring-manifest.js";
12
12
  import { MERGE_PROMPT_VERSION, promptHashOf } from "./prompts.js";
13
+ import { targetAdmission } from "./authoring-flow.js";
13
14
  /** Commit-side causes that mean "the world moved"; they must not be retried blindly. */
14
15
  const STALE_CAUSES = new Set(['target-changed', 'source-changed', 'support-changed', 'target-taken', 'target-gone']);
15
16
  export async function runNewChildAuthoring(options, row, decision, signal) {
16
- if (decision.target.includes('/') || decision.target.includes(':'))
17
- return { kind: 'failed', failure: { stage: 'author', code: 'target-invalid', message: 'target must be a bare skill name: ' + decision.target, retryable: false, retryFrom: 'none' } };
18
17
  const targetCard = options.cards.find(card => card.name === decision.target);
19
18
  const createMode = decision.mode === 'create-umbrella';
20
- if (!createMode && targetCard !== undefined) {
21
- if (targetCard.managed !== true)
22
- return { kind: 'failed', failure: { stage: 'author', code: 'target-not-managed', message: 'target is not family-managed: ' + decision.target, retryable: false, retryFrom: 'none' } };
23
- if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
24
- return { kind: 'failed', failure: { stage: 'author', code: 'target-protected', message: 'target is protected: ' + decision.target, retryable: false, retryFrom: 'none' } };
25
- }
26
- if (!createMode && await options.isBlocked?.(decision.target) === true)
27
- return { kind: 'failed', failure: { stage: 'author', code: 'target-blocked', message: 'target is blocked by curator: ' + decision.target, retryable: false, retryFrom: 'none' } };
19
+ const targetRefusal = await targetAdmission(decision.target, targetCard, createMode, options.isBlocked);
20
+ if (targetRefusal !== null)
21
+ return { kind: 'failed', failure: { stage: 'author', code: targetRefusal.code, message: targetRefusal.message, retryable: false, retryFrom: 'none' } };
28
22
  const at = options.now();
29
23
  // NFR-06: an expired stage is never reused and never re-presented. Drop its tree and
30
24
  // index and author into a fresh one; the recorded decision anchors still guard the
@@ -9,5 +9,4 @@ export declare function buildRound1Prompt(candidate: {
9
9
  readonly b: string;
10
10
  }, cards: readonly SkillCard[]): string;
11
11
  export declare function buildRound2Prompt(row: MergeRow, decision: MergeDecision, mode?: 'continue' | 'fresh'): string;
12
- export declare function buildStandbyPrompt(): string;
13
12
  //# sourceMappingURL=prompts.d.ts.map
@@ -69,5 +69,4 @@ export function buildRound2Prompt(row, decision, mode = 'continue') {
69
69
  ];
70
70
  return (mode === 'fresh' ? ['基于已持久化的 decision。', ...head, ...body] : [...head, ...body]).join('\n');
71
71
  }
72
- export function buildStandbyPrompt() { return STANDBY_PROMPT; }
73
72
  //# sourceMappingURL=prompts.js.map
@@ -7,5 +7,4 @@ import type { LiveRowFacts } from './domain/live-facts.ts';
7
7
  export declare function handleReadOnlyMergeCommand(command: MergeCliCommand, io: EvolutionIoLike, home: string, cards?: readonly {
8
8
  readonly name: string;
9
9
  }[], runs?: readonly RunRecord[], approval?: boolean, live?: () => Promise<LiveRowFacts | undefined>): Promise<string | null>;
10
- export declare function mergeCommandHelp(): string;
11
10
  //# sourceMappingURL=read-commands.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import { readMergeSnapshot } from "./domain/merge-snapshot.js";
2
2
  import { buildFacts, toRowFact } from "./surface/merge-view.js";
3
3
  import { formatFacts, formatSnapshotQueue, formatSnapshotStatus } from "./status.js";
4
- import { mergeUsage } from "./commands.js";
5
4
  export async function handleReadOnlyMergeCommand(command, io, home, cards, runs, approval = false, live) {
6
5
  if (!['status', 'pending', 'candidates', 'skipped'].includes(command.verb))
7
6
  return null;
@@ -27,7 +26,4 @@ export async function handleReadOnlyMergeCommand(command, io, home, cards, runs,
27
26
  }
28
27
  return formatSnapshotQueue(rows.filter(row => row.queue === 'skipped'), 'skipped');
29
28
  }
30
- export function mergeCommandHelp() {
31
- return mergeUsage();
32
- }
33
29
  //# sourceMappingURL=read-commands.js.map
@@ -3,7 +3,6 @@ import { mergeReportPath, readMergeReport, writeMergeReport } from "./report.js"
3
3
  import { pairLedger, readPairLedgerV4 } from "./domain/pair-ledger.js";
4
4
  import { readAllStages, readStageTree, writeStageIndex } from "./domain/stage-aggregate.js";
5
5
  import { dropStage } from "./domain/stage-aggregate.js";
6
- import { recordPair } from "./domain/pair-index-store.js";
7
6
  import { readRunMeta, writeRunMeta } from "./domain/run-coordinator.js";
8
7
  import { evidenceSha256 } from "./domain/evidence-hash.js";
9
8
  export async function projectRecoveredUnit(options) {
@@ -79,8 +78,6 @@ export async function projectRecoveredUnit(options) {
79
78
  await options.onPhase?.('stage-drop');
80
79
  await dropStage(options.io, options.home, stageRowId);
81
80
  }
82
- await options.onPhase?.('pair-index');
83
- await recordPair(options.io, options.home, { pairKey, runId: options.runId, result: 'merged', target });
84
81
  await options.onPhase?.('run-meta');
85
82
  const meta = await readRunMeta(options.io, options.home, options.runId);
86
83
  await writeRunMeta(options.io, options.home, { version: 1, runId: options.runId, ...(meta?.pid === undefined ? {} : { pid: meta.pid }), phase: 'settled', ...(options.interrupted === true ? { stopReason: 'interrupted' } : {}), reportRef: reportPath, pairCount: 1, ...(meta?.parentRunId === undefined ? {} : { parentRunId: meta.parentRunId }), ...(meta?.stagingRef === undefined ? {} : { stagingRef: meta.stagingRef }), ...(meta?.pairs === undefined ? {} : { pairs: meta.pairs }), createdAt: meta?.createdAt ?? new Date(at).toISOString(), updatedAt: new Date(at).toISOString() });