ushman-ledger 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/AGENTS.md +7 -5
  2. package/ARCHITECTURE.md +85 -0
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +114 -5
  5. package/TROUBLESHOOTING.md +184 -0
  6. package/dist/blobs.d.ts +3 -0
  7. package/dist/blobs.d.ts.map +1 -1
  8. package/dist/blobs.js +41 -15
  9. package/dist/builders.d.ts +33 -0
  10. package/dist/builders.d.ts.map +1 -1
  11. package/dist/builders.js +10 -1
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +176 -59
  14. package/dist/coverage.d.ts.map +1 -1
  15. package/dist/coverage.js +3 -2
  16. package/dist/doctor.d.ts +17 -4
  17. package/dist/doctor.d.ts.map +1 -1
  18. package/dist/doctor.js +263 -62
  19. package/dist/handle.d.ts.map +1 -1
  20. package/dist/handle.js +67 -30
  21. package/dist/helpers.d.ts +1 -0
  22. package/dist/helpers.d.ts.map +1 -1
  23. package/dist/helpers.js +23 -0
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -2
  27. package/dist/list.d.ts +34 -1
  28. package/dist/list.d.ts.map +1 -1
  29. package/dist/list.js +19 -9
  30. package/dist/patch-resolver.d.ts.map +1 -1
  31. package/dist/patch-resolver.js +193 -53
  32. package/dist/process.d.ts +2 -0
  33. package/dist/process.d.ts.map +1 -0
  34. package/dist/process.js +16 -0
  35. package/dist/read-index.d.ts +7 -7
  36. package/dist/read-index.d.ts.map +1 -1
  37. package/dist/read-index.js +18 -13
  38. package/dist/record.js +2 -2
  39. package/dist/recovery.d.ts +8 -0
  40. package/dist/recovery.d.ts.map +1 -1
  41. package/dist/recovery.js +142 -30
  42. package/dist/render/retro.d.ts.map +1 -1
  43. package/dist/render/retro.js +4 -1
  44. package/dist/runtime-config.d.ts +14 -0
  45. package/dist/runtime-config.d.ts.map +1 -0
  46. package/dist/runtime-config.js +97 -0
  47. package/dist/schema/entry-core.d.ts +5 -2
  48. package/dist/schema/entry-core.d.ts.map +1 -1
  49. package/dist/schema/entry-core.js +3 -0
  50. package/dist/schema/entry-read.d.ts +57 -0
  51. package/dist/schema/entry-read.d.ts.map +1 -1
  52. package/dist/schema/entry-read.js +9 -1
  53. package/dist/schema/entry-write.d.ts +51 -0
  54. package/dist/schema/entry-write.d.ts.map +1 -1
  55. package/dist/schema/entry-write.js +9 -1
  56. package/dist/storage/filesystem.d.ts +15 -2
  57. package/dist/storage/filesystem.d.ts.map +1 -1
  58. package/dist/storage/filesystem.js +234 -37
  59. package/dist/storage/lock.d.ts.map +1 -1
  60. package/dist/storage/lock.js +38 -16
  61. package/dist/text-lines.d.ts +8 -0
  62. package/dist/text-lines.d.ts.map +1 -0
  63. package/dist/text-lines.js +20 -0
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.d.ts.map +1 -1
  66. package/dist/version.js +2 -1
  67. package/package.json +4 -2
@@ -2,11 +2,10 @@ import { readFile } from 'node:fs/promises';
2
2
  import * as v from 'valibot';
3
3
  import { mapWithConcurrencyLimit } from "./async.js";
4
4
  import { stableStringify } from "./json.js";
5
+ import { getLedgerRuntimeConfig } from "./runtime-config.js";
5
6
  import { LEDGER_KINDS, parseLedgerEntry } from "./schema/entry.js";
6
7
  import { readPhaseEntryText, resolveLedgerPaths, writeAtomicTextFile } from "./storage/filesystem.js";
7
8
  const READ_INDEX_SCHEMA_VERSION = 'ushman-ledger-read-index/v1';
