@tiangong-lca/cli 0.0.22 → 0.0.24
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.
- package/README.md +23 -7
- package/dist/src/cli.js +25 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-apply.js +274 -6
- package/dist/src/lib/dataset-maintenance-apply.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-clear-account.js +121 -58
- package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-contract.js +126 -16
- package/dist/src/lib/dataset-maintenance-contract.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-derivatives.js +265 -0
- package/dist/src/lib/dataset-maintenance-derivatives.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-pagination.js +270 -0
- package/dist/src/lib/dataset-maintenance-pagination.js.map +1 -0
- package/dist/src/lib/dataset-maintenance-plan.js +41 -6
- package/dist/src/lib/dataset-maintenance-plan.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-remote.js +76 -28
- package/dist/src/lib/dataset-maintenance-remote.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-verify.js +314 -5
- package/dist/src/lib/dataset-maintenance-verify.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { inspectMaintenanceSupportPayload } from './dataset-maintenance-support-validation.js';
|
|
5
|
+
import { isDatasetMaintenanceSnapshotCompleteness, } from './dataset-maintenance-pagination.js';
|
|
5
6
|
import { CliError } from './errors.js';
|
|
6
7
|
export const MAINTENANCE_MUTABLE_TABLES = ['contacts', 'sources', 'flows', 'processes'];
|
|
7
8
|
export const MAINTENANCE_SUPPORT_TABLES = ['unitgroups', 'flowproperties'];
|
|
@@ -10,6 +11,7 @@ export const MAINTENANCE_SCAN_TABLES = [
|
|
|
10
11
|
'lifecyclemodels',
|
|
11
12
|
...MAINTENANCE_SUPPORT_TABLES,
|
|
12
13
|
];
|
|
14
|
+
export const DERIVATIVE_REBUILD_COMPONENTS = ['extracted_md', 'embedding_ft'];
|
|
13
15
|
function token(value) {
|
|
14
16
|
if (typeof value !== 'string') {
|
|
15
17
|
return null;
|
|
@@ -158,16 +160,19 @@ function parseAction(value, index, accountUserId, operation) {
|
|
|
158
160
|
}
|
|
159
161
|
const action = requireToken(value.action, `actions[${index}].action`);
|
|
160
162
|
const table = requireToken(value.table, `actions[${index}].table`);
|
|
161
|
-
if (!['save_draft', 'delete', 'update_json_ordered'].includes(action)) {
|
|
163
|
+
if (!['save_draft', 'delete', 'update_json_ordered', 'rebuild_derivatives'].includes(action)) {
|
|
162
164
|
throw new CliError(`Unsupported maintenance action: ${action}`, {
|
|
163
165
|
code: 'DATASET_MAINTENANCE_ACTION_UNSUPPORTED',
|
|
164
166
|
exitCode: 2,
|
|
165
167
|
});
|
|
166
168
|
}
|
|
167
169
|
const aliasAction = action === 'update_json_ordered';
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
171
176
|
if (!allowedTable) {
|
|
172
177
|
throw new CliError(`Maintenance cannot mutate protected or unsupported dataset table: ${table}`, {
|
|
173
178
|
code: 'DATASET_MAINTENANCE_TABLE_PROTECTED',
|
|
@@ -175,7 +180,9 @@ function parseAction(value, index, accountUserId, operation) {
|
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
if ((operation === 'merge-support-aliases' && !aliasAction) ||
|
|
178
|
-
(operation
|
|
183
|
+
(operation === 'rebuild-derivatives' && !derivativeAction) ||
|
|
184
|
+
(!['merge-support-aliases', 'rebuild-derivatives'].includes(operation) &&
|
|
185
|
+
(aliasAction || derivativeAction))) {
|
|
179
186
|
throw new CliError(`Maintenance operation ${operation} cannot contain ${action} actions.`, {
|
|
180
187
|
code: 'DATASET_MAINTENANCE_OPERATION_ACTION_MISMATCH',
|
|
181
188
|
exitCode: 2,
|
|
@@ -239,6 +246,23 @@ function parseAction(value, index, accountUserId, operation) {
|
|
|
239
246
|
exitCode: 2,
|
|
240
247
|
});
|
|
241
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
|
+
}
|
|
242
266
|
const exchangeKeys = new Set(exchangeInstances.map((entry) => `${entry.exchange_index}\u0000${entry.data_set_internal_id}\u0000${entry.flow_id}`));
|
|
243
267
|
if (exchangeKeys.size !== exchangeInstances.length) {
|
|
244
268
|
throw new CliError(`Alias action ${index} contains duplicate exchange instances.`, {
|
|
@@ -268,6 +292,7 @@ function parseAction(value, index, accountUserId, operation) {
|
|
|
268
292
|
...(exchangeInstances.length ? { exchange_instances: exchangeInstances } : {}),
|
|
269
293
|
...(desiredPayloadPath ? { desired_payload_path: desiredPayloadPath } : {}),
|
|
270
294
|
...(expectedBeforeSha256 ? { expected_before_sha256: expectedBeforeSha256 } : {}),
|
|
295
|
+
...(derivativeAction ? { components: [...DERIVATIVE_REBUILD_COMPONENTS] } : {}),
|
|
271
296
|
};
|
|
272
297
|
}
|
|
273
298
|
export function parseMaintenanceScope(value, expectedOperation) {
|
|
@@ -278,7 +303,14 @@ export function parseMaintenanceScope(value, expectedOperation) {
|
|
|
278
303
|
});
|
|
279
304
|
}
|
|
280
305
|
const operation = requireToken(value.operation, 'operation');
|
|
281
|
-
if (![
|
|
306
|
+
if (![
|
|
307
|
+
'delete',
|
|
308
|
+
'retire',
|
|
309
|
+
'redo-import',
|
|
310
|
+
'repair-references',
|
|
311
|
+
'merge-support-aliases',
|
|
312
|
+
'rebuild-derivatives',
|
|
313
|
+
].includes(operation)) {
|
|
282
314
|
throw new CliError(`Unsupported maintenance operation: ${operation}`, {
|
|
283
315
|
code: 'DATASET_MAINTENANCE_OPERATION_UNSUPPORTED',
|
|
284
316
|
exitCode: 2,
|
|
@@ -299,9 +331,10 @@ export function parseMaintenanceScope(value, expectedOperation) {
|
|
|
299
331
|
}
|
|
300
332
|
const actions = value.actions.map((entry, index) => parseAction(entry, index, userId, operation));
|
|
301
333
|
const targetMode = token(value.target_mode);
|
|
302
|
-
if ((
|
|
303
|
-
|
|
304
|
-
|
|
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.', {
|
|
305
338
|
code: 'DATASET_MAINTENANCE_TARGET_MODE_INVALID',
|
|
306
339
|
exitCode: 2,
|
|
307
340
|
details: { operation, target_mode: targetMode },
|
|
@@ -317,6 +350,12 @@ export function parseMaintenanceScope(value, expectedOperation) {
|
|
|
317
350
|
exitCode: 2,
|
|
318
351
|
});
|
|
319
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
|
+
}
|
|
320
359
|
if (aliasBatches.length) {
|
|
321
360
|
const batchIds = new Set(aliasBatches.map((batch) => batch.batch_id));
|
|
322
361
|
const dimensions = new Set(aliasBatches.map((batch) => batch.dimension));
|
|
@@ -473,6 +512,32 @@ export function computePlanSha256(plan) {
|
|
|
473
512
|
const body = { ...plan, plan_sha256: '' };
|
|
474
513
|
return sha256Json(body);
|
|
475
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
|
+
}
|
|
476
541
|
export function parseMaintenancePlan(value) {
|
|
477
542
|
if (!isJsonObject(value) ||
|
|
478
543
|
value.schema_version !== 1 ||
|
|
@@ -487,6 +552,13 @@ export function parseMaintenancePlan(value) {
|
|
|
487
552
|
});
|
|
488
553
|
}
|
|
489
554
|
const plan = value;
|
|
555
|
+
if (plan.snapshot_completeness !== undefined &&
|
|
556
|
+
!isDatasetMaintenanceSnapshotCompleteness(plan.snapshot_completeness, MAINTENANCE_SCAN_TABLES)) {
|
|
557
|
+
throw new CliError('Maintenance plan snapshot completeness proof is invalid.', {
|
|
558
|
+
code: 'DATASET_MAINTENANCE_PLAN_INVALID',
|
|
559
|
+
exitCode: 2,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
490
562
|
const expected = computePlanSha256(plan);
|
|
491
563
|
if (plan.plan_sha256 !== expected) {
|
|
492
564
|
throw new CliError('Maintenance plan hash does not match its canonical contents.', {
|
|
@@ -585,13 +657,23 @@ export function parseMaintenancePlan(value) {
|
|
|
585
657
|
? 'save_before_snapshot'
|
|
586
658
|
: action.action === 'delete'
|
|
587
659
|
? 'restore_deleted_before_snapshot'
|
|
588
|
-
: '
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
action.rollback.
|
|
594
|
-
|
|
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) {
|
|
595
677
|
throw new CliError('Ready maintenance plan action rollback snapshot is invalid.', {
|
|
596
678
|
code: 'DATASET_MAINTENANCE_PLAN_INVALID',
|
|
597
679
|
exitCode: 2,
|
|
@@ -621,6 +703,8 @@ export function parseMaintenancePlan(value) {
|
|
|
621
703
|
plan.summary.delete === plan.actions.filter((action) => action.action === 'delete').length &&
|
|
622
704
|
(plan.summary.update_json_ordered ?? 0) ===
|
|
623
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 &&
|
|
624
708
|
(plan.summary.atomic_batches ?? 0) === (plan.alias_batches?.length ?? 0) &&
|
|
625
709
|
(plan.summary.scaled_exchanges ?? 0) ===
|
|
626
710
|
(plan.alias_batches?.reduce((sum, batch) => sum + batch.summary.exchanges, 0) ?? 0) &&
|
|
@@ -831,6 +915,32 @@ export function parseMaintenancePlan(value) {
|
|
|
831
915
|
});
|
|
832
916
|
}
|
|
833
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
|
+
}
|
|
834
944
|
return plan;
|
|
835
945
|
}
|
|
836
946
|
//# sourceMappingURL=dataset-maintenance-contract.js.map
|