@lmzhen/dsh-evolution-merge 0.46.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.
- package/lib/index.js +248 -1224
- 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.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 +0 -6
- package/lib/types/merge-routes.d.ts +5 -21
- package/lib/types/merge-routes.js +53 -93
- 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 +1 -39
- package/lib/types/state.js +2 -78
- package/lib/types/surface/merge-view.d.ts +1 -2
- package/lib/types/surface/merge-view.js +1 -2
- 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 +4 -4
- 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/facts.d.ts +0 -26
- package/lib/types/facts.js +0 -65
- package/lib/types/metrics.d.ts +0 -16
- package/lib/types/metrics.js +0 -27
- package/lib/types/wire-projection.d.ts +0 -36
- package/lib/types/wire-projection.js +0 -77
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) {
|
|
@@ -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
|
@@ -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";
|
|
@@ -331,11 +330,6 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
331
330
|
} });
|
|
332
331
|
},
|
|
333
332
|
async bodyRef(ref) { return await resolveBody(io, home, ref, async () => ref.side === 'archive' ? await skills.readArchived(ref.name) : await skills.read(ref.name)); },
|
|
334
|
-
approval() { return getParams().mergeApproval; },
|
|
335
|
-
readiness() { return ready; },
|
|
336
|
-
commands() {
|
|
337
|
-
return ['merge scan', 'merge doctor', 'merge status', 'merge pending', 'merge skipped', 'merge report', 'merge runs', 'merge cancel', 'merge reconcile'];
|
|
338
|
-
},
|
|
339
333
|
};
|
|
340
334
|
doctorFact = async () => {
|
|
341
335
|
const current = source;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Four read-only merge routes: view / rows / body / history.
|
|
3
|
-
* The host loads
|
|
3
|
+
* The host loads one snapshot; these handlers only project wire envelopes.
|
|
4
|
+
* There is no second (legacy rows/runs/history) source: the production constructor has
|
|
5
|
+
* always provided the snapshot, and the legacy branches were the only consumers of
|
|
6
|
+
* facts.ts / wire-projection.ts (W11 D2b).
|
|
4
7
|
*/
|
|
5
|
-
import type { RunRecord } from '@lmzhen/dsh-evolution-core';
|
|
6
8
|
import { type ConnectionFetchRouteLike, type DecisionAudit } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
7
|
-
import type { MergeRow } from './contracts.ts';
|
|
8
|
-
import type { StateReadiness } from './domain/state-bootstrap.ts';
|
|
9
9
|
import type { MergeSnapshot, BodyRef } from './domain/merge-snapshot.ts';
|
|
10
10
|
export declare const MERGE_ROUTES: {
|
|
11
11
|
view: `/api/dsh-evolution/${string}`;
|
|
@@ -25,25 +25,9 @@ export interface MergeDecisionResult {
|
|
|
25
25
|
readonly code?: string;
|
|
26
26
|
readonly runId?: string;
|
|
27
27
|
}
|
|
28
|
-
export interface MergeHistoryItem {
|
|
29
|
-
readonly name: string;
|
|
30
|
-
readonly kind: 'merged' | 'deleted' | 'split' | 'other';
|
|
31
|
-
readonly reason: string;
|
|
32
|
-
readonly lastVersion: number;
|
|
33
|
-
readonly archivedAt: string | null;
|
|
34
|
-
readonly sourceRunId?: string;
|
|
35
|
-
}
|
|
36
28
|
export interface MergeRouteSource {
|
|
37
|
-
snapshot
|
|
29
|
+
snapshot(): Promise<MergeSnapshot>;
|
|
38
30
|
bodyRef?(ref: BodyRef): Promise<string | null>;
|
|
39
|
-
rows?(): Promise<readonly MergeRow[]>;
|
|
40
|
-
run?(id: string): Promise<RunRecord | undefined>;
|
|
41
|
-
runs?(cursor?: string, limit?: number): Promise<readonly RunRecord[]>;
|
|
42
|
-
body?(id: string, side: 'a' | 'b'): Promise<string | null>;
|
|
43
|
-
history?(): Promise<readonly MergeHistoryItem[]>;
|
|
44
|
-
commands(): readonly string[];
|
|
45
|
-
approval?(): boolean;
|
|
46
|
-
readiness?(): Promise<StateReadiness>;
|
|
47
31
|
}
|
|
48
32
|
export declare function makeMergeRoutes(deps: {
|
|
49
33
|
getSource: () => MergeRouteSource | undefined;
|