ushman-ledger 0.3.0 → 1.1.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 (44) hide show
  1. package/CHANGELOG.md +4 -14
  2. package/README.md +8 -56
  3. package/dist/archive-journal.d.ts +29 -18
  4. package/dist/archive-journal.d.ts.map +1 -1
  5. package/dist/archive-journal.js +17 -17
  6. package/dist/builders.d.ts +52 -374
  7. package/dist/builders.d.ts.map +1 -1
  8. package/dist/builders.js +10 -60
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +8 -14
  11. package/dist/handle.d.ts +2 -2
  12. package/dist/handle.d.ts.map +1 -1
  13. package/dist/handle.js +1 -14
  14. package/dist/index.d.ts +3 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -4
  17. package/dist/lab-min.d.ts +7 -7
  18. package/dist/lab-min.d.ts.map +1 -1
  19. package/dist/lab-min.js +7 -9
  20. package/dist/list.d.ts +84 -325
  21. package/dist/list.d.ts.map +1 -1
  22. package/dist/read-index.d.ts +45 -57
  23. package/dist/read-index.d.ts.map +1 -1
  24. package/dist/read-index.js +16 -34
  25. package/dist/record.d.ts.map +1 -1
  26. package/dist/record.js +27 -114
  27. package/dist/recovery.d.ts +19 -8
  28. package/dist/recovery.d.ts.map +1 -1
  29. package/dist/recovery.js +13 -13
  30. package/dist/render/retro.d.ts.map +1 -1
  31. package/dist/render/retro.js +1 -4
  32. package/dist/schema/entry.d.ts +1365 -3291
  33. package/dist/schema/entry.d.ts.map +1 -1
  34. package/dist/schema/entry.js +184 -516
  35. package/dist/schema/manifest.d.ts +28 -41
  36. package/dist/schema/manifest.d.ts.map +1 -1
  37. package/dist/schema/manifest.js +20 -24
  38. package/dist/schema/note.d.ts +3 -9
  39. package/dist/schema/note.d.ts.map +1 -1
  40. package/dist/schema/note.js +2 -2
  41. package/dist/storage/filesystem.d.ts +0 -1
  42. package/dist/storage/filesystem.d.ts.map +1 -1
  43. package/dist/storage/filesystem.js +4 -4
  44. package/package.json +3 -4
@@ -1,6 +1,5 @@
1
1
  import path from 'node:path';
2
- import { z } from 'zod';
3
- import { createDeterministicUuidV7, isUuidV7 } from "../uuid.js";
2
+ import * as v from 'valibot';
4
3
  import { NoteSubkindSchema } from "./note.js";
5
4
  export const LedgerSchemaVersion = 'ushman-ledger-entry/v1';
