@lmzhen/dsh-evolution-merge 0.47.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +224 -775
- package/lib/types/adapters/engine-screen.d.ts +0 -3
- package/lib/types/authoring-flow.d.ts +5 -0
- package/lib/types/authoring-flow.js +17 -10
- package/lib/types/commit-execute.js +1 -22
- package/lib/types/contracts.d.ts +0 -1
- package/lib/types/contracts.js +0 -1
- package/lib/types/doctor.d.ts +0 -1
- package/lib/types/doctor.js +2 -31
- package/lib/types/domain/consistency.d.ts +0 -1
- package/lib/types/domain/consistency.js +2 -11
- package/lib/types/domain/merge-limits.d.ts +0 -10
- package/lib/types/domain/merge-limits.js +0 -3
- package/lib/types/domain/merge-snapshot.js +4 -6
- package/lib/types/domain/pair-ledger.d.ts +0 -1
- package/lib/types/domain/row-v3.d.ts +0 -45
- package/lib/types/domain/row-v3.js +0 -68
- package/lib/types/domain/row.d.ts +2 -84
- package/lib/types/domain/row.js +1 -110
- package/lib/types/domain/run-coordinator.d.ts +0 -24
- package/lib/types/domain/run-coordinator.js +0 -1
- package/lib/types/domain/stage-aggregate.d.ts +0 -42
- package/lib/types/domain/stage-aggregate.js +1 -116
- package/lib/types/domain/state-migrate.d.ts +27 -0
- package/lib/types/domain/state-migrate.js +29 -0
- package/lib/types/domain/state.d.ts +1 -53
- package/lib/types/domain/state.js +0 -55
- package/lib/types/domain/undo-journal.d.ts +4 -4
- package/lib/types/domain/undo-journal.js +5 -7
- package/lib/types/index.d.ts +0 -1
- package/lib/types/index.js +8 -14
- package/lib/types/new-child-authoring.js +4 -10
- package/lib/types/prompts.d.ts +0 -1
- package/lib/types/prompts.js +0 -1
- package/lib/types/read-commands.d.ts +0 -1
- package/lib/types/read-commands.js +0 -4
- package/lib/types/recovery-projection.js +0 -3
- package/lib/types/round2-outcome.js +15 -37
- package/lib/types/scan-run.js +0 -3
- package/lib/types/skipped.d.ts +1 -2
- package/lib/types/skipped.js +0 -8
- package/lib/types/staging.d.ts +0 -2
- package/lib/types/staging.js +0 -9
- package/lib/types/state.d.ts +0 -2
- package/lib/types/state.js +0 -13
- package/lib/types/surface/merge-view.d.ts +1 -2
- package/lib/types/surface/merge-view.js +0 -1
- package/lib/types/undo-operation.js +0 -9
- package/lib/types/unit-protocol.d.ts +6 -0
- package/lib/types/unit-protocol.js +72 -102
- package/package.json +10 -4
- package/scripts/merge-backup.mjs +31 -0
- package/scripts/merge-migrate.mjs +163 -0
- package/scripts/merge-restore.mjs +36 -0
- package/lib/types/commit-recovery.d.ts +0 -14
- package/lib/types/commit-recovery.js +0 -96
- package/lib/types/coordinator.d.ts +0 -77
- package/lib/types/coordinator.js +0 -137
- package/lib/types/domain/commit-journal-store.d.ts +0 -6
- package/lib/types/domain/commit-journal-store.js +0 -26
- package/lib/types/domain/commit-journal.d.ts +0 -16
- package/lib/types/domain/commit-journal.js +0 -26
- package/lib/types/domain/indexes.d.ts +0 -21
- package/lib/types/domain/indexes.js +0 -15
- package/lib/types/domain/merge-report.d.ts +0 -41
- package/lib/types/domain/merge-report.js +0 -2
- package/lib/types/domain/pair-index-store.d.ts +0 -6
- package/lib/types/domain/pair-index-store.js +0 -25
- package/lib/types/domain/pair-index.d.ts +0 -9
- package/lib/types/domain/pair-index.js +0 -28
- package/lib/types/domain/resolve-pair.d.ts +0 -4
- package/lib/types/domain/resolve-pair.js +0 -4
- package/lib/types/domain/row-tx.d.ts +0 -27
- package/lib/types/domain/row-tx.js +0 -31
- package/lib/types/domain/run-record.d.ts +0 -45
- package/lib/types/domain/run-record.js +0 -24
- package/lib/types/domain/state-v3.d.ts +0 -4
- package/lib/types/domain/state-v3.js +0 -4
package/lib/types/domain/row.js
CHANGED
|
@@ -1,111 +1,2 @@
|
|
|
1
|
-
|
|
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,
|
|
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) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one v2/v3→v4 migration seam. The startup gate and the operator CLI both call this,
|
|
3
|
+
* so a backup/journal fix in one path cannot drift from the other.
|
|
4
|
+
*/
|
|
5
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
6
|
+
/** Outcome of the one-time migration. */
|
|
7
|
+
export type MigrateToV4Result = {
|
|
8
|
+
readonly ok: true;
|
|
9
|
+
readonly migrated: boolean;
|
|
10
|
+
readonly backupRef?: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly code: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Migrate v1/v2/v3 merge state to v4. v4 is a no-op; forward-only/needs-repair are reported
|
|
18
|
+
* before attempting anything, so a newer state is never overwritten.
|
|
19
|
+
* @param io - the evolution IO seam.
|
|
20
|
+
* @param home - the evolution home.
|
|
21
|
+
* @param fence - optional home write fence used by the host.
|
|
22
|
+
* @returns the migration outcome.
|
|
23
|
+
*/
|
|
24
|
+
export declare function migrateMergeStateToV4(io: EvolutionIoLike, home: string, fence?: {
|
|
25
|
+
run<T>(body: () => Promise<T>): Promise<T>;
|
|
26
|
+
}): Promise<MigrateToV4Result>;
|
|
27
|
+
//# sourceMappingURL=state-migrate.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ensureMergeStateReady } from "./state-bootstrap.js";
|
|
2
|
+
import { normalizePairLedger } from "./pair-ledger.js";
|
|
3
|
+
import { readV4Readiness } from "./schema4.js";
|
|
4
|
+
/**
|
|
5
|
+
* Migrate v1/v2/v3 merge state to v4. v4 is a no-op; forward-only/needs-repair are reported
|
|
6
|
+
* before attempting anything, so a newer state is never overwritten.
|
|
7
|
+
* @param io - the evolution IO seam.
|
|
8
|
+
* @param home - the evolution home.
|
|
9
|
+
* @param fence - optional home write fence used by the host.
|
|
10
|
+
* @returns the migration outcome.
|
|
11
|
+
*/
|
|
12
|
+
export async function migrateMergeStateToV4(io, home, fence) {
|
|
13
|
+
const v4 = await readV4Readiness(io, home);
|
|
14
|
+
if (v4.ok)
|
|
15
|
+
return { ok: true, migrated: false };
|
|
16
|
+
if (v4.code === 'merge-state-forward-only' || v4.code === 'merge-state-needs-repair') {
|
|
17
|
+
return { ok: false, code: v4.code, message: v4.message };
|
|
18
|
+
}
|
|
19
|
+
const v3 = await ensureMergeStateReady(io, home, fence);
|
|
20
|
+
if (!v3.ok)
|
|
21
|
+
return { ok: false, code: v3.code, message: v3.message };
|
|
22
|
+
const normalized = await normalizePairLedger(io, home);
|
|
23
|
+
if (!normalized.ok)
|
|
24
|
+
return { ok: false, code: 'merge-state-needs-repair', message: normalized.message ?? 'v3->v4 normalize failed' };
|
|
25
|
+
return normalized.backupRef === undefined
|
|
26
|
+
? { ok: true, migrated: normalized.migrated }
|
|
27
|
+
: { ok: true, migrated: normalized.migrated, backupRef: normalized.backupRef };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=state-migrate.js.map
|
|
@@ -1,57 +1,5 @@
|
|
|
1
|
-
/**
|
|
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' | '
|
|
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): `
|
|
19
|
-
* phases, because "the receipt was written but the
|
|
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): `
|
|
7
|
-
* phases, because "the receipt was written but the
|
|
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', '
|
|
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: ['
|
|
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
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/types/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { authorityFrom, createSubagentChannel, evolutionHome, evolutionIoAdapter
|
|
|
8
8
|
import { newSkillLibrary } from '@lmzhen/dsh-evolution-core/skills';
|
|
9
9
|
import { registerHostRoutes } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
10
10
|
import { loadMergeState } from "./state.js";
|
|
11
|
-
import { pairLedger
|
|
11
|
+
import { pairLedger } from "./domain/pair-ledger.js";
|
|
12
12
|
import { legacyToV3 } from "./domain/row-v3-bridge.js";
|
|
13
13
|
import { readV4Readiness } from "./domain/schema4.js";
|
|
14
14
|
import { readMergeSnapshot } from "./domain/merge-snapshot.js";
|
|
@@ -27,7 +27,7 @@ import { buildDoctorView } from "./surface/merge-view.js";
|
|
|
27
27
|
import { mergeDoctorFact } from "./doctor.js";
|
|
28
28
|
import { commandError, formatMergeError } from "./domain/errors.js";
|
|
29
29
|
import { resolveBody } from "./surface/body.js";
|
|
30
|
-
import {
|
|
30
|
+
import { migrateMergeStateToV4 } from "./domain/state-migrate.js";
|
|
31
31
|
import { recoverCommitUnits } from "./recovery-coordinator.js";
|
|
32
32
|
import { pairKey, rowIdOf } from "./contracts.js";
|
|
33
33
|
import { makeMergeRoutes } from "./merge-routes.js";
|
|
@@ -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";
|
|
@@ -172,19 +171,14 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
172
171
|
return;
|
|
173
172
|
const io = evolutionIoAdapter(() => registry.provider());
|
|
174
173
|
const home = evolutionHome();
|
|
174
|
+
const homeFence = ctx.get('evolutionMutationAuthority')?.homeFence;
|
|
175
175
|
const ready = (async () => {
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
const state = await ensureMergeStateReady(io, home, ctx.get('evolutionMutationAuthority')?.homeFence);
|
|
182
|
-
if (!state.ok)
|
|
183
|
-
return v4.code === 'merge-state-forward-only' || v4.code === 'merge-state-needs-repair' ? { ok: false, code: v4.code, message: v4.message } : state;
|
|
184
|
-
const normalized = await normalizePairLedger(io, home);
|
|
185
|
-
return normalized.ok ? state : { ok: false, code: 'merge-state-needs-repair', message: normalized.message ?? 'v4 normalize failed' };
|
|
176
|
+
const migration = await migrateMergeStateToV4(io, home, homeFence);
|
|
177
|
+
if (!migration.ok)
|
|
178
|
+
return migration;
|
|
179
|
+
const loaded = await loadMergeState(io, home);
|
|
180
|
+
return { ok: true, schema: 3, migrated: migration.migrated, state: { schemaVersion: 3, rows: loaded.state.rows.map(legacyToV3).filter((row) => row !== null) }, quarantine: [], report: null };
|
|
186
181
|
})().catch(error => ({ ok: false, code: 'merge-state-migration-blocked', message: error instanceof Error ? error.message : String(error) }));
|
|
187
|
-
const homeFence = ctx.get('evolutionMutationAuthority')?.homeFence;
|
|
188
182
|
const runs = newRunRegistry({ io, home, warn: (message) => { ctx.logger.warn(message); }, ...(homeFence === undefined ? {} : { fence: homeFence }) });
|
|
189
183
|
ctx.effect(() => {
|
|
190
184
|
const host = ctx;
|