@lmzhen/dsh-evolution-merge 0.30.17 → 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.
Files changed (187) hide show
  1. package/README.md +25 -7
  2. package/data/minhash_params.json +1063 -0
  3. package/lib/index.js +7308 -1800
  4. package/lib/types/adapters/channel.d.ts +1 -0
  5. package/lib/types/adapters/channel.js +15 -1
  6. package/lib/types/adapters/engine-screen.d.ts +1 -1
  7. package/lib/types/adapters/engine-screen.js +5 -2
  8. package/lib/types/adapters/library.js +4 -1
  9. package/lib/types/adapters/model-runner.d.ts +0 -5
  10. package/lib/types/adapters/model-runner.js +8 -2
  11. package/lib/types/adapters/model.d.ts +31 -0
  12. package/lib/types/adapters/model.js +47 -0
  13. package/lib/types/adapters/select.js +12 -8
  14. package/lib/types/approve-command.d.ts +14 -1
  15. package/lib/types/approve-command.js +72 -44
  16. package/lib/types/authoring-flow.d.ts +19 -1
  17. package/lib/types/authoring-flow.js +49 -15
  18. package/lib/types/channel-port.d.ts +3 -1
  19. package/lib/types/channel-port.js +8 -3
  20. package/lib/types/command-defs.d.ts +5 -0
  21. package/lib/types/command-defs.js +33 -0
  22. package/lib/types/command-surface.d.ts +5 -0
  23. package/lib/types/command-surface.js +11 -0
  24. package/lib/types/commands.d.ts +3 -4
  25. package/lib/types/commands.js +33 -72
  26. package/lib/types/commit-assembly.d.ts +16 -1
  27. package/lib/types/commit-assembly.js +72 -11
  28. package/lib/types/commit-execute.d.ts +14 -2
  29. package/lib/types/commit-execute.js +76 -14
  30. package/lib/types/commit-plan.d.ts +7 -1
  31. package/lib/types/commit-plan.js +4 -0
  32. package/lib/types/commit-receipt.d.ts +3 -0
  33. package/lib/types/commit-recovery.d.ts +7 -2
  34. package/lib/types/commit-recovery.js +73 -96
  35. package/lib/types/contracts.d.ts +44 -1
  36. package/lib/types/contracts.js +1 -1
  37. package/lib/types/coordinator.d.ts +3 -2
  38. package/lib/types/coordinator.js +4 -3
  39. package/lib/types/decision.js +1 -1
  40. package/lib/types/doctor.d.ts +18 -1
  41. package/lib/types/doctor.js +164 -10
  42. package/lib/types/domain/authoring-manifest.d.ts +26 -0
  43. package/lib/types/domain/authoring-manifest.js +43 -0
  44. package/lib/types/domain/claim.d.ts +4 -1
  45. package/lib/types/domain/claim.js +2 -2
  46. package/lib/types/domain/commit-journal-store.js +5 -1
  47. package/lib/types/domain/commit-journal.js +1 -1
  48. package/lib/types/domain/consistency.d.ts +22 -0
  49. package/lib/types/domain/consistency.js +50 -0
  50. package/lib/types/domain/engine-criteria.d.ts +10 -0
  51. package/lib/types/domain/engine-criteria.js +11 -0
  52. package/lib/types/domain/engine-input.d.ts +10 -0
  53. package/lib/types/domain/engine-input.js +26 -0
  54. package/lib/types/domain/errors.d.ts +15 -1
  55. package/lib/types/domain/errors.js +23 -4
  56. package/lib/types/domain/evidence-hash.d.ts +3 -0
  57. package/lib/types/domain/evidence-hash.js +14 -0
  58. package/lib/types/domain/failure-route.d.ts +14 -0
  59. package/lib/types/domain/failure-route.js +12 -0
  60. package/lib/types/domain/ledger.js +10 -1
  61. package/lib/types/domain/live-facts.d.ts +31 -0
  62. package/lib/types/domain/live-facts.js +54 -0
  63. package/lib/types/domain/merge-limits.d.ts +18 -0
  64. package/lib/types/domain/merge-limits.js +22 -0
  65. package/lib/types/domain/merge-report.d.ts +41 -0
  66. package/lib/types/domain/merge-report.js +2 -0
  67. package/lib/types/domain/merge-snapshot.d.ts +99 -0
  68. package/lib/types/domain/merge-snapshot.js +82 -0
  69. package/lib/types/domain/migrate-v3.d.ts +20 -0
  70. package/lib/types/domain/migrate-v3.js +68 -0
  71. package/lib/types/domain/pair-ledger.d.ts +34 -0
  72. package/lib/types/domain/pair-ledger.js +96 -0
  73. package/lib/types/domain/pre-1b-backup.d.ts +43 -0
  74. package/lib/types/domain/pre-1b-backup.js +140 -0
  75. package/lib/types/domain/provider-key.d.ts +2 -0
  76. package/lib/types/domain/provider-key.js +11 -0
  77. package/lib/types/domain/read-probe.d.ts +5 -0
  78. package/lib/types/domain/read-probe.js +11 -0
  79. package/lib/types/domain/reconcile.d.ts +47 -0
  80. package/lib/types/domain/reconcile.js +178 -0
  81. package/lib/types/domain/resolve-pair.d.ts +2 -9
  82. package/lib/types/domain/resolve-pair.js +2 -15
  83. package/lib/types/domain/row-tx.d.ts +27 -0
  84. package/lib/types/domain/row-tx.js +31 -0
  85. package/lib/types/domain/row-v3-bridge.d.ts +6 -0
  86. package/lib/types/domain/row-v3-bridge.js +47 -0
  87. package/lib/types/domain/row-v3.d.ts +113 -0
  88. package/lib/types/domain/row-v3.js +107 -0
  89. package/lib/types/domain/row-v4.d.ts +148 -0
  90. package/lib/types/domain/row-v4.js +159 -0
  91. package/lib/types/domain/row.d.ts +1 -0
  92. package/lib/types/domain/run-coordinator.d.ts +51 -0
  93. package/lib/types/domain/run-coordinator.js +45 -0
  94. package/lib/types/domain/schema4.d.ts +53 -0
  95. package/lib/types/domain/schema4.js +129 -0
  96. package/lib/types/domain/selection-policy.d.ts +14 -0
  97. package/lib/types/domain/selection-policy.js +6 -0
  98. package/lib/types/domain/skipped-expiry.d.ts +5 -0
  99. package/lib/types/domain/skipped-expiry.js +16 -0
  100. package/lib/types/domain/stage-aggregate.d.ts +96 -0
  101. package/lib/types/domain/stage-aggregate.js +293 -0
  102. package/lib/types/domain/staging-index.d.ts +15 -0
  103. package/lib/types/domain/staging-index.js +41 -0
  104. package/lib/types/domain/state-bootstrap.d.ts +43 -0
  105. package/lib/types/domain/state-bootstrap.js +267 -0
  106. package/lib/types/domain/state-v3.d.ts +4 -0
  107. package/lib/types/domain/state-v3.js +4 -0
  108. package/lib/types/domain/undo-journal.d.ts +19 -0
  109. package/lib/types/domain/undo-journal.js +20 -0
  110. package/lib/types/domain/undo-receipt.d.ts +18 -0
  111. package/lib/types/domain/undo-receipt.js +22 -0
  112. package/lib/types/domain/validation-plan.d.ts +65 -0
  113. package/lib/types/domain/validation-plan.js +49 -0
  114. package/lib/types/facts.d.ts +26 -0
  115. package/lib/types/facts.js +65 -0
  116. package/lib/types/gate-filter.d.ts +31 -0
  117. package/lib/types/gate-filter.js +64 -0
  118. package/lib/types/index.d.ts +7 -2
  119. package/lib/types/index.js +185 -82
  120. package/lib/types/ladder.js +1 -1
  121. package/lib/types/merge-command.d.ts +3 -1
  122. package/lib/types/merge-command.js +10 -6
  123. package/lib/types/merge-routes.d.ts +12 -7
  124. package/lib/types/merge-routes.js +87 -23
  125. package/lib/types/merge-tool.d.ts +1 -1
  126. package/lib/types/merge-tool.js +2 -2
  127. package/lib/types/metrics.d.ts +16 -0
  128. package/lib/types/metrics.js +27 -0
  129. package/lib/types/new-child-authoring.d.ts +5 -1
  130. package/lib/types/new-child-authoring.js +71 -13
  131. package/lib/types/params.d.ts +2 -4
  132. package/lib/types/params.js +13 -0
  133. package/lib/types/pending-actions.d.ts +2 -2
  134. package/lib/types/pending-actions.js +17 -17
  135. package/lib/types/prompts.d.ts +2 -4
  136. package/lib/types/prompts.js +21 -8
  137. package/lib/types/propose.d.ts +26 -0
  138. package/lib/types/propose.js +69 -0
  139. package/lib/types/read-commands.d.ts +4 -4
  140. package/lib/types/read-commands.js +19 -26
  141. package/lib/types/recovery-coordinator.d.ts +34 -0
  142. package/lib/types/recovery-coordinator.js +84 -0
  143. package/lib/types/recovery-projection.d.ts +31 -0
  144. package/lib/types/recovery-projection.js +96 -0
  145. package/lib/types/resume-command.d.ts +7 -1
  146. package/lib/types/resume-command.js +50 -14
  147. package/lib/types/revision.d.ts +17 -0
  148. package/lib/types/revision.js +39 -3
  149. package/lib/types/round2-outcome.d.ts +2 -1
  150. package/lib/types/round2-outcome.js +68 -12
  151. package/lib/types/run-shell.d.ts +16 -2
  152. package/lib/types/run-shell.js +49 -5
  153. package/lib/types/runtime.d.ts +4 -6
  154. package/lib/types/runtime.js +341 -86
  155. package/lib/types/scan-command.d.ts +1 -0
  156. package/lib/types/scan-command.js +2 -0
  157. package/lib/types/scan-ports.d.ts +21 -1
  158. package/lib/types/scan-ports.js +51 -23
  159. package/lib/types/scan-run.d.ts +17 -0
  160. package/lib/types/scan-run.js +221 -63
  161. package/lib/types/screening-port.d.ts +47 -0
  162. package/lib/types/screening-port.js +93 -0
  163. package/lib/types/screening.d.ts +30 -0
  164. package/lib/types/screening.js +75 -0
  165. package/lib/types/stage-tool-registry.d.ts +7 -0
  166. package/lib/types/stage-tool-registry.js +12 -1
  167. package/lib/types/state.d.ts +15 -1
  168. package/lib/types/state.js +39 -17
  169. package/lib/types/status.d.ts +8 -0
  170. package/lib/types/status.js +64 -0
  171. package/lib/types/surface/body.d.ts +4 -0
  172. package/lib/types/surface/body.js +16 -0
  173. package/lib/types/surface/merge-view.d.ts +14 -0
  174. package/lib/types/surface/merge-view.js +126 -0
  175. package/lib/types/target-claim.d.ts +1 -0
  176. package/lib/types/target-claim.js +63 -18
  177. package/lib/types/undo-operation.d.ts +24 -0
  178. package/lib/types/undo-operation.js +135 -0
  179. package/lib/types/unit-claim.d.ts +5 -0
  180. package/lib/types/unit-claim.js +15 -0
  181. package/lib/types/wire-projection.d.ts +36 -0
  182. package/lib/types/wire-projection.js +69 -0
  183. package/lib/types/wire.d.ts +273 -0
  184. package/lib/types/wire.js +3 -0
  185. package/package.json +10 -4
  186. package/lib/types/routes.d.ts +0 -70
  187. package/lib/types/routes.js +0 -78
