@tiangong-lca/cli 0.0.11 → 0.0.13

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 (60) hide show
  1. package/README.md +18 -7
  2. package/assets/tidas-schemas/tidas_contacts.json +312 -0
  3. package/assets/tidas-schemas/tidas_contacts_category.json +126 -0
  4. package/assets/tidas-schemas/tidas_data_types.json +359 -0
  5. package/assets/tidas-schemas/tidas_flowproperties.json +303 -0
  6. package/assets/tidas-schemas/tidas_flowproperties_category.json +61 -0
  7. package/assets/tidas-schemas/tidas_flows.json +809 -0
  8. package/assets/tidas-schemas/tidas_flows_elementary_category.json +720 -0
  9. package/assets/tidas-schemas/tidas_flows_product_category.json +59623 -0
  10. package/assets/tidas-schemas/tidas_lciamethods.json +1326 -0
  11. package/assets/tidas-schemas/tidas_lciamethods_category.json +685 -0
  12. package/assets/tidas-schemas/tidas_lifecyclemodels.json +1374 -0
  13. package/assets/tidas-schemas/tidas_locations_category.json +2593 -0
  14. package/assets/tidas-schemas/tidas_processes.json +1646 -0
  15. package/assets/tidas-schemas/tidas_processes_category.json +10795 -0
  16. package/assets/tidas-schemas/tidas_sources.json +228 -0
  17. package/assets/tidas-schemas/tidas_sources_category.json +100 -0
  18. package/assets/tidas-schemas/tidas_unitgroups.json +338 -0
  19. package/assets/tidas-schemas/tidas_unitgroups_category.json +61 -0
  20. package/dist/src/cli.js +847 -13
  21. package/dist/src/cli.js.map +1 -1
  22. package/dist/src/lib/dataset-classification.js +947 -0
  23. package/dist/src/lib/dataset-classification.js.map +1 -0
  24. package/dist/src/lib/dataset-command.js +11 -0
  25. package/dist/src/lib/dataset-command.js.map +1 -1
  26. package/dist/src/lib/dataset-contract.js +2 -0
  27. package/dist/src/lib/dataset-contract.js.map +1 -1
  28. package/dist/src/lib/dataset-curation-queue.js +359 -2
  29. package/dist/src/lib/dataset-curation-queue.js.map +1 -1
  30. package/dist/src/lib/dataset-import-lca.js +18 -0
  31. package/dist/src/lib/dataset-import-lca.js.map +1 -1
  32. package/dist/src/lib/dataset-local.js +94 -1
  33. package/dist/src/lib/dataset-local.js.map +1 -1
  34. package/dist/src/lib/dataset-maintenance-clear-account.js +506 -0
  35. package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -0
  36. package/dist/src/lib/dataset-patch.js +797 -0
  37. package/dist/src/lib/dataset-patch.js.map +1 -0
  38. package/dist/src/lib/dataset-remote-verify.js +188 -3
  39. package/dist/src/lib/dataset-remote-verify.js.map +1 -1
  40. package/dist/src/lib/dataset-save-draft-run.js +725 -0
  41. package/dist/src/lib/dataset-save-draft-run.js.map +1 -0
  42. package/dist/src/lib/dataset-validate.js +32 -2
  43. package/dist/src/lib/dataset-validate.js.map +1 -1
  44. package/dist/src/lib/flow-publish-version.js +7 -2
  45. package/dist/src/lib/flow-publish-version.js.map +1 -1
  46. package/dist/src/lib/flow-qa.js +8 -0
  47. package/dist/src/lib/flow-qa.js.map +1 -1
  48. package/dist/src/lib/identity-preflight.js +895 -117
  49. package/dist/src/lib/identity-preflight.js.map +1 -1
  50. package/dist/src/lib/lifecyclemodel-qa.js +159 -34
  51. package/dist/src/lib/lifecyclemodel-qa.js.map +1 -1
  52. package/dist/src/lib/process-required-fields.js +62 -12
  53. package/dist/src/lib/process-required-fields.js.map +1 -1
  54. package/dist/src/lib/process-save-draft-run.js +10 -0
  55. package/dist/src/lib/process-save-draft-run.js.map +1 -1
  56. package/dist/src/lib/process-save-draft.js +59 -3
  57. package/dist/src/lib/process-save-draft.js.map +1 -1
  58. package/dist/src/lib/supabase-client.js +19 -8
  59. package/dist/src/lib/supabase-client.js.map +1 -1
  60. package/package.json +2 -1