6
5
  export const LEDGER_PHASES = [
@@ -21,27 +20,12 @@ export const LEDGER_KINDS = [
21
20
  'tool-invocation',
22
21
  'agent-patch',
23
22
  'operator-patch',
24
- 'stage-transition',
25
23
  'operator-decision',
26
24
  'validator-result',
27
25
  'runtime-event',
28
26
  'note',
29
27
  'correction',
30
28
  'strip-decision-reverted',
31
- 'descope-brief',
32
- 'merge-return',
33
- 'merge-return-rejected',
34
- 'revert',
35
- 'rollback',
36
- 'rework.test_retired',
37
- ];
38
- export const STAGE_TRANSITION_STAGES = [
39
- 'intake',
40
- 'seed',
41
- 'vendor-extract',
42
- 'cleanup',
43
- 'parity',
44
- 'characterize',
45
29
  ];
46
30
  export const OPERATOR_DECISION_ACTIONS = [
47
31
  'bypass-doctor',
@@ -52,16 +36,18 @@ export const OPERATOR_DECISION_ACTIONS = [
52
36
  'ledger-hand-edit',
53
37
  'escalation',
54
38
  ];
39
+ const STRIP_DECISION_INVALIDATED_STAGES = [
40
+ 'intake',
41
+ 'seed',
42
+ 'vendor-extract',
43
+ 'cleanup',
44
+ 'parity',
45
+ 'characterize',
46
+ ];
55
47
  const SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/u;
56
48
  const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[a-z]:[\\/]/iu;
57
49
  const normalizeWorkspaceRelativePath = (value) => value.replaceAll('\\', '/');
58
- const mergeAffectedFiles = (left, right) => [
59
- ...new Set([...(left ?? []), ...right]),
60
- ];
61
- const WorkspaceRelativePathSchema = z
62
- .string()
63
- .min(1)
64
- .refine((value) => {
50
+ const isNormalizedWorkspaceRelativePath = (value) => {
65
51
  const normalized = normalizeWorkspaceRelativePath(value);
66
52
  if (normalized.startsWith('/') || WINDOWS_ABSOLUTE_PATH_PATTERN.test(value)) {
67
53
  return false;
@@ -77,543 +63,225 @@ const WorkspaceRelativePathSchema = z
77
63
  return false;
78
64
  }
79
65
  return !normalized.split('/').includes('..');
80
- }, 'Expected a normalized workspace-relative path.');
81
- const SHA256HexSchema = z.string().regex(SHA256_HEX_PATTERN, 'Expected a lowercase SHA-256 hex digest.');
82
- const UuidV7Schema = z.string().refine(isUuidV7, 'Expected a UUIDv7 value.');
83
- const LegacyOperatorDecisionTypeSchema = z.enum([
84
- 'strip-decision',
85
- 'strip-decision-revised',
86
- 'vendor-boundary',
87
- 'intent-exclusion',
88
- 'other',
89
- ]);
90
- export const mapLegacyDecisionTypeToAction = (decisionType) => {
91
- switch (decisionType) {
92
- case 'strip-decision':
93
- case 'strip-decision-revised':
94
- return 'override-strip-decision';
95
- default:
96
- return 'escalation';
97
- }
98
- };
99
- const makeDigestRecordSchema = () => z.record(z.string().min(1), SHA256HexSchema);
100
- const validatePatchPayload = ({ ctx, payload, }) => {
101
- const touchedPaths = payload.touchedPaths ?? [];
102
- const beforeMap = payload.fileSha256Before ?? {};
103
- const afterMap = payload.fileSha256After ?? {};
104
- if (touchedPaths.length === 0) {
105
- ctx.addIssue({
106
- code: 'custom',
107
- message: 'payload.touchedPaths must contain at least one path',
108
- path: ['payload', 'touchedPaths'],
109
- });
110
- }
111
- for (const touchedPath of touchedPaths) {
112
- if (!(touchedPath in beforeMap)) {
113
- ctx.addIssue({
114
- code: 'custom',
115
- message: `payload.fileSha256Before is missing ${touchedPath}`,
116
- path: ['payload', 'fileSha256Before', touchedPath],
117
- });
118
- }
119
- if (!(touchedPath in afterMap)) {
120
- ctx.addIssue({
121
- code: 'custom',
122
- message: `payload.fileSha256After is missing ${touchedPath}`,
123
- path: ['payload', 'fileSha256After', touchedPath],
124
- });
125
- }
126
- }
127
- for (const hunk of payload.hunks ?? []) {
128
- if (!touchedPaths.includes(hunk.path)) {
129
- ctx.addIssue({
130
- code: 'custom',
131
- message: `payload.hunks path ${hunk.path} must appear in payload.touchedPaths`,
132
- path: ['payload', 'hunks'],
133
- });
134
- }
135
- }
136
66
  };
137
- export const LedgerPhaseSchema = z.enum(LEDGER_PHASES);
138
- export const StageTransitionStageSchema = z.enum(STAGE_TRANSITION_STAGES);
139
- export const OperatorDecisionActionSchema = z.enum(OPERATOR_DECISION_ACTIONS);
140
- export const EmitterSchema = z.object({
141
- tool: z.string().min(1),
142
- user: z.string().min(1).optional(),
143
- version: z.string().min(1),
144
- });
145
- export const LedgerLinksSchema = z
146
- .object({
147
- affectedFiles: z.array(WorkspaceRelativePathSchema).optional(),
148
- blobs: z.array(z.string()).optional(),
149
- briefId: z.string().optional(),
150
- correctsLedgerId: z.string().optional(),
151
- gitRef: z.string().optional(),
152
- idempotencyKey: z.string().optional(),
153
- stripDecisionId: z.string().optional(),
154
- supersedesLedgerId: z.string().optional(),
155
- validatorVerdictId: z.string().optional(),
156
- })
157
- .catchall(z.unknown());
158
- const LedgerEntryBaseSchema = z.object({
159
- _legacy: z.literal(true).optional(),
160
- details: z.record(z.string(), z.unknown()).optional(),
67
+ const WorkspaceRelativePathSchema = v.pipe(v.string(), v.minLength(1), v.check(isNormalizedWorkspaceRelativePath, 'Expected a normalized workspace-relative path.'));
68
+ const SHA256HexSchema = v.pipe(v.string(), v.regex(SHA256_HEX_PATTERN, 'Expected a lowercase SHA-256 hex digest.'));
69
+ export const LedgerPhaseSchema = v.picklist(LEDGER_PHASES);
70
+ export const OperatorDecisionActionSchema = v.picklist(OPERATOR_DECISION_ACTIONS);
71
+ const StripDecisionInvalidatedStageSchema = v.picklist(STRIP_DECISION_INVALIDATED_STAGES);
72
+ export const EmitterSchema = v.object({
73
+ tool: v.pipe(v.string(), v.minLength(1)),
74
+ user: v.optional(v.pipe(v.string(), v.minLength(1))),
75
+ version: v.pipe(v.string(), v.minLength(1)),
76
+ });
77
+ export const LedgerLinksSchema = v.objectWithRest({
78
+ affectedFiles: v.optional(v.array(WorkspaceRelativePathSchema)),
79
+ blobs: v.optional(v.array(v.string())),
80
+ briefId: v.optional(v.string()),
81
+ correctsLedgerId: v.optional(v.string()),
82
+ gitRef: v.optional(v.string()),
83
+ idempotencyKey: v.optional(v.string()),
84
+ stripDecisionId: v.optional(v.string()),
85
+ supersedesLedgerId: v.optional(v.string()),
86
+ validatorVerdictId: v.optional(v.string()),
87
+ }, v.unknown());
88
+ const ledgerEntryBaseEntries = {
89
+ details: v.optional(v.record(v.string(), v.unknown())),
161
90
  emitter: EmitterSchema,
162
- id: z.string().min(1),
163
- kind: z.string().min(1),
164
- links: LedgerLinksSchema.default({}),
91
+ id: v.pipe(v.string(), v.minLength(1)),
92
+ links: v.optional(LedgerLinksSchema, {}),
165
93
  phase: LedgerPhaseSchema,
166
- prevEntryId: z.string().min(1).nullable(),
167
- schemaVersion: z.literal(LedgerSchemaVersion),
168
- summary: z.string().min(1),
169
- ts: z.string().datetime({ offset: true }),
170
- });
171
- export const PatchHunkSchema = z
172
- .object({
173
- endLine: z.number().int().positive(),
174
- path: WorkspaceRelativePathSchema,
175
- startLine: z.number().int().positive(),
176
- })
177
- .refine((value) => value.endLine >= value.startLine, {
178
- message: 'payload.hunks endLine must be greater than or equal to startLine',
179
- path: ['endLine'],
180
- });
181
- export const PatchPayloadSchema = z.object({
182
- diff: z.string().min(1).optional(),
183
- diffSha256: SHA256HexSchema,
184
- fileSha256After: makeDigestRecordSchema().default({}),
185
- fileSha256Before: makeDigestRecordSchema().default({}),
186
- hunks: z.array(PatchHunkSchema).default([]),
187
- touchedPaths: z.array(WorkspaceRelativePathSchema).default([]),
188
- });
189
- export const PatchPayloadWriteSchema = PatchPayloadSchema.superRefine((value, ctx) => {
190
- validatePatchPayload({
191
- ctx,
192
- payload: value,
193
- });
194
- if (!value.diff) {
195
- ctx.addIssue({
196
- code: 'custom',
197
- message: 'payload.diff is required for new patch records',
198
- path: ['diff'],
199
- });
200
- }
201
- });
202
- export const OperatorDecisionPayloadSchema = z.object({
203
- action: OperatorDecisionActionSchema,
204
- checkId: z.string().min(1).optional(),
205
- rationale: z.string().trim().min(1, 'payload.rationale must not be empty'),
206
- });
207
- export const ValidatorResultPayloadSchema = z.object({
208
- id: UuidV7Schema,
209
- });
210
- export const CorrectionPayloadSchema = z.object({
211
- correctsValidatorResultId: UuidV7Schema.optional(),
212
- });
213
- export const StageTransitionPayloadSchema = z.object({
214
- doctorBaselineId: z.string().min(1).optional(),
215
- endedAt: z.string().datetime({ offset: true }),
216
- exitCode: z.number().int(),
217
- stage: StageTransitionStageSchema,
218
- startedAt: z.string().datetime({ offset: true }),
219
- });
220
- export const StripDecisionRevertedPayloadSchema = z.object({
221
- invalidatedStages: z.array(StageTransitionStageSchema).optional(),
222
- rationale: z.string().trim().min(1),
223
- stripDecisionId: z.string().min(1),
224
- });
225
- export const DescopeBriefPayloadSchema = z.object({
226
- briefId: z.string().min(1),
227
- reason: z.string().trim().min(1).optional(),
228
- round: z.number().int().positive().optional(),
229
- stripDecisionIds: z.array(z.string().min(1)).optional(),
230
- });
231
- export const MergeReturnPayloadSchema = z.object({
232
- briefId: z.string().min(1).optional(),
233
- returnId: z.string().min(1),
234
- source: z.string().min(1).optional(),
235
- });
236
- export const MergeReturnRejectedPayloadSchema = MergeReturnPayloadSchema.extend({
237
- reason: z.string().trim().min(1),
238
- });
239
- export const RevertPayloadSchema = z.object({
240
- reason: z.string().trim().min(1),
241
- targetEntryId: z.string().min(1).optional(),
242
- });
243
- export const RollbackPayloadSchema = RevertPayloadSchema.extend({
244
- targetStage: StageTransitionStageSchema.optional(),
245
- });
246
- export const ReworkTestRetiredPayloadSchema = z.object({
247
- briefId: z.string().min(1).optional(),
248
- removedBy: z.enum(['descope', 'decompose', 'operator']),
249
- removedTestPath: WorkspaceRelativePathSchema,
250
- removedTestSymbols: z.array(z.string().min(1)),
251
- sourceSymbolDeletedFrom: WorkspaceRelativePathSchema,
252
- });
253
- export const ToolInvocationEntrySchema = LedgerEntryBaseSchema.extend({
254
- args: z.array(z.string()).optional(),
255
- cwd: z.string().optional(),
256
- exitCode: z.number().int().optional(),
257
- kind: z.literal('tool-invocation'),
258
- });
259
- export const AgentPatchDiffSchema = z.object({
260
- addedLines: z.number().int().nonnegative(),
94
+ prevEntryId: v.nullable(v.pipe(v.string(), v.minLength(1))),
95
+ schemaVersion: v.literal(LedgerSchemaVersion),
96
+ summary: v.pipe(v.string(), v.minLength(1)),
97
+ ts: v.pipe(v.string(), v.isoTimestamp()),
98
+ };
99
+ export const AgentPatchDiffSchema = v.object({
100
+ addedLines: v.pipe(v.number(), v.integer(), v.minValue(0)),
261
101
  blobSha256: SHA256HexSchema,
262
- bytes: z.number().int().nonnegative(),
263
- removedLines: z.number().int().nonnegative(),
102
+ bytes: v.pipe(v.number(), v.integer(), v.minValue(0)),
103
+ removedLines: v.pipe(v.number(), v.integer(), v.minValue(0)),
264
104
  });
265
- const PatchEntryBaseSchema = LedgerEntryBaseSchema.extend({
105
+ export const OperatorDecisionPayloadSchema = v.object({
106
+ action: OperatorDecisionActionSchema,
107
+ checkId: v.optional(v.pipe(v.string(), v.minLength(1))),
108
+ rationale: v.pipe(v.string(), v.trim(), v.minLength(1, 'payload.rationale must not be empty')),
109
+ });
110
+ export const StripDecisionRevertedPayloadSchema = v.object({
111
+ invalidatedStages: v.optional(v.array(StripDecisionInvalidatedStageSchema)),
112
+ rationale: v.pipe(v.string(), v.trim(), v.minLength(1)),
113
+ stripDecisionId: v.pipe(v.string(), v.minLength(1)),
114
+ });
115
+ export const ToolInvocationEntrySchema = v.object({
116
+ ...ledgerEntryBaseEntries,
117
+ args: v.optional(v.array(v.string())),
118
+ cwd: v.optional(v.string()),
119
+ exitCode: v.optional(v.pipe(v.number(), v.integer())),
120
+ kind: v.literal('tool-invocation'),
121
+ });
122
+ const patchEntryEntries = {
123
+ ...ledgerEntryBaseEntries,
266
124
  diff: AgentPatchDiffSchema,
267
- payload: PatchPayloadSchema,
268
- rationale: z.string().min(1),
269
- }).superRefine((value, ctx) => {
270
- if (value.payload.diffSha256 !== value.diff.blobSha256) {
271
- ctx.addIssue({
272
- code: 'custom',
273
- message: 'payload.diffSha256 must match diff.blobSha256',
274
- path: ['payload', 'diffSha256'],
275
- });
276
- }
277
- validatePatchPayload({
278
- ctx,
279
- payload: value.payload,
280
- });
281
- });
282
- export const AgentPatchEntrySchema = PatchEntryBaseSchema.safeExtend({
283
- agent: z.object({
284
- name: z.string().min(1),
285
- sessionId: z.string().min(1).optional(),
125
+ rationale: v.pipe(v.string(), v.minLength(1)),
126
+ };
127
+ export const AgentPatchEntrySchema = v.object({
128
+ ...patchEntryEntries,
129
+ agent: v.object({
130
+ name: v.pipe(v.string(), v.minLength(1)),
131
+ sessionId: v.optional(v.pipe(v.string(), v.minLength(1))),
286
132
  }),
287
- kind: z.literal('agent-patch'),
133
+ kind: v.literal('agent-patch'),
288
134
  });
289
- export const OperatorPatchEntrySchema = PatchEntryBaseSchema.safeExtend({
290
- kind: z.literal('operator-patch'),
291
- operator: z.object({
292
- name: z.string().min(1),
135
+ export const OperatorPatchEntrySchema = v.object({
136
+ ...patchEntryEntries,
137
+ kind: v.literal('operator-patch'),
138
+ operator: v.object({
139
+ name: v.pipe(v.string(), v.minLength(1)),
293
140
  }),
294
141
  });
295
- export const StageTransitionEntrySchema = LedgerEntryBaseSchema.extend({
296
- kind: z.literal('stage-transition'),
297
- payload: StageTransitionPayloadSchema,
298
- });
299
- export const OperatorDecisionEntrySchema = LedgerEntryBaseSchema.extend({
300
- body: z.record(z.string(), z.unknown()).optional(),
301
- decisionType: LegacyOperatorDecisionTypeSchema.optional(),
302
- kind: z.literal('operator-decision'),
142
+ export const OperatorDecisionEntrySchema = v.object({
143
+ ...ledgerEntryBaseEntries,
144
+ kind: v.literal('operator-decision'),
303
145
  payload: OperatorDecisionPayloadSchema,
304
146
  });
305
- export const ValidatorResultEntrySchema = LedgerEntryBaseSchema.extend({
306
- kind: z.literal('validator-result'),
307
- metrics: z.record(z.string(), z.unknown()).optional(),
308
- payload: ValidatorResultPayloadSchema,
309
- resultPath: z.string().optional(),
310
- validator: z.enum(['parity', 'equiv', 'characterize', 'verify', 'doctor']),
311
- verdict: z.enum(['green', 'yellow', 'red']),
312
- });
313
- export const RuntimeEventEntrySchema = LedgerEntryBaseSchema.extend({
314
- kind: z.literal('runtime-event'),
315
- level: z.enum(['info', 'warn', 'error']),
316
- message: z.string().min(1),
317
- source: z.string().min(1),
318
- stripDecisionId: z.string().optional(),
319
- });
320
- export const NoteEntrySchema = LedgerEntryBaseSchema.extend({
321
- body: z.string(),
322
- kind: z.literal('note'),
147
+ export const ValidatorResultEntrySchema = v.object({
148
+ ...ledgerEntryBaseEntries,
149
+ kind: v.literal('validator-result'),
150
+ metrics: v.optional(v.record(v.string(), v.unknown())),
151
+ resultPath: v.optional(v.string()),
152
+ validator: v.picklist(['parity', 'equiv', 'characterize', 'verify', 'doctor']),
153
+ verdict: v.picklist(['green', 'yellow', 'red']),
154
+ });
155
+ export const RuntimeEventEntrySchema = v.object({
156
+ ...ledgerEntryBaseEntries,
157
+ kind: v.literal('runtime-event'),
158
+ level: v.picklist(['info', 'warn', 'error']),
159
+ message: v.pipe(v.string(), v.minLength(1)),
160
+ source: v.pipe(v.string(), v.minLength(1)),
161
+ stripDecisionId: v.optional(v.string()),
162
+ });
163
+ export const NoteEntrySchema = v.object({
164
+ ...ledgerEntryBaseEntries,
165
+ body: v.string(),
166
+ kind: v.literal('note'),
323
167
  subkind: NoteSubkindSchema,
324
168
  });
325
- export const CorrectionEntrySchema = LedgerEntryBaseSchema.extend({
326
- kind: z.literal('correction'),
327
- payload: CorrectionPayloadSchema.default({}),
328
- rationale: z.string().min(1),
329
- }).superRefine((value, ctx) => {
330
- if (!value.links?.correctsLedgerId) {
331
- ctx.addIssue({
332
- code: 'custom',
333
- message: 'correction entries require links.correctsLedgerId',
334
- path: ['links', 'correctsLedgerId'],
335
- });
336
- }
337
- });
338
- export const StripDecisionRevertedEntrySchema = LedgerEntryBaseSchema.extend({
339
- kind: z.literal('strip-decision-reverted'),
169
+ export const CorrectionEntrySchema = v.pipe(v.object({
170
+ ...ledgerEntryBaseEntries,
171
+ kind: v.literal('correction'),
172
+ rationale: v.pipe(v.string(), v.minLength(1)),
173
+ }), v.check((value) => Boolean(value.links?.correctsLedgerId), 'correction entries require links.correctsLedgerId'));
174
+ export const StripDecisionRevertedEntrySchema = v.object({
175
+ ...ledgerEntryBaseEntries,
176
+ kind: v.literal('strip-decision-reverted'),
340
177
  payload: StripDecisionRevertedPayloadSchema,
341
178
  });
342
- export const DescopeBriefEntrySchema = LedgerEntryBaseSchema.extend({
343
- kind: z.literal('descope-brief'),
344
- payload: DescopeBriefPayloadSchema,
345
- });
346
- export const MergeReturnEntrySchema = LedgerEntryBaseSchema.extend({
347
- kind: z.literal('merge-return'),
348
- payload: MergeReturnPayloadSchema,
349
- });
350
- export const MergeReturnRejectedEntrySchema = LedgerEntryBaseSchema.extend({
351
- kind: z.literal('merge-return-rejected'),
352
- payload: MergeReturnRejectedPayloadSchema,
353
- });
354
- export const RevertEntrySchema = LedgerEntryBaseSchema.extend({
355
- kind: z.literal('revert'),
356
- payload: RevertPayloadSchema,
357
- });
358
- export const RollbackEntrySchema = LedgerEntryBaseSchema.extend({
359
- kind: z.literal('rollback'),
360
- payload: RollbackPayloadSchema,
361
- });
362
- export const ReworkTestRetiredEntrySchema = LedgerEntryBaseSchema.extend({
363
- kind: z.literal('rework.test_retired'),
364
- payload: ReworkTestRetiredPayloadSchema,
365
- });
366
- export const LedgerEntrySchema = z.discriminatedUnion('kind', [
179
+ export const LedgerEntrySchema = v.variant('kind', [
367
180
  ToolInvocationEntrySchema,
368
181
  AgentPatchEntrySchema,
369
182
  OperatorPatchEntrySchema,
370
- StageTransitionEntrySchema,
371
183
  OperatorDecisionEntrySchema,
372
184
  ValidatorResultEntrySchema,
373
185
  RuntimeEventEntrySchema,
374
186
  NoteEntrySchema,
375
187
  CorrectionEntrySchema,
376
188
  StripDecisionRevertedEntrySchema,
377
- DescopeBriefEntrySchema,
378
- MergeReturnEntrySchema,
379
- MergeReturnRejectedEntrySchema,
380
- RevertEntrySchema,
381
- RollbackEntrySchema,
382
- ReworkTestRetiredEntrySchema,
383
189
  ]);
384
- const LegacyAgentPatchEntrySchema = LedgerEntryBaseSchema.extend({
385
- agent: z.object({
386
- name: z.string().min(1),
387
- sessionId: z.string().min(1).optional(),
388
- }),
389
- diff: AgentPatchDiffSchema,
390
- kind: z.literal('agent-patch'),
391
- rationale: z.string().min(1),
392
- });
393
- const LegacyOperatorDecisionEntrySchema = LedgerEntryBaseSchema.extend({
394
- body: z.record(z.string(), z.unknown()),
395
- decisionType: LegacyOperatorDecisionTypeSchema,
396
- kind: z.literal('operator-decision'),
397
- });
398
- const LegacyValidatorResultEntrySchema = LedgerEntryBaseSchema.extend({
399
- kind: z.literal('validator-result'),
400
- metrics: z.record(z.string(), z.unknown()).optional(),
401
- resultPath: z.string().optional(),
402
- validator: z.enum(['parity', 'equiv', 'characterize', 'verify', 'doctor']),
403
- verdict: z.enum(['green', 'yellow', 'red']),
404
- });
405
- const RecordEntryBaseSchema = z.object({
406
- details: z.record(z.string(), z.unknown()).optional(),
190
+ const recordEntryBaseEntries = {
191
+ details: v.optional(v.record(v.string(), v.unknown())),
407
192
  emitter: EmitterSchema,
408
- idempotencyKey: z.string().min(1).optional(),
409
- links: LedgerLinksSchema.optional(),
193
+ idempotencyKey: v.optional(v.pipe(v.string(), v.minLength(1))),
194
+ links: v.optional(LedgerLinksSchema),
410
195
  phase: LedgerPhaseSchema,
411
- summary: z.string().min(1),
412
- });
413
- export const ToolInvocationRecordSchema = RecordEntryBaseSchema.extend({
414
- args: z.array(z.string()).optional(),
415
- cwd: z.string().optional(),
416
- exitCode: z.number().int().optional(),
417
- kind: z.literal('tool-invocation'),
418
- });
419
- const PatchRecordPayloadInputSchema = z.object({
420
- diff: z.string().min(1).optional(),
421
- diffSha256: SHA256HexSchema.optional(),
422
- fileSha256After: makeDigestRecordSchema().optional(),
423
- fileSha256Before: makeDigestRecordSchema().optional(),
424
- hunks: z.array(PatchHunkSchema).optional(),
425
- touchedPaths: z.array(WorkspaceRelativePathSchema).min(1).optional(),
426
- });
427
- const PatchRecordBaseSchema = RecordEntryBaseSchema.extend({
428
- diff: AgentPatchDiffSchema.optional(),
429
- diffPath: z.string().min(1).optional(),
430
- payload: PatchRecordPayloadInputSchema.optional(),
431
- rationale: z.string().min(1),
432
- }).superRefine((value, ctx) => {
433
- if (!value.payload && !value.diff && !value.diffPath) {
434
- ctx.addIssue({
435
- code: 'custom',
436
- message: 'patch records require payload, diff, or diffPath',
437
- path: ['payload'],
438
- });
196
+ summary: v.pipe(v.string(), v.minLength(1)),
197
+ };
198
+ export const ToolInvocationRecordSchema = v.object({
199
+ ...recordEntryBaseEntries,
200
+ args: v.optional(v.array(v.string())),
201
+ cwd: v.optional(v.string()),
202
+ exitCode: v.optional(v.pipe(v.number(), v.integer())),
203
+ kind: v.literal('tool-invocation'),
204
+ });
205
+ const patchRecordEntries = {
206
+ ...recordEntryBaseEntries,
207
+ diff: v.optional(AgentPatchDiffSchema),
208
+ diffPath: v.optional(v.pipe(v.string(), v.minLength(1))),
209
+ diffText: v.optional(v.pipe(v.string(), v.minLength(1))),
210
+ rationale: v.pipe(v.string(), v.minLength(1)),
211
+ };
212
+ const validatePatchRecord = (value) => {
213
+ const provided = [value.diff, value.diffPath, value.diffText].filter((entry) => Boolean(entry));
214
+ if (provided.length === 0) {
215
+ return false;
439
216
  }
440
- if (value.diff && value.diffPath) {
441
- ctx.addIssue({
442
- code: 'custom',
443
- message: 'patch records must provide either diff or diffPath, not both',
444
- path: ['diffPath'],
445
- });
217
+ if (value.diffPath && value.diffText) {
218
+ return false;
446
219
  }
447
- });
448
- export const AgentPatchRecordSchema = PatchRecordBaseSchema.safeExtend({
449
- agent: z.object({
450
- name: z.string().min(1),
451
- sessionId: z.string().min(1).optional(),
220
+ return true;
221
+ };
222
+ const PATCH_RECORD_REFINEMENT_MESSAGE = 'patch records require exactly one source: diff, diffPath, or diffText (diffPath and diffText cannot be combined).';
223
+ export const AgentPatchRecordSchema = v.pipe(v.object({
224
+ ...patchRecordEntries,
225
+ agent: v.object({
226
+ name: v.pipe(v.string(), v.minLength(1)),
227
+ sessionId: v.optional(v.pipe(v.string(), v.minLength(1))),
452
228
  }),
453
- kind: z.literal('agent-patch'),
454
- });
455
- export const OperatorPatchRecordSchema = PatchRecordBaseSchema.safeExtend({
456
- kind: z.literal('operator-patch'),
457
- operator: z.object({
458
- name: z.string().min(1),
229
+ kind: v.literal('agent-patch'),
230
+ }), v.check(validatePatchRecord, PATCH_RECORD_REFINEMENT_MESSAGE));
231
+ export const OperatorPatchRecordSchema = v.pipe(v.object({
232
+ ...patchRecordEntries,
233
+ kind: v.literal('operator-patch'),
234
+ operator: v.object({
235
+ name: v.pipe(v.string(), v.minLength(1)),
459
236
  }),
460
- });
461
- export const StageTransitionRecordSchema = RecordEntryBaseSchema.extend({
462
- kind: z.literal('stage-transition'),
463
- payload: StageTransitionPayloadSchema,
464
- });
465
- export const OperatorDecisionRecordSchema = RecordEntryBaseSchema.extend({
466
- kind: z.literal('operator-decision'),
237
+ }), v.check(validatePatchRecord, PATCH_RECORD_REFINEMENT_MESSAGE));
238
+ export const OperatorDecisionRecordSchema = v.object({
239
+ ...recordEntryBaseEntries,
240
+ kind: v.literal('operator-decision'),
467
241
  payload: OperatorDecisionPayloadSchema,
468
242
  });
469
- export const ValidatorResultRecordSchema = RecordEntryBaseSchema.extend({
470
- kind: z.literal('validator-result'),
471
- metrics: z.record(z.string(), z.unknown()).optional(),
472
- payload: z
473
- .object({
474
- id: UuidV7Schema.optional(),
475
- })
476
- .optional(),
477
- resultPath: z.string().optional(),
478
- validator: z.enum(['parity', 'equiv', 'characterize', 'verify', 'doctor']),
479
- verdict: z.enum(['green', 'yellow', 'red']),
480
- });
481
- export const RuntimeEventRecordSchema = RecordEntryBaseSchema.extend({
482
- kind: z.literal('runtime-event'),
483
- level: z.enum(['info', 'warn', 'error']),
484
- message: z.string().min(1),
485
- source: z.string().min(1),
486
- stripDecisionId: z.string().optional(),
487
- });
488
- export const NoteRecordSchema = RecordEntryBaseSchema.extend({
489
- body: z.string(),
490
- kind: z.literal('note'),
243
+ export const ValidatorResultRecordSchema = v.object({
244
+ ...recordEntryBaseEntries,
245
+ kind: v.literal('validator-result'),
246
+ metrics: v.optional(v.record(v.string(), v.unknown())),
247
+ resultPath: v.optional(v.string()),
248
+ validator: v.picklist(['parity', 'equiv', 'characterize', 'verify', 'doctor']),
249
+ verdict: v.picklist(['green', 'yellow', 'red']),
250
+ });
251
+ export const RuntimeEventRecordSchema = v.object({
252
+ ...recordEntryBaseEntries,
253
+ kind: v.literal('runtime-event'),
254
+ level: v.picklist(['info', 'warn', 'error']),
255
+ message: v.pipe(v.string(), v.minLength(1)),
256
+ source: v.pipe(v.string(), v.minLength(1)),
257
+ stripDecisionId: v.optional(v.string()),
258
+ });
259
+ export const NoteRecordSchema = v.object({
260
+ ...recordEntryBaseEntries,
261
+ body: v.string(),
262
+ kind: v.literal('note'),
491
263
  subkind: NoteSubkindSchema,
492
264
  });
493
- export const CorrectionRecordSchema = RecordEntryBaseSchema.extend({
494
- kind: z.literal('correction'),
495
- payload: CorrectionPayloadSchema.optional(),
496
- rationale: z.string().min(1),
497
- }).superRefine((value, ctx) => {
498
- if (!value.links?.correctsLedgerId) {
499
- ctx.addIssue({
500
- code: 'custom',
501
- message: 'correction records require links.correctsLedgerId',
502
- path: ['links', 'correctsLedgerId'],
503
- });
504
- }
505
- });
506
- export const StripDecisionRevertedRecordSchema = RecordEntryBaseSchema.extend({
507
- kind: z.literal('strip-decision-reverted'),
265
+ export const CorrectionRecordSchema = v.pipe(v.object({
266
+ ...recordEntryBaseEntries,
267
+ kind: v.literal('correction'),
268
+ rationale: v.pipe(v.string(), v.minLength(1)),
269
+ }), v.check((value) => Boolean(value.links?.correctsLedgerId), 'correction records require links.correctsLedgerId'));
270
+ export const StripDecisionRevertedRecordSchema = v.object({
271
+ ...recordEntryBaseEntries,
272
+ kind: v.literal('strip-decision-reverted'),
508
273
  payload: StripDecisionRevertedPayloadSchema,
509
274
  });
510
- export const DescopeBriefRecordSchema = RecordEntryBaseSchema.extend({
511
- kind: z.literal('descope-brief'),
512
- payload: DescopeBriefPayloadSchema,
513
- });
514
- export const MergeReturnRecordSchema = RecordEntryBaseSchema.extend({
515
- kind: z.literal('merge-return'),
516
- payload: MergeReturnPayloadSchema,
517
- });
518
- export const MergeReturnRejectedRecordSchema = RecordEntryBaseSchema.extend({
519
- kind: z.literal('merge-return-rejected'),
520
- payload: MergeReturnRejectedPayloadSchema,
521
- });
522
- export const RevertRecordSchema = RecordEntryBaseSchema.extend({
523
- kind: z.literal('revert'),
524
- payload: RevertPayloadSchema,
525
- });
526
- export const RollbackRecordSchema = RecordEntryBaseSchema.extend({
527
- kind: z.literal('rollback'),
528
- payload: RollbackPayloadSchema,
529
- });
530
- export const ReworkTestRetiredRecordSchema = RecordEntryBaseSchema.extend({
531
- kind: z.literal('rework.test_retired'),
532
- payload: ReworkTestRetiredPayloadSchema,
533
- });
534
- export const LedgerRecordSchema = z.discriminatedUnion('kind', [
275
+ export const LedgerRecordSchema = v.variant('kind', [
535
276
  ToolInvocationRecordSchema,
536
277
  AgentPatchRecordSchema,
537
278
  OperatorPatchRecordSchema,
538
- StageTransitionRecordSchema,
539
279
  OperatorDecisionRecordSchema,
540
280
  ValidatorResultRecordSchema,
541
281
  RuntimeEventRecordSchema,
542
282
  NoteRecordSchema,
543
283
  CorrectionRecordSchema,
544
284
  StripDecisionRevertedRecordSchema,
545
- DescopeBriefRecordSchema,
546
- MergeReturnRecordSchema,
547
- MergeReturnRejectedRecordSchema,
548
- RevertRecordSchema,
549
- RollbackRecordSchema,
550
- ReworkTestRetiredRecordSchema,
551
285
  ]);
552
- const normalizePatchEntry = (entry) => ({
553
- ...entry,
554
- links: {
555
- ...entry.links,
556
- affectedFiles: mergeAffectedFiles(entry.links.affectedFiles, entry.payload.touchedPaths),
557
- },
558
- });
559
- const normalizeLegacyAgentPatchEntry = (entry) => normalizePatchEntry({
560
- ...entry,
561
- _legacy: true,
562
- payload: {
563
- diffSha256: entry.diff.blobSha256,
564
- fileSha256After: {},
565
- fileSha256Before: {},
566
- hunks: [],
567
- touchedPaths: entry.links.affectedFiles ?? [],
568
- },
569
- });
570
- const normalizeLegacyOperatorDecisionEntry = (entry) => ({
571
- ...entry,
572
- _legacy: true,
573
- payload: {
574
- action: mapLegacyDecisionTypeToAction(entry.decisionType),
575
- checkId: typeof entry.links.stripDecisionId === 'string' ? entry.links.stripDecisionId : undefined,
576
- rationale: typeof entry.body.rationale === 'string' && entry.body.rationale.trim().length > 0
577
- ? entry.body.rationale
578
- : entry.summary,
579
- },
580
- });
581
- const normalizeLegacyValidatorResultEntry = (entry) => ({
582
- ...entry,
583
- _legacy: true,
584
- payload: {
585
- id: createDeterministicUuidV7({
586
- seed: `${entry.id}:${entry.summary}:${entry.validator}:${entry.verdict}`,
587
- timestamp: entry.ts,
588
- }),
589
- },
590
- });
591
- const normalizeCurrentEntry = (entry) => {
592
- switch (entry.kind) {
593
- case 'agent-patch':
594
- case 'operator-patch':
595
- return normalizePatchEntry(entry);
596
- default:
597
- return entry;
598
- }
599
- };
600
- export const parseLedgerEntry = (input) => {
601
- const current = LedgerEntrySchema.safeParse(input);
602
- if (current.success) {
603
- return normalizeCurrentEntry(current.data);
604
- }
605
- const legacyAgentPatch = LegacyAgentPatchEntrySchema.safeParse(input);
606
- if (legacyAgentPatch.success) {
607
- return normalizeLegacyAgentPatchEntry(legacyAgentPatch.data);
608
- }
609
- const legacyOperatorDecision = LegacyOperatorDecisionEntrySchema.safeParse(input);
610
- if (legacyOperatorDecision.success) {
611
- return normalizeLegacyOperatorDecisionEntry(legacyOperatorDecision.data);
612
- }
613
- const legacyValidatorResult = LegacyValidatorResultEntrySchema.safeParse(input);
614
- if (legacyValidatorResult.success) {
615
- return normalizeLegacyValidatorResultEntry(legacyValidatorResult.data);
616
- }
617
- return LedgerEntrySchema.parse(input);
618
- };
619
- export const parseLedgerRecord = (input) => LedgerRecordSchema.parse(input);
286
+ export const parseLedgerEntry = (input) => v.parse(LedgerEntrySchema, input);
287
+ export const parseLedgerRecord = (input) => v.parse(LedgerRecordSchema, input);