@lmzhen/dsh-evolution-merge 0.30.18 → 0.30.19
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,99 @@
|
|
|
1
|
+
import type { EvolutionIoLike, RunRecord } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import { type V4Readiness } from './schema4.ts';
|
|
3
|
+
import { type StageEntry } from './stage-aggregate.ts';
|
|
4
|
+
import { type MergeRunMeta } from './run-coordinator.ts';
|
|
5
|
+
import type { MergeReport } from '../contracts.ts';
|
|
6
|
+
import type { V4Row } from './row-v4.ts';
|
|
7
|
+
import { type LiveRowFacts, type PendingExpiryKind } from './live-facts.ts';
|
|
8
|
+
import type { QueueId, MergeCounts, DoctorFact } from '../wire.ts';
|
|
9
|
+
export interface SnapshotRow {
|
|
10
|
+
readonly row: V4Row;
|
|
11
|
+
readonly stage: StageEntry | null;
|
|
12
|
+
readonly bodies: readonly BodyRef[];
|
|
13
|
+
readonly queue: QueueId;
|
|
14
|
+
readonly expired: boolean;
|
|
15
|
+
readonly expiredKind?: PendingExpiryKind;
|
|
16
|
+
}
|
|
17
|
+
export interface BodyRef {
|
|
18
|
+
readonly rowId: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly side: 'live' | 'archive';
|
|
21
|
+
}
|
|
22
|
+
export interface SnapshotRun {
|
|
23
|
+
readonly record: RunRecord;
|
|
24
|
+
readonly meta: MergeRunMeta | null;
|
|
25
|
+
readonly pairs: readonly {
|
|
26
|
+
readonly pairKey: string;
|
|
27
|
+
readonly runId: string;
|
|
28
|
+
readonly result: string;
|
|
29
|
+
readonly target?: string;
|
|
30
|
+
}[];
|
|
31
|
+
readonly report: MergeReport | null;
|
|
32
|
+
readonly reportRef: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface SnapshotDoctor {
|
|
35
|
+
readonly counts: MergeCounts;
|
|
36
|
+
readonly staging: {
|
|
37
|
+
readonly state: 'ok' | 'absent' | 'unreadable';
|
|
38
|
+
readonly dirs: number;
|
|
39
|
+
readonly oldestAgeMs?: number;
|
|
40
|
+
readonly detail?: string;
|
|
41
|
+
};
|
|
42
|
+
readonly rows: readonly DoctorFact[];
|
|
43
|
+
}
|
|
44
|
+
export interface HistoryItem {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly kind: 'merged' | 'deleted' | 'split' | 'other';
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
readonly reasonCode?: string;
|
|
49
|
+
readonly lastVersion: number;
|
|
50
|
+
readonly archivedAt: number;
|
|
51
|
+
readonly sourceRunId?: string;
|
|
52
|
+
readonly runId?: string;
|
|
53
|
+
readonly absorbedInto?: string;
|
|
54
|
+
readonly generation?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface MergeSnapshot {
|
|
57
|
+
readonly wireVersion: 1;
|
|
58
|
+
readonly schemaVersion: 4;
|
|
59
|
+
readonly readAt: number;
|
|
60
|
+
readonly queueRevision: string;
|
|
61
|
+
readonly consistency: 'eventual';
|
|
62
|
+
readonly blocked?: {
|
|
63
|
+
readonly code: string;
|
|
64
|
+
readonly message: string;
|
|
65
|
+
readonly readiness: V4Readiness;
|
|
66
|
+
};
|
|
67
|
+
readonly rows: readonly SnapshotRow[];
|
|
68
|
+
readonly stages: readonly StageEntry[];
|
|
69
|
+
readonly runs: readonly SnapshotRun[];
|
|
70
|
+
readonly reports: readonly {
|
|
71
|
+
readonly runId: string;
|
|
72
|
+
readonly path: string;
|
|
73
|
+
readonly report: MergeReport;
|
|
74
|
+
}[];
|
|
75
|
+
readonly report: MergeReport | null;
|
|
76
|
+
readonly history: readonly HistoryItem[];
|
|
77
|
+
readonly bodyRefs: readonly BodyRef[];
|
|
78
|
+
readonly doctor: SnapshotDoctor;
|
|
79
|
+
readonly approval: {
|
|
80
|
+
readonly mergeApproval: boolean;
|
|
81
|
+
readonly platformWrite?: boolean;
|
|
82
|
+
};
|
|
83
|
+
readonly notes: readonly string[];
|
|
84
|
+
readonly degraded: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface ReadSnapshotOptions {
|
|
87
|
+
readonly io: EvolutionIoLike;
|
|
88
|
+
readonly home: string;
|
|
89
|
+
readonly runs: readonly RunRecord[];
|
|
90
|
+
readonly approval: boolean;
|
|
91
|
+
readonly platformWrite?: boolean;
|
|
92
|
+
readonly now?: () => number;
|
|
93
|
+
readonly history?: () => Promise<readonly HistoryItem[]>;
|
|
94
|
+
readonly notes?: readonly string[];
|
|
95
|
+
/** Lazy live-library facts; only invoked when a pending row still needs derivation. */
|
|
96
|
+
readonly live?: () => Promise<LiveRowFacts | undefined>;
|
|
97
|
+
}
|
|
98
|
+
export declare function readMergeSnapshot(options: ReadSnapshotOptions): Promise<MergeSnapshot>;
|
|
99
|
+
//# sourceMappingURL=merge-snapshot.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** 1b-3 one read aggregate over schema4 and its sidecars. No writes. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readV4Readiness } from "./schema4.js";
|
|
4
|
+
import { readPairLedgerV4 } from "./pair-ledger.js";
|
|
5
|
+
import { readAllStages } from "./stage-aggregate.js";
|
|
6
|
+
import { readRunMeta } from "./run-coordinator.js";
|
|
7
|
+
import { loadPairIndex } from "./pair-index-store.js";
|
|
8
|
+
import { mergeReportPath, readMergeReport } from "../report.js";
|
|
9
|
+
import { readCommitReceipts } from "../commit-receipt.js";
|
|
10
|
+
import { pendingExpiry, persistedExpiry } from "./live-facts.js";
|
|
11
|
+
const emptyCounts = { active: { verify: 0, pending: 0, fuse: 0, skipped: 0 }, expired: 0 };
|
|
12
|
+
const queueOf = (state) => state === 'approved' || state === 'fuse' ? 'fuse' : state === 'verify' ? 'verify' : state === 'pending' ? 'pending' : 'skipped';
|
|
13
|
+
export async function readMergeSnapshot(options) {
|
|
14
|
+
const now = options.now ?? (() => Date.now());
|
|
15
|
+
const readAt = now();
|
|
16
|
+
const readiness = await readV4Readiness(options.io, options.home);
|
|
17
|
+
if (!readiness.ok) {
|
|
18
|
+
return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision: 'blocked', consistency: 'eventual', blocked: { code: readiness.code, message: readiness.message, readiness }, rows: [], stages: [], runs: [], reports: [], report: null, history: [], bodyRefs: [], doctor: { counts: emptyCounts, staging: { state: 'absent', dirs: 0 }, rows: [] }, approval: { mergeApproval: options.approval, ...(options.platformWrite === undefined ? {} : { platformWrite: options.platformWrite }) }, notes: options.notes ?? [], degraded: true };
|
|
19
|
+
}
|
|
20
|
+
const ledger = await readPairLedgerV4(options.io, options.home);
|
|
21
|
+
// Read-time derivation only: a stage past its TTL reads as expired even when the
|
|
22
|
+
// index still says present. Reconcile is the writer that persists the correction.
|
|
23
|
+
const stages = (await readAllStages(options.io, options.home)).map(entry => entry.state === 'present' && entry.expiresAt <= readAt ? { ...entry, state: 'expired' } : entry);
|
|
24
|
+
const rows = [];
|
|
25
|
+
const bodyRefs = [];
|
|
26
|
+
// FR-LIFE-14: pending rows whose sources/target moved read as expired even before the
|
|
27
|
+
// reconcile writer stamps them. The live library is read ONLY when such a row exists,
|
|
28
|
+
// and the derivation never writes (GET routes stay zero-write).
|
|
29
|
+
const needsLive = options.live !== undefined && (ledger?.rows ?? []).some(row => (row.state === 'pending' && row.expired === undefined) || row.state === 'verify' || row.state === 'fuse' || row.state === 'approved');
|
|
30
|
+
const live = needsLive ? await options.live().catch(() => undefined) : undefined;
|
|
31
|
+
for (const row of ledger?.rows ?? []) {
|
|
32
|
+
const stage = stages.find(item => item.rowId === row.id) ?? null;
|
|
33
|
+
const queue = queueOf(row.state);
|
|
34
|
+
const bodies = [{ rowId: row.id, name: row.a, side: 'live' }, { rowId: row.id, name: row.b, side: 'archive' }];
|
|
35
|
+
const expiredKind = persistedExpiry(row) ?? pendingExpiry(row, live);
|
|
36
|
+
rows.push({ row, stage, bodies, queue, expired: expiredKind !== null, ...(expiredKind === null ? {} : { expiredKind }) });
|
|
37
|
+
bodyRefs.push(...bodies);
|
|
38
|
+
}
|
|
39
|
+
const pairIndex = await loadPairIndex(options.io, options.home);
|
|
40
|
+
const runs = [];
|
|
41
|
+
for (const record of options.runs) {
|
|
42
|
+
const meta = await readRunMeta(options.io, options.home, record.id);
|
|
43
|
+
const pairs = [...pairIndex.values()].filter(item => item.runId === record.id).map(item => ({ pairKey: item.pairKey, runId: item.runId, result: item.result, ...(item.target === undefined ? {} : { target: item.target }) }));
|
|
44
|
+
const reportPath = mergeReportPath(options.home, record.id);
|
|
45
|
+
const probe = await readMergeReport(options.io, options.home, record.id);
|
|
46
|
+
const report = probe.kind === 'present' ? probe.value : null;
|
|
47
|
+
runs.push({ record, meta, pairs, report, reportRef: report === null ? null : reportPath });
|
|
48
|
+
}
|
|
49
|
+
const reports = runs.filter(run => run.report !== null).map(run => ({ runId: run.record.id, path: run.reportRef, report: run.report }));
|
|
50
|
+
const history = options.history === undefined ? await readHistory(options.io, options.home) : await options.history();
|
|
51
|
+
const historyDegraded = options.history === undefined;
|
|
52
|
+
const counts = { active: { verify: 0, pending: 0, fuse: 0, skipped: 0 }, expired: 0 };
|
|
53
|
+
for (const item of rows)
|
|
54
|
+
if (item.expired)
|
|
55
|
+
counts.expired += 1;
|
|
56
|
+
else
|
|
57
|
+
counts.active[item.queue] += 1;
|
|
58
|
+
const stagingLost = stages.filter(item => item.state === 'lost' || item.state === 'expired').length;
|
|
59
|
+
const doctor = { counts, staging: { state: stages.length === 0 ? 'absent' : 'ok', dirs: stages.length, ...(stagingLost === 0 ? {} : { detail: String(stagingLost) + ' lost/expired stage(s)' }) }, rows: [
|
|
60
|
+
{ key: 'state', status: 'ok', detail: String(rows.length) + ' rows' },
|
|
61
|
+
{ key: 'staging-index', status: stages.length === 0 ? 'warn' : 'ok', detail: String(stages.length) + ' entries; ' + String(stagingLost) + ' lost/expired' },
|
|
62
|
+
{ key: 'consistency', status: rows.some(item => item.expired) ? 'warn' : 'ok', detail: String(counts.expired) + ' expired' },
|
|
63
|
+
] };
|
|
64
|
+
const queueRevision = createHash('sha256').update(JSON.stringify({ schemaVersion: 4, rows: rows.map(item => [item.row.id, item.row.version, item.row.state, item.row.updatedAt]), stages: stages.map(item => [item.rowId, item.version, item.state, item.updatedAt, item.ref, item.expiresAt]) })).digest('hex');
|
|
65
|
+
const notes = [...(options.notes ?? [])];
|
|
66
|
+
if (historyDegraded)
|
|
67
|
+
notes.push('history archive port unavailable; receipts only');
|
|
68
|
+
const ledgerConflicts = [...pairIndex.values()].filter(entry => (entry.result === 'merged' || entry.result === 'undone')
|
|
69
|
+
&& rows.some(item => item.row.pairKey === entry.pairKey && (item.row.state === 'verify' || item.row.state === 'pending' || item.row.state === 'approved' || item.row.state === 'fuse')));
|
|
70
|
+
if (ledgerConflicts.length > 0)
|
|
71
|
+
notes.push('ledger-conflict: pair-index says completed but the ledger still has it active (' + String(ledgerConflicts.length) + ')');
|
|
72
|
+
return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision, consistency: 'eventual', rows, stages, runs, reports, report: reports.at(-1)?.report ?? null, history, bodyRefs, doctor, approval: { mergeApproval: options.approval, ...(options.platformWrite === undefined ? {} : { platformWrite: options.platformWrite }) }, notes, degraded: notes.length > 0 };
|
|
73
|
+
}
|
|
74
|
+
async function readHistory(io, home) {
|
|
75
|
+
const receipts = await readCommitReceipts(io, home);
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const receipt of receipts)
|
|
78
|
+
for (const source of receipt.sources)
|
|
79
|
+
out.push({ name: source, kind: 'merged', reason: 'Merged into ' + receipt.target, reasonCode: 'merged', lastVersion: 1, archivedAt: receipt.at, sourceRunId: receipt.runId });
|
|
80
|
+
return out.map((item, index) => ({ ...item, generation: index + 1 }));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=merge-snapshot.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import type { V4Row } from './row-v4.ts';
|
|
3
|
+
import { type V3Row } from './row-v3.ts';
|
|
4
|
+
import { type V4RowTx } from './schema4.ts';
|
|
5
|
+
export type PairRow = V3Row;
|
|
6
|
+
export interface PairLedgerRead {
|
|
7
|
+
readonly schemaVersion: 3;
|
|
8
|
+
readonly ledgerVersion?: number;
|
|
9
|
+
readonly migration?: unknown;
|
|
10
|
+
readonly rows: readonly V3Row[];
|
|
11
|
+
}
|
|
12
|
+
export interface PairLedger {
|
|
13
|
+
read(): Promise<PairLedgerRead>;
|
|
14
|
+
transact<T>(fn: (tx: V4RowTx) => T): Promise<T>;
|
|
15
|
+
normalize(): Promise<NormalizeResult>;
|
|
16
|
+
}
|
|
17
|
+
export interface NormalizeResult {
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly migrated: boolean;
|
|
20
|
+
readonly backupRef?: string;
|
|
21
|
+
readonly message?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Read-only 1b-1 bypass. It does not normalize, back up, or write state. */
|
|
24
|
+
export declare function readPairLedger(io: EvolutionIoLike, home: string): Promise<PairLedgerRead>;
|
|
25
|
+
/** 1b-2a write skeleton. Production stays on the v3 gate until 1b-2b. */
|
|
26
|
+
export declare function pairLedger(io: EvolutionIoLike, home: string): PairLedger;
|
|
27
|
+
/** v3-to-v4 normalize: backup -> production stage adopt -> v4 state. Not called in 1b-2a. */
|
|
28
|
+
export declare function normalizePairLedger(io: EvolutionIoLike, home: string): Promise<NormalizeResult>;
|
|
29
|
+
/** Read schema4 rows only; no legacy projection. */
|
|
30
|
+
export declare function readPairLedgerV4(io: EvolutionIoLike, home: string): Promise<{
|
|
31
|
+
readonly rows: readonly V4Row[];
|
|
32
|
+
readonly revision: string;
|
|
33
|
+
} | null>;
|
|
34
|
+
//# sourceMappingURL=pair-ledger.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readTextOrNull } from "./read-probe.js";
|
|
2
|
+
/** 1b-1 additive read view; the production row writer stays in state.ts until 1b-2. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readStateReadiness, mergeStatePath, StateBlockedError } from "./state-bootstrap.js";
|
|
6
|
+
import { transactIo } from '@lmzhen/dsh-evolution-core';
|
|
7
|
+
import { createPre1bBackup } from "./pre-1b-backup.js";
|
|
8
|
+
import { adoptLegacyProduction } from "./stage-aggregate.js";
|
|
9
|
+
import { normalizeV3State, parseV4State, readV4Readiness } from "./schema4.js";
|
|
10
|
+
import { isV3Row } from "./row-v3.js";
|
|
11
|
+
import { transactMergeState } from "./schema4.js";
|
|
12
|
+
/** Read-only 1b-1 bypass. It does not normalize, back up, or write state. */
|
|
13
|
+
export async function readPairLedger(io, home) {
|
|
14
|
+
const ready = await readStateReadiness(io, home);
|
|
15
|
+
if (!ready.ok)
|
|
16
|
+
throw new StateBlockedError(ready);
|
|
17
|
+
let header = {};
|
|
18
|
+
try {
|
|
19
|
+
const raw = await io.readText(join(home, 'merge', 'merge-state.json'));
|
|
20
|
+
if (raw !== null) {
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
header = parsed;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch { /* readiness already validated the state; header is optional */ }
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion: 3,
|
|
28
|
+
...(typeof header.ledgerVersion === 'number' ? { ledgerVersion: header.ledgerVersion } : {}),
|
|
29
|
+
...(header.migration === undefined ? {} : { migration: header.migration }),
|
|
30
|
+
rows: ready.state.rows,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** 1b-2a write skeleton. Production stays on the v3 gate until 1b-2b. */
|
|
34
|
+
export function pairLedger(io, home) {
|
|
35
|
+
return {
|
|
36
|
+
read: () => readPairLedger(io, home),
|
|
37
|
+
transact: async (fn) => {
|
|
38
|
+
const current = await readV4Readiness(io, home);
|
|
39
|
+
if (!current.ok) {
|
|
40
|
+
const normalized = await normalizePairLedger(io, home);
|
|
41
|
+
if (!normalized.ok)
|
|
42
|
+
throw new Error(normalized.message ?? 'normalize failed');
|
|
43
|
+
}
|
|
44
|
+
return await transactMergeState(io, home, fn);
|
|
45
|
+
},
|
|
46
|
+
normalize: () => normalizePairLedger(io, home),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** v3-to-v4 normalize: backup -> production stage adopt -> v4 state. Not called in 1b-2a. */
|
|
50
|
+
export async function normalizePairLedger(io, home) {
|
|
51
|
+
const v4 = await readV4Readiness(io, home);
|
|
52
|
+
if (v4.ok)
|
|
53
|
+
return { ok: true, migrated: false };
|
|
54
|
+
if (v4.code !== 'merge-state-normalize-required' && v4.code !== 'merge-state-v4-required')
|
|
55
|
+
return { ok: false, migrated: false, message: v4.code + ': ' + v4.message };
|
|
56
|
+
let v3 = await readStateReadiness(io, home);
|
|
57
|
+
if (!v3.ok)
|
|
58
|
+
return { ok: false, migrated: false, message: v3.code + ': ' + v3.message };
|
|
59
|
+
if (v4.code === 'merge-state-v4-required') {
|
|
60
|
+
await transactIo(io, mergeStatePath(home), current => current === null ? JSON.stringify({ schemaVersion: 3, rows: [] }) + '\n' : current);
|
|
61
|
+
v3 = await readStateReadiness(io, home);
|
|
62
|
+
if (!v3.ok)
|
|
63
|
+
return { ok: false, migrated: false, message: v3.code + ': ' + v3.message };
|
|
64
|
+
}
|
|
65
|
+
const backup = await createPre1bBackup(io, home);
|
|
66
|
+
if (!backup.ok)
|
|
67
|
+
return { ok: false, migrated: false, message: backup.code + ': ' + backup.message };
|
|
68
|
+
await adoptLegacyProduction(io, home);
|
|
69
|
+
let wrote = false;
|
|
70
|
+
await transactIo(io, mergeStatePath(home), current => {
|
|
71
|
+
if (current === null)
|
|
72
|
+
throw new Error('merge-state-v4-required');
|
|
73
|
+
const header = JSON.parse(current);
|
|
74
|
+
if (header.schemaVersion === 4)
|
|
75
|
+
return current;
|
|
76
|
+
if (header.schemaVersion !== 3 || !Array.isArray(header.rows) || !header.rows.every(isV3Row))
|
|
77
|
+
throw new Error('merge-state-normalize-required');
|
|
78
|
+
const v4State = normalizeV3State({ schemaVersion: 3, rows: header.rows, ...(header.migration === undefined ? {} : { migration: header.migration }) }, { backupRef: backup.id, at: new Date().toISOString() });
|
|
79
|
+
wrote = true;
|
|
80
|
+
return JSON.stringify(v4State, null, 2) + '\n';
|
|
81
|
+
});
|
|
82
|
+
return { ok: true, migrated: wrote, backupRef: backup.id };
|
|
83
|
+
}
|
|
84
|
+
/** Read schema4 rows only; no legacy projection. */
|
|
85
|
+
export async function readPairLedgerV4(io, home) {
|
|
86
|
+
const raw = await readTextOrNull(io, mergeStatePath(home));
|
|
87
|
+
if (raw === null)
|
|
88
|
+
return null;
|
|
89
|
+
const state = parseV4State(raw);
|
|
90
|
+
if (state === null)
|
|
91
|
+
return null;
|
|
92
|
+
const rows = state.rows;
|
|
93
|
+
const revision = createHash('sha256').update(JSON.stringify(rows.map(row => [row.id, row.version, row.state, row.updatedAt]))).digest('hex');
|
|
94
|
+
return { rows, revision };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=pair-ledger.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
export interface Pre1bFileEntry {
|
|
3
|
+
readonly source: string;
|
|
4
|
+
readonly copy: string;
|
|
5
|
+
readonly sha256: string;
|
|
6
|
+
readonly bytes: number;
|
|
7
|
+
readonly mode: string;
|
|
8
|
+
}
|
|
9
|
+
export interface Pre1bTreeEntry {
|
|
10
|
+
readonly source: string;
|
|
11
|
+
readonly copy: string;
|
|
12
|
+
readonly sha256: string;
|
|
13
|
+
readonly files: number;
|
|
14
|
+
readonly bytes: number;
|
|
15
|
+
}
|
|
16
|
+
export interface Pre1bBackupManifest {
|
|
17
|
+
readonly version: 1;
|
|
18
|
+
readonly backupId: string;
|
|
19
|
+
readonly sourceSchema: 3;
|
|
20
|
+
readonly sourceStateSha256: string;
|
|
21
|
+
readonly createdAt: string;
|
|
22
|
+
readonly state: Pre1bFileEntry;
|
|
23
|
+
readonly trees: readonly Pre1bTreeEntry[];
|
|
24
|
+
readonly files: readonly Pre1bFileEntry[];
|
|
25
|
+
readonly readonly: true;
|
|
26
|
+
}
|
|
27
|
+
export type Pre1bBackupResult = {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly path: string;
|
|
31
|
+
readonly manifest: Pre1bBackupManifest;
|
|
32
|
+
} | {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly code: 'pre-1b-backup-conflict' | 'pre-1b-backup-invalid' | 'pre-1b-backup-failed';
|
|
35
|
+
readonly message: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function pre1bRoot(home: string): string;
|
|
38
|
+
export declare function pre1bDir(home: string, id: string): string;
|
|
39
|
+
export declare function pre1bManifestPath(home: string, id: string): string;
|
|
40
|
+
export declare function readPre1bManifest(io: EvolutionIoLike, home: string, id: string): Promise<Pre1bBackupManifest | null>;
|
|
41
|
+
/** Create or reuse the single pre-1b backup. Never writes merge-state. */
|
|
42
|
+
export declare function createPre1bBackup(io: EvolutionIoLike, home: string, at?: string): Promise<Pre1bBackupResult>;
|
|
43
|
+
//# sourceMappingURL=pre-1b-backup.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { readTextOrNull } from "./read-probe.js";
|
|
2
|
+
/** Batch 1b-2a pre-1b backup manifest. Read-only, idempotent, no production normalize. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { chmod, stat } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
export function pre1bRoot(home) { return join(home, 'merge', '.backup', 'pre-1b'); }
|
|
7
|
+
export function pre1bDir(home, id) { return join(pre1bRoot(home), id); }
|
|
8
|
+
export function pre1bManifestPath(home, id) { return join(pre1bDir(home, id), 'manifest.json'); }
|
|
9
|
+
const hash = (text) => createHash('sha256').update(text).digest('hex');
|
|
10
|
+
const statePath = (home) => join(home, 'merge', 'merge-state.json');
|
|
11
|
+
async function walk(io, root, rel = '') {
|
|
12
|
+
const path = rel === '' ? root : join(root, rel);
|
|
13
|
+
if (!await io.exists(path))
|
|
14
|
+
return [];
|
|
15
|
+
try {
|
|
16
|
+
const text = await io.readText(path);
|
|
17
|
+
if (text !== null)
|
|
18
|
+
return [rel];
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const code = error instanceof Error && 'code' in error ? String(error.code) : '';
|
|
22
|
+
if (code !== 'EISDIR' && code !== 'ENOTDIR')
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const names = await io.list(path);
|
|
26
|
+
const found = [];
|
|
27
|
+
for (const name of names.sort())
|
|
28
|
+
found.push(...await walk(io, root, rel === '' ? name : join(rel, name)));
|
|
29
|
+
return found;
|
|
30
|
+
}
|
|
31
|
+
async function chmodReadOnly(path) {
|
|
32
|
+
await chmod(path, 0o444);
|
|
33
|
+
const info = await stat(path);
|
|
34
|
+
const mode = (info.mode & 0o777).toString(8).padStart(4, '0');
|
|
35
|
+
if ((info.mode & 0o222) !== 0)
|
|
36
|
+
throw new Error('backup is not read-only: ' + path);
|
|
37
|
+
return mode;
|
|
38
|
+
}
|
|
39
|
+
export async function readPre1bManifest(io, home, id) {
|
|
40
|
+
const raw = await readTextOrNull(io, pre1bManifestPath(home, id));
|
|
41
|
+
if (raw === null)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const value = JSON.parse(raw);
|
|
45
|
+
if (value.version !== 1 || value.readonly !== true || value.sourceSchema !== 3 || typeof value.sourceStateSha256 !== 'string' || !Array.isArray(value.trees) || !Array.isArray(value.files) || value.state === undefined)
|
|
46
|
+
return null;
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Create or reuse the single pre-1b backup. Never writes merge-state. */
|
|
54
|
+
export async function createPre1bBackup(io, home, at = new Date().toISOString()) {
|
|
55
|
+
const raw = await readTextOrNull(io, statePath(home));
|
|
56
|
+
if (raw === null)
|
|
57
|
+
return { ok: false, code: 'pre-1b-backup-invalid', message: 'merge state is missing' };
|
|
58
|
+
let state;
|
|
59
|
+
try {
|
|
60
|
+
state = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { ok: false, code: 'pre-1b-backup-invalid', message: 'merge state is not JSON' };
|
|
64
|
+
}
|
|
65
|
+
if (state.schemaVersion !== 3)
|
|
66
|
+
return { ok: false, code: 'pre-1b-backup-invalid', message: 'pre-1b backup requires schema3 state' };
|
|
67
|
+
const stateSha = hash(raw);
|
|
68
|
+
const id = 'pre-1b-' + stateSha.slice(0, 16);
|
|
69
|
+
const existing = await readPre1bManifest(io, home, id);
|
|
70
|
+
if (existing !== null)
|
|
71
|
+
return { ok: true, id, path: pre1bManifestPath(home, id), manifest: existing };
|
|
72
|
+
const target = pre1bDir(home, id);
|
|
73
|
+
const stateBytes = await io.readText(statePath(home));
|
|
74
|
+
if (stateBytes === null)
|
|
75
|
+
return { ok: false, code: 'pre-1b-backup-failed', message: 'merge state disappeared' };
|
|
76
|
+
const files = [];
|
|
77
|
+
const trees = [];
|
|
78
|
+
try {
|
|
79
|
+
const sources = [
|
|
80
|
+
{ source: join(home, 'merge', 'staging-index'), kind: 'tree' },
|
|
81
|
+
{ source: join(home, 'merge', 'runs'), kind: 'tree' },
|
|
82
|
+
{ source: join(home, 'merge', 'run-meta'), kind: 'tree' },
|
|
83
|
+
{ source: join(home, 'reports'), kind: 'tree' },
|
|
84
|
+
{ source: join(home, 'merge', 'receipts'), kind: 'tree' },
|
|
85
|
+
{ source: join(home, 'merge', 'journals'), kind: 'tree' },
|
|
86
|
+
{ source: join(home, 'merge', 'unit-journals'), kind: 'tree' },
|
|
87
|
+
{ source: join(home, 'merge', 'units'), kind: 'tree' },
|
|
88
|
+
{ source: join(home, 'merge', 'merge-state.v2.bak.json'), kind: 'file' },
|
|
89
|
+
{ source: join(home, 'merge', 'merge-state.migration.json'), kind: 'file' },
|
|
90
|
+
{ source: join(home, 'merge', 'merge-state.migration.report.json'), kind: 'file' },
|
|
91
|
+
{ source: join(home, 'merge', 'merge-state.quarantine.json'), kind: 'file' },
|
|
92
|
+
{ source: join(home, 'merge', 'pair-index.json'), kind: 'file' },
|
|
93
|
+
{ source: join(home, 'merge', 'recovery.json'), kind: 'file' },
|
|
94
|
+
{ source: join(home, 'runs.json'), kind: 'file' },
|
|
95
|
+
];
|
|
96
|
+
for (const item of sources) {
|
|
97
|
+
if (item.kind === 'file') {
|
|
98
|
+
const text = await readTextOrNull(io, item.source);
|
|
99
|
+
if (text === null)
|
|
100
|
+
continue;
|
|
101
|
+
const rel = item.source.slice(home.length + 1);
|
|
102
|
+
const copy = join(target, 'snapshot', rel);
|
|
103
|
+
await io.copy(item.source, copy);
|
|
104
|
+
const mode = await chmodReadOnly(copy);
|
|
105
|
+
files.push({ source: item.source, copy, sha256: hash(text), bytes: Buffer.byteLength(text, 'utf8'), mode });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const rels = await walk(io, item.source);
|
|
109
|
+
if (rels.length === 0)
|
|
110
|
+
continue;
|
|
111
|
+
let bytes = 0;
|
|
112
|
+
const hashes = [];
|
|
113
|
+
for (const rel of rels) {
|
|
114
|
+
const source = join(item.source, rel);
|
|
115
|
+
const text = await io.readText(source);
|
|
116
|
+
if (text === null)
|
|
117
|
+
continue;
|
|
118
|
+
bytes += Buffer.byteLength(text, 'utf8');
|
|
119
|
+
hashes.push(rel + ':' + hash(text));
|
|
120
|
+
const copy = join(target, 'snapshot', item.source.slice(home.length + 1), rel);
|
|
121
|
+
await io.copy(source, copy);
|
|
122
|
+
await chmodReadOnly(copy);
|
|
123
|
+
}
|
|
124
|
+
trees.push({ source: item.source, copy: join(target, 'snapshot', item.source.slice(home.length + 1)), sha256: hash(hashes.sort().join('\n')), files: hashes.length, bytes });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const stateCopy = join(target, 'snapshot', 'merge', 'merge-state.json');
|
|
128
|
+
await io.copy(statePath(home), stateCopy);
|
|
129
|
+
const stateMode = await chmodReadOnly(stateCopy);
|
|
130
|
+
const stateEntry = { source: statePath(home), copy: stateCopy, sha256: stateSha, bytes: Buffer.byteLength(raw, 'utf8'), mode: stateMode };
|
|
131
|
+
const manifest = { version: 1, backupId: id, sourceSchema: 3, sourceStateSha256: stateSha, createdAt: at, state: stateEntry, trees, files, readonly: true };
|
|
132
|
+
await io.writeText(pre1bManifestPath(home, id), JSON.stringify(manifest, null, 2) + '\n');
|
|
133
|
+
await chmodReadOnly(pre1bManifestPath(home, id));
|
|
134
|
+
return { ok: true, id, path: pre1bManifestPath(home, id), manifest };
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return { ok: false, code: 'pre-1b-backup-failed', message: error instanceof Error ? error.message : String(error) };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=pre-1b-backup.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Provider lexical normalization shared by plan/report. */
|
|
2
|
+
const PROVIDER = /^[a-z0-9][a-z0-9._-]*$/;
|
|
3
|
+
export function normalizeProviderKey(value) {
|
|
4
|
+
if (typeof value !== 'string')
|
|
5
|
+
throw new Error('provider must be a string');
|
|
6
|
+
const normalized = value.trim().toLowerCase();
|
|
7
|
+
if (normalized === '' || normalized.includes('/') || normalized.includes(':') || !PROVIDER.test(normalized))
|
|
8
|
+
throw new Error('provider key is invalid: ' + String(value));
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=provider-key.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Three-state reads for merge diagnostics: callers that render absence as none. */
|
|
2
|
+
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
3
|
+
export declare function readTextOrNull(io: EvolutionIoLike, path: string): Promise<string | null>;
|
|
4
|
+
export declare function listOrEmpty(io: EvolutionIoLike, path: string): Promise<readonly string[]>;
|
|
5
|
+
//# sourceMappingURL=read-probe.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Three-state reads for merge diagnostics: callers that render absence as none. */
|
|
2
|
+
import { probeList, probeText } from '@lmzhen/dsh-evolution-core';
|
|
3
|
+
export async function readTextOrNull(io, path) {
|
|
4
|
+
const probe = await probeText(io, path);
|
|
5
|
+
return probe.kind === 'present' ? probe.value : null;
|
|
6
|
+
}
|
|
7
|
+
export async function listOrEmpty(io, path) {
|
|
8
|
+
const probe = await probeList(io, path);
|
|
9
|
+
return probe.kind === 'present' ? probe.value : [];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=read-probe.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import { type LiveRowFacts } from './live-facts.ts';
|
|
3
|
+
import type { V4Row } from './row-v4.ts';
|
|
4
|
+
export declare const RECONCILE_REPORT_VERSION = 1;
|
|
5
|
+
export type ReconcileActionKind = 'skipped-expired' | 'pending-expired' | 'approved-fused' | 'source-gone';
|
|
6
|
+
export type ReconcileActionCode = 'skipped-ttl' | 'target-taken' | 'source-changed' | 'stage-present' | 'stage-lost' | 'stage-expired' | 'source-gone';
|
|
7
|
+
/** One durable row transition reconcile performed. */
|
|
8
|
+
export interface ReconcileAction {
|
|
9
|
+
readonly kind: ReconcileActionKind;
|
|
10
|
+
readonly rowId: string;
|
|
11
|
+
readonly from: V4Row['state'];
|
|
12
|
+
readonly to: V4Row['state'] | 'removed';
|
|
13
|
+
readonly code: ReconcileActionCode;
|
|
14
|
+
readonly detail?: string;
|
|
15
|
+
}
|
|
16
|
+
/** The persisted report a CLI/doctor consumer reads; `state:'blocked'` means nothing was written. */
|
|
17
|
+
export interface ReconcileReport {
|
|
18
|
+
readonly version: typeof RECONCILE_REPORT_VERSION;
|
|
19
|
+
readonly at: number;
|
|
20
|
+
readonly state: 'ok' | 'blocked';
|
|
21
|
+
readonly code?: string;
|
|
22
|
+
readonly message?: string;
|
|
23
|
+
readonly actions: readonly ReconcileAction[];
|
|
24
|
+
readonly notes: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ReconcileOptions {
|
|
27
|
+
readonly io: EvolutionIoLike;
|
|
28
|
+
readonly home: string;
|
|
29
|
+
readonly now: () => number;
|
|
30
|
+
/** Skipped rows past this TTL get their durable expiredAt stamp. */
|
|
31
|
+
readonly ttlDays?: number;
|
|
32
|
+
/** Live managed skill names. When supplied, active rows whose sources vanished are cleared. */
|
|
33
|
+
readonly managed?: ReadonlySet<string>;
|
|
34
|
+
/** Live revision facts; when supplied, pending rows that went stale are stamped expired. */
|
|
35
|
+
readonly live?: LiveRowFacts;
|
|
36
|
+
}
|
|
37
|
+
export declare function reconcileReportPath(home: string): string;
|
|
38
|
+
/** Read the last reconcile report; a malformed report is absent, never a throw. */
|
|
39
|
+
export declare function readReconcileReport(io: EvolutionIoLike, home: string): Promise<ReconcileReport | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Converge the row layer once, atomically, then persist the report.
|
|
42
|
+
* A blocked state writes no rows and no report; every other run is idempotent.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reconcileMergeState(options: ReconcileOptions): Promise<ReconcileReport>;
|
|
45
|
+
/** One-line text face for the CLI/doctor consumers. */
|
|
46
|
+
export declare function formatReconcileReport(report: ReconcileReport): string;
|
|
47
|
+
//# sourceMappingURL=reconcile.d.ts.map
|