@@ -0,0 +1,797 @@
1
+ import crypto from 'node:crypto';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
6
+ import { cloneJson, isRecord, materializeDatasetRows, trimToken, } from './dataset-local.js';
7
+ import { CliError } from './errors.js';
8
+ import { readJsonInput } from './io.js';
9
+ const decisionOnlyActionKinds = new Set([
10
+ 'identity_decision_authoring',
11
+ 'classification_decision_authoring',
12
+ 'location_decision_authoring',
13
+ ]);
14
+ function nowIso(now) {
15
+ return (now ?? new Date()).toISOString();
16
+ }
17
+ function hasOwn(value, key) {
18
+ return Object.prototype.hasOwnProperty.call(value, key);
19
+ }
20
+ function parseRowIndex(value) {
21
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
22
+ return value;
23
+ }
24
+ if (typeof value === 'string' && /^\d+$/u.test(value)) {
25
+ return Number.parseInt(value, 10);
26
+ }
27
+ return null;
28
+ }
29
+ function nonEmptyString(value) {
30
+ return trimToken(value);
31
+ }
32
+ function sha256Text(value) {
33
+ return crypto.createHash('sha256').update(value).digest('hex');
34
+ }
35
+ function caughtErrorMessage(error) {
36
+ return error instanceof Error ? error.message : String(error);
37
+ }
38
+ function normalizeClosure(value) {
39
+ if (typeof value === 'string') {
40
+ const code = value.trim();
41
+ return code ? { code, path: null } : null;
42
+ }
43
+ if (!isRecord(value)) {
44
+ return null;
45
+ }
46
+ const code = nonEmptyString(value.code) ??
47
+ nonEmptyString(value.action_item_code) ??
48
+ nonEmptyString(value.actionItemCode) ??
49
+ nonEmptyString(value.rule_id) ??
50
+ nonEmptyString(value.ruleId);
51
+ if (!code) {
52
+ return null;
53
+ }
54
+ return {
55
+ code,
56
+ path: nonEmptyString(value.path) ??
57
+ nonEmptyString(value.json_path) ??
58
+ nonEmptyString(value.jsonPath) ??
59
+ null,
60
+ };
61
+ }
62
+ function actionItemClosureKey(value) {
63
+ return `${value.code}\u0000${value.path ?? ''}`;
64
+ }
65
+ function normalizeClosureList(value) {
66
+ const values = Array.isArray(value)
67
+ ? value
68
+ : value === undefined || value === null
69
+ ? []
70
+ : [value];
71
+ const closures = values
72
+ .map(normalizeClosure)
73
+ .filter((closure) => closure !== null);
74
+ return [...new Map(closures.map((closure) => [actionItemClosureKey(closure), closure])).values()];
75
+ }
76
+ function operationClosures(operation) {
77
+ return normalizeClosureList(operation.closes ??
78
+ operation.closes_action_items ??
79
+ operation.closesActionItems ??
80
+ operation.action_items ??
81
+ operation.actionItems);
82
+ }
83
+ function actionItemFromPackage(value) {
84
+ if (!isRecord(value) || value.ai_required === false) {
85
+ return null;
86
+ }
87
+ const actionKind = nonEmptyString(value.action_kind) ?? nonEmptyString(value.actionKind);
88
+ if (actionKind && decisionOnlyActionKinds.has(actionKind)) {
89
+ return null;
90
+ }
91
+ const code = nonEmptyString(value.code) ?? nonEmptyString(value.rule_id) ?? nonEmptyString(value.ruleId);
92
+ if (!code) {
93
+ return null;
94
+ }
95
+ return {
96
+ code,
97
+ path: nonEmptyString(value.path) ?? null,
98
+ };
99
+ }
100
+ function closureMatchesActionItem(closure, actionItem) {
101
+ return (closure.code === actionItem.code &&
102
+ (!closure.path || !actionItem.path || closure.path === actionItem.path));
103
+ }
104
+ function normalizeOperationArray(value) {
105
+ if (!Array.isArray(value)) {
106
+ return null;
107
+ }
108
+ if (!value.every(looksLikeOperation)) {
109
+ return null;
110
+ }
111
+ return value;
112
+ }
113
+ function looksLikeOperation(value) {
114
+ return isRecord(value) && typeof value.op === 'string' && typeof value.path === 'string';
115
+ }
116
+ function normalizePatchSet(value) {
117
+ if (!isRecord(value)) {
118
+ return null;
119
+ }
120
+ const operations = normalizeOperationArray(value.operations) ?? normalizeOperationArray(value.patches);
121
+ if (!operations) {
122
+ return null;
123
+ }
124
+ return {
125
+ rowIndex: parseRowIndex(value.row_index ?? value.rowIndex),
126
+ datasetId: nonEmptyString(value.dataset_id) ??
127
+ nonEmptyString(value.id) ??
128
+ nonEmptyString(value.uuid) ??
129
+ nonEmptyString(value.entity_id),
130
+ datasetVersion: nonEmptyString(value.dataset_version) ?? nonEmptyString(value.version),
131
+ authoringPackage: nonEmptyString(value.authoring_package) ?? nonEmptyString(value.authoringPackage),
132
+ operations,
133
+ };
134
+ }
135
+ function patchPayloadCompletionStatus(rawPatch) {
136
+ if (typeof rawPatch !== 'object' || rawPatch === null) {
137
+ return null;
138
+ }
139
+ const value = rawPatch;
140
+ return (nonEmptyString(value.patch_status) ??
141
+ nonEmptyString(value.patchStatus) ??
142
+ nonEmptyString(value.status));
143
+ }
144
+ function normalizePatchPayload(rawPatch) {
145
+ const blockers = [];
146
+ const patches = [];
147
+ const addPatchSet = (value, patchIndex) => {
148
+ const patch = normalizePatchSet(value);
149
+ if (patch) {
150
+ patches.push(patch);
151
+ return;
152
+ }
153
+ blockers.push({
154
+ code: 'patch_set_invalid',
155
+ message: 'Patch set must be an object with row_index or dataset_id and operations[].',
156
+ patch_index: patchIndex,
157
+ });
158
+ };
159
+ const patchStatus = patchPayloadCompletionStatus(rawPatch);
160
+ if (patchStatus !== 'completed') {
161
+ blockers.push({
162
+ code: 'ai_patch_status_not_completed',
163
+ message: 'Patch payload must declare patch_status=completed before deterministic apply.',
164
+ });
165
+ return { patches, blockers };
166
+ }
167
+ if (Array.isArray(rawPatch)) {
168
+ if (rawPatch.every(looksLikeOperation)) {
169
+ blockers.push({
170
+ code: 'patch_row_required',
171
+ message: 'Top-level operation arrays are not supported because they do not identify a row.',
172
+ });
173
+ return { patches, blockers };
174
+ }
175
+ rawPatch.forEach(addPatchSet);
176
+ return { patches, blockers };
177
+ }
178
+ const patchObject = rawPatch;
179
+ const directPatch = normalizePatchSet(patchObject);
180
+ if (directPatch) {
181
+ patches.push(directPatch);
182
+ return { patches, blockers };
183
+ }
184
+ const candidateList = (Array.isArray(patchObject.patch_sets) ? patchObject.patch_sets : null) ??
185
+ (Array.isArray(patchObject.patches) ? patchObject.patches : null) ??
186
+ (Array.isArray(patchObject.suggestions) ? patchObject.suggestions : null) ??
187
+ (Array.isArray(patchObject.items) ? patchObject.items : null);
188
+ if (candidateList) {
189
+ if (candidateList.every(looksLikeOperation)) {
190
+ blockers.push({
191
+ code: 'patch_row_required',
192
+ message: 'Top-level operation arrays are not supported because they do not identify a row.',
193
+ });
194
+ return { patches, blockers };
195
+ }
196
+ candidateList.forEach(addPatchSet);
197
+ return { patches, blockers };
198
+ }
199
+ blockers.push({
200
+ code: 'patch_payload_invalid',
201
+ message: 'Patch payload must contain operations[], patches[], patch_sets[], suggestions[], or items[].',
202
+ });
203
+ return { patches, blockers };
204
+ }
205
+ function decodePointerToken(token) {
206
+ if (/(^|[^~])~([^01]|$)/u.test(token)) {
207
+ throw new Error(`Invalid JSON Pointer escape in token: ${token}`);
208
+ }
209
+ return token.replace(/~1/gu, '/').replace(/~0/gu, '~');
210
+ }
211
+ function parsePointer(pointer) {
212
+ if (typeof pointer !== 'string' || !pointer.startsWith('/')) {
213
+ throw new Error('Patch path must be a JSON Pointer starting with /.');
214
+ }
215
+ return pointer
216
+ .slice(1)
217
+ .split('/')
218
+ .map((token) => decodePointerToken(token));
219
+ }
220
+ function parseArrayIndex(token, length, allowAppend) {
221
+ if (allowAppend && token === '-') {
222
+ return length;
223
+ }
224
+ if (!/^(0|[1-9]\d*)$/u.test(token)) {
225
+ throw new Error(`Expected array index, got ${token}.`);
226
+ }
227
+ const index = Number.parseInt(token, 10);
228
+ if (index < 0 || index > length || (!allowAppend && index === length)) {
229
+ throw new Error(`Array index out of bounds: ${token}.`);
230
+ }
231
+ return index;
232
+ }
233
+ function resolvePatchTarget(root, pointer, allowAdd) {
234
+ const tokens = parsePointer(pointer);
235
+ let current = root;
236
+ for (const token of tokens.slice(0, -1)) {
237
+ if (Array.isArray(current)) {
238
+ current = current[parseArrayIndex(token, current.length, false)];
239
+ continue;
240
+ }
241
+ if (isRecord(current)) {
242
+ if (!hasOwn(current, token)) {
243
+ throw new Error(`Patch parent path does not exist at ${token}.`);
244
+ }
245
+ current = current[token];
246
+ continue;
247
+ }
248
+ throw new Error(`Patch parent is not an object or array at ${token}.`);
249
+ }
250
+ const keyToken = tokens[tokens.length - 1];
251
+ if (Array.isArray(current)) {
252
+ return {
253
+ container: current,
254
+ key: parseArrayIndex(keyToken, current.length, allowAdd),
255
+ };
256
+ }
257
+ if (isRecord(current)) {
258
+ return { container: current, key: keyToken };
259
+ }
260
+ throw new Error('Patch parent is not an object or array.');
261
+ }
262
+ function targetExists(target) {
263
+ if (Array.isArray(target.container)) {
264
+ return (typeof target.key === 'number' && target.key >= 0 && target.key < target.container.length);
265
+ }
266
+ return typeof target.key === 'string' && hasOwn(target.container, target.key);
267
+ }
268
+ function getTargetValue(target) {
269
+ if (!targetExists(target)) {
270
+ throw new Error('Patch target path does not exist.');
271
+ }
272
+ return target.container[target.key];
273
+ }
274
+ function setTargetValue(target, value, insert) {
275
+ if (Array.isArray(target.container)) {
276
+ const index = target.key;
277
+ if (insert) {
278
+ target.container.splice(index, 0, value);
279
+ return;
280
+ }
281
+ target.container[index] = value;
282
+ return;
283
+ }
284
+ target.container[target.key] = value;
285
+ }
286
+ function removeTargetValue(target) {
287
+ if (!targetExists(target)) {
288
+ throw new Error('Patch target path does not exist.');
289
+ }
290
+ if (Array.isArray(target.container)) {
291
+ target.container.splice(target.key, 1);
292
+ return;
293
+ }
294
+ delete target.container[target.key];
295
+ }
296
+ function evidenceIsPresent(evidence) {
297
+ if (typeof evidence === 'string') {
298
+ return evidence.trim().length > 0;
299
+ }
300
+ if (Array.isArray(evidence)) {
301
+ return evidence.length > 0;
302
+ }
303
+ if (isRecord(evidence)) {
304
+ return Object.keys(evidence).length > 0;
305
+ }
306
+ return false;
307
+ }
308
+ function operationBasis(operation) {
309
+ return nonEmptyString(operation.basis);
310
+ }
311
+ function requiresEvidence(operation) {
312
+ return operation.op !== 'test';
313
+ }
314
+ function validateOperationShape(operation, patchIndex, operationIndex, rowIndex, datasetId, datasetVersion) {
315
+ if (!['add', 'remove', 'replace', 'test'].includes(operation.op)) {
316
+ return {
317
+ code: 'patch_operation_unsupported',
318
+ message: `Unsupported patch operation: ${operation.op}`,
319
+ row_index: rowIndex,
320
+ dataset_id: datasetId,
321
+ dataset_version: datasetVersion,
322
+ patch_index: patchIndex,
323
+ operation_index: operationIndex,
324
+ op: operation.op,
325
+ path: operation.path,
326
+ };
327
+ }
328
+ if (typeof operation.path !== 'string' || !operation.path.startsWith('/')) {
329
+ return {
330
+ code: 'patch_path_invalid',
331
+ message: 'Patch operation path must be a JSON Pointer starting with /.',
332
+ row_index: rowIndex,
333
+ dataset_id: datasetId,
334
+ dataset_version: datasetVersion,
335
+ patch_index: patchIndex,
336
+ operation_index: operationIndex,
337
+ op: operation.op,
338
+ path: operation.path,
339
+ };
340
+ }
341
+ if (['add', 'replace', 'test'].includes(operation.op) && !hasOwn(operation, 'value')) {
342
+ return {
343
+ code: 'patch_value_required',
344
+ message: `${operation.op} operation requires value.`,
345
+ row_index: rowIndex,
346
+ dataset_id: datasetId,
347
+ dataset_version: datasetVersion,
348
+ patch_index: patchIndex,
349
+ operation_index: operationIndex,
350
+ op: operation.op,
351
+ path: operation.path,
352
+ };
353
+ }
354
+ if (requiresEvidence(operation) &&
355
+ !operationBasis(operation) &&
356
+ !evidenceIsPresent(operation.evidence)) {
357
+ return {
358
+ code: 'patch_evidence_required',
359
+ message: 'Non-test patch operations require basis or evidence.',
360
+ row_index: rowIndex,
361
+ dataset_id: datasetId,
362
+ dataset_version: datasetVersion,
363
+ patch_index: patchIndex,
364
+ operation_index: operationIndex,
365
+ op: operation.op,
366
+ path: operation.path,
367
+ };
368
+ }
369
+ return null;
370
+ }
371
+ function applyOperation(root, operation) {
372
+ const op = operation.op;
373
+ const target = resolvePatchTarget(root, operation.path, op === 'add');
374
+ if (op === 'test') {
375
+ const actual = getTargetValue(target);
376
+ if (!isDeepStrictEqual(actual, operation.value)) {
377
+ throw new Error('Patch test failed.');
378
+ }
379
+ return;
380
+ }
381
+ if (op === 'remove') {
382
+ removeTargetValue(target);
383
+ return;
384
+ }
385
+ if (op === 'replace') {
386
+ if (!targetExists(target)) {
387
+ throw new Error('Patch target path does not exist.');
388
+ }
389
+ setTargetValue(target, operation.value, false);
390
+ return;
391
+ }
392
+ setTargetValue(target, operation.value, Array.isArray(target.container));
393
+ }
394
+ function findTargetRow(patch, rows, patchIndex) {
395
+ if (patch.rowIndex !== null) {
396
+ if (patch.rowIndex >= rows.length) {
397
+ return {
398
+ rowIndex: null,
399
+ blocker: {
400
+ code: 'patch_row_index_invalid',
401
+ message: `Patch row_index ${patch.rowIndex} is outside input row count ${rows.length}.`,
402
+ row_index: patch.rowIndex,
403
+ dataset_id: patch.datasetId,
404
+ dataset_version: patch.datasetVersion,
405
+ patch_index: patchIndex,
406
+ },
407
+ };
408
+ }
409
+ const row = rows[patch.rowIndex];
410
+ if (patch.datasetId && row?.id !== patch.datasetId) {
411
+ return {
412
+ rowIndex: null,
413
+ blocker: {
414
+ code: 'patch_dataset_id_mismatch',
415
+ message: `Patch dataset_id ${patch.datasetId} does not match row_index ${patch.rowIndex}.`,
416
+ row_index: patch.rowIndex,
417
+ dataset_id: patch.datasetId,
418
+ dataset_version: patch.datasetVersion,
419
+ patch_index: patchIndex,
420
+ },
421
+ };
422
+ }
423
+ if (patch.datasetVersion && row?.version !== patch.datasetVersion) {
424
+ return {
425
+ rowIndex: null,
426
+ blocker: {
427
+ code: 'patch_dataset_version_mismatch',
428
+ message: `Patch dataset_version ${patch.datasetVersion} does not match row_index ${patch.rowIndex}.`,
429
+ row_index: patch.rowIndex,
430
+ dataset_id: patch.datasetId,
431
+ dataset_version: patch.datasetVersion,
432
+ patch_index: patchIndex,
433
+ },
434
+ };
435
+ }
436
+ return { rowIndex: patch.rowIndex, blocker: null };
437
+ }
438
+ if (!patch.datasetId) {
439
+ return {
440
+ rowIndex: null,
441
+ blocker: {
442
+ code: 'patch_row_required',
443
+ message: 'Patch set must identify a target row by row_index or dataset_id.',
444
+ dataset_id: patch.datasetId,
445
+ dataset_version: patch.datasetVersion,
446
+ patch_index: patchIndex,
447
+ },
448
+ };
449
+ }
450
+ const matches = rows.filter((row) => row.id === patch.datasetId && (!patch.datasetVersion || row.version === patch.datasetVersion));
451
+ if (matches.length === 0) {
452
+ return {
453
+ rowIndex: null,
454
+ blocker: {
455
+ code: 'patch_dataset_not_found',
456
+ message: `No input row matched dataset_id ${patch.datasetId}.`,
457
+ dataset_id: patch.datasetId,
458
+ dataset_version: patch.datasetVersion,
459
+ patch_index: patchIndex,
460
+ },
461
+ };
462
+ }
463
+ if (matches.length > 1) {
464
+ return {
465
+ rowIndex: null,
466
+ blocker: {
467
+ code: 'patch_dataset_ambiguous',
468
+ message: `Multiple input rows matched dataset_id ${patch.datasetId}; use row_index or dataset_version.`,
469
+ dataset_id: patch.datasetId,
470
+ dataset_version: patch.datasetVersion,
471
+ patch_index: patchIndex,
472
+ },
473
+ };
474
+ }
475
+ return { rowIndex: matches[0].index, blocker: null };
476
+ }
477
+ function resolveAuthoringPackagePath(authoringPackage, authoringPackageDir) {
478
+ if (!authoringPackage) {
479
+ return null;
480
+ }
481
+ const directPath = path.resolve(authoringPackage);
482
+ if (existsSync(directPath)) {
483
+ return directPath;
484
+ }
485
+ if (authoringPackageDir) {
486
+ const fromDir = path.resolve(authoringPackageDir, authoringPackage);
487
+ if (existsSync(fromDir)) {
488
+ return fromDir;
489
+ }
490
+ const byBasename = path.resolve(authoringPackageDir, path.basename(authoringPackage));
491
+ if (existsSync(byBasename)) {
492
+ return byBasename;
493
+ }
494
+ }
495
+ return directPath;
496
+ }
497
+ function readAuthoringPackageContext(options) {
498
+ const blockers = [];
499
+ const packagePath = resolveAuthoringPackagePath(options.patch.authoringPackage, options.authoringPackageDir);
500
+ if (!packagePath) {
501
+ if (options.requireAuthoringPackage) {
502
+ blockers.push({
503
+ code: 'authoring_package_required',
504
+ message: 'Strict patch apply requires each patch set to identify an authoring_package.',
505
+ row_index: options.rowIndex,
506
+ dataset_id: options.rowId,
507
+ dataset_version: options.rowVersion,
508
+ patch_index: options.patchIndex,
509
+ });
510
+ }
511
+ return { context: null, blockers };
512
+ }
513
+ if (!existsSync(packagePath)) {
514
+ blockers.push({
515
+ code: 'authoring_package_not_found',
516
+ message: `Authoring package was not found: ${packagePath}`,
517
+ row_index: options.rowIndex,
518
+ dataset_id: options.rowId,
519
+ dataset_version: options.rowVersion,
520
+ patch_index: options.patchIndex,
521
+ });
522
+ return { context: null, blockers };
523
+ }
524
+ let rawText;
525
+ let payload;
526
+ try {
527
+ rawText = readFileSync(packagePath, 'utf8');
528
+ payload = JSON.parse(rawText);
529
+ }
530
+ catch (error) {
531
+ blockers.push({
532
+ code: 'authoring_package_invalid',
533
+ message: caughtErrorMessage(error),
534
+ row_index: options.rowIndex,
535
+ dataset_id: options.rowId,
536
+ dataset_version: options.rowVersion,
537
+ patch_index: options.patchIndex,
538
+ });
539
+ return { context: null, blockers };
540
+ }
541
+ if (!isRecord(payload)) {
542
+ blockers.push({
543
+ code: 'authoring_package_invalid',
544
+ message: 'Authoring package must be a JSON object.',
545
+ row_index: options.rowIndex,
546
+ dataset_id: options.rowId,
547
+ dataset_version: options.rowVersion,
548
+ patch_index: options.patchIndex,
549
+ });
550
+ return { context: null, blockers };
551
+ }
552
+ const packageEntityId = nonEmptyString(payload.entity_id) ?? nonEmptyString(payload.process_id);
553
+ const packageVersion = nonEmptyString(payload.version);
554
+ const rowId = nonEmptyString(options.rowId) ?? options.patch.datasetId;
555
+ const rowVersion = nonEmptyString(options.rowVersion) ?? options.patch.datasetVersion;
556
+ if (packageEntityId && rowId && packageEntityId !== rowId) {
557
+ blockers.push({
558
+ code: 'authoring_package_entity_mismatch',
559
+ message: `Authoring package entity_id ${packageEntityId} does not match target row ${rowId}.`,
560
+ row_index: options.rowIndex,
561
+ dataset_id: rowId,
562
+ dataset_version: rowVersion,
563
+ patch_index: options.patchIndex,
564
+ });
565
+ }
566
+ if (packageVersion && rowVersion && packageVersion !== rowVersion) {
567
+ blockers.push({
568
+ code: 'authoring_package_version_mismatch',
569
+ message: `Authoring package version ${packageVersion} does not match target row ${rowVersion}.`,
570
+ row_index: options.rowIndex,
571
+ dataset_id: rowId,
572
+ dataset_version: rowVersion,
573
+ patch_index: options.patchIndex,
574
+ });
575
+ }
576
+ const actionItems = normalizeClosureList((Array.isArray(payload.action_items) ? payload.action_items : [])
577
+ .map(actionItemFromPackage)
578
+ .filter((item) => item !== null));
579
+ return {
580
+ context: {
581
+ path: packagePath,
582
+ sha256: sha256Text(rawText),
583
+ payload,
584
+ actionItems,
585
+ },
586
+ blockers,
587
+ };
588
+ }
589
+ export async function runDatasetPatchApply(options) {
590
+ if (!options.inputPath) {
591
+ throw new CliError('Missing required --input value.', {
592
+ code: 'DATASET_PATCH_INPUT_REQUIRED',
593
+ exitCode: 2,
594
+ });
595
+ }
596
+ if (!options.patchPath) {
597
+ throw new CliError('Missing required --patch value.', {
598
+ code: 'DATASET_PATCH_PATCH_REQUIRED',
599
+ exitCode: 2,
600
+ });
601
+ }
602
+ if (!options.outPath) {
603
+ throw new CliError('Missing required --out value.', {
604
+ code: 'DATASET_PATCH_OUT_REQUIRED',
605
+ exitCode: 2,
606
+ });
607
+ }
608
+ const rows = materializeDatasetRows(options.inputPath, options.rawInput);
609
+ const originalRows = rows.map((row) => cloneJson(row.row));
610
+ const candidateRows = rows.map((row) => cloneJson(row.row));
611
+ const rawPatch = options.rawPatch ?? readJsonInput(path.resolve(options.patchPath));
612
+ const normalized = normalizePatchPayload(rawPatch);
613
+ const blockers = [...normalized.blockers];
614
+ const evidenceEntries = [];
615
+ let operationCount = 0;
616
+ let tentativeAppliedCount = 0;
617
+ normalized.patches.forEach((patch, patchIndex) => {
618
+ operationCount += patch.operations.length;
619
+ const target = findTargetRow(patch, rows, patchIndex);
620
+ if (target.blocker) {
621
+ blockers.push(target.blocker);
622
+ return;
623
+ }
624
+ const rowIndex = target.rowIndex;
625
+ const row = rows[rowIndex];
626
+ const rowId = row.id ?? patch.datasetId;
627
+ const rowVersion = row.version ?? patch.datasetVersion;
628
+ if (patch.operations.length === 0) {
629
+ blockers.push({
630
+ code: 'patch_operations_missing',
631
+ message: 'Patch set operations[] must not be empty.',
632
+ row_index: rowIndex,
633
+ dataset_id: rowId,
634
+ dataset_version: rowVersion,
635
+ patch_index: patchIndex,
636
+ });
637
+ return;
638
+ }
639
+ const candidateRow = candidateRows[rowIndex];
640
+ const shouldValidateAuthoringPackage = Boolean(options.authoringPackageDir) ||
641
+ options.requireAuthoringPackage === true ||
642
+ options.requireActionItemClosure === true;
643
+ const packageResult = shouldValidateAuthoringPackage
644
+ ? readAuthoringPackageContext({
645
+ patch,
646
+ rowIndex,
647
+ rowId: row.id,
648
+ rowVersion: row.version,
649
+ patchIndex,
650
+ authoringPackageDir: options.authoringPackageDir,
651
+ requireAuthoringPackage: options.requireAuthoringPackage === true || options.requireActionItemClosure === true,
652
+ })
653
+ : { context: null, blockers: [] };
654
+ blockers.push(...packageResult.blockers);
655
+ if (packageResult.blockers.length > 0) {
656
+ return;
657
+ }
658
+ const packageContext = packageResult.context;
659
+ const closedActionItems = [];
660
+ patch.operations.forEach((operation, operationIndex) => {
661
+ const shapeBlocker = validateOperationShape(operation, patchIndex, operationIndex, rowIndex, rowId, rowVersion);
662
+ if (shapeBlocker) {
663
+ blockers.push(shapeBlocker);
664
+ return;
665
+ }
666
+ const closesActionItems = operationClosures(operation);
667
+ if (options.requireActionItemClosure === true && packageContext) {
668
+ for (const closure of closesActionItems) {
669
+ if (!packageContext.actionItems.some((item) => closureMatchesActionItem(closure, item))) {
670
+ blockers.push({
671
+ code: 'authoring_action_item_unknown',
672
+ message: `Patch operation closes unknown authoring action item ${closure.code}.`,
673
+ row_index: rowIndex,
674
+ dataset_id: rowId,
675
+ dataset_version: rowVersion,
676
+ patch_index: patchIndex,
677
+ operation_index: operationIndex,
678
+ op: operation.op,
679
+ path: operation.path,
680
+ });
681
+ }
682
+ }
683
+ }
684
+ closedActionItems.push(...closesActionItems);
685
+ try {
686
+ applyOperation(candidateRow, operation);
687
+ tentativeAppliedCount += operation.op === 'test' ? 0 : 1;
688
+ if (operation.op !== 'test') {
689
+ evidenceEntries.push({
690
+ row_index: rowIndex,
691
+ dataset_id: rowId,
692
+ dataset_version: rowVersion,
693
+ patch_index: patchIndex,
694
+ operation_index: operationIndex,
695
+ op: operation.op,
696
+ path: operation.path,
697
+ basis: operationBasis(operation),
698
+ evidence: operation.evidence ?? null,
699
+ resolution: operation.resolution ?? null,
700
+ authoring_package: patch.authoringPackage,
701
+ authoring_package_sha256: packageContext?.sha256 ?? null,
702
+ closes_action_items: closesActionItems,
703
+ });
704
+ }
705
+ }
706
+ catch (error) {
707
+ blockers.push({
708
+ code: operation.op === 'test' ? 'patch_test_failed' : 'patch_apply_failed',
709
+ message: caughtErrorMessage(error),
710
+ row_index: rowIndex,
711
+ dataset_id: rowId,
712
+ dataset_version: rowVersion,
713
+ patch_index: patchIndex,
714
+ operation_index: operationIndex,
715
+ op: operation.op,
716
+ path: operation.path,
717
+ });
718
+ }
719
+ });
720
+ if (options.requireActionItemClosure === true && packageContext) {
721
+ for (const actionItem of packageContext.actionItems) {
722
+ if (!closedActionItems.some((closure) => closureMatchesActionItem(closure, actionItem))) {
723
+ blockers.push({
724
+ code: 'authoring_action_item_unclosed',
725
+ message: `Authoring action item ${actionItem.code} was not closed by the AI patch set.`,
726
+ row_index: rowIndex,
727
+ dataset_id: rowId,
728
+ dataset_version: rowVersion,
729
+ patch_index: patchIndex,
730
+ path: actionItem.path ?? undefined,
731
+ });
732
+ }
733
+ }
734
+ }
735
+ });
736
+ const resolvedOut = path.resolve(options.outPath);
737
+ const resolvedOutDir = options.outDir ? path.resolve(options.outDir) : path.dirname(resolvedOut);
738
+ const evidenceFile = path.join(resolvedOutDir, 'outputs', 'patch-evidence.jsonl');
739
+ const reportFile = path.join(resolvedOutDir, 'outputs', 'dataset-patch-apply-report.json');
740
+ const status = blockers.length > 0 ? 'blocked' : 'completed';
741
+ const outputRows = status === 'completed' ? candidateRows : originalRows;
742
+ writeJsonLinesArtifact(resolvedOut, outputRows);
743
+ writeJsonLinesArtifact(evidenceFile, status === 'completed' ? evidenceEntries : []);
744
+ const report = {
745
+ schema_version: 1,
746
+ generated_at_utc: nowIso(options.now),
747
+ input_path: options.inputPath,
748
+ patch_path: options.patchPath,
749
+ out_path: resolvedOut,
750
+ status,
751
+ row_count: rows.length,
752
+ patch_count: normalized.patches.length,
753
+ operation_count: operationCount,
754
+ applied_operation_count: status === 'completed' ? tentativeAppliedCount : 0,
755
+ evidence_count: status === 'completed' ? evidenceEntries.length : 0,
756
+ closed_action_item_count: status === 'completed'
757
+ ? evidenceEntries.reduce((total, entry) => total + entry.closes_action_items.length, 0)
758
+ : 0,
759
+ blockers,
760
+ files: {
761
+ patched_rows: resolvedOut,
762
+ patch_evidence: evidenceFile,
763
+ report: reportFile,
764
+ },
765
+ };
766
+ writeJsonArtifact(reportFile, report);
767
+ return report;
768
+ }
769
+ export const __testInternals = {
770
+ actionItemFromPackage,
771
+ applyOperation,
772
+ closureMatchesActionItem,
773
+ caughtErrorMessage,
774
+ evidenceIsPresent,
775
+ findTargetRow,
776
+ getTargetValue,
777
+ looksLikeOperation,
778
+ normalizeClosure,
779
+ normalizeClosureList,
780
+ normalizeOperationArray,
781
+ normalizePatchPayload,
782
+ normalizePatchSet,
783
+ operationBasis,
784
+ operationClosures,
785
+ parseArrayIndex,
786
+ parsePointer,
787
+ parseRowIndex,
788
+ patchPayloadCompletionStatus,
789
+ readAuthoringPackageContext,
790
+ removeTargetValue,
791
+ resolveAuthoringPackagePath,
792
+ resolvePatchTarget,
793
+ setTargetValue,
794
+ targetExists,
795
+ validateOperationShape,
796
+ };
797
+ //# sourceMappingURL=dataset-patch.js.map