@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.
@@ -3,9 +3,11 @@ import path from 'node:path';
3
3
  import { writeJsonArtifact } from './artifacts.js';
4
4
  import { collectRemoteReferences } from './dataset-remote-verify.js';
5
5
  import { CliError } from './errors.js';
6
- import { isJsonObject, maintenanceRowKey, parseMaintenancePlan, readJsonFile, readJsonLinesIfPresent, resolveMaintenancePlanArtifactPath, sha256Json, snapshotRemoteRow, } from './dataset-maintenance-contract.js';
6
+ import { MAINTENANCE_SCAN_TABLES, isJsonObject, maintenanceRowKey, parseMaintenancePlan, readJsonFile, readJsonLinesIfPresent, resolveMaintenancePlanArtifactPath, sha256Json, snapshotRemoteRow, } from './dataset-maintenance-contract.js';
7
+ import { derivativePlanAction, derivativeStatusCategory, parseDerivativeSnapshotResponse, parseDerivativeStatusResponse, parseDerivativeSubmitResponse, } from './dataset-maintenance-derivatives.js';
7
8
  import { maintenanceProjectedReferenceFingerprint } from './dataset-maintenance-plan.js';
8
- import { fetchMaintenanceAccountRows, fetchMaintenanceExactRows, normalizeMaintenancePageSize, resolveMaintenanceRemoteContext, } from './dataset-maintenance-remote.js';
9
+ import { isSnapshotCompletenessCompatible, } from './dataset-maintenance-pagination.js';
10
+ import { fetchMaintenanceAccountRows, fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, normalizeMaintenancePageSize, readMaintenanceDerivativeRebuild, resolveMaintenanceRemoteContext, } from './dataset-maintenance-remote.js';
9
11
  const POSITIVE_INTEGER_TEXT = /^[1-9]\d*$/u;
10
12
  function issue(code, message, action, details) {
11
13
  return {
@@ -50,19 +52,308 @@ function deletedTargetReferences(options) {
50
52
  }));
51
53
  });
52
54
  }
