@lmzhen/dsh-evolution-merge 0.29.0 → 0.30.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 +1125 -568
- package/lib/types/adapters/channel.js +29 -0
- package/lib/types/adapters/engine-screen.d.ts +1 -1
- package/lib/types/adapters/engine-screen.js +45 -0
- package/lib/types/adapters/library.js +14 -0
- package/lib/types/adapters/model-runner.js +34 -0
- package/lib/types/adapters/select.d.ts +3 -0
- package/lib/types/adapters/select.js +35 -0
- package/lib/types/adapters/undo.js +35 -0
- package/lib/types/approve-command.js +42 -0
- package/lib/types/authoring-flow.js +48 -0
- package/lib/types/channel-port.js +74 -0
- package/lib/types/commands.d.ts +9 -0
- package/lib/types/commands.js +79 -0
- package/lib/types/commit-assembly.js +48 -0
- package/lib/types/commit-execute.js +52 -0
- package/lib/types/commit-plan.js +56 -0
- package/lib/types/commit-receipt.d.ts +2 -0
- package/lib/types/commit-receipt.js +85 -0
- package/lib/types/contracts.d.ts +14 -5
- package/lib/types/contracts.js +57 -0
- package/lib/types/coordinator.d.ts +3 -0
- package/lib/types/coordinator.js +131 -0
- package/lib/types/decision.js +61 -0
- package/lib/types/doctor.js +50 -0
- package/lib/types/domain/claim.d.ts +10 -0
- package/lib/types/domain/claim.js +12 -0
- package/lib/types/domain/commit-journal-store.d.ts +6 -0
- package/lib/types/domain/commit-journal-store.js +22 -0
- package/lib/types/domain/commit-journal.d.ts +16 -0
- package/lib/types/domain/commit-journal.js +25 -0
- package/lib/types/domain/errors.d.ts +10 -0
- package/lib/types/domain/errors.js +7 -0
- package/lib/types/domain/failure-route.d.ts +25 -0
- package/lib/types/domain/failure-route.js +22 -0
- package/lib/types/domain/indexes.d.ts +21 -0
- package/lib/types/domain/indexes.js +15 -0
- package/lib/types/domain/ledger.d.ts +17 -0
- package/lib/types/domain/ledger.js +65 -0
- package/lib/types/domain/markdown.d.ts +2 -0
- package/lib/types/domain/markdown.js +57 -0
- package/lib/types/domain/migrate-v1.d.ts +41 -0
- package/lib/types/domain/migrate-v1.js +23 -0
- package/lib/types/domain/pair-index-store.d.ts +6 -0
- package/lib/types/domain/pair-index-store.js +25 -0
- package/lib/types/domain/pair-index.d.ts +9 -0
- package/lib/types/domain/pair-index.js +28 -0
- package/lib/types/domain/resolve-pair.d.ts +11 -0
- package/lib/types/domain/resolve-pair.js +17 -0
- package/lib/types/domain/row.d.ts +113 -0
- package/lib/types/domain/row.js +111 -0
- package/lib/types/domain/run-record.d.ts +45 -0
- package/lib/types/domain/run-record.js +24 -0
- package/lib/types/domain/state.d.ts +57 -0
- package/lib/types/domain/state.js +65 -0
- package/lib/types/gate-cache.js +36 -0
- package/lib/types/index.js +241 -0
- package/lib/types/ladder.js +162 -0
- package/lib/types/merge-command.js +43 -0
- package/lib/types/merge-routes.d.ts +14 -4
- package/lib/types/merge-routes.js +99 -0
- package/lib/types/merge-tool.js +69 -0
- package/lib/types/new-child-authoring.js +83 -0
- package/lib/types/pair-command.js +18 -0
- package/lib/types/params.js +49 -0
- package/lib/types/pending-actions.js +28 -0
- package/lib/types/prompts.js +60 -0
- package/lib/types/read-commands.js +32 -0
- package/lib/types/report.js +70 -0
- package/lib/types/resume-command.js +57 -0
- package/lib/types/round2-outcome.js +50 -0
- package/lib/types/routes.d.ts +5 -2
- package/lib/types/routes.js +75 -0
- package/lib/types/rules.js +55 -0
- package/lib/types/run-shell.d.ts +7 -0
- package/lib/types/run-shell.js +69 -0
- package/lib/types/runtime.d.ts +2 -0
- package/lib/types/runtime.js +269 -0
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +30 -0
- package/lib/types/scan-ports.js +93 -0
- package/lib/types/scan-run.d.ts +2 -0
- package/lib/types/scan-run.js +186 -0
- package/lib/types/skill-card.js +2 -0
- package/lib/types/skipped.js +24 -0
- package/lib/types/stage-tool-registry.js +32 -0
- package/lib/types/stage-tool.js +31 -0
- package/lib/types/staged-tree.js +75 -0
- package/lib/types/staging-files.js +86 -0
- package/lib/types/staging.d.ts +7 -0
- package/lib/types/staging.js +149 -0
- package/lib/types/state.js +103 -0
- package/lib/types/status.d.ts +2 -1
- package/lib/types/status.js +28 -0
- package/lib/types/supersede.js +14 -0
- package/lib/types/undo-merge.js +23 -0
- package/package.json +4 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function putPair(index, entry) {
|
|
2
|
+
const next = new Map(index);
|
|
3
|
+
next.set(entry.pairKey, entry);
|
|
4
|
+
return next;
|
|
5
|
+
}
|
|
6
|
+
export function historyRecord(input) {
|
|
7
|
+
return { ...input, lastVersion: Math.max(1, Math.trunc(input.lastVersion)) };
|
|
8
|
+
}
|
|
9
|
+
export function summarizeHistory(records) {
|
|
10
|
+
const counts = { merged: 0, deleted: 0, split: 0, other: 0 };
|
|
11
|
+
for (const record of records)
|
|
12
|
+
counts[record.kind] += 1;
|
|
13
|
+
return counts;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=indexes.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MergeRowV2 } from './row.ts';
|
|
2
|
+
import { type MergeQueue } from './state.ts';
|
|
3
|
+
export interface LedgerV2 {
|
|
4
|
+
readonly schemaVersion: 2;
|
|
5
|
+
readonly rows: readonly MergeRowV2[];
|
|
6
|
+
}
|
|
7
|
+
export interface LedgerParseResult {
|
|
8
|
+
readonly ok: boolean;
|
|
9
|
+
readonly ledger: LedgerV2;
|
|
10
|
+
readonly note?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseLedger(raw: string | null): LedgerParseResult;
|
|
13
|
+
export declare function upsert(rows: readonly MergeRowV2[], row: MergeRowV2): readonly MergeRowV2[];
|
|
14
|
+
export declare function remove(rows: readonly MergeRowV2[], pairKey: string): readonly MergeRowV2[];
|
|
15
|
+
export declare function rowsOfQueue(rows: readonly MergeRowV2[], queue: MergeQueue): readonly MergeRowV2[];
|
|
16
|
+
export declare function activeCounts(rows: readonly MergeRowV2[]): Record<MergeQueue, number>;
|
|
17
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Pure v2 ledger normalization/projection. No IO. */
|
|
2
|
+
import { migrateV1Row } from "./migrate-v1.js";
|
|
3
|
+
import { queueOf } from "./state.js";
|
|
4
|
+
function isRow(value) {
|
|
5
|
+
const row = value;
|
|
6
|
+
return typeof row.id === 'string' && typeof row.pairKey === 'string' && typeof row.a === 'string' && typeof row.b === 'string'
|
|
7
|
+
&& typeof row.state === 'string' && typeof row.createdAt === 'string' && typeof row.updatedAt === 'string';
|
|
8
|
+
}
|
|
9
|
+
export function parseLedger(raw) {
|
|
10
|
+
if (raw === null)
|
|
11
|
+
return { ok: true, ledger: { schemaVersion: 2, rows: [] } };
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { ok: false, ledger: { schemaVersion: 2, rows: [] }, note: 'invalid json' };
|
|
18
|
+
}
|
|
19
|
+
const body = parsed;
|
|
20
|
+
if (!Array.isArray(body.rows))
|
|
21
|
+
return { ok: false, ledger: { schemaVersion: 2, rows: [] }, note: 'rows missing' };
|
|
22
|
+
const rows = [];
|
|
23
|
+
let skipped = 0;
|
|
24
|
+
for (const value of body.rows) {
|
|
25
|
+
if (body.schemaVersion === 1) {
|
|
26
|
+
const legacy = value;
|
|
27
|
+
if (typeof legacy.id !== 'string' || typeof legacy.pairKey !== 'string' || typeof legacy.a !== 'string' || typeof legacy.b !== 'string' || typeof legacy.state !== 'string' || typeof legacy.createdAt !== 'string' || typeof legacy.updatedAt !== 'string') {
|
|
28
|
+
skipped += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const row = migrateV1Row(legacy);
|
|
32
|
+
rows.push(row);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!isRow(value)) {
|
|
36
|
+
skipped += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
rows.push(value);
|
|
40
|
+
}
|
|
41
|
+
const note = skipped === 0 ? undefined : String(skipped) + ' invalid row(s) skipped';
|
|
42
|
+
return { ok: true, ledger: { schemaVersion: 2, rows }, ...(note === undefined ? {} : { note }) };
|
|
43
|
+
}
|
|
44
|
+
export function upsert(rows, row) {
|
|
45
|
+
const index = rows.findIndex(current => current.pairKey === row.pairKey);
|
|
46
|
+
if (index < 0)
|
|
47
|
+
return [...rows, row];
|
|
48
|
+
return rows.map((current, at) => at === index ? row : current);
|
|
49
|
+
}
|
|
50
|
+
export function remove(rows, pairKey) {
|
|
51
|
+
return rows.filter(row => row.pairKey !== pairKey);
|
|
52
|
+
}
|
|
53
|
+
export function rowsOfQueue(rows, queue) {
|
|
54
|
+
return rows.filter(row => queueOf(row.state) === queue);
|
|
55
|
+
}
|
|
56
|
+
export function activeCounts(rows) {
|
|
57
|
+
const counts = { verify: 0, pending: 0, fuse: 0, skipped: 0 };
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
if (row.expired !== undefined)
|
|
60
|
+
continue;
|
|
61
|
+
counts[queueOf(row.state)] += 1;
|
|
62
|
+
}
|
|
63
|
+
return counts;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Minimal safe Markdown renderer for the read-only body route. */
|
|
2
|
+
function escapeHtml(text) {
|
|
3
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
4
|
+
}
|
|
5
|
+
export function renderMarkdown(source) {
|
|
6
|
+
const fence = String.fromCharCode(96, 96, 96);
|
|
7
|
+
const lines = source.split(/\r?\n/);
|
|
8
|
+
const out = [];
|
|
9
|
+
let inCode = false;
|
|
10
|
+
let inList = false;
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
if (line.startsWith(fence)) {
|
|
13
|
+
if (inCode) {
|
|
14
|
+
out.push('</code></pre>');
|
|
15
|
+
inCode = false;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
out.push('<pre><code>');
|
|
19
|
+
inCode = true;
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (inCode) {
|
|
24
|
+
out.push(escapeHtml(line));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
28
|
+
if (heading !== null) {
|
|
29
|
+
if (inList) {
|
|
30
|
+
out.push('</ul>');
|
|
31
|
+
inList = false;
|
|
32
|
+
}
|
|
33
|
+
const level = heading[1]?.length ?? 1;
|
|
34
|
+
out.push('<h' + String(level) + '>' + escapeHtml(heading[2] ?? '') + '</h' + String(level) + '>');
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (/^[-*]\s+/.test(line)) {
|
|
38
|
+
if (!inList) {
|
|
39
|
+
out.push('<ul>');
|
|
40
|
+
inList = true;
|
|
41
|
+
}
|
|
42
|
+
out.push('<li>' + escapeHtml(line.replace(/^[-*]\s+/, '')) + '</li>');
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (inList) {
|
|
46
|
+
out.push('</ul>');
|
|
47
|
+
inList = false;
|
|
48
|
+
}
|
|
49
|
+
out.push(line.trim() === '' ? '' : '<p>' + escapeHtml(line) + '</p>');
|
|
50
|
+
}
|
|
51
|
+
if (inList)
|
|
52
|
+
out.push('</ul>');
|
|
53
|
+
if (inCode)
|
|
54
|
+
out.push('</code></pre>');
|
|
55
|
+
return out.join('\n');
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Pure v1→v2 row migration. No IO. */
|
|
2
|
+
import type { MergeState } from './state.ts';
|
|
3
|
+
export interface LegacyMergeRow {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly pairKey: string;
|
|
6
|
+
readonly a: string;
|
|
7
|
+
readonly b: string;
|
|
8
|
+
readonly state: 'verify' | 'pending' | 'fuse' | 'skipped';
|
|
9
|
+
readonly queue?: 'pending' | 'retry' | 'skipped';
|
|
10
|
+
readonly failure?: unknown;
|
|
11
|
+
readonly decision?: unknown;
|
|
12
|
+
readonly runId?: string;
|
|
13
|
+
readonly unitId?: string;
|
|
14
|
+
readonly origin?: 'agent' | 'human';
|
|
15
|
+
readonly attempts?: number;
|
|
16
|
+
readonly stagingRef?: string;
|
|
17
|
+
readonly stagingExpiresAt?: number;
|
|
18
|
+
readonly createdAt: string;
|
|
19
|
+
readonly updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface V2Row {
|
|
22
|
+
readonly schemaVersion: 2;
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly pairKey: string;
|
|
25
|
+
readonly a: string;
|
|
26
|
+
readonly b: string;
|
|
27
|
+
readonly state: MergeState;
|
|
28
|
+
readonly queue: 'verify' | 'pending' | 'fuse' | 'skipped';
|
|
29
|
+
readonly failure?: unknown;
|
|
30
|
+
readonly decision?: unknown;
|
|
31
|
+
readonly runId?: string;
|
|
32
|
+
readonly unitId?: string;
|
|
33
|
+
readonly origin?: 'agent' | 'human';
|
|
34
|
+
readonly attempts?: number;
|
|
35
|
+
readonly stagingRef?: string;
|
|
36
|
+
readonly stagingExpiresAt?: number;
|
|
37
|
+
readonly createdAt: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function migrateV1Row(row: LegacyMergeRow): V2Row;
|
|
41
|
+
//# sourceMappingURL=migrate-v1.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function migrateV1Row(row) {
|
|
2
|
+
const queue = row.state === 'pending' ? 'pending' : row.state === 'skipped' ? 'skipped' : row.state === 'fuse' ? 'fuse' : 'verify';
|
|
3
|
+
return {
|
|
4
|
+
schemaVersion: 2,
|
|
5
|
+
id: row.id,
|
|
6
|
+
pairKey: row.pairKey,
|
|
7
|
+
a: row.a,
|
|
8
|
+
b: row.b,
|
|
9
|
+
state: row.state,
|
|
10
|
+
queue,
|
|
11
|
+
...(row.failure === undefined ? {} : { failure: row.failure }),
|
|
12
|
+
...(row.decision === undefined ? {} : { decision: row.decision }),
|
|
13
|
+
...(row.runId === undefined ? {} : { runId: row.runId }),
|
|
14
|
+
...(row.unitId === undefined ? {} : { unitId: row.unitId }),
|
|
15
|
+
...(row.origin === undefined ? {} : { origin: row.origin }),
|
|
16
|
+
...(row.attempts === undefined ? {} : { attempts: row.attempts }),
|
|
17
|
+
...(row.stagingRef === undefined ? {} : { stagingRef: row.stagingRef }),
|
|
18
|
+
...(row.stagingExpiresAt === undefined ? {} : { stagingExpiresAt: row.stagingExpiresAt }),
|
|
19
|
+
createdAt: row.createdAt,
|
|
20
|
+
updatedAt: row.updatedAt,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=migrate-v1.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import type { PairIndexEntryV2 } from './indexes.ts';
|
|
3
|
+
export declare function pairIndexPath(home: string): string;
|
|
4
|
+
export declare function loadPairIndex(io: EvolutionIoLike, home: string): Promise<Map<string, PairIndexEntryV2>>;
|
|
5
|
+
export declare function recordPair(io: EvolutionIoLike, home: string, entry: PairIndexEntryV2): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=pair-index-store.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Durable pairKey index at <home>/merge/pair-index.json. */
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { transactIo } from '@lmzhen/dsh-evolution-core';
|
|
4
|
+
import { parsePairIndex, serializePairIndex } from "./pair-index.js";
|
|
5
|
+
export function pairIndexPath(home) {
|
|
6
|
+
return join(home, 'merge', 'pair-index.json');
|
|
7
|
+
}
|
|
8
|
+
export async function loadPairIndex(io, home) {
|
|
9
|
+
let raw = null;
|
|
10
|
+
try {
|
|
11
|
+
raw = await io.readText(pairIndexPath(home));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return new Map();
|
|
15
|
+
}
|
|
16
|
+
return parsePairIndex(raw);
|
|
17
|
+
}
|
|
18
|
+
export async function recordPair(io, home, entry) {
|
|
19
|
+
await transactIo(io, pairIndexPath(home), (current) => {
|
|
20
|
+
const index = parsePairIndex(current);
|
|
21
|
+
index.set(entry.pairKey, entry);
|
|
22
|
+
return serializePairIndex(index);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=pair-index-store.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Pure pairKey index serialization. No IO. */
|
|
2
|
+
import type { PairIndexEntryV2 } from './indexes.ts';
|
|
3
|
+
export interface PairIndexFile {
|
|
4
|
+
readonly schemaVersion: 1;
|
|
5
|
+
readonly entries: readonly PairIndexEntryV2[];
|
|
6
|
+
}
|
|
7
|
+
export declare function parsePairIndex(raw: string | null): Map<string, PairIndexEntryV2>;
|
|
8
|
+
export declare function serializePairIndex(index: ReadonlyMap<string, PairIndexEntryV2>): string;
|
|
9
|
+
//# sourceMappingURL=pair-index.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function parsePairIndex(raw) {
|
|
2
|
+
const index = new Map();
|
|
3
|
+
if (raw === null)
|
|
4
|
+
return index;
|
|
5
|
+
let parsed;
|
|
6
|
+
try {
|
|
7
|
+
parsed = JSON.parse(raw);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return index;
|
|
11
|
+
}
|
|
12
|
+
const body = parsed;
|
|
13
|
+
if (!Array.isArray(body.entries))
|
|
14
|
+
return index;
|
|
15
|
+
for (const value of body.entries) {
|
|
16
|
+
const entry = value;
|
|
17
|
+
if (typeof entry.pairKey !== 'string' || typeof entry.runId !== 'string' || typeof entry.result !== 'string')
|
|
18
|
+
continue;
|
|
19
|
+
if (!['merged', 'kept', 'failed', 'cancelled', 'undone'].includes(entry.result))
|
|
20
|
+
continue;
|
|
21
|
+
index.set(entry.pairKey, entry);
|
|
22
|
+
}
|
|
23
|
+
return index;
|
|
24
|
+
}
|
|
25
|
+
export function serializePairIndex(index) {
|
|
26
|
+
return JSON.stringify({ schemaVersion: 1, entries: [...index.values()] }, null, 2);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=pair-index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Pure pairKey lookup over run records. No IO. */
|
|
2
|
+
import type { PairIndexEntryV2 } from './indexes.ts';
|
|
3
|
+
import type { PairRecord } from './run-record.ts';
|
|
4
|
+
export interface PairRunLike {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly pairKey?: string;
|
|
7
|
+
readonly pairs: readonly PairRecord[];
|
|
8
|
+
readonly resultRef?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function findPairRun(runs: readonly PairRunLike[], pairKey: string): PairIndexEntryV2 | undefined;
|
|
11
|
+
//# sourceMappingURL=resolve-pair.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function findPairRun(runs, pairKey) {
|
|
2
|
+
for (const run of runs) {
|
|
3
|
+
if (run.pairKey === pairKey) {
|
|
4
|
+
return { pairKey, runId: run.id, result: 'merged', ...(run.resultRef === undefined ? {} : { reportRef: run.resultRef }) };
|
|
5
|
+
}
|
|
6
|
+
const pair = run.pairs.find(item => item.pairKey === pairKey);
|
|
7
|
+
if (pair !== undefined) {
|
|
8
|
+
return {
|
|
9
|
+
pairKey, runId: run.id, result: pair.result === 'undone' ? 'undone' : pair.result === 'kept' ? 'kept' : pair.result === 'failed' ? 'failed' : pair.result === 'cancelled' ? 'cancelled' : 'merged',
|
|
10
|
+
...(pair.target?.actual === undefined ? {} : { target: pair.target.actual }),
|
|
11
|
+
...(run.resultRef === undefined ? {} : { reportRef: run.resultRef }),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=resolve-pair.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type MergeState } from './state.ts';
|
|
2
|
+
import type { MergeDecision, MergeFailure } from '../contracts.ts';
|
|
3
|
+
export interface MergeRowV2 {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly pairKey: string;
|
|
6
|
+
readonly a: string;
|
|
7
|
+
readonly b: string;
|
|
8
|
+
readonly state: MergeState;
|
|
9
|
+
readonly queue: 'verify' | 'pending' | 'fuse' | 'skipped';
|
|
10
|
+
readonly failure?: MergeFailure;
|
|
11
|
+
readonly decision?: MergeDecision;
|
|
12
|
+
readonly sourceRevision?: string;
|
|
13
|
+
readonly targetRevision?: string;
|
|
14
|
+
readonly runId?: string;
|
|
15
|
+
readonly unitId?: string;
|
|
16
|
+
readonly origin?: 'agent' | 'human' | 'system';
|
|
17
|
+
readonly attempts?: number;
|
|
18
|
+
readonly stagingRef?: string;
|
|
19
|
+
readonly stagingExpiresAt?: number;
|
|
20
|
+
readonly target?: {
|
|
21
|
+
readonly requested: string;
|
|
22
|
+
readonly actual?: string;
|
|
23
|
+
readonly created?: boolean;
|
|
24
|
+
};
|
|
25
|
+
readonly reason?: string;
|
|
26
|
+
readonly expired?: 'target-taken' | 'source-changed';
|
|
27
|
+
readonly createdAt: string;
|
|
28
|
+
readonly updatedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export type MergeRowEvent = {
|
|
31
|
+
readonly type: 'candidate';
|
|
32
|
+
readonly a: string;
|
|
33
|
+
readonly b: string;
|
|
34
|
+
readonly at: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: 'round1-start';
|
|
37
|
+
readonly at: string;
|
|
38
|
+
} | {
|
|
39
|
+
readonly type: 'round1-keep';
|
|
40
|
+
readonly at: string;
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: 'round1-merge';
|
|
43
|
+
readonly approval: 'on' | 'off';
|
|
44
|
+
readonly decision?: MergeDecision;
|
|
45
|
+
readonly stagingRef?: string;
|
|
46
|
+
readonly stagingExpiresAt?: number;
|
|
47
|
+
readonly at: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly type: 'round1-fail';
|
|
50
|
+
readonly failure: MergeFailure;
|
|
51
|
+
readonly at: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: 'approve';
|
|
54
|
+
readonly runId?: string;
|
|
55
|
+
readonly at: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly type: 'reject';
|
|
58
|
+
readonly at: string;
|
|
59
|
+
} | {
|
|
60
|
+
readonly type: 'round2-start';
|
|
61
|
+
readonly at: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly type: 'round2-fail';
|
|
64
|
+
readonly failure: MergeFailure;
|
|
65
|
+
readonly at: string;
|
|
66
|
+
} | {
|
|
67
|
+
readonly type: 'round2-success';
|
|
68
|
+
readonly unitId?: string;
|
|
69
|
+
readonly at: string;
|
|
70
|
+
} | {
|
|
71
|
+
readonly type: 'restore';
|
|
72
|
+
readonly at: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly type: 'discard';
|
|
75
|
+
readonly at: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly type: 'source-gone';
|
|
78
|
+
readonly at: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly type: 'target-taken';
|
|
81
|
+
readonly at: string;
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: 'source-changed';
|
|
84
|
+
readonly at: string;
|
|
85
|
+
} | {
|
|
86
|
+
readonly type: 'staging-expired';
|
|
87
|
+
readonly at: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly type: 'round2-cancel';
|
|
90
|
+
readonly at: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly type: 'timeout';
|
|
93
|
+
readonly at: string;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: 'cancel';
|
|
96
|
+
readonly at: string;
|
|
97
|
+
} | {
|
|
98
|
+
readonly type: 'gate-keep';
|
|
99
|
+
readonly a: string;
|
|
100
|
+
readonly b: string;
|
|
101
|
+
readonly reason?: string;
|
|
102
|
+
readonly at: string;
|
|
103
|
+
};
|
|
104
|
+
export type RowTransitionResult = {
|
|
105
|
+
readonly ok: true;
|
|
106
|
+
readonly row: MergeRowV2 | null;
|
|
107
|
+
} | {
|
|
108
|
+
readonly ok: false;
|
|
109
|
+
readonly code: 'invalid-transition';
|
|
110
|
+
readonly message: string;
|
|
111
|
+
};
|
|
112
|
+
export declare function transitionRow(current: MergeRowV2 | undefined, event: MergeRowEvent): RowTransitionResult;
|
|
113
|
+
//# sourceMappingURL=row.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
}
|
|
111
|
+
//# sourceMappingURL=row.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Pure RunRecord v2 types and derived 9-state projection. No IO. */
|
|
2
|
+
export type RunStatus = 'running' | 'succeeded' | 'failed' | 'cancelled' | 'interrupted' | 'orphan';
|
|
3
|
+
export type MergeStatus = 'running' | 'succeeded' | 'failed' | 'cancelled' | 'awaiting-approval' | 'undone' | 'superseded' | 'interrupted' | 'orphan';
|
|
4
|
+
export type PairResult = 'pending' | 'merged' | 'kept' | 'failed' | 'cancelled' | 'undone';
|
|
5
|
+
export interface PairRecord {
|
|
6
|
+
readonly pairKey: string;
|
|
7
|
+
readonly a: string;
|
|
8
|
+
readonly b: string;
|
|
9
|
+
readonly result: PairResult;
|
|
10
|
+
readonly target?: {
|
|
11
|
+
readonly requested: string;
|
|
12
|
+
readonly actual?: string;
|
|
13
|
+
readonly created?: boolean;
|
|
14
|
+
};
|
|
15
|
+
readonly stagingRef?: string;
|
|
16
|
+
readonly committed?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PairIndexEntry {
|
|
19
|
+
readonly pairKey: string;
|
|
20
|
+
readonly runId: string;
|
|
21
|
+
readonly result: PairResult;
|
|
22
|
+
readonly target?: string;
|
|
23
|
+
readonly reportRef?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RunRecordV2 {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly kind: 'merge';
|
|
28
|
+
readonly status: RunStatus;
|
|
29
|
+
readonly stage?: 'screening' | 'decide' | 'author' | 'commit' | 'settle';
|
|
30
|
+
readonly approval?: 'on' | 'off';
|
|
31
|
+
readonly parentRunId?: string;
|
|
32
|
+
readonly pairKey?: string;
|
|
33
|
+
readonly plane?: 'local' | 'foreign';
|
|
34
|
+
readonly startedAt: number;
|
|
35
|
+
readonly updatedAt: number;
|
|
36
|
+
readonly endedAt?: number;
|
|
37
|
+
readonly resultRef?: string;
|
|
38
|
+
readonly failure?: string;
|
|
39
|
+
readonly stagingRef?: string;
|
|
40
|
+
readonly pairs: readonly PairRecord[];
|
|
41
|
+
readonly supersededAt?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function deriveMergeStatus(record: RunRecordV2): MergeStatus;
|
|
44
|
+
export declare function allCommittedUndone(record: RunRecordV2): boolean;
|
|
45
|
+
//# sourceMappingURL=run-record.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function deriveMergeStatus(record) {
|
|
2
|
+
if (record.supersededAt !== undefined)
|
|
3
|
+
return 'superseded';
|
|
4
|
+
if (record.status === 'interrupted')
|
|
5
|
+
return 'interrupted';
|
|
6
|
+
if (record.status === 'orphan')
|
|
7
|
+
return 'orphan';
|
|
8
|
+
if (record.status === 'failed' || record.status === 'cancelled')
|
|
9
|
+
return record.status;
|
|
10
|
+
if (record.status === 'running') {
|
|
11
|
+
return record.approval === 'on' && record.pairs.some(pair => pair.result === 'pending')
|
|
12
|
+
? 'awaiting-approval'
|
|
13
|
+
: 'running';
|
|
14
|
+
}
|
|
15
|
+
const committed = record.pairs.filter(pair => pair.committed === true);
|
|
16
|
+
if (committed.length > 0 && committed.every(pair => pair.result === 'undone'))
|
|
17
|
+
return 'undone';
|
|
18
|
+
return 'succeeded';
|
|
19
|
+
}
|
|
20
|
+
export function allCommittedUndone(record) {
|
|
21
|
+
const committed = record.pairs.filter(pair => pair.committed === true);
|
|
22
|
+
return committed.length > 0 && committed.every(pair => pair.result === 'undone');
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=run-record.js.map
|