@tiangong-lca/cli 0.0.23 → 0.0.25

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.
@@ -11,6 +11,7 @@ export const MAINTENANCE_SCAN_TABLES = [
11
11
  'lifecyclemodels',
12
12
  ...MAINTENANCE_SUPPORT_TABLES,
13
13
  ];
14
+ export const DERIVATIVE_REBUILD_COMPONENTS = ['extracted_md', 'embedding_ft'];
14
15
  function token(value) {
15
16
  if (typeof value !== 'string') {
16
17
  return null;
@@ -159,16 +160,19 @@ function parseAction(value, index, accountUserId, operation) {
159
160
  }
160
161
  const action = requireToken(value.action, `actions[${index}].action`);
161
162
  const table = requireToken(value.table, `actions[${index}].table`);
162
- if (!['save_draft', 'delete', 'update_json_ordered'].includes(action)) {
163
+ if (!['save_draft', 'delete', 'update_json_ordered', 'rebuild_derivatives'].includes(action)) {
163
164
  throw new CliError(`Unsupported maintenance action: ${action}`, {
164
165
  code: 'DATASET_MAINTENANCE_ACTION_UNSUPPORTED',
165
166
  exitCode: 2,
166
167
  });
167
168
  }
168
169
  const aliasAction = action === 'update_json_ordered';
169
- const allowedTable = aliasAction
170
- ? ['flowproperties', 'flows', 'processes'].includes(table)
171
- : MAINTENANCE_MUTABLE_TABLES.includes(table);
170
+ const derivativeAction = action === 'rebuild_derivatives';
171
+ const allowedTable = derivativeAction
172
+ ? table === 'processes'
173
+ : aliasAction
174
+ ? ['flowproperties', 'flows', 'processes'].includes(table)
175
+ : MAINTENANCE_MUTABLE_TABLES.includes(table);
172
176
  if (!allowedTable) {
173
177
  throw new CliError(`Maintenance cannot mutate protected or unsupported dataset table: ${table}`, {
174
178
  code: 'DATASET_MAINTENANCE_TABLE_PROTECTED',
@@ -176,7 +180,9 @@ function parseAction(value, index, accountUserId, operation) {
176
180
  });
177
181
  }
178
182
  if ((operation === 'merge-support-aliases' && !aliasAction) ||
179
- (operation !== 'merge-support-aliases' && aliasAction)) {
183
+ (operation === 'rebuild-derivatives' && !derivativeAction) ||
184
+ (!['merge-support-aliases', 'rebuild-derivatives'].includes(operation) &&
185
+ (aliasAction || derivativeAction))) {
180
186
  throw new CliError(`Maintenance operation ${operation} cannot contain ${action} actions.`, {
181
187
  code: 'DATASET_MAINTENANCE_OPERATION_ACTION_MISMATCH',
182
188
  exitCode: 2,
@@ -240,6 +246,23 @@ function parseAction(value, index, accountUserId, operation) {
240
246
  exitCode: 2,
241
247
  });
242
248
  }
249
+ const components = Array.isArray(value.components)
250
+ ? value.components.map((component, componentIndex) => requireToken(component, `actions[${index}].components[${componentIndex}]`))
251
+ : [];
252
+ if (derivativeAction &&
253
+ (components.length !== DERIVATIVE_REBUILD_COMPONENTS.length ||
254
+ components.some((component, componentIndex) => component !== DERIVATIVE_REBUILD_COMPONENTS[componentIndex]))) {
255
+ throw new CliError(`Derivative rebuild action ${index} requires components in exact order: ${DERIVATIVE_REBUILD_COMPONENTS.join(', ')}.`, {
256
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_COMPONENTS_INVALID',
257
+ exitCode: 2,
258
+ });
259
+ }
260
+ if (!derivativeAction && 'components' in value) {
261
+ throw new CliError(`Maintenance non-derivative action ${index} cannot include components.`, {
262
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_COMPONENTS_INVALID',
263
+ exitCode: 2,
264
+ });
265
+ }
243
266
  const exchangeKeys = new Set(exchangeInstances.map((entry) => `${entry.exchange_index}\u0000${entry.data_set_internal_id}\u0000${entry.flow_id}`));
244
267
  if (exchangeKeys.size !== exchangeInstances.length) {
245
268
  throw new CliError(`Alias action ${index} contains duplicate exchange instances.`, {
@@ -269,6 +292,7 @@ function parseAction(value, index, accountUserId, operation) {
269
292
  ...(exchangeInstances.length ? { exchange_instances: exchangeInstances } : {}),
270
293
  ...(desiredPayloadPath ? { desired_payload_path: desiredPayloadPath } : {}),
271
294
  ...(expectedBeforeSha256 ? { expected_before_sha256: expectedBeforeSha256 } : {}),
295
+ ...(derivativeAction ? { components: [...DERIVATIVE_REBUILD_COMPONENTS] } : {}),
272
296
  };
273
297
  }
274
298
  export function parseMaintenanceScope(value, expectedOperation) {
@@ -279,7 +303,14 @@ export function parseMaintenanceScope(value, expectedOperation) {
279
303
  });
280
304
  }
281
305
  const operation = requireToken(value.operation, 'operation');
282
- if (!['delete', 'retire', 'redo-import', 'repair-references', 'merge-support-aliases'].includes(operation)) {
306
+ if (![
307
+ 'delete',
308
+ 'retire',
309
+ 'redo-import',
310
+ 'repair-references',
311
+ 'merge-support-aliases',
312
+ 'rebuild-derivatives',
313
+ ].includes(operation)) {
283
314
  throw new CliError(`Unsupported maintenance operation: ${operation}`, {
284
315
  code: 'DATASET_MAINTENANCE_OPERATION_UNSUPPORTED',
285
316
  exitCode: 2,
@@ -300,9 +331,10 @@ export function parseMaintenanceScope(value, expectedOperation) {
300
331
  }
301
332
  const actions = value.actions.map((entry, index) => parseAction(entry, index, userId, operation));
302
333
  const targetMode = token(value.target_mode);
303
- if ((operation === 'merge-support-aliases' && targetMode !== 'owner_draft') ||
304
- (operation !== 'merge-support-aliases' && targetMode !== null)) {
305
- throw new CliError('merge-support-aliases requires target_mode=owner_draft; other operations forbid target_mode.', {
334
+ if ((['merge-support-aliases', 'rebuild-derivatives'].includes(operation) &&
335
+ targetMode !== 'owner_draft') ||
336
+ (!['merge-support-aliases', 'rebuild-derivatives'].includes(operation) && targetMode !== null)) {
337
+ throw new CliError('merge-support-aliases and rebuild-derivatives require target_mode=owner_draft; other operations forbid target_mode.', {
306
338
  code: 'DATASET_MAINTENANCE_TARGET_MODE_INVALID',
307
339
  exitCode: 2,
308
340
  details: { operation, target_mode: targetMode },
@@ -318,6 +350,12 @@ export function parseMaintenanceScope(value, expectedOperation) {
318
350
  exitCode: 2,
319
351
  });
320
352
  }
353
+ if (operation === 'rebuild-derivatives' && actions.length !== 1) {
354
+ throw new CliError('rebuild-derivatives requires exactly one action.', {
355
+ code: 'DATASET_MAINTENANCE_DERIVATIVE_ACTION_COUNT_INVALID',
356
+ exitCode: 2,
357
+ });
358
+ }
321
359
  if (aliasBatches.length) {
322
360
  const batchIds = new Set(aliasBatches.map((batch) => batch.batch_id));
323
361
  const dimensions = new Set(aliasBatches.map((batch) => batch.dimension));
@@ -474,6 +512,32 @@ export function computePlanSha256(plan) {
474
512
  const body = { ...plan, plan_sha256: '' };
475
513
  return sha256Json(body);
476
514
  }
515
+ function validDerivativeSnapshot(value, action) {
516
+ if (!isJsonObject(value))
517
+ return false;
518
+ const requiredHashes = [
519
+ value.json_sha256,
520
+ value.json_ordered_sha256,
521
+ value.extracted_text_sha256,
522
+ value.snapshot_sha256,
523
+ ];
524
+ const nullableHashes = [value.extracted_md_sha256, value.embedding_ft_sha256];
525
+ return Boolean(value.schema_version === 'dataset-derivative-snapshot.v1' &&
526
+ value.table === 'processes' &&
527
+ value.id === action.id &&
528
+ value.version === action.version &&
529
+ value.user_id === action.expected_user_id &&
530
+ value.state_code === 0 &&
531
+ value.modified_at === action.before?.modified_at &&
532
+ typeof value.modified_at === 'string' &&
533
+ Number.isFinite(Date.parse(value.modified_at)) &&
534
+ requiredHashes.every((hash) => typeof hash === 'string' && /^[a-f0-9]{64}$/u.test(hash)) &&
535
+ nullableHashes.every((hash) => hash === null || (typeof hash === 'string' && /^[a-f0-9]{64}$/u.test(hash))) &&
536
+ value.json_sha256 === value.json_ordered_sha256 &&
537
+ (value.embedding_ft_at === null ||
538
+ (typeof value.embedding_ft_at === 'string' &&
539
+ Number.isFinite(Date.parse(value.embedding_ft_at)))));
540
+ }
477
541
  export function parseMaintenancePlan(value) {
478
542
  if (!isJsonObject(value) ||
479
543
  value.schema_version !== 1 ||
@@ -593,13 +657,23 @@ export function parseMaintenancePlan(value) {
593
657
  ? 'save_before_snapshot'
594
658
  : action.action === 'delete'
595
659
  ? 'restore_deleted_before_snapshot'
596
- : 'restore_atomic_alias_before_snapshot';
597
- if (action.rollback.strategy !== expectedRollbackStrategy ||
598
- action.rollback.before_payload_sha256 !== before.payload_sha256 ||
599
- !isJsonObject(action.rollback.before_payload) ||
600
- sha256Json(action.rollback.before_payload) !== before.payload_sha256 ||
601
- action.rollback.model_id !== before.model_id ||
602
- action.rollback.rule_verification !== before.rule_verification) {
660
+ : action.action === 'update_json_ordered'
661
+ ? 'restore_atomic_alias_before_snapshot'
662
+ : 'none_derivative_only';
663
+ const derivativeRollback = action.action === 'rebuild_derivatives';
664
+ const rollbackMatches = derivativeRollback
665
+ ? action.rollback.strategy === expectedRollbackStrategy &&
666
+ action.rollback.before_payload_sha256 === null &&
667
+ action.rollback.before_payload === null &&
668
+ action.rollback.model_id === null &&
669
+ action.rollback.rule_verification === null
670
+ : action.rollback.strategy === expectedRollbackStrategy &&
671
+ action.rollback.before_payload_sha256 === before.payload_sha256 &&
672
+ isJsonObject(action.rollback.before_payload) &&
673
+ sha256Json(action.rollback.before_payload) === before.payload_sha256 &&
674
+ action.rollback.model_id === before.model_id &&
675
+ action.rollback.rule_verification === before.rule_verification;
676
+ if (!rollbackMatches) {
603
677
  throw new CliError('Ready maintenance plan action rollback snapshot is invalid.', {
604
678
  code: 'DATASET_MAINTENANCE_PLAN_INVALID',
605
679
  exitCode: 2,
@@ -629,6 +703,8 @@ export function parseMaintenancePlan(value) {
629
703
  plan.summary.delete === plan.actions.filter((action) => action.action === 'delete').length &&
630
704
  (plan.summary.update_json_ordered ?? 0) ===
631
705
  plan.actions.filter((action) => action.action === 'update_json_ordered').length &&
706
+ (plan.summary.rebuild_derivatives ?? 0) ===
707
+ plan.actions.filter((action) => action.action === 'rebuild_derivatives').length &&
632
708
  (plan.summary.atomic_batches ?? 0) === (plan.alias_batches?.length ?? 0) &&
633
709
  (plan.summary.scaled_exchanges ?? 0) ===
634
710
  (plan.alias_batches?.reduce((sum, batch) => sum + batch.summary.exchanges, 0) ?? 0) &&
@@ -839,6 +915,32 @@ export function parseMaintenancePlan(value) {
839
915
  });
840
916
  }
841
917
  }
918
+ if (plan.operation === 'rebuild-derivatives') {
919
+ const action = plan.actions[0];
920
+ const validDerivativePlan = Boolean(plan.target_mode === 'owner_draft' &&
921
+ plan.actions.length === 1 &&
922
+ !plan.alias_batches &&
923
+ plan.artifacts.derivative_baseline === 'derivative-baseline.json' &&
924
+ action?.action === 'rebuild_derivatives' &&
925
+ action.table === 'processes' &&
926
+ sha256Json(action.components) === sha256Json(DERIVATIVE_REBUILD_COMPONENTS) &&
927
+ action.desired_payload === null &&
928
+ action.rollback.strategy === 'none_derivative_only' &&
929
+ (action.status === 'blocked' || validDerivativeSnapshot(action.derivative_before, action)));
930
+ if (!validDerivativePlan) {
931
+ throw new CliError('Maintenance derivative rebuild plan contract is inconsistent.', {
932
+ code: 'DATASET_MAINTENANCE_PLAN_INVALID',
933
+ exitCode: 2,
934
+ });
935
+ }
936
+ }
937
+ else if (plan.actions.some((action) => action.derivative_before !== undefined) ||
938
+ plan.artifacts.derivative_baseline !== undefined) {
939
+ throw new CliError('Non-derivative maintenance plan contains derivative-only fields.', {
940
+ code: 'DATASET_MAINTENANCE_PLAN_INVALID',
941
+ exitCode: 2,
942
+ });
943
+ }
842
944
  return plan;
843
945
  }
844
946
  //# sourceMappingURL=dataset-maintenance-contract.js.map