55
+ function derivativeSubmitIdentity(proof) {
56
+ return sha256Json({
57
+ schema_version: proof.schema_version,
58
+ plan_sha256: proof.plan_sha256,
59
+ operation_id: proof.operation_id,
60
+ target_visibility: proof.target_visibility,
61
+ plan_request_sha256: proof.plan_request_sha256,
62
+ action_count: proof.action_count,
63
+ accepted_count: proof.accepted_count,
64
+ summary_audit_id: proof.summary_audit_id,
65
+ request_id: proof.request_id,
66
+ action_request_sha256: proof.action_request_sha256,
67
+ database_audit_id: proof.database_audit_id,
68
+ });
69
+ }
70
+ function readDerivativeSubmitProof(options) {
71
+ const action = derivativePlanAction(options.plan);
72
+ const proofs = [];
73
+ for (const [index, value] of readJsonLinesIfPresent(options.progressPath).entries()) {
74
+ try {
75
+ if (!isJsonObject(value) ||
76
+ value.schema_version !== 1 ||
77
+ value.plan_sha256 !== options.plan.plan_sha256 ||
78
+ value.operation_id !== options.plan.operation_id ||
79
+ value.action_id !== action.action_id ||
80
+ value.target_mode !== 'owner_draft' ||
81
+ !isJsonObject(value.actor) ||
82
+ value.actor.user_id !== options.plan.account.user_id ||
83
+ value.actor.email !== options.plan.account.email ||
84
+ typeof value.started_at_utc !== 'string' ||
85
+ typeof value.ended_at_utc !== 'string' ||
86
+ value.result !== 'accepted' ||
87
+ !isJsonObject(value.proof)) {
88
+ throw new Error('wrapper mismatch');
89
+ }
90
+ proofs.push(parseDerivativeSubmitResponse({
91
+ ok: true,
92
+ command: 'cmd_dataset_derivative_rebuild_plan_guarded',
93
+ ...value.proof,
94
+ }, options.plan));
95
+ }
96
+ catch (error) {
97
+ options.problems.push({
98
+ code: 'DERIVATIVE_SUBMIT_PROGRESS_INVALID',
99
+ message: 'Derivative submit progress contains an invalid or foreign entry.',
100
+ details: { line: index + 1, error: String(error) },
101
+ });
102
+ }
103
+ }
104
+ if (proofs.length === 0) {
105
+ options.problems.push({
106
+ code: 'DERIVATIVE_ADMISSION_MISSING',
107
+ message: 'No valid guarded-RPC derivative admission proof exists.',
108
+ });
109
+ return { proof: null, admissions: 0 };
110
+ }
111
+ if (new Set(proofs.map(derivativeSubmitIdentity)).size !== 1) {
112
+ options.problems.push({
113
+ code: 'DERIVATIVE_ADMISSION_REPLAY_MISMATCH',
114
+ message: 'Derivative admission entries do not identify one durable request.',
115
+ });
116
+ return { proof: null, admissions: proofs.length };
117
+ }
118
+ return { proof: proofs.at(-1), admissions: proofs.length };
119
+ }
120
+ async function runDerivativeMaintenanceVerify(options) {
121
+ const action = derivativePlanAction(options.plan);
122
+ const baseline = action.derivative_before;
123
+ const problems = [];
124
+ const currentByKey = new Map(options.current.rows.map((row) => [maintenanceRowKey(row), row]));
125
+ let protectedPassed = 0;
126
+ for (const protectedRow of options.plan.protected_rows) {
127
+ const row = currentByKey.get(maintenanceRowKey(protectedRow));
128
+ if (row && snapshotRemoteRow(row).row_sha256 === protectedRow.row_sha256) {
129
+ protectedPassed += 1;
130
+ }
131
+ else {
132
+ problems.push({
133
+ code: 'PROTECTED_ROW_CHANGED',
134
+ message: 'Protected primary row changed or disappeared after derivative admission.',
135
+ table: protectedRow.table,
136
+ id: protectedRow.id,
137
+ version: protectedRow.version,
138
+ });
139
+ }
140
+ }
141
+ const expectedKeys = new Set([
142
+ ...options.plan.protected_rows.map(maintenanceRowKey),
143
+ maintenanceRowKey(action),
144
+ ]);
145
+ for (const row of options.current.rows) {
146
+ if (!expectedKeys.has(maintenanceRowKey(row))) {
147
+ problems.push({
148
+ code: 'UNEXPECTED_ACCOUNT_ROW',
149
+ message: 'Unexpected current-account primary row exists after derivative admission.',
150
+ table: row.table,
151
+ id: row.id,
152
+ version: row.version,
153
+ });
154
+ }
155
+ }
156
+ const primaryRow = currentByKey.get(maintenanceRowKey(action));
157
+ if (!primaryRow || snapshotRemoteRow(primaryRow).row_sha256 !== action.before?.row_sha256) {
158
+ problems.push(issue('DERIVATIVE_PRIMARY_ROW_DRIFT', 'The process primary row changed after derivative planning.', action));
159
+ }
160
+ const referenceSha256 = sha256Json(maintenanceProjectedReferenceFingerprint(options.current.rows));
161
+ if (referenceSha256 !== options.plan.projected_reference_sha256) {
162
+ problems.push({
163
+ code: 'PROJECTED_REFERENCE_CLOSURE_MISMATCH',
164
+ message: 'Primary readback reference closure differs from the approved plan.',
165
+ details: { expected: options.plan.projected_reference_sha256, actual: referenceSha256 },
166
+ });
167
+ }
168
+ if (!existsSync(options.approvalRecordPath)) {
169
+ problems.push({ code: 'APPROVAL_RECORD_MISSING', message: 'approval-record.json is missing.' });
170
+ }
171
+ else {
172
+ const approval = readJsonFile(options.approvalRecordPath, 'Maintenance approval record');
173
+ if (!isJsonObject(approval) ||
174
+ approval.schema_version !== 1 ||
175
+ approval.plan_sha256 !== options.plan.plan_sha256 ||
176
+ approval.task_id !== options.plan.task_id ||
177
+ approval.operation !== 'rebuild-derivatives' ||
178
+ approval.operation_id !== options.plan.operation_id ||
179
+ approval.target_mode !== 'owner_draft' ||
180
+ !isJsonObject(approval.account) ||
181
+ approval.account.user_id !== options.plan.account.user_id ||
182
+ approval.account.email !== options.plan.account.email ||
183
+ approval.confirmed_email !== options.plan.account.email ||
184
+ !isJsonObject(approval.row_counts) ||
185
+ sha256Json(approval.row_counts) !== sha256Json(options.plan.summary) ||
186
+ !isSnapshotCompletenessCompatible(approval.snapshot_completeness, options.plan.snapshot_completeness, MAINTENANCE_SCAN_TABLES)) {
187
+ problems.push({
188
+ code: 'APPROVAL_RECORD_INVALID',
189
+ message: 'approval-record.json does not match the immutable derivative plan and actor.',
190
+ });
191
+ }
192
+ }
193
+ const admission = readDerivativeSubmitProof({
194
+ plan: options.plan,
195
+ progressPath: options.progressPath,
196
+ problems,
197
+ });
198
+ if (!existsSync(options.commitReportPath)) {
199
+ problems.push({ code: 'COMMIT_REPORT_MISSING', message: 'commit-report.json is missing.' });
200
+ }
201
+ else {
202
+ const commit = readJsonFile(options.commitReportPath, 'Maintenance commit report');
203
+ const commitProof = isJsonObject(commit) && isJsonObject(commit.derivative_admission)
204
+ ? commit.derivative_admission
205
+ : null;
206
+ if (!isJsonObject(commit) ||
207
+ commit.schema_version !== 1 ||
208
+ commit.status !== 'accepted' ||
209
+ commit.plan_sha256 !== options.plan.plan_sha256 ||
210
+ commit.operation !== 'rebuild-derivatives' ||
211
+ commit.operation_id !== options.plan.operation_id ||
212
+ !commitProof ||
213
+ commitProof.admission !== 'accepted' ||
214
+ !admission.proof ||
215
+ derivativeSubmitIdentity(commitProof) !==
216
+ derivativeSubmitIdentity(admission.proof)) {
217
+ problems.push({
218
+ code: 'COMMIT_REPORT_INCOMPLETE',
219
+ message: 'commit-report.json does not prove guarded derivative admission.',
220
+ });
221
+ }
222
+ }
223
+ let statusProof = null;
224
+ let rawStatus = null;
225
+ let category = 'failed';
226
+ if (admission.proof) {
227
+ try {
228
+ rawStatus = await readMaintenanceDerivativeRebuild({
229
+ context: options.context,
230
+ requestId: admission.proof.request_id,
231
+ });
232
+ statusProof = parseDerivativeStatusResponse(rawStatus, options.plan, admission.proof);
233
+ category = derivativeStatusCategory(statusProof.status);
234
+ }
235
+ catch (error) {
236
+ problems.push({
237
+ code: 'DERIVATIVE_STATUS_READ_FAILED',
238
+ message: 'Guarded derivative request status could not be validated.',
239
+ details: String(error),
240
+ });
241
+ }
242
+ }
243
+ let currentSnapshot = null;
244
+ try {
245
+ currentSnapshot = parseDerivativeSnapshotResponse(await fetchMaintenanceDerivativeSnapshot({
246
+ context: options.context,
247
+ id: action.id,
248
+ version: action.version,
249
+ }), { id: action.id, version: action.version, userId: action.expected_user_id });
250
+ }
251
+ catch (error) {
252
+ problems.push(issue('DERIVATIVE_SNAPSHOT_READ_FAILED', 'Fresh action-scoped derivative snapshot could not be validated.', action, String(error)));
253
+ }
254
+ if (currentSnapshot &&
255
+ (currentSnapshot.modified_at !== baseline.modified_at ||
256
+ currentSnapshot.json_sha256 !== baseline.json_sha256 ||
257
+ currentSnapshot.json_ordered_sha256 !== baseline.json_ordered_sha256 ||
258
+ currentSnapshot.extracted_text_sha256 !== baseline.extracted_text_sha256)) {
259
+ problems.push(issue('DERIVATIVE_PRIMARY_SNAPSHOT_DRIFT', 'Fresh derivative snapshot no longer matches frozen primary preconditions.', action));
260
+ }
261
+ if (statusProof && category === 'failed') {
262
+ problems.push(issue('DERIVATIVE_REQUEST_FAILED', 'The durable derivative request reached a failed terminal state. This does not prove that its primary-write fence has been released; inspect raw_evidence.', action, { status: statusProof.status, error: statusProof.error }));
263
+ }
264
+ if (statusProof && category === 'passed') {
265
+ const currentEmbeddingAt = currentSnapshot?.embedding_ft_at
266
+ ? Date.parse(currentSnapshot.embedding_ft_at)
267
+ : Number.NaN;
268
+ const baselineEmbeddingAt = baseline.embedding_ft_at
269
+ ? Date.parse(baseline.embedding_ft_at)
270
+ : Number.NEGATIVE_INFINITY;
271
+ if (!currentSnapshot ||
272
+ statusProof.completed_snapshot_sha256 !== currentSnapshot.snapshot_sha256 ||
273
+ !currentSnapshot.extracted_md_sha256 ||
274
+ !currentSnapshot.embedding_ft_sha256 ||
275
+ !Number.isFinite(currentEmbeddingAt) ||
276
+ currentEmbeddingAt <= baselineEmbeddingAt) {
277
+ problems.push(issue('DERIVATIVE_COMPLETION_PROOF_MISMATCH', 'Completed status lacks matching current markdown/vector freshness proof.', action));
278
+ }
279
+ }
280
+ const finalStatus = problems.length
281
+ ? 'failed'
282
+ : category;
283
+ const actionStatus = finalStatus;
284
+ const report = {
285
+ schema_version: 1,
286
+ generated_at_utc: (options.now ?? new Date()).toISOString(),
287
+ status: finalStatus,
288
+ task_id: options.plan.task_id,
289
+ operation: options.plan.operation,
290
+ operation_id: options.plan.operation_id,
291
+ target_mode: options.plan.target_mode,
292
+ plan_sha256: options.plan.plan_sha256,
293
+ actor: {
294
+ user_id: options.context.account.user_id,
295
+ email: options.context.account.email,
296
+ },
297
+ snapshot_completeness: options.current.completeness,
298
+ summary: {
299
+ actions: 1,
300
+ action_checks_passed: actionStatus === 'passed' ? 1 : 0,
301
+ protected_rows: options.plan.protected_rows.length,
302
+ protected_checks_passed: protectedPassed,
303
+ progress_successes: 0,
304
+ derivative_admissions: admission.admissions,
305
+ derivative_request_status: statusProof?.status ?? 'unknown',
306
+ dangling_deleted_target_references: 0,
307
+ issues: problems.length,
308
+ },
309
+ action_checks: [
310
+ {
311
+ action_id: action.action_id,
312
+ status: actionStatus,
313
+ observed: actionStatus === 'passed'
314
+ ? 'derivative_current'
315
+ : actionStatus === 'pending'
316
+ ? 'derivative_pending'
317
+ : 'mismatch',
318
+ },
319
+ ],
320
+ issues: problems,
321
+ artifacts: {
322
+ plan: options.planPath,
323
+ approval_record: options.approvalRecordPath,
324
+ apply_progress: options.progressPath,
325
+ derivative_submit_progress: options.progressPath,
326
+ commit_report: options.commitReportPath,
327
+ report: options.reportPath,
328
+ },
329
+ ...(statusProof && rawStatus
330
+ ? {
331
+ derivative_status: {
332
+ proof: statusProof,
333
+ raw_evidence: rawStatus,
334
+ note: 'raw_evidence preserves database fence and timeout state; failed does not imply the primary row is editable unless that evidence explicitly proves fence release.',
335
+ },
336
+ }
337
+ : {}),
338
+ };
339
+ writeJsonArtifact(options.reportPath, report);
340
+ return report;
341
+ }
53
342
  export async function runDatasetMaintenanceVerify(options) {
54
343
  const planPath = path.resolve(options.planPath);
55
344
  const planDir = path.dirname(planPath);
56
345
  const outDir = path.resolve(options.outDir ?? path.join(planDir, 'verify'));
57
346
  const reportPath = path.join(outDir, 'readback-verify-report.json');
58
347
  const approvalRecordPath = path.join(planDir, 'approval-record.json');
59
- const progressPath = path.join(planDir, 'apply-progress.jsonl');
348
+ const plan = parseMaintenancePlan(readJsonFile(planPath, 'Maintenance plan'));
349
+ const progressPath = path.join(planDir, plan.operation === 'rebuild-derivatives'
350
+ ? 'derivative-submit-progress.jsonl'
351
+ : 'apply-progress.jsonl');
60
352
  const commitReportPath = path.join(planDir, 'commit-report.json');
61
353
  const aliasPlanProgressPath = path.join(planDir, 'alias-plan-progress.jsonl');
62
354
  const aliasBatchProgressPath = path.join(planDir, 'alias-batch-progress.jsonl');
63
355
  const aliasExchangeProgressPath = path.join(planDir, 'alias-exchange-progress.jsonl');
64
356
  const pageSize = normalizeMaintenancePageSize(options.pageSize);
65
- const plan = parseMaintenancePlan(readJsonFile(planPath, 'Maintenance plan'));
66
357
  const context = await resolveMaintenanceRemoteContext({
67
358
  env: options.env,
68
359
  fetchImpl: options.fetchImpl,
@@ -82,6 +373,20 @@ export async function runDatasetMaintenanceVerify(options) {
82
373
  userId: plan.account.user_id,
83
374
  pageSize,
84
375
  });
376
+ if (plan.operation === 'rebuild-derivatives') {
377
+ return runDerivativeMaintenanceVerify({
378
+ plan,
379
+ planPath,
380
+ planDir,
381
+ reportPath,
382
+ approvalRecordPath,
383
+ commitReportPath,
384
+ progressPath,
385
+ context,
386
+ current,
387
+ now: options.now,
388
+ });
389
+ }
85
390
  const currentByKey = new Map(current.rows.map((row) => [maintenanceRowKey(row), row]));
86
391
  const actionChecks = [];
87
392
  for (const action of plan.actions) {
@@ -480,7 +785,8 @@ export async function runDatasetMaintenanceVerify(options) {
480
785
  approvalRecord.account.email !== plan.account.email ||
481
786
  approvalRecord.confirmed_email !== plan.account.email ||
482
787
  !isJsonObject(approvalRecord.row_counts) ||
483
- sha256Json(approvalRecord.row_counts) !== sha256Json(plan.summary)) {
788
+ sha256Json(approvalRecord.row_counts) !== sha256Json(plan.summary) ||
789
+ !isSnapshotCompletenessCompatible(approvalRecord.snapshot_completeness, plan.snapshot_completeness, MAINTENANCE_SCAN_TABLES)) {
484
790
  problems.push({
485
791
  code: 'APPROVAL_RECORD_INVALID',
486
792
  message: 'approval-record.json does not match the immutable plan and actor.',
@@ -645,6 +951,7 @@ export async function runDatasetMaintenanceVerify(options) {
645
951
  target_mode: plan.target_mode,
646
952
  plan_sha256: plan.plan_sha256,
647
953
  actor: { user_id: context.account.user_id, email: context.account.email },
954
+ snapshot_completeness: current.completeness,
648
955
  summary: {
649
956
  actions: plan.actions.length,
650
957
  action_checks_passed: actionChecks.filter((check) => check.status === 'passed').length,
@@ -684,7 +991,9 @@ export async function runDatasetMaintenanceVerify(options) {
684
991
  }
685
992
  export const __testInternals = {
686
993
  deletedTargetReferences,
994
+ derivativeSubmitIdentity,
687
995
  desiredPayload,
688
996
  issue,
997
+ readDerivativeSubmitProof,
689
998
  };
690
999
  //# sourceMappingURL=dataset-maintenance-verify.js.map