@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
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four read-only merge routes: view / rows / body / history.
|
|
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).
|
|
7
|
+
*/
|
|
1
8
|
import { HOST_ROUTES, json, methodNotAllowed } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
2
|
-
import { buildFacts as buildLegacyFacts } from "./facts.js";
|
|
3
|
-
import { toBodyView, toHistoryView, toQueuePage } from "./wire-projection.js";
|
|
4
9
|
import { buildBodyView, buildFacts as buildSnapshotFacts, buildHistoryView, buildQueuePage } from "./surface/merge-view.js";
|
|
5
10
|
const MERGE_PATHS = HOST_ROUTES.merge;
|
|
6
11
|
const MERGE_BASE = MERGE_PATHS.view.slice(0, -'view'.length);
|
|
@@ -13,17 +18,16 @@ export const MERGE_ROUTES = {
|
|
|
13
18
|
};
|
|
14
19
|
export function makeMergeRoutes(deps, _audit) {
|
|
15
20
|
const absent = () => json(404, { wireVersion: 1, code: 'E-325', message: 'merge service is not mounted' });
|
|
16
|
-
const blocked = (
|
|
21
|
+
const blocked = (snapshot) => snapshot.blocked === undefined ? null : json(503, { wireVersion: 1, code: 'merge-state-blocked', readiness: snapshot.blocked.readiness });
|
|
17
22
|
const hostOnly = (snapshot) => snapshot.hostOnly === undefined ? null : json(503, { wireVersion: 1, code: 'E-323', message: snapshot.hostOnly.detail ?? 'host data plane unavailable' });
|
|
18
|
-
const notReady = async (source) => {
|
|
19
|
-
if (source.readiness === undefined)
|
|
20
|
-
return null;
|
|
21
|
-
const readiness = await source.readiness();
|
|
22
|
-
return readiness.ok ? null : blocked(readiness);
|
|
23
|
-
};
|
|
24
23
|
const bad = (message) => json(400, { wireVersion: 1, code: 'merge-bad-query', message });
|
|
25
24
|
const get = (request) => (request.method === 'GET' ? null : methodNotAllowed(['GET']));
|
|
26
25
|
const url = (request) => new URL(request.url, 'http://local');
|
|
26
|
+
const snapshotOf = async () => {
|
|
27
|
+
const source = deps.getSource();
|
|
28
|
+
return source === undefined ? null : await source.snapshot();
|
|
29
|
+
};
|
|
30
|
+
const unavailable = (snapshot) => snapshot === null ? absent() : blocked(snapshot) ?? hostOnly(snapshot);
|
|
27
31
|
return [
|
|
28
32
|
{
|
|
29
33
|
path: MERGE_ROUTES.view,
|
|
@@ -33,25 +37,11 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
33
37
|
const denied = get(request);
|
|
34
38
|
if (denied !== null)
|
|
35
39
|
return denied;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (snapshot.blocked !== undefined)
|
|
42
|
-
return json(503, { wireVersion: 1, code: 'merge-state-blocked', readiness: snapshot.blocked.readiness });
|
|
43
|
-
const capability = hostOnly(snapshot);
|
|
44
|
-
if (capability !== null)
|
|
45
|
-
return capability;
|
|
46
|
-
return json(200, buildSnapshotFacts(snapshot));
|
|
47
|
-
}
|
|
48
|
-
const readiness = await notReady(source);
|
|
49
|
-
if (readiness !== null)
|
|
50
|
-
return readiness;
|
|
51
|
-
const readAt = Date.now();
|
|
52
|
-
const rows = await source.rows?.() ?? [];
|
|
53
|
-
const runs = await source.runs?.(undefined, 20) ?? [];
|
|
54
|
-
return json(200, buildLegacyFacts({ rows, runs, approval: source.approval?.() === true, readAt }));
|
|
40
|
+
const snapshot = await snapshotOf();
|
|
41
|
+
const notReady = unavailable(snapshot);
|
|
42
|
+
if (notReady !== null)
|
|
43
|
+
return notReady;
|
|
44
|
+
return json(200, buildSnapshotFacts(snapshot));
|
|
55
45
|
},
|
|
56
46
|
},
|
|
57
47
|
{
|
|
@@ -62,9 +52,6 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
62
52
|
const denied = get(request);
|
|
63
53
|
if (denied !== null)
|
|
64
54
|
return denied;
|
|
65
|
-
const source = deps.getSource();
|
|
66
|
-
if (source === undefined)
|
|
67
|
-
return absent();
|
|
68
55
|
const query = url(request).searchParams;
|
|
69
56
|
const queue = query.get('queue') ?? 'verify';
|
|
70
57
|
if (queue !== 'verify' && queue !== 'pending' && queue !== 'fuse' && queue !== 'skipped')
|
|
@@ -73,27 +60,16 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
73
60
|
if (expiredOnly && queue !== 'pending')
|
|
74
61
|
return bad('expired is only allowed for pending');
|
|
75
62
|
const limit = Number.parseInt(query.get('limit') ?? '50', 10);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
return bad(error instanceof Error ? error.message : 'queue cursor is stale');
|
|
88
|
-
}
|
|
63
|
+
const snapshot = await snapshotOf();
|
|
64
|
+
const notReady = unavailable(snapshot);
|
|
65
|
+
if (notReady !== null)
|
|
66
|
+
return notReady;
|
|
67
|
+
try {
|
|
68
|
+
return json(200, buildQueuePage(snapshot, queue, expiredOnly, query.get('cursor') ?? undefined, Number.isFinite(limit) ? limit : 50));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return bad(error instanceof Error ? error.message : 'queue cursor is stale');
|
|
89
72
|
}
|
|
90
|
-
const readiness = await notReady(source);
|
|
91
|
-
if (readiness !== null)
|
|
92
|
-
return readiness;
|
|
93
|
-
const offset = Number.parseInt(query.get('cursor') ?? '0', 10);
|
|
94
|
-
const rows = await source.rows?.() ?? [];
|
|
95
|
-
const page = toQueuePage(rows, queue, expiredOnly, Date.now(), Number.isFinite(limit) ? limit : 50);
|
|
96
|
-
return json(200, Number.isFinite(offset) && offset > 0 ? { ...page, rows: page.rows.slice(offset) } : page);
|
|
97
73
|
},
|
|
98
74
|
},
|
|
99
75
|
{
|
|
@@ -113,36 +89,26 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
113
89
|
const side = query.get('side');
|
|
114
90
|
if ((id === null || id === '') && (name === null || name === ''))
|
|
115
91
|
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge body requires id or name' });
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
const readiness = await notReady(source);
|
|
138
|
-
if (readiness !== null)
|
|
139
|
-
return readiness;
|
|
140
|
-
if (id === null || id === '' || (side !== 'a' && side !== 'b'))
|
|
141
|
-
return bad('id and side=a|b are required');
|
|
142
|
-
const content = await source.body?.(id, side) ?? null;
|
|
143
|
-
return content === null
|
|
144
|
-
? json(200, { wireVersion: 1, code: 'merge-body-missing', message: 'merge body not found: ' + id })
|
|
145
|
-
: json(200, toBodyView(id, side === 'a' ? 'live' : 'archive', content, Date.now()));
|
|
92
|
+
const snapshot = await source.snapshot();
|
|
93
|
+
const notReady = blocked(snapshot);
|
|
94
|
+
if (notReady !== null)
|
|
95
|
+
return notReady;
|
|
96
|
+
// Body/history are read-only projections of already-written files; a host without
|
|
97
|
+
// the live agent data plane can still read past reports and archived bodies.
|
|
98
|
+
const sideFilter = side === 'a' ? 'live' : side === 'b' ? 'archive' : side === 'live' || side === 'archive' ? side : undefined;
|
|
99
|
+
const refs = snapshot.bodyRefs.filter(ref => (id === null || id === '' || ref.rowId === id)
|
|
100
|
+
&& (name === null || name === '' || ref.name === name)
|
|
101
|
+
&& (sideFilter === undefined || ref.side === sideFilter));
|
|
102
|
+
if (refs.length === 0)
|
|
103
|
+
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge body not found' });
|
|
104
|
+
if (refs.length > 1)
|
|
105
|
+
return bad('merge body is ambiguous; pass id, name and side');
|
|
106
|
+
const row = snapshot.rows.find(item => item.row.id === refs[0].rowId);
|
|
107
|
+
if (row?.stage?.state === 'lost' || row?.stage?.state === 'expired')
|
|
108
|
+
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge stage is lost' });
|
|
109
|
+
const ref = refs[0];
|
|
110
|
+
const content = source.bodyRef === undefined ? null : await source.bodyRef(ref);
|
|
111
|
+
return json(200, buildBodyView(snapshot, ref, content ?? ''));
|
|
146
112
|
},
|
|
147
113
|
},
|
|
148
114
|
{
|
|
@@ -153,19 +119,13 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
153
119
|
const denied = get(request);
|
|
154
120
|
if (denied !== null)
|
|
155
121
|
return denied;
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
122
|
+
const snapshot = await snapshotOf();
|
|
123
|
+
if (snapshot === null)
|
|
158
124
|
return absent();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return json(200, buildHistoryView(snapshot));
|
|
164
|
-
}
|
|
165
|
-
const readiness = await notReady(source);
|
|
166
|
-
if (readiness !== null)
|
|
167
|
-
return readiness;
|
|
168
|
-
return json(200, toHistoryView(await source.history?.() ?? [], Date.now()));
|
|
125
|
+
const notReady = blocked(snapshot);
|
|
126
|
+
if (notReady !== null)
|
|
127
|
+
return notReady;
|
|
128
|
+
return json(200, buildHistoryView(snapshot));
|
|
169
129
|
},
|
|
170
130
|
},
|
|
171
131
|
{
|
|
@@ -10,21 +10,15 @@ import { buildValidationPlan } from "./domain/validation-plan.js";
|
|
|
10
10
|
import { commitStagedRun } from "./commit-execute.js";
|
|
11
11
|
import { manifestFilesOf, missingManifestFile, parseAuthoringManifest } from "./domain/authoring-manifest.js";
|
|
12
12
|
import { MERGE_PROMPT_VERSION, promptHashOf } from "./prompts.js";
|
|
13
|
+
import { targetAdmission } from "./authoring-flow.js";
|
|
13
14
|
/** Commit-side causes that mean "the world moved"; they must not be retried blindly. */
|
|
14
15
|
const STALE_CAUSES = new Set(['target-changed', 'source-changed', 'support-changed', 'target-taken', 'target-gone']);
|
|
15
16
|
export async function runNewChildAuthoring(options, row, decision, signal) {
|
|
16
|
-
if (decision.target.includes('/') || decision.target.includes(':'))
|
|
17
|
-
return { kind: 'failed', failure: { stage: 'author', code: 'target-invalid', message: 'target must be a bare skill name: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
18
17
|
const targetCard = options.cards.find(card => card.name === decision.target);
|
|
19
18
|
const createMode = decision.mode === 'create-umbrella';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
|
|
24
|
-
return { kind: 'failed', failure: { stage: 'author', code: 'target-protected', message: 'target is protected: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
25
|
-
}
|
|
26
|
-
if (!createMode && await options.isBlocked?.(decision.target) === true)
|
|
27
|
-
return { kind: 'failed', failure: { stage: 'author', code: 'target-blocked', message: 'target is blocked by curator: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
19
|
+
const targetRefusal = await targetAdmission(decision.target, targetCard, createMode, options.isBlocked);
|
|
20
|
+
if (targetRefusal !== null)
|
|
21
|
+
return { kind: 'failed', failure: { stage: 'author', code: targetRefusal.code, message: targetRefusal.message, retryable: false, retryFrom: 'none' } };
|
|
28
22
|
const at = options.now();
|
|
29
23
|
// NFR-06: an expired stage is never reused and never re-presented. Drop its tree and
|
|
30
24
|
// index and author into a fresh one; the recorded decision anchors still guard the
|
package/lib/types/prompts.d.ts
CHANGED
|
@@ -9,5 +9,4 @@ export declare function buildRound1Prompt(candidate: {
|
|
|
9
9
|
readonly b: string;
|
|
10
10
|
}, cards: readonly SkillCard[]): string;
|
|
11
11
|
export declare function buildRound2Prompt(row: MergeRow, decision: MergeDecision, mode?: 'continue' | 'fresh'): string;
|
|
12
|
-
export declare function buildStandbyPrompt(): string;
|
|
13
12
|
//# sourceMappingURL=prompts.d.ts.map
|
package/lib/types/prompts.js
CHANGED
|
@@ -69,5 +69,4 @@ export function buildRound2Prompt(row, decision, mode = 'continue') {
|
|
|
69
69
|
];
|
|
70
70
|
return (mode === 'fresh' ? ['基于已持久化的 decision。', ...head, ...body] : [...head, ...body]).join('\n');
|
|
71
71
|
}
|
|
72
|
-
export function buildStandbyPrompt() { return STANDBY_PROMPT; }
|
|
73
72
|
//# sourceMappingURL=prompts.js.map
|
|
@@ -7,5 +7,4 @@ import type { LiveRowFacts } from './domain/live-facts.ts';
|
|
|
7
7
|
export declare function handleReadOnlyMergeCommand(command: MergeCliCommand, io: EvolutionIoLike, home: string, cards?: readonly {
|
|
8
8
|
readonly name: string;
|
|
9
9
|
}[], runs?: readonly RunRecord[], approval?: boolean, live?: () => Promise<LiveRowFacts | undefined>): Promise<string | null>;
|
|
10
|
-
export declare function mergeCommandHelp(): string;
|
|
11
10
|
//# sourceMappingURL=read-commands.d.ts.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readMergeSnapshot } from "./domain/merge-snapshot.js";
|
|
2
2
|
import { buildFacts, toRowFact } from "./surface/merge-view.js";
|
|
3
3
|
import { formatFacts, formatSnapshotQueue, formatSnapshotStatus } from "./status.js";
|
|
4
|
-
import { mergeUsage } from "./commands.js";
|
|
5
4
|
export async function handleReadOnlyMergeCommand(command, io, home, cards, runs, approval = false, live) {
|
|
6
5
|
if (!['status', 'pending', 'candidates', 'skipped'].includes(command.verb))
|
|
7
6
|
return null;
|
|
@@ -27,7 +26,4 @@ export async function handleReadOnlyMergeCommand(command, io, home, cards, runs,
|
|
|
27
26
|
}
|
|
28
27
|
return formatSnapshotQueue(rows.filter(row => row.queue === 'skipped'), 'skipped');
|
|
29
28
|
}
|
|
30
|
-
export function mergeCommandHelp() {
|
|
31
|
-
return mergeUsage();
|
|
32
|
-
}
|
|
33
29
|
//# sourceMappingURL=read-commands.js.map
|
|
@@ -3,7 +3,6 @@ import { mergeReportPath, readMergeReport, writeMergeReport } from "./report.js"
|
|
|
3
3
|
import { pairLedger, readPairLedgerV4 } from "./domain/pair-ledger.js";
|
|
4
4
|
import { readAllStages, readStageTree, writeStageIndex } from "./domain/stage-aggregate.js";
|
|
5
5
|
import { dropStage } from "./domain/stage-aggregate.js";
|
|
6
|
-
import { recordPair } from "./domain/pair-index-store.js";
|
|
7
6
|
import { readRunMeta, writeRunMeta } from "./domain/run-coordinator.js";
|
|
8
7
|
import { evidenceSha256 } from "./domain/evidence-hash.js";
|
|
9
8
|
export async function projectRecoveredUnit(options) {
|
|
@@ -79,8 +78,6 @@ export async function projectRecoveredUnit(options) {
|
|
|
79
78
|
await options.onPhase?.('stage-drop');
|
|
80
79
|
await dropStage(options.io, options.home, stageRowId);
|
|
81
80
|
}
|
|
82
|
-
await options.onPhase?.('pair-index');
|
|
83
|
-
await recordPair(options.io, options.home, { pairKey, runId: options.runId, result: 'merged', target });
|
|
84
81
|
await options.onPhase?.('run-meta');
|
|
85
82
|
const meta = await readRunMeta(options.io, options.home, options.runId);
|
|
86
83
|
await writeRunMeta(options.io, options.home, { version: 1, runId: options.runId, ...(meta?.pid === undefined ? {} : { pid: meta.pid }), phase: 'settled', ...(options.interrupted === true ? { stopReason: 'interrupted' } : {}), reportRef: reportPath, pairCount: 1, ...(meta?.parentRunId === undefined ? {} : { parentRunId: meta.parentRunId }), ...(meta?.stagingRef === undefined ? {} : { stagingRef: meta.stagingRef }), ...(meta?.pairs === undefined ? {} : { pairs: meta.pairs }), createdAt: meta?.createdAt ?? new Date(at).toISOString(), updatedAt: new Date(at).toISOString() });
|
|
@@ -2,7 +2,6 @@ import { v4ToLegacy } from "./domain/row-v4.js";
|
|
|
2
2
|
import { pairLedger } from "./domain/pair-ledger.js";
|
|
3
3
|
import { dropStage } from "./domain/stage-aggregate.js";
|
|
4
4
|
import { casExpiredHook } from "./domain/failure-route.js";
|
|
5
|
-
import { recordPair } from "./domain/run-coordinator.js";
|
|
6
5
|
export async function recordRound2Outcome(io, home, now, row, outcome, deferProjection = false, fence) {
|
|
7
6
|
void now;
|
|
8
7
|
const reportExtra = {
|
|
@@ -10,9 +9,13 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
10
9
|
...(outcome.criteria === undefined ? {} : { criteria: outcome.criteria }),
|
|
11
10
|
...(outcome.validationIssues === undefined ? {} : { validation: { issues: outcome.validationIssues } }),
|
|
12
11
|
};
|
|
12
|
+
const pair = (outcomeName, extra = {}, withDecision = true) => ({
|
|
13
|
+
id: row.id, pairKey: row.pairKey, a: row.a, b: row.b, queue: row.queue,
|
|
14
|
+
...(withDecision && row.decision !== undefined ? { decision: row.decision } : {}),
|
|
15
|
+
outcome: outcomeName, archived: [], ...extra,
|
|
16
|
+
});
|
|
13
17
|
if (outcome.kind === 'merged') {
|
|
14
18
|
const unitRef = { unitId: outcome.unitId ?? 'unknown', evidenceSha256: outcome.evidenceSha256 ?? 'unknown' };
|
|
15
|
-
const target = outcome.target?.actual ?? outcome.target?.requested ?? row.decision?.target;
|
|
16
19
|
const finalize = async () => {
|
|
17
20
|
const removed = await pairLedger(io, home, fence).transact(tx => {
|
|
18
21
|
const current = tx.rows().find(item => item.id === row.id);
|
|
@@ -21,31 +24,20 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
21
24
|
if (!removed.ok)
|
|
22
25
|
throw new Error('row changed before commit record: ' + row.id);
|
|
23
26
|
await dropStage(io, home, row.id);
|
|
24
|
-
await recordPair(io, home, {
|
|
25
|
-
pairKey: row.pairKey,
|
|
26
|
-
runId: row.runId ?? 'merge',
|
|
27
|
-
result: 'merged',
|
|
28
|
-
...(target === undefined ? {} : { target }),
|
|
29
|
-
...(outcome.unitId === undefined ? {} : { reportRef: outcome.unitId }),
|
|
30
|
-
});
|
|
31
27
|
};
|
|
32
28
|
if (deferProjection)
|
|
33
|
-
return { merged: true, finalize, pair:
|
|
34
|
-
|
|
35
|
-
await finalize();
|
|
36
|
-
}
|
|
29
|
+
return { merged: true, finalize, pair: pair('merged', { ...(outcome.lengths === undefined ? {} : { lengths: outcome.lengths }), ...(outcome.unitId === undefined ? {} : { unitId: outcome.unitId }), ...(outcome.target === undefined ? {} : { target: outcome.target }), ...reportExtra }) };
|
|
30
|
+
await finalize();
|
|
37
31
|
return {
|
|
38
32
|
merged: true,
|
|
39
|
-
pair: {
|
|
40
|
-
|
|
41
|
-
...(row.decision === undefined ? {} : { decision: row.decision }),
|
|
42
|
-
outcome: 'merged', archived: outcome.archived ?? [],
|
|
33
|
+
pair: pair('merged', {
|
|
34
|
+
...(outcome.archived === undefined ? {} : { archived: outcome.archived }),
|
|
43
35
|
...(outcome.lengths === undefined ? {} : { lengths: outcome.lengths }),
|
|
44
36
|
...reportExtra,
|
|
45
37
|
...(outcome.unitId === undefined ? {} : { unitId: outcome.unitId }),
|
|
46
38
|
...(outcome.target === undefined ? {} : { target: outcome.target }),
|
|
47
39
|
...(outcome.validationPlan === undefined ? {} : { validationPlan: outcome.validationPlan }),
|
|
48
|
-
},
|
|
40
|
+
}),
|
|
49
41
|
};
|
|
50
42
|
}
|
|
51
43
|
const failure = outcome.failure ?? { stage: 'author', code: 'merge-failed', message: 'round2 failed', retryable: true, retryFrom: 'agent2' };
|
|
@@ -57,7 +49,7 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
57
49
|
});
|
|
58
50
|
if (skipped)
|
|
59
51
|
await dropStage(io, home, row.id);
|
|
60
|
-
return { merged: false, pair:
|
|
52
|
+
return { merged: false, pair: pair('failed', { failure, ...reportExtra }) };
|
|
61
53
|
}
|
|
62
54
|
const cas = casExpiredHook(failure.code);
|
|
63
55
|
if (cas !== null) {
|
|
@@ -70,7 +62,7 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
70
62
|
return moved.ok;
|
|
71
63
|
});
|
|
72
64
|
await dropStage(io, home, row.id);
|
|
73
|
-
return { merged: false, pair:
|
|
65
|
+
return { merged: false, pair: pair('failed', { failure: terminalFailure, ...reportExtra }) };
|
|
74
66
|
}
|
|
75
67
|
if (failure.code === 'commit-failed' || failure.code === 'validation-failed') {
|
|
76
68
|
// FR-EXEC-11/14: report-only failure. The pair leaves the active queue (so a later scan
|
|
@@ -81,7 +73,6 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
81
73
|
});
|
|
82
74
|
if (dropped) {
|
|
83
75
|
await dropStage(io, home, row.id, row.stagingRef);
|
|
84
|
-
await recordPair(io, home, { pairKey: row.pairKey, runId: row.runId ?? 'merge', result: 'failed', ...(row.decision?.target === undefined ? {} : { target: row.decision.target }) });
|
|
85
76
|
}
|
|
86
77
|
}
|
|
87
78
|
if (failure.code === 'source-gone') {
|
|
@@ -91,7 +82,7 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
91
82
|
});
|
|
92
83
|
if (dropped)
|
|
93
84
|
await dropStage(io, home, row.id, row.stagingRef);
|
|
94
|
-
return { merged: false, pair:
|
|
85
|
+
return { merged: false, pair: pair('failed', { failure }, false) };
|
|
95
86
|
}
|
|
96
87
|
const projected = await pairLedger(io, home, fence).transact(tx => {
|
|
97
88
|
const current = tx.rows().find(item => item.id === row.id);
|
|
@@ -101,23 +92,10 @@ export async function recordRound2Outcome(io, home, now, row, outcome, deferProj
|
|
|
101
92
|
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
102
93
|
});
|
|
103
94
|
if (projected === null)
|
|
104
|
-
return { merged: false, pair:
|
|
105
|
-
{
|
|
106
|
-
await recordPair(io, home, {
|
|
107
|
-
pairKey: row.pairKey,
|
|
108
|
-
runId: row.runId ?? 'merge',
|
|
109
|
-
result: 'failed',
|
|
110
|
-
...(row.decision?.target === undefined ? {} : { target: row.decision.target }),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
95
|
+
return { merged: false, pair: pair('failed', { failure: { ...failure, code: 'row-changed', message: 'row changed before failure record' }, ...reportExtra }, false) };
|
|
113
96
|
return {
|
|
114
97
|
merged: false,
|
|
115
|
-
pair: {
|
|
116
|
-
id: row.id, pairKey: row.pairKey, a: row.a, b: row.b, queue: row.queue,
|
|
117
|
-
...(row.decision === undefined ? {} : { decision: row.decision }),
|
|
118
|
-
outcome: 'failed', archived: [], failure,
|
|
119
|
-
...reportExtra,
|
|
120
|
-
},
|
|
98
|
+
pair: pair('failed', { failure, ...reportExtra }),
|
|
121
99
|
};
|
|
122
100
|
}
|
|
123
101
|
//# sourceMappingURL=round2-outcome.js.map
|
package/lib/types/scan-run.js
CHANGED
|
@@ -6,7 +6,6 @@ import { pairKey, rowIdOf, } from "./contracts.js";
|
|
|
6
6
|
import { loadMergeState } from "./state.js";
|
|
7
7
|
import { pairLedger } from "./domain/pair-ledger.js";
|
|
8
8
|
import { presentStage, dropStage } from "./domain/stage-aggregate.js";
|
|
9
|
-
import { recordPair } from "./domain/run-coordinator.js";
|
|
10
9
|
import { v4ToLegacy } from "./domain/row-v4.js";
|
|
11
10
|
import { criteriaFrom } from "./domain/engine-criteria.js";
|
|
12
11
|
import { withDecisionRevisions } from "./revision.js";
|
|
@@ -176,7 +175,6 @@ export async function runScan(options) {
|
|
|
176
175
|
continue;
|
|
177
176
|
}
|
|
178
177
|
row = projected;
|
|
179
|
-
await recordPair(options.io, options.home, { pairKey: row.pairKey, runId: row.runId ?? 'merge', result: 'kept' });
|
|
180
178
|
pairs.push(pairOf(row, candidate, 'kept', { ...(round1.decision === undefined ? {} : { decision: round1.decision }), ...(round1.usage === undefined ? {} : { usage: round1.usage }) }));
|
|
181
179
|
totals.kept += 1;
|
|
182
180
|
continue;
|
|
@@ -324,7 +322,6 @@ export async function runScan(options) {
|
|
|
324
322
|
});
|
|
325
323
|
if (dropped) {
|
|
326
324
|
await dropStage(options.io, options.home, row.id, row.stagingRef);
|
|
327
|
-
await recordPair(options.io, options.home, { pairKey: row.pairKey, runId: row.runId ?? 'merge', result: 'failed', ...(row.decision?.target === undefined ? {} : { target: row.decision.target }) });
|
|
328
325
|
}
|
|
329
326
|
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed, ...(round2.manifestFiles === undefined ? {} : { manifestFiles: round2.manifestFiles }), ...(round2.criteria === undefined ? {} : { criteria: round2.criteria }), ...(round2.validationIssues === undefined ? {} : { validation: { issues: round2.validationIssues } }) }));
|
|
330
327
|
totals.failed += 1;
|
package/lib/types/skipped.d.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Skipped filtering: a pair the operator marked as not-worth-merging stays out of automatic
|
|
3
3
|
* screening until mergeSkippedTtlDays expires. Explicit /merge pair ignores this set.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import type { MergeRow } from './contracts.ts';
|
|
6
6
|
export declare function skippedPairKeys(rows: readonly MergeRow[], ttlDays: number, now: number): ReadonlySet<string>;
|
|
7
|
-
export declare function isSkippedPair(keys: ReadonlySet<string>, a: string, b: string): boolean;
|
|
8
7
|
//# sourceMappingURL=skipped.d.ts.map
|
package/lib/types/skipped.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skipped filtering: a pair the operator marked as not-worth-merging stays out of automatic
|
|
3
|
-
* screening until mergeSkippedTtlDays expires. Explicit /merge pair ignores this set.
|
|
4
|
-
*/
|
|
5
|
-
import { pairKey } from "./contracts.js";
|
|
6
1
|
export function skippedPairKeys(rows, ttlDays, now) {
|
|
7
2
|
const keys = new Set();
|
|
8
3
|
for (const row of rows) {
|
|
@@ -18,7 +13,4 @@ export function skippedPairKeys(rows, ttlDays, now) {
|
|
|
18
13
|
}
|
|
19
14
|
return keys;
|
|
20
15
|
}
|
|
21
|
-
export function isSkippedPair(keys, a, b) {
|
|
22
|
-
return keys.has(pairKey(a, b));
|
|
23
|
-
}
|
|
24
16
|
//# sourceMappingURL=skipped.js.map
|
package/lib/types/staging.d.ts
CHANGED
|
@@ -43,9 +43,7 @@ export interface StagedView {
|
|
|
43
43
|
readonly manifest: StagingManifest;
|
|
44
44
|
}
|
|
45
45
|
export declare function readStaging(io: EvolutionIoLike, home: string, runId: string): Promise<Probe<StagedView>>;
|
|
46
|
-
export declare function ensureStaging(io: EvolutionIoLike, home: string, input: CreateStagingInput): Promise<CreateStagingResult>;
|
|
47
46
|
export declare function setStagingTarget(io: EvolutionIoLike, home: string, stageKey: string, target: string, mode?: 'merge-into-existing' | 'create-umbrella' | 'keep'): Promise<boolean>;
|
|
48
|
-
export declare function dropStaging(io: EvolutionIoLike, home: string, stageKey: string): Promise<void>;
|
|
49
47
|
export interface StagingCleanupResult {
|
|
50
48
|
readonly removed: readonly string[];
|
|
51
49
|
readonly kept: readonly string[];
|
package/lib/types/staging.js
CHANGED
|
@@ -93,12 +93,6 @@ export async function readStaging(io, home, runId) {
|
|
|
93
93
|
return probeUnknown('staging manifest is not JSON: ' + (error instanceof Error ? error.message : String(error)));
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
export async function ensureStaging(io, home, input) {
|
|
97
|
-
const existing = await readStaging(io, home, input.runId);
|
|
98
|
-
if (existing.kind === 'present')
|
|
99
|
-
return { ok: true, path: stagingRoot(home, input.runId), manifest: existing.value.manifest };
|
|
100
|
-
return createStaging(io, home, input);
|
|
101
|
-
}
|
|
102
96
|
export async function setStagingTarget(io, home, stageKey, target, mode) {
|
|
103
97
|
const view = await readStaging(io, home, stageKey);
|
|
104
98
|
if (view.kind !== 'present')
|
|
@@ -107,9 +101,6 @@ export async function setStagingTarget(io, home, stageKey, target, mode) {
|
|
|
107
101
|
await io.writeText(join(stagingRoot(home, stageKey), 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
108
102
|
return true;
|
|
109
103
|
}
|
|
110
|
-
export async function dropStaging(io, home, stageKey) {
|
|
111
|
-
await io.remove(stagingRoot(home, stageKey));
|
|
112
|
-
}
|
|
113
104
|
/** Remove expired run-level staging trees; `keep` names runs a live row still points at. */
|
|
114
105
|
export async function cleanupExpiredStaging(io, home, now, keep = new Set()) {
|
|
115
106
|
let runs;
|
package/lib/types/state.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
-
import { type
|
|
3
|
-
import type { RowWriteResult } from './domain/row-tx.ts';
|
|
4
|
-
import type { SkippedRowV3, V3Event, V3Row, VerifyRowV3 } from './domain/row-v3.ts';
|
|
2
|
+
import { type MergeQueue, type MergeRow, type MergeState } from './contracts.ts';
|
|
5
3
|
export declare function mergeStatePath(home: string): string;
|
|
6
4
|
export interface LoadMergeStateResult {
|
|
7
5
|
readonly ok: boolean;
|
|
@@ -10,41 +8,5 @@ export interface LoadMergeStateResult {
|
|
|
10
8
|
}
|
|
11
9
|
/** Pure read: never writes, never deletes a row. */
|
|
12
10
|
export declare function loadMergeState(io: EvolutionIoLike, home: string): Promise<LoadMergeStateResult>;
|
|
13
|
-
/** Schema probe for doctor; the only other reader of mergeStatePath. */
|
|
14
|
-
export declare function loadStateSchema(io: EvolutionIoLike, home: string): Promise<number | null>;
|
|
15
|
-
export interface RowTx {
|
|
16
|
-
rows(): readonly V3Row[];
|
|
17
|
-
createVerify(row: VerifyRowV3): RowWriteResult;
|
|
18
|
-
createSkipped(row: SkippedRowV3): RowWriteResult;
|
|
19
|
-
transition(id: string, event: V3Event, expectedVersion: number): RowWriteResult;
|
|
20
|
-
}
|
|
21
|
-
/** G3-5 runtime write API: only createVerify/createSkipped/transition. v2 writes are rejected. */
|
|
22
|
-
export declare function transactRows<T>(_io: EvolutionIoLike, _home: string, _fn: (tx: RowTx) => T): Promise<T>;
|
|
23
|
-
/** The ONE write point. The callback returns the whole row set, not a delta. */
|
|
24
|
-
export declare function mutateMergeState<T>(_io: EvolutionIoLike, _home: string, _now: () => number, _fn: (rows: readonly MergeRow[]) => {
|
|
25
|
-
rows: readonly MergeRow[];
|
|
26
|
-
result: T;
|
|
27
|
-
}): Promise<T>;
|
|
28
|
-
export interface MoveRowPatch {
|
|
29
|
-
readonly queue: MergeQueue;
|
|
30
|
-
readonly state?: MergePairState;
|
|
31
|
-
readonly failure?: MergeRow['failure'];
|
|
32
|
-
readonly runId?: string;
|
|
33
|
-
readonly unitId?: string;
|
|
34
|
-
readonly origin?: MergeRow['origin'];
|
|
35
|
-
readonly attempts?: number;
|
|
36
|
-
}
|
|
37
|
-
export type UpsertExpect = 'any' | 'absent' | 'verify' | 'pending' | 'fuse' | 'skipped';
|
|
38
|
-
export type UpsertResult = {
|
|
39
|
-
readonly ok: true;
|
|
40
|
-
} | {
|
|
41
|
-
readonly ok: false;
|
|
42
|
-
readonly current: MergeRow;
|
|
43
|
-
};
|
|
44
|
-
export declare function upsertRow(io: EvolutionIoLike, home: string, now: () => number, row: MergeRow, expect?: UpsertExpect, cas?: string | null): Promise<UpsertResult>;
|
|
45
|
-
export declare function moveRow(io: EvolutionIoLike, home: string, now: () => number, id: string, patch: MoveRowPatch): Promise<MergeRow | undefined>;
|
|
46
|
-
/** G3-5 v3-aware drop: only terminal events. */
|
|
47
|
-
export declare function dropRowV3(_io: EvolutionIoLike, _home: string, _id: string, _event: 'committed' | 'superseded' | 'restored', _unitId?: string): Promise<boolean>;
|
|
48
|
-
export declare function dropRow(io: EvolutionIoLike, home: string, now: () => number, id: string): Promise<boolean>;
|
|
49
11
|
export declare function rowsOf(io: EvolutionIoLike, home: string, queue?: MergeQueue): Promise<readonly MergeRow[]>;
|
|
50
12
|
//# sourceMappingURL=state.d.ts.map
|