@lmzhen/dsh-evolution-merge 0.30.18 → 0.30.20
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/README.md +21 -4
- package/data/minhash_params.json +1063 -0
- package/lib/index.js +7313 -3272
- package/lib/types/adapters/channel.d.ts +1 -0
- package/lib/types/adapters/channel.js +15 -1
- package/lib/types/adapters/engine-screen.d.ts +1 -1
- package/lib/types/adapters/engine-screen.js +5 -2
- package/lib/types/adapters/model-runner.d.ts +0 -5
- package/lib/types/adapters/model-runner.js +8 -2
- package/lib/types/adapters/model.js +2 -1
- package/lib/types/adapters/select.js +12 -8
- package/lib/types/approve-command.d.ts +12 -1
- package/lib/types/approve-command.js +39 -6
- package/lib/types/authoring-flow.d.ts +16 -0
- package/lib/types/authoring-flow.js +37 -13
- package/lib/types/channel-port.d.ts +3 -1
- package/lib/types/channel-port.js +8 -3
- package/lib/types/command-defs.d.ts +5 -0
- package/lib/types/command-defs.js +33 -0
- package/lib/types/command-surface.d.ts +1 -1
- package/lib/types/command-surface.js +10 -43
- package/lib/types/commands.d.ts +3 -4
- package/lib/types/commands.js +33 -72
- package/lib/types/commit-assembly.d.ts +14 -1
- package/lib/types/commit-assembly.js +65 -9
- package/lib/types/commit-execute.d.ts +7 -1
- package/lib/types/commit-execute.js +54 -22
- package/lib/types/commit-plan.d.ts +7 -1
- package/lib/types/commit-plan.js +4 -0
- package/lib/types/commit-receipt.d.ts +3 -0
- package/lib/types/contracts.d.ts +43 -0
- package/lib/types/doctor.d.ts +11 -1
- package/lib/types/doctor.js +81 -27
- package/lib/types/domain/authoring-manifest.d.ts +26 -0
- package/lib/types/domain/authoring-manifest.js +43 -0
- package/lib/types/domain/engine-criteria.d.ts +10 -0
- package/lib/types/domain/engine-criteria.js +11 -0
- package/lib/types/domain/engine-input.d.ts +10 -0
- package/lib/types/domain/engine-input.js +26 -0
- package/lib/types/domain/errors.d.ts +15 -1
- package/lib/types/domain/errors.js +23 -4
- package/lib/types/domain/evidence-hash.d.ts +3 -0
- package/lib/types/domain/evidence-hash.js +14 -0
- package/lib/types/domain/failure-route.d.ts +14 -0
- package/lib/types/domain/failure-route.js +12 -0
- package/lib/types/domain/live-facts.d.ts +31 -0
- package/lib/types/domain/live-facts.js +54 -0
- package/lib/types/domain/merge-limits.d.ts +18 -0
- package/lib/types/domain/merge-limits.js +22 -0
- package/lib/types/domain/merge-report.d.ts +41 -0
- package/lib/types/domain/merge-report.js +2 -0
- package/lib/types/domain/merge-snapshot.d.ts +99 -0
- package/lib/types/domain/merge-snapshot.js +82 -0
- package/lib/types/domain/pair-ledger.d.ts +34 -0
- package/lib/types/domain/pair-ledger.js +96 -0
- package/lib/types/domain/pre-1b-backup.d.ts +43 -0
- package/lib/types/domain/pre-1b-backup.js +140 -0
- package/lib/types/domain/provider-key.d.ts +2 -0
- package/lib/types/domain/provider-key.js +11 -0
- package/lib/types/domain/read-probe.d.ts +5 -0
- package/lib/types/domain/read-probe.js +11 -0
- package/lib/types/domain/reconcile.d.ts +47 -0
- package/lib/types/domain/reconcile.js +178 -0
- package/lib/types/domain/resolve-pair.d.ts +2 -9
- package/lib/types/domain/resolve-pair.js +2 -15
- package/lib/types/domain/row-v4.d.ts +148 -0
- package/lib/types/domain/row-v4.js +159 -0
- package/lib/types/domain/run-coordinator.d.ts +51 -0
- package/lib/types/domain/run-coordinator.js +45 -0
- package/lib/types/domain/schema4.d.ts +53 -0
- package/lib/types/domain/schema4.js +129 -0
- package/lib/types/domain/selection-policy.d.ts +14 -0
- package/lib/types/domain/selection-policy.js +6 -0
- package/lib/types/domain/skipped-expiry.d.ts +5 -0
- package/lib/types/domain/skipped-expiry.js +16 -0
- package/lib/types/domain/stage-aggregate.d.ts +96 -0
- package/lib/types/domain/stage-aggregate.js +293 -0
- package/lib/types/domain/staging-index.d.ts +1 -0
- package/lib/types/domain/state-bootstrap.d.ts +43 -0
- package/lib/types/domain/state-bootstrap.js +267 -0
- package/lib/types/domain/state-v3.d.ts +3 -14
- package/lib/types/domain/state-v3.js +3 -79
- package/lib/types/domain/undo-journal.d.ts +19 -0
- package/lib/types/domain/undo-journal.js +20 -0
- package/lib/types/domain/undo-receipt.d.ts +18 -0
- package/lib/types/domain/undo-receipt.js +22 -0
- package/lib/types/domain/validation-plan.d.ts +65 -0
- package/lib/types/domain/validation-plan.js +49 -0
- package/lib/types/facts.d.ts +3 -1
- package/lib/types/facts.js +13 -16
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +181 -68
- package/lib/types/merge-command.d.ts +3 -1
- package/lib/types/merge-command.js +10 -6
- package/lib/types/merge-routes.d.ts +10 -5
- package/lib/types/merge-routes.js +72 -7
- package/lib/types/merge-tool.d.ts +1 -1
- package/lib/types/merge-tool.js +2 -2
- package/lib/types/metrics.js +1 -1
- package/lib/types/new-child-authoring.d.ts +2 -0
- package/lib/types/new-child-authoring.js +57 -10
- package/lib/types/params.d.ts +2 -4
- package/lib/types/params.js +13 -0
- package/lib/types/pending-actions.d.ts +2 -2
- package/lib/types/pending-actions.js +4 -4
- package/lib/types/prompts.d.ts +2 -4
- package/lib/types/prompts.js +21 -8
- package/lib/types/read-commands.d.ts +3 -3
- package/lib/types/read-commands.js +19 -28
- package/lib/types/recovery-coordinator.d.ts +34 -0
- package/lib/types/recovery-coordinator.js +84 -0
- package/lib/types/recovery-projection.d.ts +31 -0
- package/lib/types/recovery-projection.js +96 -0
- package/lib/types/resume-command.d.ts +5 -1
- package/lib/types/resume-command.js +46 -13
- package/lib/types/revision.d.ts +17 -0
- package/lib/types/revision.js +39 -3
- package/lib/types/round2-outcome.d.ts +2 -1
- package/lib/types/round2-outcome.js +59 -11
- package/lib/types/run-shell.d.ts +16 -2
- package/lib/types/run-shell.js +49 -5
- package/lib/types/runtime.d.ts +0 -5
- package/lib/types/runtime.js +274 -89
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +2 -0
- package/lib/types/scan-ports.d.ts +18 -0
- package/lib/types/scan-ports.js +49 -23
- package/lib/types/scan-run.d.ts +17 -0
- package/lib/types/scan-run.js +143 -48
- package/lib/types/stage-tool-registry.d.ts +7 -0
- package/lib/types/stage-tool-registry.js +12 -1
- package/lib/types/state.d.ts +5 -5
- package/lib/types/state.js +22 -69
- package/lib/types/status.d.ts +6 -1
- package/lib/types/status.js +47 -0
- package/lib/types/surface/body.d.ts +4 -0
- package/lib/types/surface/body.js +16 -0
- package/lib/types/surface/merge-view.d.ts +14 -0
- package/lib/types/surface/merge-view.js +126 -0
- package/lib/types/undo-operation.d.ts +24 -0
- package/lib/types/undo-operation.js +135 -0
- package/lib/types/wire.d.ts +22 -3
- package/package.json +6 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import { type QuarantinedRow, type V3State } from './migrate-v3.ts';
|
|
3
|
+
export declare function mergeStatePath(home: string): string;
|
|
4
|
+
export declare function v2BackupPath(home: string): string;
|
|
5
|
+
export declare function migrationJournalPath(home: string): string;
|
|
6
|
+
export declare function migrationReportPath(home: string): string;
|
|
7
|
+
export declare function quarantinePath(home: string): string;
|
|
8
|
+
export type StateBlockCode = 'merge-state-migration-required' | 'merge-state-migration-blocked' | 'merge-state-needs-repair' | 'merge-state-forward-only';
|
|
9
|
+
export interface MigrationReport {
|
|
10
|
+
readonly version: 1;
|
|
11
|
+
readonly ready: boolean;
|
|
12
|
+
readonly migrated: boolean;
|
|
13
|
+
readonly sourceSha256?: string;
|
|
14
|
+
readonly targetSha256?: string;
|
|
15
|
+
readonly rows?: number;
|
|
16
|
+
readonly quarantined?: number;
|
|
17
|
+
readonly backupReadOnly?: boolean;
|
|
18
|
+
readonly at: string;
|
|
19
|
+
readonly steps: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export type StateReadiness = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
readonly schema: 3;
|
|
24
|
+
readonly migrated: boolean;
|
|
25
|
+
readonly state: V3State;
|
|
26
|
+
readonly quarantine: readonly QuarantinedRow[];
|
|
27
|
+
readonly report: MigrationReport | null;
|
|
28
|
+
} | {
|
|
29
|
+
readonly ok: false;
|
|
30
|
+
readonly code: StateBlockCode;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
};
|
|
33
|
+
export declare class StateBlockedError extends Error {
|
|
34
|
+
readonly readiness: StateReadiness;
|
|
35
|
+
constructor(readiness: StateReadiness);
|
|
36
|
+
}
|
|
37
|
+
/** Read-only readiness probe. Never migrates, never writes. */
|
|
38
|
+
export declare function readStateReadiness(io: EvolutionIoLike, home: string): Promise<StateReadiness>;
|
|
39
|
+
/** Compatibility name for the v3 gate; 1b-2b switches the lifecycle to v4. */
|
|
40
|
+
export declare const ensureV3Ready: typeof ensureMergeStateReady;
|
|
41
|
+
/** One readiness/migration gate; used by startup and every write entry. */
|
|
42
|
+
export declare function ensureMergeStateReady(io: EvolutionIoLike, home: string): Promise<StateReadiness>;
|
|
43
|
+
//# sourceMappingURL=state-bootstrap.d.ts.map
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { readTextOrNull } from "./read-probe.js";
|
|
2
|
+
/** G3 Batch -1 state bootstrap: one readiness gate, backup/journal migration. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { chmod } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { transactIo } from '@lmzhen/dsh-evolution-core';
|
|
7
|
+
import { migrateV2ToV3 } from "./migrate-v3.js";
|
|
8
|
+
import { isV3Row } from "./row-v3.js";
|
|
9
|
+
import { stagingIndexPath, writeStagingIndex } from "./staging-index.js";
|
|
10
|
+
export function mergeStatePath(home) { return join(home, 'merge', 'merge-state.json'); }
|
|
11
|
+
export function v2BackupPath(home) { return join(home, 'merge', 'merge-state.v2.bak.json'); }
|
|
12
|
+
export function migrationJournalPath(home) { return join(home, 'merge', 'merge-state.migration.json'); }
|
|
13
|
+
export function migrationReportPath(home) { return join(home, 'merge', 'merge-state.migration.report.json'); }
|
|
14
|
+
export function quarantinePath(home) { return join(home, 'merge', 'merge-state.quarantine.json'); }
|
|
15
|
+
export class StateBlockedError extends Error {
|
|
16
|
+
readiness;
|
|
17
|
+
constructor(readiness) {
|
|
18
|
+
super(readiness.ok ? 'merge state is ready' : readiness.code + ': ' + readiness.message);
|
|
19
|
+
this.name = 'StateBlockedError';
|
|
20
|
+
this.readiness = readiness;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const hash = (text) => createHash('sha256').update(text).digest('hex');
|
|
24
|
+
const json = (value) => JSON.stringify(value, null, 2) + '\n';
|
|
25
|
+
const now = () => new Date().toISOString();
|
|
26
|
+
function blocked(code, message) { return { ok: false, code, message }; }
|
|
27
|
+
function parseBody(raw) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function readMarker(body) {
|
|
37
|
+
const value = body.migration;
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return null;
|
|
40
|
+
if (value === null || typeof value !== 'object')
|
|
41
|
+
return null;
|
|
42
|
+
const marker = value;
|
|
43
|
+
if (marker.migratedFromV2 !== true || typeof marker.sourceSha256 !== 'string' || typeof marker.targetSha256 !== 'string' || !Array.isArray(marker.staging) || typeof marker.at !== 'string')
|
|
44
|
+
return null;
|
|
45
|
+
return marker;
|
|
46
|
+
}
|
|
47
|
+
function rowsSha(rows) {
|
|
48
|
+
return hash(json({ schemaVersion: 3, rows }));
|
|
49
|
+
}
|
|
50
|
+
async function missingIndex(io, home, staging) {
|
|
51
|
+
for (const entry of staging)
|
|
52
|
+
if (!(await io.exists(stagingIndexPath(home, entry.rowId))))
|
|
53
|
+
return entry.rowId;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function v3State(body) {
|
|
57
|
+
const rows = body.rows;
|
|
58
|
+
if (!Array.isArray(rows) || !rows.every(isV3Row))
|
|
59
|
+
return null;
|
|
60
|
+
return { schemaVersion: 3, rows: rows };
|
|
61
|
+
}
|
|
62
|
+
async function readJournal(io, home) {
|
|
63
|
+
const raw = await readTextOrNull(io, migrationJournalPath(home));
|
|
64
|
+
if (raw === null)
|
|
65
|
+
return null;
|
|
66
|
+
try {
|
|
67
|
+
const value = JSON.parse(raw);
|
|
68
|
+
if (value.version !== 1 || typeof value.sourceSha256 !== 'string' || typeof value.targetSha256 !== 'string' || !Array.isArray(value.staging))
|
|
69
|
+
return null;
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function readReport(io, home) {
|
|
77
|
+
const raw = await readTextOrNull(io, migrationReportPath(home));
|
|
78
|
+
if (raw === null)
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
const value = JSON.parse(raw);
|
|
82
|
+
return value.version === 1 ? value : null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function writeReport(io, home, report) {
|
|
89
|
+
await transactIo(io, migrationReportPath(home), () => json(report));
|
|
90
|
+
}
|
|
91
|
+
async function completeJournal(io, home, journal, current) {
|
|
92
|
+
if (hash(current) !== journal.targetSha256)
|
|
93
|
+
return blocked('merge-state-needs-repair', 'migration journal does not match the v3 state');
|
|
94
|
+
for (const entry of journal.staging)
|
|
95
|
+
await writeStagingIndex(io, home, entry);
|
|
96
|
+
const report = { version: 1, ready: true, migrated: false, sourceSha256: journal.sourceSha256, targetSha256: journal.targetSha256, rows: journal.rows, quarantined: journal.quarantine.length, at: now(), steps: ['journal:index', 'journal:ready'] };
|
|
97
|
+
await writeReport(io, home, report);
|
|
98
|
+
await io.remove(migrationJournalPath(home)).catch(() => { });
|
|
99
|
+
const body = parseBody(current);
|
|
100
|
+
const state = body === null ? null : v3State(body);
|
|
101
|
+
return state === null ? blocked('merge-state-needs-repair', 'v3 state is invalid') : { ok: true, schema: 3, migrated: false, state, quarantine: journal.quarantine, report };
|
|
102
|
+
}
|
|
103
|
+
/** Read-only readiness probe. Never migrates, never writes. */
|
|
104
|
+
export async function readStateReadiness(io, home) {
|
|
105
|
+
let raw;
|
|
106
|
+
try {
|
|
107
|
+
raw = await io.readText(mergeStatePath(home));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return blocked('merge-state-needs-repair', 'state file is unreadable');
|
|
111
|
+
}
|
|
112
|
+
if (raw === null)
|
|
113
|
+
return { ok: true, schema: 3, migrated: false, state: { schemaVersion: 3, rows: [] }, quarantine: [], report: await readReport(io, home) };
|
|
114
|
+
const body = parseBody(raw);
|
|
115
|
+
if (body === null)
|
|
116
|
+
return blocked('merge-state-needs-repair', 'state file is not a JSON object');
|
|
117
|
+
const version = body.schemaVersion;
|
|
118
|
+
if (typeof version === 'number' && version > 3)
|
|
119
|
+
return blocked('merge-state-forward-only', 'state schema ' + String(version) + ' is newer than this build');
|
|
120
|
+
if (version === 3) {
|
|
121
|
+
const state = v3State(body);
|
|
122
|
+
if (state === null)
|
|
123
|
+
return blocked('merge-state-needs-repair', 'v3 state has an invalid row');
|
|
124
|
+
const journal = await readJournal(io, home);
|
|
125
|
+
const report = await readReport(io, home);
|
|
126
|
+
const marker = readMarker(body);
|
|
127
|
+
if (body.migration !== undefined && marker === null)
|
|
128
|
+
return blocked('merge-state-needs-repair', 'migration marker is invalid');
|
|
129
|
+
if (journal !== null)
|
|
130
|
+
return blocked('merge-state-migration-blocked', 'migration journal is pending');
|
|
131
|
+
if (report !== null && report.ready !== true)
|
|
132
|
+
return blocked('merge-state-migration-blocked', 'migration report is not ready');
|
|
133
|
+
if (marker !== null && rowsSha(state.rows) === marker.targetSha256) {
|
|
134
|
+
if (report === null)
|
|
135
|
+
return blocked('merge-state-migration-blocked', 'migration report is missing');
|
|
136
|
+
const missing = await missingIndex(io, home, marker.staging);
|
|
137
|
+
if (missing !== null)
|
|
138
|
+
return blocked('merge-state-migration-blocked', 'staging index is missing: ' + missing);
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, schema: 3, migrated: false, state, quarantine: [], report };
|
|
141
|
+
}
|
|
142
|
+
return blocked('merge-state-migration-required', 'v2 state requires v3 migration');
|
|
143
|
+
}
|
|
144
|
+
/** Compatibility name for the v3 gate; 1b-2b switches the lifecycle to v4. */
|
|
145
|
+
export const ensureV3Ready = ensureMergeStateReady;
|
|
146
|
+
/** One readiness/migration gate; used by startup and every write entry. */
|
|
147
|
+
export async function ensureMergeStateReady(io, home) {
|
|
148
|
+
let outcome;
|
|
149
|
+
let migrated = false;
|
|
150
|
+
try {
|
|
151
|
+
await transactIo(io, mergeStatePath(home), async (current) => {
|
|
152
|
+
if (current === null) {
|
|
153
|
+
outcome = { ok: true, schema: 3, migrated: false, state: { schemaVersion: 3, rows: [] }, quarantine: [], report: await readReport(io, home) };
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const body = parseBody(current);
|
|
157
|
+
if (body === null) {
|
|
158
|
+
outcome = blocked('merge-state-needs-repair', 'state file is not a JSON object');
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
const version = body.schemaVersion;
|
|
162
|
+
if (typeof version === 'number' && version > 3) {
|
|
163
|
+
outcome = blocked('merge-state-forward-only', 'state schema ' + String(version) + ' is newer than this build');
|
|
164
|
+
return current;
|
|
165
|
+
}
|
|
166
|
+
if (version === 3) {
|
|
167
|
+
const state = v3State(body);
|
|
168
|
+
if (state === null) {
|
|
169
|
+
outcome = blocked('merge-state-needs-repair', 'v3 state has an invalid row');
|
|
170
|
+
return current;
|
|
171
|
+
}
|
|
172
|
+
const journal = await readJournal(io, home);
|
|
173
|
+
if (journal !== null) {
|
|
174
|
+
outcome = await completeJournal(io, home, journal, current);
|
|
175
|
+
return current;
|
|
176
|
+
}
|
|
177
|
+
const report = await readReport(io, home);
|
|
178
|
+
const marker = readMarker(body);
|
|
179
|
+
if (body.migration !== undefined && marker === null) {
|
|
180
|
+
outcome = blocked('merge-state-needs-repair', 'migration marker is invalid');
|
|
181
|
+
return current;
|
|
182
|
+
}
|
|
183
|
+
if (report !== null && report.ready !== true) {
|
|
184
|
+
outcome = blocked('merge-state-migration-blocked', 'migration report is not ready');
|
|
185
|
+
return current;
|
|
186
|
+
}
|
|
187
|
+
if (marker !== null && rowsSha(state.rows) === marker.targetSha256) {
|
|
188
|
+
if (report === null) {
|
|
189
|
+
outcome = blocked('merge-state-migration-blocked', 'migration report is missing');
|
|
190
|
+
return current;
|
|
191
|
+
}
|
|
192
|
+
for (const entry of marker.staging)
|
|
193
|
+
if (!(await io.exists(stagingIndexPath(home, entry.rowId))))
|
|
194
|
+
await writeStagingIndex(io, home, entry);
|
|
195
|
+
}
|
|
196
|
+
outcome = { ok: true, schema: 3, migrated: false, state, quarantine: [], report };
|
|
197
|
+
return current;
|
|
198
|
+
}
|
|
199
|
+
const mapped = migrateV2ToV3(body);
|
|
200
|
+
if (!mapped.ok) {
|
|
201
|
+
outcome = blocked('merge-state-migration-blocked', mapped.message);
|
|
202
|
+
return current;
|
|
203
|
+
}
|
|
204
|
+
const sourceSha256 = hash(current);
|
|
205
|
+
const backup = v2BackupPath(home);
|
|
206
|
+
const existingBackup = await readTextOrNull(io, backup);
|
|
207
|
+
if (existingBackup !== null && existingBackup !== current) {
|
|
208
|
+
outcome = blocked('merge-state-migration-blocked', 'v2 backup exists with different bytes');
|
|
209
|
+
return current;
|
|
210
|
+
}
|
|
211
|
+
if (existingBackup === null)
|
|
212
|
+
await transactIo(io, backup, () => current);
|
|
213
|
+
try {
|
|
214
|
+
await chmod(backup, 0o444);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
outcome = blocked('merge-state-migration-blocked', 'v2 backup is not read-only');
|
|
218
|
+
return current;
|
|
219
|
+
}
|
|
220
|
+
const staging = [];
|
|
221
|
+
for (const row of body.rows) {
|
|
222
|
+
if (typeof row.id !== 'string' || typeof row.pairKey !== 'string' || typeof row.stagingRef !== 'string')
|
|
223
|
+
continue;
|
|
224
|
+
if (row.state !== 'pending' && row.state !== 'approved')
|
|
225
|
+
continue;
|
|
226
|
+
staging.push({
|
|
227
|
+
rowId: row.id, pairKey: row.pairKey, ref: row.stagingRef,
|
|
228
|
+
expiresAt: typeof row.stagingExpiresAt === 'number' ? row.stagingExpiresAt : Date.now() + 7 * 86_400_000,
|
|
229
|
+
...(typeof row.runId === 'string' ? { runId: row.runId } : {}),
|
|
230
|
+
createdAt: typeof row.updatedAt === 'string' && !Number.isNaN(Date.parse(row.updatedAt)) ? Date.parse(row.updatedAt) : Date.now(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const marker = { migratedFromV2: true, sourceSha256, targetSha256: rowsSha(mapped.state.rows), staging, at: now() };
|
|
234
|
+
const targetText = json({ ...mapped.state, migration: marker });
|
|
235
|
+
const targetSha256 = hash(targetText);
|
|
236
|
+
const journal = { version: 1, sourceSha256, targetSha256, rows: mapped.state.rows.length, quarantine: mapped.quarantine, staging, backupPath: backup, at: now() };
|
|
237
|
+
await transactIo(io, migrationJournalPath(home), () => json(journal));
|
|
238
|
+
for (const entry of staging)
|
|
239
|
+
await writeStagingIndex(io, home, entry);
|
|
240
|
+
if (mapped.quarantine.length > 0)
|
|
241
|
+
await transactIo(io, quarantinePath(home), () => json(mapped.quarantine));
|
|
242
|
+
const prepared = { version: 1, ready: false, migrated: true, sourceSha256, targetSha256, rows: mapped.state.rows.length, quarantined: mapped.quarantine.length, backupReadOnly: true, at: now(), steps: ['backup', 'journal', 'staging-index', 'quarantine', 'v3-write'] };
|
|
243
|
+
await writeReport(io, home, prepared);
|
|
244
|
+
migrated = true;
|
|
245
|
+
outcome = { ok: true, schema: 3, migrated: true, state: mapped.state, quarantine: mapped.quarantine, report: prepared };
|
|
246
|
+
return targetText;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return blocked('merge-state-migration-blocked', 'state migration failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
251
|
+
}
|
|
252
|
+
if (outcome === undefined)
|
|
253
|
+
return blocked('merge-state-migration-blocked', 'state migration did not produce a result');
|
|
254
|
+
if (migrated && outcome.ok) {
|
|
255
|
+
try {
|
|
256
|
+
const report = { ...outcome.report, ready: true, at: now(), steps: ['backup', 'journal', 'staging-index', 'quarantine', 'v3-write', 'ready'] };
|
|
257
|
+
await writeReport(io, home, report);
|
|
258
|
+
await io.remove(migrationJournalPath(home)).catch(() => { });
|
|
259
|
+
outcome = { ...outcome, report };
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// The v3 state is committed; leave the journal/report for the next ensure.
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return outcome;
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=state-bootstrap.js.map
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
export
|
|
4
|
-
export type StateMigration = {
|
|
5
|
-
readonly ok: true;
|
|
6
|
-
readonly migrated: boolean;
|
|
7
|
-
readonly state: V3State;
|
|
8
|
-
readonly quarantine: readonly QuarantinedRow[];
|
|
9
|
-
} | {
|
|
10
|
-
readonly ok: false;
|
|
11
|
-
readonly code: 'state-migration-blocked';
|
|
12
|
-
readonly message: string;
|
|
13
|
-
};
|
|
14
|
-
export declare function loadOrMigrateStateV3(io: EvolutionIoLike, home: string): Promise<StateMigration>;
|
|
1
|
+
/** G3 schema-3 migration path constants; the only production gate is state-bootstrap. */
|
|
2
|
+
import { v2BackupPath } from './state-bootstrap.ts';
|
|
3
|
+
export { v2BackupPath };
|
|
15
4
|
//# sourceMappingURL=state-v3.d.ts.map
|
|
@@ -1,80 +1,4 @@
|
|
|
1
|
-
/** G3 schema-3
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { migrateV2ToV3 } from "./migrate-v3.js";
|
|
5
|
-
import { isV3Row } from "./row-v3.js";
|
|
6
|
-
import { writeStagingIndex } from "./staging-index.js";
|
|
7
|
-
export function v2BackupPath(home) { return join(home, 'merge', 'merge-state.v2.bak.json'); }
|
|
8
|
-
function blocked(message) { return { ok: false, code: 'state-migration-blocked', message }; }
|
|
9
|
-
async function readOnly(path) { try {
|
|
10
|
-
await chmod(path, 0o444);
|
|
11
|
-
}
|
|
12
|
-
catch { /* io seam without chmod; v2 bytes still preserved */ } }
|
|
13
|
-
export async function loadOrMigrateStateV3(io, home) {
|
|
14
|
-
const path = join(home, 'merge', 'merge-state.json');
|
|
15
|
-
const raw = await io.readText(path);
|
|
16
|
-
if (raw === null)
|
|
17
|
-
return { ok: true, migrated: false, state: { schemaVersion: 3, rows: [] }, quarantine: [] };
|
|
18
|
-
let parsed;
|
|
19
|
-
try {
|
|
20
|
-
parsed = JSON.parse(raw);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return blocked('v2 state is not JSON');
|
|
24
|
-
}
|
|
25
|
-
if (parsed !== null && typeof parsed === 'object' && parsed.schemaVersion === 3) {
|
|
26
|
-
const rows = parsed.rows;
|
|
27
|
-
if (!Array.isArray(rows) || !rows.every(isV3Row))
|
|
28
|
-
return blocked('v3 state has an invalid row');
|
|
29
|
-
return { ok: true, migrated: false, state: { schemaVersion: 3, rows }, quarantine: [] };
|
|
30
|
-
}
|
|
31
|
-
const mapped = migrateV2ToV3(parsed);
|
|
32
|
-
if (!mapped.ok)
|
|
33
|
-
return blocked(mapped.message);
|
|
34
|
-
const backup = v2BackupPath(home);
|
|
35
|
-
const existing = await io.readText(backup);
|
|
36
|
-
if (existing !== null && existing !== raw)
|
|
37
|
-
return blocked('v2 backup exists with different bytes');
|
|
38
|
-
if (existing === null) {
|
|
39
|
-
try {
|
|
40
|
-
if (io.transact !== undefined)
|
|
41
|
-
await io.transact(backup, () => raw);
|
|
42
|
-
else
|
|
43
|
-
await io.writeText(backup, raw);
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return blocked('v2 backup write failed');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
await readOnly(backup);
|
|
50
|
-
const rawRows = parsed.rows;
|
|
51
|
-
if (Array.isArray(rawRows)) {
|
|
52
|
-
for (const row of rawRows) {
|
|
53
|
-
if (typeof row.id !== 'string' || typeof row.pairKey !== 'string' || typeof row.stagingRef !== 'string')
|
|
54
|
-
continue;
|
|
55
|
-
if (row.state !== 'pending' && row.state !== 'approved')
|
|
56
|
-
continue;
|
|
57
|
-
await writeStagingIndex(io, home, { rowId: row.id, pairKey: row.pairKey, ref: row.stagingRef, expiresAt: typeof row.stagingExpiresAt === 'number' ? row.stagingExpiresAt : Date.now() + 7 * 86_400_000, ...(typeof row.runId === 'string' ? { runId: row.runId } : {}), createdAt: typeof row.updatedAt === 'string' && !Number.isNaN(Date.parse(row.updatedAt)) ? Date.parse(row.updatedAt) : Date.now() });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (mapped.quarantine.length > 0) {
|
|
61
|
-
try {
|
|
62
|
-
await io.writeText(join(home, 'merge', 'merge-state.quarantine.json'), JSON.stringify(mapped.quarantine, null, 2) + '\n');
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return blocked('quarantine write failed; v3 not written');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const text = JSON.stringify(mapped.state, null, 2) + '\n';
|
|
69
|
-
try {
|
|
70
|
-
if (io.transact !== undefined)
|
|
71
|
-
await io.transact(path, () => text);
|
|
72
|
-
else
|
|
73
|
-
await io.writeText(path, text);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return blocked('v3 state write failed; v2 backup + quarantine retained');
|
|
77
|
-
}
|
|
78
|
-
return { ok: true, migrated: true, state: mapped.state, quarantine: mapped.quarantine };
|
|
79
|
-
}
|
|
1
|
+
/** G3 schema-3 migration path constants; the only production gate is state-bootstrap. */
|
|
2
|
+
import { v2BackupPath } from "./state-bootstrap.js";
|
|
3
|
+
export { v2BackupPath };
|
|
80
4
|
//# sourceMappingURL=state-v3.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
export type UndoPairPhase = 'pending' | 'restore-target' | 'restore-source' | 'receipt' | 'done';
|
|
3
|
+
export interface UndoPairState {
|
|
4
|
+
readonly pairKey: string;
|
|
5
|
+
readonly unitId: string;
|
|
6
|
+
readonly phase: UndoPairPhase;
|
|
7
|
+
readonly message?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UndoJournal {
|
|
10
|
+
readonly version: 1;
|
|
11
|
+
readonly runId: string;
|
|
12
|
+
readonly phase: 'prepared' | 'running' | 'settled';
|
|
13
|
+
readonly pairs: readonly UndoPairState[];
|
|
14
|
+
readonly updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function undoJournalPath(home: string, runId: string): string;
|
|
17
|
+
export declare function readUndoJournal(io: EvolutionIoLike, home: string, runId: string): Promise<UndoJournal | null>;
|
|
18
|
+
export declare function writeUndoJournal(io: EvolutionIoLike, home: string, journal: UndoJournal): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=undo-journal.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readTextOrNull } from "./read-probe.js";
|
|
2
|
+
/** Per-run undo journal: one pair at a time, durable phase before each write. */
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { transactIo } from '@lmzhen/dsh-evolution-core';
|
|
5
|
+
export function undoJournalPath(home, runId) { return join(home, 'merge', 'undo-journal', encodeURIComponent(runId) + '.json'); }
|
|
6
|
+
export async function readUndoJournal(io, home, runId) {
|
|
7
|
+
const raw = await readTextOrNull(io, undoJournalPath(home, runId));
|
|
8
|
+
if (raw === null)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function writeUndoJournal(io, home, journal) {
|
|
18
|
+
await transactIo(io, undoJournalPath(home, journal.runId), () => JSON.stringify(journal, null, 2) + '\n');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=undo-journal.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
export interface UndoReceipt {
|
|
3
|
+
readonly version: 1;
|
|
4
|
+
readonly runId: string;
|
|
5
|
+
readonly parentRunId?: string;
|
|
6
|
+
readonly pairKey: string;
|
|
7
|
+
readonly unitId: string;
|
|
8
|
+
readonly evidenceSha256: string;
|
|
9
|
+
readonly target: string;
|
|
10
|
+
readonly sources: readonly string[];
|
|
11
|
+
readonly restoredTargetHash: string | null;
|
|
12
|
+
readonly restoredSourceHashes: Readonly<Record<string, string>>;
|
|
13
|
+
readonly at: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function undoReceiptPath(home: string, runId: string, pairKey: string): string;
|
|
16
|
+
export declare function readUndoReceipt(io: EvolutionIoLike, home: string, runId: string, pairKey: string): Promise<UndoReceipt | null>;
|
|
17
|
+
export declare function writeUndoReceipt(io: EvolutionIoLike, home: string, receipt: UndoReceipt): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=undo-receipt.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readTextOrNull } from "./read-probe.js";
|
|
2
|
+
/** Undo receipt: immutable per-pair settlement, separate from the commit receipt. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
export function undoReceiptPath(home, runId, pairKey) {
|
|
6
|
+
return join(home, 'merge', 'undo-receipts', encodeURIComponent(runId), createHash('sha256').update(pairKey).digest('hex') + '.json');
|
|
7
|
+
}
|
|
8
|
+
export async function readUndoReceipt(io, home, runId, pairKey) {
|
|
9
|
+
const raw = await readTextOrNull(io, undoReceiptPath(home, runId, pairKey));
|
|
10
|
+
if (raw === null)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeUndoReceipt(io, home, receipt) {
|
|
20
|
+
await io.writeText(undoReceiptPath(home, receipt.runId, receipt.pairKey), JSON.stringify(receipt, null, 2) + '\n');
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=undo-receipt.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { SkillCard } from '../skill-card.ts';
|
|
2
|
+
import type { MergeLimitsSnapshot } from './merge-limits.ts';
|
|
3
|
+
import type { StagedTree, RuleFinding } from '../rules.ts';
|
|
4
|
+
export interface StagedFile {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly content: string;
|
|
7
|
+
readonly digest: string;
|
|
8
|
+
readonly bytes: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ValidationPlan {
|
|
11
|
+
readonly planVersion: 1;
|
|
12
|
+
readonly stageDigest: string;
|
|
13
|
+
readonly target: string;
|
|
14
|
+
readonly body: string;
|
|
15
|
+
readonly files: readonly StagedFile[];
|
|
16
|
+
readonly supportFiles: readonly {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly digest: string;
|
|
19
|
+
readonly source: string;
|
|
20
|
+
}[];
|
|
21
|
+
readonly manifestFiles: readonly string[];
|
|
22
|
+
readonly q09: {
|
|
23
|
+
readonly forwardRefs: readonly string[];
|
|
24
|
+
readonly reverseRefs: readonly string[];
|
|
25
|
+
readonly orphanFiles: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
readonly supportConflicts: readonly {
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly sources: readonly string[];
|
|
30
|
+
}[];
|
|
31
|
+
readonly threat: {
|
|
32
|
+
readonly body: boolean;
|
|
33
|
+
readonly files: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
readonly sourceRevisions: Readonly<Record<string, string>>;
|
|
36
|
+
readonly targetRevision: string | null;
|
|
37
|
+
readonly limitsDigest: string;
|
|
38
|
+
readonly promptVersion?: string;
|
|
39
|
+
readonly promptHash?: string;
|
|
40
|
+
readonly revision: string;
|
|
41
|
+
readonly findings: readonly RuleFinding[];
|
|
42
|
+
}
|
|
43
|
+
export declare function buildValidationPlan(input: {
|
|
44
|
+
readonly tree: StagedTree;
|
|
45
|
+
readonly target: string;
|
|
46
|
+
readonly limits: MergeLimitsSnapshot;
|
|
47
|
+
readonly cards?: readonly SkillCard[];
|
|
48
|
+
readonly sourceRevisions?: Readonly<Record<string, string>>;
|
|
49
|
+
readonly targetRevision?: string | null;
|
|
50
|
+
readonly manifestFiles?: readonly string[];
|
|
51
|
+
readonly existingSupport?: readonly string[];
|
|
52
|
+
readonly supportFiles?: readonly {
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly content: string;
|
|
55
|
+
readonly source: string;
|
|
56
|
+
}[];
|
|
57
|
+
readonly supportSources?: readonly {
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly content: string;
|
|
60
|
+
readonly source: string;
|
|
61
|
+
}[];
|
|
62
|
+
readonly promptVersion?: string;
|
|
63
|
+
readonly promptHash?: string;
|
|
64
|
+
}): ValidationPlan;
|
|
65
|
+
//# sourceMappingURL=validation-plan.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Immutable post-stage validation plan: Q09/support/threat/revision facts. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { scanContentThreats, supportRefs } from '@lmzhen/dsh-evolution-core';
|
|
4
|
+
function digest(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
5
|
+
export function buildValidationPlan(input) {
|
|
6
|
+
const files = input.tree.files.map(file => ({ path: file.path, content: file.content, digest: digest(file.content), bytes: Buffer.byteLength(file.content, 'utf8') }));
|
|
7
|
+
const manifestFiles = [...new Set(input.manifestFiles ?? files.map(file => file.path))].sort();
|
|
8
|
+
const supportFiles = (input.supportFiles ?? files.map(file => ({ path: file.path, content: file.content, source: 'staged' }))).map(file => ({ path: file.path, digest: digest(file.content), source: file.source })).sort((a, b) => a.path.localeCompare(b.path));
|
|
9
|
+
const refs = new Set(supportRefs(input.tree.body));
|
|
10
|
+
const staged = new Set(files.map(file => file.path));
|
|
11
|
+
const manifest = new Set(manifestFiles);
|
|
12
|
+
const forwardRefs = [...refs].filter(ref => !staged.has(ref)).sort();
|
|
13
|
+
const reverseRefs = files.map(file => file.path).filter(path => !refs.has(path) && manifest.has(path)).sort();
|
|
14
|
+
const existing = new Set(input.existingSupport ?? []);
|
|
15
|
+
const orphanFiles = [...existing].filter(path => !staged.has(path)).sort();
|
|
16
|
+
const byPath = new Map();
|
|
17
|
+
for (const file of input.supportSources ?? []) {
|
|
18
|
+
const entry = byPath.get(file.path) ?? { contents: new Set(), sources: [] };
|
|
19
|
+
entry.contents.add(file.content);
|
|
20
|
+
entry.sources.push(file.source);
|
|
21
|
+
byPath.set(file.path, entry);
|
|
22
|
+
}
|
|
23
|
+
const supportConflicts = [...byPath.entries()].filter(([, value]) => value.contents.size > 1).map(([path, value]) => ({ path, sources: value.sources }));
|
|
24
|
+
const sourceThreatFiles = (input.cards ?? []).flatMap(card => (card.files ?? []).filter(file => scanContentThreats(file.content) !== null).map(file => card.name + '/' + file.path));
|
|
25
|
+
const threat = { body: scanContentThreats(input.tree.body) !== null || (input.cards ?? []).some(card => scanContentThreats(card.body) !== null), files: [...files.filter(file => scanContentThreats(file.content) !== null).map(file => file.path), ...sourceThreatFiles] };
|
|
26
|
+
const sourceRevisions = input.sourceRevisions ?? Object.fromEntries((input.cards ?? []).map(card => [card.name, digest(card.body)]));
|
|
27
|
+
const revision = digest(JSON.stringify({ target: input.target, body: digest(input.tree.body), files: files.map(file => [file.path, file.digest]), q09: { forwardRefs, reverseRefs, orphanFiles }, supportConflicts, threat, sourceRevisions, targetRevision: input.targetRevision ?? null }));
|
|
28
|
+
const findings = [];
|
|
29
|
+
const stageDigest = digest(JSON.stringify(files.map(file => [file.path, file.digest])));
|
|
30
|
+
if (forwardRefs.length > 0)
|
|
31
|
+
findings.push({ rule: 'q09-forward', path: forwardRefs.join(','), severity: 'block', message: 'body references unstaged support' });
|
|
32
|
+
if (reverseRefs.length > 0)
|
|
33
|
+
findings.push({ rule: 'q09-reverse', path: reverseRefs.join(','), severity: 'block', message: 'staged manifest file is not referenced by body' });
|
|
34
|
+
if (orphanFiles.length > 0)
|
|
35
|
+
findings.push({ rule: 'q09-orphan', path: orphanFiles.join(','), severity: 'block', message: 'target tree support is not declared by plan' });
|
|
36
|
+
if (supportConflicts.length > 0)
|
|
37
|
+
findings.push({ rule: 'support-conflict', path: supportConflicts.map(item => item.path).join(','), severity: 'block', message: 'same support path has different contents' });
|
|
38
|
+
if (threat.body)
|
|
39
|
+
findings.push({ rule: 'threat', path: 'SKILL.md', severity: 'block', message: 'body tripped content threat scanner' });
|
|
40
|
+
for (const path of threat.files)
|
|
41
|
+
findings.push({ rule: 'threat', path, severity: 'block', message: 'support file tripped content threat scanner' });
|
|
42
|
+
if (Buffer.byteLength(input.tree.body, 'utf8') > input.limits.skillLimits.maxSkillFileBytes)
|
|
43
|
+
findings.push({ rule: 'limit', path: 'SKILL.md', severity: 'block', message: 'body exceeds maxSkillFileBytes' });
|
|
44
|
+
for (const file of files)
|
|
45
|
+
if (file.bytes > input.limits.skillLimits.maxSkillFileBytes)
|
|
46
|
+
findings.push({ rule: 'limit', path: file.path, severity: 'block', message: 'file exceeds maxSkillFileBytes' });
|
|
47
|
+
return { planVersion: 1, stageDigest, target: input.target, body: input.tree.body, files, supportFiles, manifestFiles, q09: { forwardRefs, reverseRefs, orphanFiles }, supportConflicts, threat, sourceRevisions, targetRevision: input.targetRevision ?? null, limitsDigest: input.limits.limitsDigest, ...(input.promptVersion === undefined ? {} : { promptVersion: input.promptVersion }), ...(input.promptHash === undefined ? {} : { promptHash: input.promptHash }), revision, findings };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=validation-plan.js.map
|
package/lib/types/facts.d.ts
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import type { EvolutionIoLike, RunRecord } from '@lmzhen/dsh-evolution-core';
|
|
3
3
|
import type { MergeRow } from './contracts.ts';
|
|
4
4
|
import type { Facts, MergeCounts, RunView } from './wire.ts';
|
|
5
|
+
import type { MergeRunMeta } from './domain/run-coordinator.ts';
|
|
5
6
|
export declare function countsOf(rows: readonly MergeRow[]): MergeCounts;
|
|
6
|
-
export declare function toRunView(record: RunRecord, readAt: number): RunView;
|
|
7
|
+
export declare function toRunView(record: RunRecord, readAt: number, meta?: MergeRunMeta | null): RunView;
|
|
7
8
|
export interface FactsInput {
|
|
8
9
|
readonly rows: readonly MergeRow[];
|
|
9
10
|
readonly runs: readonly RunRecord[];
|
|
11
|
+
readonly runMeta?: ReadonlyMap<string, MergeRunMeta>;
|
|
10
12
|
readonly approval: boolean;
|
|
11
13
|
readonly readAt: number;
|
|
12
14
|
readonly notes?: readonly string[];
|