8
- const ENTRY_READ_BATCH_SIZE = 32;
9
- const ENTRY_READ_CONCURRENCY = 16;
10
9
  const ReadIndexEntrySchema = v.object({
11
10
  id: v.pipe(v.string(), v.minLength(1)),
12
11
  kind: v.picklist(LEDGER_KINDS),
@@ -33,6 +32,15 @@ const buildReadIndexEntry = (entry) => ({
33
32
  kind: entry.kind,
34
33
  ts: entry.ts,
35
34
  });
35
+ const getCoverageFilesForEntry = (entry) => {
36
+ if (entry.kind === 'stage-write') {
37
+ return [...new Set([entry.filePath, ...(entry.links.affectedFiles ?? [])])];
38
+ }
39
+ if (entry.kind === 'agent-patch' || entry.kind === 'operator-patch') {
40
+ return entry.links.affectedFiles ?? [];
41
+ }
42
+ return [];
43
+ };
36
44
  const sortBySequence = (left, right) => left[1].sequence - right[1].sequence;
37
45
  const getManifestSequenceLocations = (manifest) => Object.entries(manifest.entryLocations).sort(sortBySequence);
38
46
  const mergeSortedUniquePaths = (existingPaths, additionalPaths) => {
@@ -83,27 +91,26 @@ const readIndexedEntry = async ({ entryId, phase, workspaceRoot, }) => {
83
91
  const text = await readPhaseEntryText(workspaceRoot, phase, `${entryId}.json`);
84
92
  return parseLedgerEntry(JSON.parse(text));
85
93
  };
86
- const readIndexedEntryBatch = async ({ entryLocations, workspaceRoot, }) => mapWithConcurrencyLimit(entryLocations, ENTRY_READ_CONCURRENCY, async ([entryId, location]) => readIndexedEntry({
94
+ const readIndexedEntryBatch = async ({ entryReadConcurrency, entryLocations, workspaceRoot, }) => mapWithConcurrencyLimit(entryLocations, entryReadConcurrency, async ([entryId, location]) => readIndexedEntry({
87
95
  entryId,
88
96
  phase: location.phase,
89
97
  workspaceRoot,
90
98
  }));
91
99
  const collectCoveredFiles = (entry, coveredFiles) => {
92
- if (entry.kind !== 'agent-patch' && entry.kind !== 'operator-patch') {
93
- return;
94
- }
95
- for (const affectedFile of entry.links.affectedFiles ?? []) {
96
- coveredFiles.add(affectedFile);
100
+ for (const coveredFile of getCoverageFilesForEntry(entry)) {
101
+ coveredFiles.add(coveredFile);
97
102
  }
98
103
  };
99
104
  export const buildReadIndexFromManifest = async (workspaceRoot, manifest) => {
105
+ const { readIndexRebuildBatchSize, readIndexRebuildConcurrency } = getLedgerRuntimeConfig();
100
106
  const orderedEntryLocations = getManifestSequenceLocations(manifest);
101
107
  const entries = [];
102
108
  const coveredFiles = new Set();
103
- for (let index = 0; index < orderedEntryLocations.length; index += ENTRY_READ_BATCH_SIZE) {
104
- const batch = orderedEntryLocations.slice(index, index + ENTRY_READ_BATCH_SIZE);
109
+ for (let index = 0; index < orderedEntryLocations.length; index += readIndexRebuildBatchSize) {
110
+ const batch = orderedEntryLocations.slice(index, index + readIndexRebuildBatchSize);
105
111
  const resolvedEntries = await readIndexedEntryBatch({
106
112
  entryLocations: batch,
113
+ entryReadConcurrency: readIndexRebuildConcurrency,
107
114
  workspaceRoot,
108
115
  });
109
116
  for (const entry of resolvedEntries) {
@@ -165,9 +172,7 @@ export const ensureReadIndexUnderLock = async (workspaceRoot, manifest) => {
165
172
  };
166
173
  export const appendEntryToReadIndex = ({ entry, readIndex, sequence, }) => {
167
174
  const nextEntries = [...readIndex.entries, buildReadIndexEntry(entry)];
168
- const nextCoveredFiles = entry.kind === 'agent-patch' || entry.kind === 'operator-patch'
169
- ? mergeSortedUniquePaths(readIndex.coveredFiles, entry.links.affectedFiles ?? [])
170
- : [...readIndex.coveredFiles];
175
+ const nextCoveredFiles = mergeSortedUniquePaths(readIndex.coveredFiles, getCoverageFilesForEntry(entry));
171
176
  return buildReadIndex({
172
177
  coveredFiles: nextCoveredFiles,
173
178
  entries: nextEntries,
package/dist/record.js CHANGED
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import * as v from 'valibot';
4
4
  import { sha256Hex, stableStringify } from "./json.js";
5
- import { updateManifestForEntry } from "./manifest-update.js";
5
+ import { getNextManifestSequence, updateManifestForEntry } from "./manifest-update.js";
6
6
  import { resolvePatchRecord } from "./patch-resolver.js";
7
7
  import { appendEntryToReadIndex, saveReadIndex } from "./read-index.js";
8
8
  import { reconcileLedgerStateUnderLock, removePendingCommit, writePendingCommit } from "./recovery.js";
@@ -105,7 +105,7 @@ export const appendRecord = async (workspaceRoot, input) => {
105
105
  id: existingId,
106
106
  };
107
107
  }
108
- const nextSequence = manifest.lastSequence + 1;
108
+ const nextSequence = getNextManifestSequence(manifest.lastSequence);
109
109
  const { idempotencyKey: _idempotencyKey, ...recordWithoutIdempotencyKey } = normalizedRecord;
110
110
  const entryWithoutId = {
111
111
  ...recordWithoutIdempotencyKey,
@@ -1,6 +1,13 @@
1
1
  import { type LedgerReadIndex } from './read-index.ts';
2
2
  import { type LedgerEntry } from './schema/entry.ts';
3
3
  import type { LedgerManifest } from './schema/manifest.ts';
4
+ export type PendingCommitQuarantine = {
5
+ readonly commitPath: string;
6
+ readonly metadataPath: string | null;
7
+ readonly originalFileName: string;
8
+ readonly quarantinedAt: string | null;
9
+ readonly reason: string;
10
+ };
4
11
  export type PreparedLedgerState = {
5
12
  readonly manifest: LedgerManifest;
6
13
  readonly readIndex: LedgerReadIndex;
@@ -12,6 +19,7 @@ export declare const writePendingCommit: ({ entry, logicalHash, manifest, worksp
12
19
  readonly workspaceRoot: string;
13
20
  }) => Promise<string>;
14
21
  export declare const removePendingCommit: (filePath: string) => Promise<void>;
22
+ export declare const listPendingCommitQuarantines: (workspaceRoot: string) => Promise<PendingCommitQuarantine[]>;
15
23
  export declare const reconcilePendingCommitsUnderLock: (workspaceRoot: string) => Promise<{
16
24
  archives: {
17
25
  createdAt: string;
@@ -1 +1 @@
1
- {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAKA,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA0B3D,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACvC,CAAC;AA0JF,eAAO,MAAM,kBAAkB,GAAU,kDAKtC;IACC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,oBAeA,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,UAAU,MAAM,kBAEzD,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAAU,eAAe,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB3E,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,mBAAmB,CAQtG,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,mBAAmB,CAQxF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,eAAe,MAAM,kBAE7D,CAAC"}
1
+ {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAOA,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEjF,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA4C3D,MAAM,MAAM,uBAAuB,GAAG;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACvC,CAAC;AAoNF,eAAO,MAAM,kBAAkB,GAAU,kDAKtC;IACC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,oBAeA,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,UAAU,MAAM,kBAEzD,CAAC;AAaF,eAAO,MAAM,4BAA4B,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,uBAAuB,EAAE,CAsB3G,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAAU,eAAe,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuD3E,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,mBAAmB,CAQtG,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,mBAAmB,CAQxF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,eAAe,MAAM,kBAE7D,CAAC"}
package/dist/recovery.js CHANGED
@@ -1,13 +1,17 @@
1
- import { readdir, readFile, rm, stat } from 'node:fs/promises';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readdir, readFile, rename, rm, stat } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import * as v from 'valibot';
5
+ import { mapWithConcurrencyLimit } from "./async.js";
4
6
  import { reconcilePendingArchivesUnderLock } from "./archive-journal.js";
5
7
  import { getNextManifestSequence, updateManifestForEntry } from "./manifest-update.js";
6
8
  import { ensureReadIndexUnderLock } from "./read-index.js";
9
+ import { getLedgerRuntimeConfig } from "./runtime-config.js";
7
10
  import { LedgerEntrySchema } from "./schema/entry.js";
8
11
  import { cleanupStaleTempFiles, ensureLedgerDirectories, readManifest, resolveLedgerPaths, saveManifest, writeAtomicJsonFile, writeEntryFile, } from "./storage/filesystem.js";
9
12
  import { acquireLock } from "./storage/lock.js";
10
13
  const PendingCommitSchemaVersion = 'ushman-ledger-pending-commit/v1';
14
+ const PendingCommitQuarantineSchemaVersion = 'ushman-ledger-pending-commit-quarantine/v1';
11
15
  // Pending commits capture the intended sequence and logical hash before the
12
16
  // entry file and manifest update diverge, so recovery can replay safely.
13
17
  const PendingCommitSchema = v.object({
@@ -18,6 +22,12 @@ const PendingCommitSchema = v.object({
18
22
  schemaVersion: v.literal(PendingCommitSchemaVersion),
19
23
  sequence: v.pipe(v.number(), v.integer(), v.minValue(1)),
20
24
  });
25
+ const PendingCommitQuarantineMetadataSchema = v.object({
26
+ originalFileName: v.pipe(v.string(), v.minLength(1)),
27
+ quarantinedAt: v.pipe(v.string(), v.isoTimestamp()),
28
+ reason: v.pipe(v.string(), v.minLength(1)),
29
+ schemaVersion: v.literal(PendingCommitQuarantineSchemaVersion),
30
+ });
21
31
  const formatSequence = (sequence) => sequence.toString().padStart(8, '0');
22
32
  const PendingCommitFileNameSchema = v.object({
23
33
  entryId: v.pipe(v.string(), v.minLength(1)),
@@ -29,10 +39,15 @@ const parsePendingCommitFileName = (name) => {
29
39
  if (!match) {
30
40
  return null;
31
41
  }
32
- return v.parse(PendingCommitFileNameSchema, {
33
- entryId: match[2],
34
- sequence: Number.parseInt(match[1], 10),
35
- });
42
+ try {
43
+ return v.parse(PendingCommitFileNameSchema, {
44
+ entryId: match[2],
45
+ sequence: Number.parseInt(match[1], 10),
46
+ });
47
+ }
48
+ catch {
49
+ return null;
50
+ }
36
51
  };
37
52
  const readPendingCommit = async (filePath) => {
38
53
  try {
@@ -43,32 +58,68 @@ const readPendingCommit = async (filePath) => {
43
58
  throw new Error(`Invalid pending commit at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
44
59
  }
45
60
  };
46
- const readPendingCommits = async (workspaceRoot) => {
61
+ const listPendingCommitFiles = async (workspaceRoot) => {
47
62
  const paths = await ensureLedgerDirectories(workspaceRoot);
48
63
  const entries = await readdir(paths.pendingCommitsDir, { withFileTypes: true });
49
- const pendingFiles = entries
50
- .filter((entry) => entry.isFile())
51
- .map((entry) => {
52
- if (!entry.name.endsWith('.json')) {
53
- return null;
64
+ const listing = {
65
+ invalidFiles: [],
66
+ pendingFiles: [],
67
+ };
68
+ for (const entry of entries) {
69
+ if (!entry.isFile() || !entry.name.endsWith('.json')) {
70
+ continue;
54
71
  }
72
+ const filePath = path.join(paths.pendingCommitsDir, entry.name);
55
73
  const parsed = parsePendingCommitFileName(entry.name);
56
74
  if (!parsed) {
57
- return null;
75
+ listing.invalidFiles.push({
76
+ filePath,
77
+ reason: `Invalid pending commit file name: ${entry.name}`,
78
+ });
79
+ continue;
58
80
  }
59
- return {
81
+ listing.pendingFiles.push({
60
82
  ...parsed,
61
- filePath: path.join(paths.pendingCommitsDir, entry.name),
62
- };
63
- })
64
- .filter((pendingFile) => pendingFile !== null)
65
- .sort((left, right) => left.sequence === right.sequence
66
- ? left.entryId.localeCompare(right.entryId)
67
- : left.sequence - right.sequence);
68
- return Promise.all(pendingFiles.map(async (pendingFile) => ({
69
- filePath: pendingFile.filePath,
70
- pending: await readPendingCommit(pendingFile.filePath),
71
- })));
83
+ filePath,
84
+ });
85
+ }
86
+ listing.pendingFiles.sort((left, right) => left.sequence === right.sequence ? left.entryId.localeCompare(right.entryId) : left.sequence - right.sequence);
87
+ return listing;
88
+ };
89
+ const formatRecoveryError = (error) => (error instanceof Error ? error.message : String(error));
90
+ const writeQuarantineMetadata = async ({ metadataPath, originalFileName, reason, }) => {
91
+ try {
92
+ await writeAtomicJsonFile(metadataPath, v.parse(PendingCommitQuarantineMetadataSchema, {
93
+ originalFileName,
94
+ quarantinedAt: new Date().toISOString(),
95
+ reason,
96
+ schemaVersion: PendingCommitQuarantineSchemaVersion,
97
+ }));
98
+ }
99
+ catch {
100
+ // The journal is already quarantined; metadata loss should not block recovery.
101
+ }
102
+ };
103
+ const quarantinePendingCommit = async ({ filePath, reason, workspaceRoot, }) => {
104
+ const paths = await ensureLedgerDirectories(workspaceRoot);
105
+ const originalFileName = path.basename(filePath);
106
+ const quarantineBaseName = `${path.basename(filePath, '.json')}.quarantined.${Date.now()}.${randomUUID()}`;
107
+ const quarantinePath = path.join(paths.pendingCommitQuarantineDir, `${quarantineBaseName}.json`);
108
+ const metadataPath = path.join(paths.pendingCommitQuarantineDir, `${quarantineBaseName}.meta.json`);
109
+ try {
110
+ await rename(filePath, quarantinePath);
111
+ }
112
+ catch (error) {
113
+ if (error.code === 'ENOENT') {
114
+ return;
115
+ }
116
+ throw error;
117
+ }
118
+ await writeQuarantineMetadata({
119
+ metadataPath,
120
+ originalFileName,
121
+ reason,
122
+ });
72
123
  };
73
124
  const ensurePendingCommitEntryFile = async (workspaceRoot, pending) => {
74
125
  const entryPath = path.join(resolveLedgerPaths(workspaceRoot).phaseDir(pending.entry.phase), `${pending.entry.id}.json`);
@@ -141,21 +192,82 @@ export const writePendingCommit = async ({ entry, logicalHash, manifest, workspa
141
192
  export const removePendingCommit = async (filePath) => {
142
193
  await rm(filePath, { force: true });
143
194
  };
195
+ const readQuarantineMetadata = async (metadataPath) => {
196
+ try {
197
+ const text = await readFile(metadataPath, 'utf8');
198
+ return v.parse(PendingCommitQuarantineMetadataSchema, JSON.parse(text));
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ };
204
+ export const listPendingCommitQuarantines = async (workspaceRoot) => {
205
+ const paths = await ensureLedgerDirectories(workspaceRoot);
206
+ const { scanConcurrency } = getLedgerRuntimeConfig();
207
+ const entries = await readdir(paths.pendingCommitQuarantineDir, { withFileTypes: true });
208
+ const quarantinedCommitFiles = entries
209
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json') && !entry.name.endsWith('.meta.json'))
210
+ .map((entry) => entry.name)
211
+ .sort((left, right) => left.localeCompare(right));
212
+ return mapWithConcurrencyLimit(quarantinedCommitFiles, scanConcurrency, async (fileName) => {
213
+ const baseName = fileName.slice(0, -'.json'.length);
214
+ const commitPath = path.join(paths.pendingCommitQuarantineDir, fileName);
215
+ const metadataPath = path.join(paths.pendingCommitQuarantineDir, `${baseName}.meta.json`);
216
+ const metadata = await readQuarantineMetadata(metadataPath);
217
+ return {
218
+ commitPath,
219
+ metadataPath: metadata ? metadataPath : null,
220
+ originalFileName: metadata?.originalFileName ?? fileName,
221
+ quarantinedAt: metadata?.quarantinedAt ?? null,
222
+ reason: metadata?.reason ?? 'Missing or invalid pending commit quarantine metadata.',
223
+ };
224
+ });
225
+ };
144
226
  export const reconcilePendingCommitsUnderLock = async (workspaceRoot) => {
145
227
  await cleanupStaleTempFiles(workspaceRoot);
146
228
  let manifest = await readManifest(workspaceRoot);
147
- const pendingCommits = await readPendingCommits(workspaceRoot);
229
+ const { invalidFiles, pendingFiles } = await listPendingCommitFiles(workspaceRoot);
148
230
  const processedPendingFiles = [];
149
231
  let manifestChanged = false;
150
- for (const pendingCommit of pendingCommits) {
151
- const replayed = await applyPendingCommit({
152
- manifest,
153
- pending: pendingCommit.pending,
232
+ for (const invalidFile of invalidFiles) {
233
+ await quarantinePendingCommit({
234
+ filePath: invalidFile.filePath,
235
+ reason: invalidFile.reason,
154
236
  workspaceRoot,
155
237
  });
238
+ }
239
+ for (const pendingCommitFile of pendingFiles) {
240
+ let pending;
241
+ try {
242
+ pending = await readPendingCommit(pendingCommitFile.filePath);
243
+ }
244
+ catch (error) {
245
+ await quarantinePendingCommit({
246
+ filePath: pendingCommitFile.filePath,
247
+ reason: formatRecoveryError(error),
248
+ workspaceRoot,
249
+ });
250
+ continue;
251
+ }
252
+ let replayed;
253
+ try {
254
+ replayed = await applyPendingCommit({
255
+ manifest,
256
+ pending,
257
+ workspaceRoot,
258
+ });
259
+ }
260
+ catch (error) {
261
+ await quarantinePendingCommit({
262
+ filePath: pendingCommitFile.filePath,
263
+ reason: formatRecoveryError(error),
264
+ workspaceRoot,
265
+ });
266
+ continue;
267
+ }
156
268
  manifest = replayed.manifest;
157
269
  manifestChanged = manifestChanged || replayed.didChangeManifest;
158
- processedPendingFiles.push(pendingCommit.filePath);
270
+ processedPendingFiles.push(pendingCommitFile.filePath);
159
271
  }
160
272
  if (manifestChanged) {
161
273
  await saveManifest(workspaceRoot, manifest);
@@ -1 +1 @@
1
- {"version":3,"file":"retro.d.ts","sourceRoot":"","sources":["../../src/render/retro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA4HtD,eAAO,MAAM,mBAAmB,GAAU,wBAGvC;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC9B,KAAG,OAAO,CAAC,MAAM,CAgDjB,CAAC"}
1
+ {"version":3,"file":"retro.d.ts","sourceRoot":"","sources":["../../src/render/retro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA+HtD,eAAO,MAAM,mBAAmB,GAAU,wBAGvC;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC9B,KAAG,OAAO,CAAC,MAAM,CAgDjB,CAAC"}
@@ -14,7 +14,10 @@ const createRetroBuckets = () => ({
14
14
  });
15
15
  const isProblemEntry = (entry) => (entry.kind === 'note' && entry.subkind === 'regression') ||
16
16
  (entry.kind === 'runtime-event' && entry.level === 'error');
17
- const isToolEntry = (entry) => entry.kind === 'tool-invocation' || entry.kind === 'agent-patch' || entry.kind === 'operator-patch';
17
+ const isToolEntry = (entry) => entry.kind === 'tool-invocation' ||
18
+ entry.kind === 'stage-write' ||
19
+ entry.kind === 'agent-patch' ||
20
+ entry.kind === 'operator-patch';
18
21
  const isToolingEntry = (entry) => (entry.kind === 'note' && entry.subkind === 'automation') ||
19
22
  (entry.kind === 'note' && entry.subkind === 'tooling-gap');
20
23
  const isRetroNote = (entry) => entry.kind === 'note' && entry.subkind === 'retro';
@@ -0,0 +1,14 @@
1
+ export type LedgerRuntimeConfig = {
2
+ readonly blobHashConcurrency: number;
3
+ readonly coverageFileStatConcurrency: number;
4
+ readonly doctorCheckpointMaxAgeMs: number;
5
+ readonly doctorOpenIssueMaxAgeMs: number;
6
+ readonly maxPatchBytes: number;
7
+ readonly readIndexRebuildBatchSize: number;
8
+ readonly readIndexRebuildConcurrency: number;
9
+ readonly scanBatchSize: number;
10
+ readonly scanConcurrency: number;
11
+ };
12
+ export declare const validateLedgerRuntimeConfig: (env?: NodeJS.ProcessEnv) => void;
13
+ export declare const getLedgerRuntimeConfig: (env?: NodeJS.ProcessEnv) => LedgerRuntimeConfig;
14
+ //# sourceMappingURL=runtime-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-config.d.ts","sourceRoot":"","sources":["../src/runtime-config.ts"],"names":[],"mappings":"AAoCA,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACpC,CAAC;AAiEF,eAAO,MAAM,2BAA2B,GAAI,MAAK,MAAM,CAAC,UAAwB,SAE/E,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,mBAgB7E,CAAC"}
@@ -0,0 +1,97 @@
1
+ const DEFAULT_SCAN_BATCH_SIZE = 32;
2
+ const DEFAULT_SCAN_CONCURRENCY = 16;
3
+ const DEFAULT_MAX_PATCH_BYTES = 10 * 1024 * 1024;
4
+ const DEFAULT_DOCTOR_CHECKPOINT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
5
+ const DEFAULT_DOCTOR_OPEN_ISSUE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
6
+ const RUNTIME_CONFIG_ENV_VARS = [
7
+ 'USHMAN_LEDGER_BLOB_HASH_CONCURRENCY',
8
+ 'USHMAN_LEDGER_COVERAGE_FILE_STAT_CONCURRENCY',
9
+ 'USHMAN_LEDGER_DOCTOR_CHECKPOINT_MAX_AGE_MS',
10
+ 'USHMAN_LEDGER_DOCTOR_OPEN_ISSUE_MAX_AGE_MS',
11
+ 'USHMAN_LEDGER_MAX_PATCH_BYTES',
12
+ 'USHMAN_LEDGER_READ_INDEX_REBUILD_BATCH_SIZE',
13
+ 'USHMAN_LEDGER_READ_INDEX_REBUILD_CONCURRENCY',
14
+ 'USHMAN_LEDGER_SCAN_BATCH_SIZE',
15
+ 'USHMAN_LEDGER_SCAN_CONCURRENCY',
16
+ ];
17
+ const parsePositiveIntegerEnv = ({ defaultValue, env, envVar, }) => {
18
+ const rawValue = env[envVar];
19
+ if (!rawValue) {
20
+ return defaultValue;
21
+ }
22
+ if (!/^[1-9]\d*$/u.test(rawValue)) {
23
+ throw new Error(`Invalid ${envVar} value: ${rawValue}. Expected a positive integer.`);
24
+ }
25
+ return Number.parseInt(rawValue, 10);
26
+ };
27
+ let processEnvRuntimeConfigCache;
28
+ const getRuntimeConfigSignature = (env) => RUNTIME_CONFIG_ENV_VARS.map((envVar) => `${envVar}=${env[envVar] ?? ''}`).join('\u0000');
29
+ const buildLedgerRuntimeConfig = (env) => {
30
+ const scanBatchSize = parsePositiveIntegerEnv({
31
+ defaultValue: DEFAULT_SCAN_BATCH_SIZE,
32
+ env,
33
+ envVar: 'USHMAN_LEDGER_SCAN_BATCH_SIZE',
34
+ });
35
+ const scanConcurrency = parsePositiveIntegerEnv({
36
+ defaultValue: DEFAULT_SCAN_CONCURRENCY,
37
+ env,
38
+ envVar: 'USHMAN_LEDGER_SCAN_CONCURRENCY',
39
+ });
40
+ return {
41
+ blobHashConcurrency: parsePositiveIntegerEnv({
42
+ defaultValue: scanConcurrency,
43
+ env,
44
+ envVar: 'USHMAN_LEDGER_BLOB_HASH_CONCURRENCY',
45
+ }),
46
+ coverageFileStatConcurrency: parsePositiveIntegerEnv({
47
+ defaultValue: scanConcurrency,
48
+ env,
49
+ envVar: 'USHMAN_LEDGER_COVERAGE_FILE_STAT_CONCURRENCY',
50
+ }),
51
+ doctorCheckpointMaxAgeMs: parsePositiveIntegerEnv({
52
+ defaultValue: DEFAULT_DOCTOR_CHECKPOINT_MAX_AGE_MS,
53
+ env,
54
+ envVar: 'USHMAN_LEDGER_DOCTOR_CHECKPOINT_MAX_AGE_MS',
55
+ }),
56
+ doctorOpenIssueMaxAgeMs: parsePositiveIntegerEnv({
57
+ defaultValue: DEFAULT_DOCTOR_OPEN_ISSUE_MAX_AGE_MS,
58
+ env,
59
+ envVar: 'USHMAN_LEDGER_DOCTOR_OPEN_ISSUE_MAX_AGE_MS',
60
+ }),
61
+ maxPatchBytes: parsePositiveIntegerEnv({
62
+ defaultValue: DEFAULT_MAX_PATCH_BYTES,
63
+ env,
64
+ envVar: 'USHMAN_LEDGER_MAX_PATCH_BYTES',
65
+ }),
66
+ readIndexRebuildBatchSize: parsePositiveIntegerEnv({
67
+ defaultValue: scanBatchSize,
68
+ env,
69
+ envVar: 'USHMAN_LEDGER_READ_INDEX_REBUILD_BATCH_SIZE',
70
+ }),
71
+ readIndexRebuildConcurrency: parsePositiveIntegerEnv({
72
+ defaultValue: scanConcurrency,
73
+ env,
74
+ envVar: 'USHMAN_LEDGER_READ_INDEX_REBUILD_CONCURRENCY',
75
+ }),
76
+ scanBatchSize,
77
+ scanConcurrency,
78
+ };
79
+ };
80
+ export const validateLedgerRuntimeConfig = (env = process.env) => {
81
+ void getLedgerRuntimeConfig(env);
82
+ };
83
+ export const getLedgerRuntimeConfig = (env = process.env) => {
84
+ if (env !== process.env) {
85
+ return buildLedgerRuntimeConfig(env);
86
+ }
87
+ const signature = getRuntimeConfigSignature(env);
88
+ if (processEnvRuntimeConfigCache?.signature === signature) {
89
+ return processEnvRuntimeConfigCache.config;
90
+ }
91
+ const config = buildLedgerRuntimeConfig(env);
92
+ processEnvRuntimeConfigCache = {
93
+ config,
94
+ signature,
95
+ };
96
+ return config;
97
+ };
@@ -1,12 +1,14 @@
1
1
  import * as v from 'valibot';
2
2
  export declare const LedgerSchemaVersion: "ushman-ledger-entry/v1";
3
3
  export declare const LEDGER_PHASES: readonly ["capture", "intake", "seed", "vendor-extract", "cleanup", "parity", "characterize", "equiv", "analyze", "recover", "ship", "migration"];
4
- export declare const LEDGER_KINDS: readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"];
4
+ export declare const LEDGER_KINDS: readonly ["tool-invocation", "stage-write", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"];
5
+ export declare const STAGE_WRITE_STAGES: readonly ["intake", "seed", "vendor-extract", "cleanup", "candidate-promotion"];
5
6
  export declare const OPERATOR_DECISION_ACTIONS: readonly ["bypass-doctor", "skip-check", "override-strip-decision", "override-ship-state", "manual-parity-assertion", "ledger-hand-edit", "escalation"];
6
7
  export declare const NonEmptyTrimmedStringSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
7
8
  export declare const WorkspaceRelativePathSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.CheckAction<string, "Expected a normalized workspace-relative path.">]>;
8
9
  export declare const LedgerPhaseSchema: v.PicklistSchema<readonly ["capture", "intake", "seed", "vendor-extract", "cleanup", "parity", "characterize", "equiv", "analyze", "recover", "ship", "migration"], undefined>;
9
- export declare const LedgerKindSchema: v.PicklistSchema<readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"], undefined>;
10
+ export declare const LedgerKindSchema: v.PicklistSchema<readonly ["tool-invocation", "stage-write", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"], undefined>;
11
+ export declare const StageWriteStageSchema: v.PicklistSchema<readonly ["intake", "seed", "vendor-extract", "cleanup", "candidate-promotion"], undefined>;
10
12
  export declare const OperatorDecisionActionSchema: v.PicklistSchema<readonly ["bypass-doctor", "skip-check", "override-strip-decision", "override-ship-state", "manual-parity-assertion", "ledger-hand-edit", "escalation"], undefined>;
11
13
  export declare const ChangeLogSubkindSchema: v.PicklistSchema<readonly ["pre-change-checkpoint", "semantic-cleanup", "vendor-extract", "decomposition", "rollback", "hotfix", "smoke"], undefined>;
12
14
  export declare const ChangeLogSmokeResultSchema: v.PicklistSchema<readonly ["pass", "fail", "partial", "not-run"], undefined>;
@@ -105,6 +107,7 @@ export type LedgerLinks = v.InferOutput<typeof LedgerLinksSchema>;
105
107
  export type LedgerPhase = v.InferOutput<typeof LedgerPhaseSchema>;
106
108
  export type OperatorDecisionAction = v.InferOutput<typeof OperatorDecisionActionSchema>;
107
109
  export type OperatorDecisionPayload = v.InferOutput<typeof OperatorDecisionPayloadSchema>;
110
+ export type StageWriteStage = v.InferOutput<typeof StageWriteStageSchema>;
108
111
  export type StripDecisionRevertedPayload = v.InferOutput<typeof StripDecisionRevertedPayloadSchema>;
109
112
  export type WorkspaceRelativePath = v.InferOutput<typeof WorkspaceRelativePathSchema>;
110
113
  //# sourceMappingURL=entry-core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entry-core.d.ts","sourceRoot":"","sources":["../../src/schema/entry-core.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAE7B,eAAO,MAAM,mBAAmB,EAAG,wBAAiC,CAAC;AAErE,eAAO,MAAM,aAAa,mJAahB,CAAC;AAEX,eAAO,MAAM,YAAY,wLAWf,CAAC;AAEX,eAAO,MAAM,yBAAyB,yJAQ5B,CAAC;AAiDX,eAAO,MAAM,2BAA2B,+GAA+C,CAAC;AAExF,eAAO,MAAM,2BAA2B,0KAIvC,CAAC;AAIF,eAAO,MAAM,iBAAiB,gLAA4B,CAAC;AAC3D,eAAO,MAAM,gBAAgB,qNAA2B,CAAC;AACzD,eAAO,MAAM,4BAA4B,sLAAwC,CAAC;AAElF,eAAO,MAAM,sBAAsB,uJAAkC,CAAC;AACtE,eAAO,MAAM,0BAA0B,8EAAuC,CAAC;AAC/E,eAAO,MAAM,2BAA2B,6EAAyC,CAAC;AAElF,eAAO,MAAM,aAAa;;;;aAIxB,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;8BAa7B,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;CAUlC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;CAOlC,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;aAK/B,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;aAIxC,CAAC;AAEH,eAAO,MAAM,kCAAkC;;;;aAI7C,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;aAIpC,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC;AACtF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACpF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC5E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,4BAA4B,CAAC,CAAC;AACxF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAC1F,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,kCAAkC,CAAC,CAAC;AACpG,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
1
+ {"version":3,"file":"entry-core.d.ts","sourceRoot":"","sources":["../../src/schema/entry-core.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAE7B,eAAO,MAAM,mBAAmB,EAAG,wBAAiC,CAAC;AAErE,eAAO,MAAM,aAAa,mJAahB,CAAC;AAEX,eAAO,MAAM,YAAY,uMAYf,CAAC;AAEX,eAAO,MAAM,kBAAkB,iFAAkF,CAAC;AAElH,eAAO,MAAM,yBAAyB,yJAQ5B,CAAC;AAiDX,eAAO,MAAM,2BAA2B,+GAA+C,CAAC;AAExF,eAAO,MAAM,2BAA2B,0KAIvC,CAAC;AAIF,eAAO,MAAM,iBAAiB,gLAA4B,CAAC;AAC3D,eAAO,MAAM,gBAAgB,oOAA2B,CAAC;AACzD,eAAO,MAAM,qBAAqB,8GAAiC,CAAC;AACpE,eAAO,MAAM,4BAA4B,sLAAwC,CAAC;AAElF,eAAO,MAAM,sBAAsB,uJAAkC,CAAC;AACtE,eAAO,MAAM,0BAA0B,8EAAuC,CAAC;AAC/E,eAAO,MAAM,2BAA2B,6EAAyC,CAAC;AAElF,eAAO,MAAM,aAAa;;;;aAIxB,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;8BAa7B,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;CAUlC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;CAOlC,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;aAK/B,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;aAIxC,CAAC;AAEH,eAAO,MAAM,kCAAkC;;;;aAI7C,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;aAIpC,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC;AACtF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACpF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC5E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,4BAA4B,CAAC,CAAC;AACxF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAC1F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,kCAAkC,CAAC,CAAC;AACpG,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -17,6 +17,7 @@ export const LEDGER_PHASES = [
17
17
  ];
18
18
  export const LEDGER_KINDS = [
19
19
  'tool-invocation',
20
+ 'stage-write',
20
21
  'agent-patch',
21
22
  'operator-patch',
22
23
  'operator-decision',
@@ -27,6 +28,7 @@ export const LEDGER_KINDS = [
27
28
  'strip-decision-reverted',
28
29
  'change-log',
29
30
  ];
31
+ export const STAGE_WRITE_STAGES = ['intake', 'seed', 'vendor-extract', 'cleanup', 'candidate-promotion'];
30
32
  export const OPERATOR_DECISION_ACTIONS = [
31
33
  'bypass-doctor',
32
34
  'skip-check',
@@ -80,6 +82,7 @@ export const WorkspaceRelativePathSchema = v.pipe(v.string(), v.minLength(1), v.
80
82
  const SHA256HexSchema = v.pipe(v.string(), v.regex(SHA256_HEX_PATTERN, 'Expected a lowercase SHA-256 hex digest.'));
81
83
  export const LedgerPhaseSchema = v.picklist(LEDGER_PHASES);
82
84
  export const LedgerKindSchema = v.picklist(LEDGER_KINDS);
85
+ export const StageWriteStageSchema = v.picklist(STAGE_WRITE_STAGES);
83
86
  export const OperatorDecisionActionSchema = v.picklist(OPERATOR_DECISION_ACTIONS);
84
87
  const StripDecisionInvalidatedStageSchema = v.picklist(STRIP_DECISION_INVALIDATED_STAGES);
85
88
  export const ChangeLogSubkindSchema = v.picklist(CHANGE_LOG_SUBKINDS);
@@ -28,6 +28,35 @@ export declare const ToolInvocationEntrySchema: v.ObjectSchema<{
28
28
  readonly summary: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
29
29
  readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
30
30
  }, undefined>;
31
+ export declare const StageWriteEntrySchema: v.ObjectSchema<{
32
+ readonly filePath: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.CheckAction<string, "Expected a normalized workspace-relative path.">]>;
33
+ readonly kind: v.LiteralSchema<"stage-write", undefined>;
34
+ readonly rationale: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
35
+ readonly stage: v.PicklistSchema<readonly ["intake", "seed", "vendor-extract", "cleanup", "candidate-promotion"], undefined>;
36
+ readonly details: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
37
+ readonly emitter: v.ObjectSchema<{
38
+ readonly tool: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
39
+ readonly user: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>, undefined>;
40
+ readonly version: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
41
+ }, undefined>;
42
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
43
+ readonly links: v.OptionalSchema<v.ObjectWithRestSchema<{
44
+ readonly affectedFiles: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.CheckAction<string, "Expected a normalized workspace-relative path.">]>, undefined>, undefined>;
45
+ readonly blobs: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
46
+ readonly briefId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
47
+ readonly correctsLedgerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
48
+ readonly gitRef: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
49
+ readonly idempotencyKey: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
50
+ readonly stripDecisionId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
51
+ readonly supersedesLedgerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
52
+ readonly validatorVerdictId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
53
+ }, v.UnknownSchema, undefined>, {}>;
54
+ readonly phase: v.PicklistSchema<readonly ["capture", "intake", "seed", "vendor-extract", "cleanup", "parity", "characterize", "equiv", "analyze", "recover", "ship", "migration"], undefined>;
55
+ readonly prevEntryId: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>, undefined>;
56
+ readonly schemaVersion: v.LiteralSchema<"ushman-ledger-entry/v1", undefined>;
57
+ readonly summary: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
58
+ readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
59
+ }, undefined>;
31
60
  export declare const AgentPatchEntrySchema: v.ObjectSchema<{
32
61
  readonly agent: v.ObjectSchema<{
33
62
  readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
@@ -375,6 +404,34 @@ export declare const LedgerEntrySchema: v.VariantSchema<"kind", [v.ObjectSchema<
375
404
  readonly schemaVersion: v.LiteralSchema<"ushman-ledger-entry/v1", undefined>;
376
405
  readonly summary: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
377
406
  readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
407
+ }, undefined>, v.ObjectSchema<{
408
+ readonly filePath: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.CheckAction<string, "Expected a normalized workspace-relative path.">]>;
409
+ readonly kind: v.LiteralSchema<"stage-write", undefined>;
410
+ readonly rationale: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
411
+ readonly stage: v.PicklistSchema<readonly ["intake", "seed", "vendor-extract", "cleanup", "candidate-promotion"], undefined>;
412
+ readonly details: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
413
+ readonly emitter: v.ObjectSchema<{
414
+ readonly tool: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
415
+ readonly user: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>, undefined>;
416
+ readonly version: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
417
+ }, undefined>;
418
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
419
+ readonly links: v.OptionalSchema<v.ObjectWithRestSchema<{
420
+ readonly affectedFiles: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.CheckAction<string, "Expected a normalized workspace-relative path.">]>, undefined>, undefined>;
421
+ readonly blobs: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
422
+ readonly briefId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
423
+ readonly correctsLedgerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
424
+ readonly gitRef: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
425
+ readonly idempotencyKey: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
426
+ readonly stripDecisionId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
427
+ readonly supersedesLedgerId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
428
+ readonly validatorVerdictId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
429
+ }, v.UnknownSchema, undefined>, {}>;
430
+ readonly phase: v.PicklistSchema<readonly ["capture", "intake", "seed", "vendor-extract", "cleanup", "parity", "characterize", "equiv", "analyze", "recover", "ship", "migration"], undefined>;
431
+ readonly prevEntryId: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>, undefined>;
432
+ readonly schemaVersion: v.LiteralSchema<"ushman-ledger-entry/v1", undefined>;
433
+ readonly summary: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
434
+ readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
378
435
  }, undefined>, v.ObjectSchema<{
379
436
  readonly agent: v.ObjectSchema<{
380
437
  readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>]>;
@@ -1 +1 @@
1
- {"version":3,"file":"entry-read.d.ts","sourceRoot":"","sources":["../../src/schema/entry-read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAc7B,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMpC,CAAC;AAQH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOhC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMnC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAItC,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOrC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOlC,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;aAK1B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yDAOjC,CAAC;AAEF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAI3C,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAY/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAW5B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"entry-read.d.ts","sourceRoot":"","sources":["../../src/schema/entry-read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAgB7B,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMpC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMhC,CAAC;AAQH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOhC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAMnC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAItC,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOrC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAOlC,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;aAK1B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yDAOjC,CAAC;AAEF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAI3C,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAY/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAY5B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC"}