@@ -0,0 +1,96 @@
1
+ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
+ import { type StagingEntry } from './staging-index.ts';
3
+ import { readStaging, type CreateStagingInput, type CreateStagingResult } from '../staging.ts';
4
+ /**
5
+ * Old production staging writers (grep baseline at Batch 1b-1). Their production
6
+ * path is merge/staging-index + runs/<runId>/pairs/<hash>. adoptLegacy must never
7
+ * call any of these; it only writes merge/.shadow/staging-index.
8
+ */
9
+ export declare const LEGACY_STAGING_WRITER_FILES: readonly ["src/scan-ports.ts", "src/scan-run.ts", "src/staging.ts", "src/new-child-authoring.ts", "src/domain/staging-index.ts", "src/state.ts", "src/domain/state-bootstrap.ts"];
10
+ export type StageState = 'reserved' | 'present' | 'expired' | 'lost';
11
+ export interface StageEntry {
12
+ readonly version: 1;
13
+ readonly rowId: string;
14
+ readonly pairKey: string;
15
+ readonly runId?: string;
16
+ readonly ref: string;
17
+ readonly root?: string;
18
+ readonly manifestPath?: string;
19
+ readonly state: StageState;
20
+ readonly createdAt: number;
21
+ readonly expiresAt: number;
22
+ readonly updatedAt: number;
23
+ readonly sourceDigest?: string;
24
+ readonly targetDigest?: string;
25
+ readonly manifestDigest?: string;
26
+ readonly sourceRevision?: string;
27
+ readonly targetRevision?: string;
28
+ readonly files?: readonly string[];
29
+ }
30
+ export interface ShadowAdoptResult {
31
+ readonly entries: readonly StageEntry[];
32
+ readonly anomalies: readonly string[];
33
+ readonly shadowPath: string;
34
+ readonly wrote: boolean;
35
+ }
36
+ export interface StageAggregateRead {
37
+ readonly entries: readonly StageEntry[];
38
+ readonly shadowPath: string;
39
+ }
40
+ export declare function shadowStagingIndexPath(home: string, rowId: string): string;
41
+ export declare function shadowStagingRoot(home: string): string;
42
+ /** Read-only adoption into the shadow index; never writes production staging-index. */
43
+ export declare function adoptLegacy(io: EvolutionIoLike, home: string): Promise<ShadowAdoptResult>;
44
+ export declare function readShadowStageAggregate(io: EvolutionIoLike, home: string, rowId?: string): Promise<StageAggregateRead>;
45
+ /** 1b-2b execution order; kept here so 1b-2a tests can pin the contract. */
46
+ export declare const STAGE_WRITE_ORDER: readonly ["index:reserved", "tree", "index:present"];
47
+ export type StageAdmission = {
48
+ readonly ok: true;
49
+ } | {
50
+ readonly ok: false;
51
+ readonly message: string;
52
+ };
53
+ export declare function admitStage(rowState: string): StageAdmission;
54
+ export type ApprovedReservedPlan = {
55
+ readonly kind: 'transition-fuse';
56
+ readonly rowId: string;
57
+ readonly ref: string;
58
+ readonly expiresAt: number;
59
+ } | {
60
+ readonly kind: 'write-present';
61
+ readonly rowId: string;
62
+ readonly ref: string;
63
+ } | {
64
+ readonly kind: 'drop-orphan';
65
+ readonly rowId: string;
66
+ } | {
67
+ readonly kind: 'none';
68
+ };
69
+ export declare function planApprovedReserved(rowState: string, entry: StageEntry | null): ApprovedReservedPlan;
70
+ /** Production adoption for 1b-2b normalize; writes the existing staging-index, not shadow. */
71
+ export declare function adoptLegacyProduction(io: EvolutionIoLike, home: string): Promise<ShadowAdoptResult>;
72
+ /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
73
+ export declare function ensureStageTree(io: EvolutionIoLike, home: string, input: CreateStagingInput): Promise<CreateStagingResult>;
74
+ export declare function createStageTree(io: EvolutionIoLike, home: string, input: CreateStagingInput): Promise<CreateStagingResult>;
75
+ export declare function readStageTree(io: EvolutionIoLike, home: string, runId: string): Promise<Awaited<ReturnType<typeof readStaging>>>;
76
+ export declare function setStageTreeTarget(io: EvolutionIoLike, home: string, stageKey: string, target: string, mode?: 'merge-into-existing' | 'create-umbrella' | 'keep'): Promise<boolean>;
77
+ export declare function dropStageTree(io: EvolutionIoLike, home: string, stageKey: string): Promise<void>;
78
+ export declare function writeStageIndex(io: EvolutionIoLike, home: string, entry: StagingEntry): Promise<void>;
79
+ export declare function readStageIndex(io: EvolutionIoLike, home: string, rowId: string, now?: number): Promise<StagingEntry | null>;
80
+ export declare function readStageEntry(io: EvolutionIoLike, home: string, rowId: string): Promise<StageEntry | null>;
81
+ export declare function reserveStage(io: EvolutionIoLike, home: string, entry: StageEntry): Promise<void>;
82
+ export declare function presentStage(io: EvolutionIoLike, home: string, entry: StageEntry): Promise<void>;
83
+ export declare function dropStage(io: EvolutionIoLike, home: string, rowId: string, ref?: string): Promise<void>;
84
+ export interface StageCleanupResult {
85
+ readonly removed: readonly string[];
86
+ readonly expired: readonly string[];
87
+ readonly kept: readonly string[];
88
+ readonly orphans: readonly string[];
89
+ }
90
+ /** Bounded cleanup: reserved/present TTL, pending/verify stale index removal, terminal/absent rows, orphan trees. */
91
+ export declare function cleanupStage(io: EvolutionIoLike, home: string, now: number, rows: ReadonlyMap<string, {
92
+ readonly state: string;
93
+ }>, limit?: number): Promise<StageCleanupResult>;
94
+ /** Read all production v2 stage entries. */
95
+ export declare function readAllStages(io: EvolutionIoLike, home: string): Promise<readonly StageEntry[]>;
96
+ //# sourceMappingURL=stage-aggregate.d.ts.map
@@ -0,0 +1,293 @@
1
+ import { readTextOrNull, listOrEmpty } from "./read-probe.js";
2
+ /** 1b-1 shadow staging adoption. Production staging is still owned by the old staged writers. */
3
+ import { join } from 'node:path';
4
+ import { readPairLedger } from "./pair-ledger.js";
5
+ import { readStagingIndex, writeStagingIndex } from "./staging-index.js";
6
+ import { createStaging, dropStaging, ensureStaging, readStaging, setStagingTarget } from "../staging.js";
7
+ /**
8
+ * Old production staging writers (grep baseline at Batch 1b-1). Their production
9
+ * path is merge/staging-index + runs/<runId>/pairs/<hash>. adoptLegacy must never
10
+ * call any of these; it only writes merge/.shadow/staging-index.
11
+ */
12
+ export const LEGACY_STAGING_WRITER_FILES = [
13
+ 'src/scan-ports.ts',
14
+ 'src/scan-run.ts',
15
+ 'src/staging.ts',
16
+ 'src/new-child-authoring.ts',
17
+ 'src/domain/staging-index.ts',
18
+ 'src/state.ts',
19
+ 'src/domain/state-bootstrap.ts',
20
+ ];
21
+ export function shadowStagingIndexPath(home, rowId) {
22
+ return join(home, 'merge', '.shadow', 'staging-index', encodeURIComponent(rowId) + '.json');
23
+ }
24
+ export function shadowStagingRoot(home) {
25
+ return join(home, 'merge', '.shadow', 'staging-index');
26
+ }
27
+ function stagingTreePath(home, ref) {
28
+ return join(home, 'merge', 'runs', ref);
29
+ }
30
+ function markerStaging(migration) {
31
+ if (migration === null || typeof migration !== 'object')
32
+ return [];
33
+ const value = migration;
34
+ if (!Array.isArray(value.staging))
35
+ return [];
36
+ const facts = [];
37
+ for (const item of value.staging) {
38
+ if (item === null || typeof item !== 'object')
39
+ continue;
40
+ const entry = item;
41
+ if (typeof entry.rowId !== 'string' || typeof entry.pairKey !== 'string' || typeof entry.ref !== 'string' || typeof entry.expiresAt !== 'number' || typeof entry.createdAt !== 'number')
42
+ continue;
43
+ facts.push({ rowId: entry.rowId, pairKey: entry.pairKey, ref: entry.ref, expiresAt: entry.expiresAt, ...(typeof entry.runId === 'string' ? { runId: entry.runId } : {}), createdAt: entry.createdAt });
44
+ }
45
+ return facts;
46
+ }
47
+ /** Read-only adoption into the shadow index; never writes production staging-index. */
48
+ export async function adoptLegacy(io, home) {
49
+ const ledger = await readPairLedger(io, home);
50
+ const entries = new Map();
51
+ const anomalies = [];
52
+ const now = Date.now();
53
+ for (const fact of markerStaging(ledger.migration)) {
54
+ const present = fact.ref !== '' && await io.exists(stagingTreePath(home, fact.ref));
55
+ entries.set(fact.rowId, { version: 1, rowId: fact.rowId, pairKey: fact.pairKey, ...(fact.runId === undefined ? {} : { runId: fact.runId }), ref: fact.ref, state: present ? 'present' : 'lost', createdAt: fact.createdAt, expiresAt: fact.expiresAt, updatedAt: now });
56
+ }
57
+ for (const row of ledger.rows) {
58
+ if (row.state === 'fuse' && !entries.has(row.id)) {
59
+ if (row.staging.kind === 'present')
60
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref, state: 'present', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
61
+ else if (row.staging.kind === 'expired')
62
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref ?? '', state: 'expired', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
63
+ continue;
64
+ }
65
+ if (row.state === 'pending') {
66
+ if (await readStagingIndex(io, home, row.id, now) !== null)
67
+ anomalies.push('pending-staging-index:' + row.id);
68
+ continue;
69
+ }
70
+ if (row.state === 'approved' && !entries.has(row.id)) {
71
+ const old = await readStagingIndex(io, home, row.id, now);
72
+ if (old !== null)
73
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: old.ref, state: 'reserved', createdAt: old.createdAt, expiresAt: old.expiresAt, updatedAt: now });
74
+ }
75
+ }
76
+ const adopted = [...entries.values()].sort((a, b) => a.rowId.localeCompare(b.rowId));
77
+ let wrote = false;
78
+ for (const entry of adopted) {
79
+ const path = shadowStagingIndexPath(home, entry.rowId);
80
+ const text = JSON.stringify(entry, null, 2) + '\n';
81
+ const current = await readTextOrNull(io, path);
82
+ if (current !== null) {
83
+ try {
84
+ const previous = JSON.parse(current);
85
+ if (previous.rowId === entry.rowId && previous.state === entry.state && previous.ref === entry.ref && previous.expiresAt === entry.expiresAt)
86
+ continue;
87
+ }
88
+ catch { /* malformed shadow entry is replaced below */ }
89
+ }
90
+ if (io.transact !== undefined)
91
+ await io.transact(path, () => text);
92
+ else
93
+ await io.writeText(path, text);
94
+ wrote = true;
95
+ }
96
+ return { entries: adopted, anomalies, shadowPath: shadowStagingRoot(home), wrote };
97
+ }
98
+ export async function readShadowStageAggregate(io, home, rowId) {
99
+ if (rowId !== undefined) {
100
+ const raw = await readTextOrNull(io, shadowStagingIndexPath(home, rowId));
101
+ if (raw === null)
102
+ return { entries: [], shadowPath: shadowStagingRoot(home) };
103
+ try {
104
+ return { entries: [JSON.parse(raw)], shadowPath: shadowStagingRoot(home) };
105
+ }
106
+ catch {
107
+ return { entries: [], shadowPath: shadowStagingRoot(home) };
108
+ }
109
+ }
110
+ const listed = await io.exists(shadowStagingRoot(home)) ? await io.list(shadowStagingRoot(home)) : [];
111
+ const entries = [];
112
+ for (const name of listed) {
113
+ const raw = await readTextOrNull(io, join(shadowStagingRoot(home), name));
114
+ if (raw === null)
115
+ continue;
116
+ try {
117
+ entries.push(JSON.parse(raw));
118
+ }
119
+ catch { /* ignore malformed shadow */ }
120
+ }
121
+ entries.sort((a, b) => a.rowId.localeCompare(b.rowId));
122
+ return { entries, shadowPath: shadowStagingRoot(home) };
123
+ }
124
+ /** 1b-2b execution order; kept here so 1b-2a tests can pin the contract. */
125
+ export const STAGE_WRITE_ORDER = ['index:reserved', 'tree', 'index:present'];
126
+ export function admitStage(rowState) {
127
+ return rowState === 'approved' || rowState === 'fuse' ? { ok: true } : { ok: false, message: 'stage requires approved or fuse: ' + rowState };
128
+ }
129
+ export function planApprovedReserved(rowState, entry) {
130
+ if (entry === null)
131
+ return rowState === 'approved' || rowState === 'fuse' ? { kind: 'drop-orphan', rowId: '' } : { kind: 'none' };
132
+ if (rowState === 'approved' && entry.state === 'reserved')
133
+ return { kind: 'transition-fuse', rowId: entry.rowId, ref: entry.ref, expiresAt: entry.expiresAt };
134
+ if (rowState === 'approved' && entry.state === 'present')
135
+ return { kind: 'transition-fuse', rowId: entry.rowId, ref: entry.ref, expiresAt: entry.expiresAt };
136
+ if (rowState === 'fuse' && entry.state === 'reserved')
137
+ return { kind: 'write-present', rowId: entry.rowId, ref: entry.ref };
138
+ return { kind: 'none' };
139
+ }
140
+ /** Production adoption for 1b-2b normalize; writes the existing staging-index, not shadow. */
141
+ export async function adoptLegacyProduction(io, home) {
142
+ const ledger = await readPairLedger(io, home);
143
+ const entries = new Map();
144
+ const anomalies = [];
145
+ const now = Date.now();
146
+ for (const fact of markerStaging(ledger.migration)) {
147
+ const present = fact.ref !== '' && await io.exists(stagingTreePath(home, fact.ref));
148
+ entries.set(fact.rowId, { version: 1, rowId: fact.rowId, pairKey: fact.pairKey, ...(fact.runId === undefined ? {} : { runId: fact.runId }), ref: fact.ref, state: present ? 'present' : 'lost', createdAt: fact.createdAt, expiresAt: fact.expiresAt, updatedAt: now });
149
+ }
150
+ for (const row of ledger.rows) {
151
+ if (row.state === 'fuse' && !entries.has(row.id)) {
152
+ if (row.staging.kind === 'present')
153
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref, state: 'present', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
154
+ else if (row.staging.kind === 'expired')
155
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: row.staging.ref ?? '', state: 'expired', createdAt: now, expiresAt: row.staging.expiresAt, updatedAt: now });
156
+ continue;
157
+ }
158
+ if (row.state === 'pending') {
159
+ if (await readStagingIndex(io, home, row.id, now) !== null)
160
+ anomalies.push('pending-staging-index:' + row.id);
161
+ continue;
162
+ }
163
+ if (row.state === 'approved' && !entries.has(row.id)) {
164
+ const old = await readStagingIndex(io, home, row.id, now);
165
+ if (old !== null)
166
+ entries.set(row.id, { version: 1, rowId: row.id, pairKey: row.pairKey, ...(row.runId === undefined ? {} : { runId: row.runId }), ref: old.ref, state: 'reserved', createdAt: old.createdAt, expiresAt: old.expiresAt, updatedAt: now });
167
+ }
168
+ }
169
+ const adopted = [...entries.values()].sort((a, b) => a.rowId.localeCompare(b.rowId));
170
+ let wrote = false;
171
+ for (const entry of adopted) {
172
+ const current = await readStagingIndex(io, home, entry.rowId, 0);
173
+ if (current !== null && current.ref === entry.ref && current.expiresAt === entry.expiresAt)
174
+ continue;
175
+ await writeStagingIndex(io, home, { rowId: entry.rowId, pairKey: entry.pairKey, ref: entry.ref, expiresAt: entry.expiresAt, ...(entry.runId === undefined ? {} : { runId: entry.runId }), createdAt: entry.createdAt, state: entry.state });
176
+ wrote = true;
177
+ }
178
+ return { entries: adopted, anomalies, shadowPath: join(home, 'merge', 'staging-index'), wrote };
179
+ }
180
+ /** Production stage-tree facade. These wrappers keep the low-level tree module private to StageAggregate. */
181
+ export async function ensureStageTree(io, home, input) { return await ensureStaging(io, home, input); }
182
+ export async function createStageTree(io, home, input) { return await createStaging(io, home, input); }
183
+ export async function readStageTree(io, home, runId) { return await readStaging(io, home, runId); }
184
+ export async function setStageTreeTarget(io, home, stageKey, target, mode) { return await setStagingTarget(io, home, stageKey, target, mode); }
185
+ export async function dropStageTree(io, home, stageKey) { await dropStaging(io, home, stageKey); }
186
+ export async function writeStageIndex(io, home, entry) { await writeStagingIndex(io, home, entry); }
187
+ export async function readStageIndex(io, home, rowId, now = Date.now()) { return await readStagingIndex(io, home, rowId, now); }
188
+ export async function readStageEntry(io, home, rowId) {
189
+ const raw = await readTextOrNull(io, join(home, 'merge', 'staging-index', encodeURIComponent(rowId) + '.json'));
190
+ if (raw === null)
191
+ return null;
192
+ try {
193
+ const entry = JSON.parse(raw);
194
+ if (entry.rowId !== rowId || typeof entry.ref !== 'string' || typeof entry.expiresAt !== 'number')
195
+ return null;
196
+ return entry;
197
+ }
198
+ catch {
199
+ return null;
200
+ }
201
+ }
202
+ export async function reserveStage(io, home, entry) {
203
+ await writeStageIndex(io, home, { rowId: entry.rowId, pairKey: entry.pairKey, ref: entry.ref, expiresAt: entry.expiresAt, ...(entry.runId === undefined ? {} : { runId: entry.runId }), createdAt: entry.createdAt, state: 'reserved' });
204
+ }
205
+ export async function presentStage(io, home, entry) {
206
+ await writeStageIndex(io, home, { rowId: entry.rowId, pairKey: entry.pairKey, ref: entry.ref, expiresAt: entry.expiresAt, ...(entry.runId === undefined ? {} : { runId: entry.runId }), createdAt: entry.createdAt, state: 'present' });
207
+ }
208
+ export async function dropStage(io, home, rowId, ref) {
209
+ await io.remove(join(home, 'merge', 'staging-index', encodeURIComponent(rowId) + '.json')).catch(() => { });
210
+ if (ref !== undefined && ref !== '')
211
+ await io.remove(join(home, 'merge', 'runs', ref)).catch(() => { });
212
+ }
213
+ /** Bounded cleanup: reserved/present TTL, pending/verify stale index removal, terminal/absent rows, orphan trees. */
214
+ export async function cleanupStage(io, home, now, rows, limit = 100) {
215
+ const removed = [];
216
+ const expired = [];
217
+ const kept = [];
218
+ const orphans = [];
219
+ const refs = new Set();
220
+ const indexRoot = join(home, 'merge', 'staging-index');
221
+ const names = await io.exists(indexRoot) ? await listOrEmpty(io, indexRoot) : [];
222
+ for (const name of names.slice(0, limit)) {
223
+ if (!name.endsWith('.json'))
224
+ continue;
225
+ const raw = await readTextOrNull(io, join(indexRoot, name));
226
+ if (raw === null)
227
+ continue;
228
+ let entry;
229
+ try {
230
+ entry = JSON.parse(raw);
231
+ }
232
+ catch {
233
+ continue;
234
+ }
235
+ if (typeof entry.rowId !== 'string' || typeof entry.ref !== 'string' || typeof entry.expiresAt !== 'number')
236
+ continue;
237
+ if (entry.ref !== '')
238
+ refs.add(entry.ref);
239
+ const row = rows.get(entry.rowId);
240
+ const terminal = row === undefined || row.state === 'skipped';
241
+ if (terminal || row?.state === 'pending' || row?.state === 'verify') {
242
+ await dropStage(io, home, entry.rowId, entry.ref);
243
+ removed.push(entry.rowId);
244
+ continue;
245
+ }
246
+ if (entry.expiresAt <= now) {
247
+ if (entry.state === 'present') {
248
+ // The index is the durable record both the read face and the resume path consult:
249
+ // stamp it expired instead of leaving a present entry that reads as live elsewhere.
250
+ try {
251
+ await writeStagingIndex(io, home, { rowId: entry.rowId, pairKey: typeof entry.pairKey === 'string' ? entry.pairKey : '', ref: entry.ref, expiresAt: entry.expiresAt, createdAt: typeof entry.createdAt === 'number' ? entry.createdAt : now, state: 'expired' });
252
+ }
253
+ catch { /* the read face still derives expired */ }
254
+ expired.push(entry.rowId);
255
+ continue;
256
+ }
257
+ await dropStage(io, home, entry.rowId, entry.ref);
258
+ removed.push(entry.rowId);
259
+ continue;
260
+ }
261
+ kept.push(entry.rowId);
262
+ }
263
+ const runRoot = join(home, 'merge', 'runs');
264
+ const runNames = await io.exists(runRoot) ? await listOrEmpty(io, runRoot) : [];
265
+ for (const run of runNames.slice(0, limit)) {
266
+ const prefix = run + '/';
267
+ if ([...refs].some(ref => ref === run || ref.startsWith(prefix)))
268
+ continue;
269
+ await io.remove(join(runRoot, run)).catch(() => { });
270
+ orphans.push(run);
271
+ }
272
+ return { removed, expired, kept, orphans };
273
+ }
274
+ /** Read all production v2 stage entries. */
275
+ export async function readAllStages(io, home) {
276
+ const root = join(home, 'merge', 'staging-index');
277
+ const names = await io.exists(root) ? await listOrEmpty(io, root) : [];
278
+ const entries = [];
279
+ for (const name of names) {
280
+ const raw = await readTextOrNull(io, join(root, name));
281
+ if (raw === null)
282
+ continue;
283
+ try {
284
+ const entry = JSON.parse(raw);
285
+ if (typeof entry.rowId !== 'string' || typeof entry.ref !== 'string' || typeof entry.expiresAt !== 'number')
286
+ continue;
287
+ entries.push(entry);
288
+ }
289
+ catch { /* skip malformed index */ }
290
+ }
291
+ return entries.sort((a, b) => a.rowId.localeCompare(b.rowId));
292
+ }
293
+ //# sourceMappingURL=stage-aggregate.js.map
@@ -0,0 +1,15 @@
1
+ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
+ export interface StagingEntry {
3
+ readonly rowId: string;
4
+ readonly pairKey: string;
5
+ readonly ref: string;
6
+ readonly expiresAt: number;
7
+ readonly runId?: string;
8
+ readonly createdAt: number;
9
+ readonly state?: 'reserved' | 'present' | 'expired' | 'lost';
10
+ }
11
+ export declare function stagingIndexPath(home: string, rowId: string): string;
12
+ export declare function readStagingIndex(io: EvolutionIoLike, home: string, rowId: string, now?: number): Promise<StagingEntry | null>;
13
+ export declare function writeStagingIndex(io: EvolutionIoLike, home: string, entry: StagingEntry): Promise<void>;
14
+ export declare function deleteStagingIndex(io: EvolutionIoLike, home: string, rowId: string): Promise<void>;
15
+ //# sourceMappingURL=staging-index.d.ts.map
@@ -0,0 +1,41 @@
1
+ /** Durable staging index for v3 pending/approved rows (rowId-keyed). */
2
+ import { join } from 'node:path';
3
+ export function stagingIndexPath(home, rowId) {
4
+ return join(home, 'merge', 'staging-index', encodeURIComponent(rowId) + '.json');
5
+ }
6
+ export async function readStagingIndex(io, home, rowId, now = Date.now()) {
7
+ let raw;
8
+ try {
9
+ raw = await io.readText(stagingIndexPath(home, rowId));
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ if (raw === null)
15
+ return null;
16
+ try {
17
+ const entry = JSON.parse(raw);
18
+ if (entry.rowId !== rowId || typeof entry.ref !== 'string' || typeof entry.expiresAt !== 'number')
19
+ return null;
20
+ if (entry.expiresAt <= now)
21
+ return null;
22
+ return entry;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export async function writeStagingIndex(io, home, entry) {
29
+ const text = JSON.stringify(entry, null, 2) + '\n';
30
+ if (io.transact !== undefined)
31
+ await io.transact(stagingIndexPath(home, entry.rowId), () => text);
32
+ else
33
+ await io.writeText(stagingIndexPath(home, entry.rowId), text);
34
+ }
35
+ export async function deleteStagingIndex(io, home, rowId) {
36
+ try {
37
+ await io.remove(stagingIndexPath(home, rowId));
38
+ }
39
+ catch { /* missing/already removed */ }
40
+ }
41
+ //# sourceMappingURL=staging-index.js.map
@@ -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