@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.
@@ -0,0 +1,771 @@
1
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { buildAliasPlanRequest, loadMaintenanceDesiredPayload, } from './dataset-maintenance-alias-request.js';
4
+ import { MAINTENANCE_SCAN_TABLES, isJsonObject, maintenanceRowKey, parseMaintenancePlan, readJsonFile, sha256Json, sha256Text, snapshotRemoteRow, stableJsonText, } from './dataset-maintenance-contract.js';
5
+ import { PROTECTED_EXECUTION_CONTRACT, assertProtectedApprovalBindings, assertProtectedFreezeMatchesPlan, buildProtectedAdmitRequest, buildProtectedExecutionIdentity, buildProtectedPreflightRequest, parseProtectedAdmissionProof, parseProtectedApproval, parseProtectedDerivativeSnapshot, parseProtectedFreeze, parseProtectedGateProof, parseProtectedPreflightProof, parseProtectedStatusProof, } from './dataset-maintenance-protected-contract.js';
6
+ import { verifyProtectedExecution, } from './dataset-maintenance-protected-verify.js';
7
+ import { maintenanceProjectedReferenceFingerprint } from './dataset-maintenance-plan.js';
8
+ import { isSnapshotCompletenessCompatible } from './dataset-maintenance-pagination.js';
9
+ import { admitMaintenanceAliasExecution, captureMaintenanceAliasExecutionGate, fetchMaintenanceAccountRows, fetchMaintenanceDerivativeSnapshot, fetchMaintenanceExactRows, normalizeMaintenancePageSize, preflightMaintenanceAliasExecution, readMaintenanceAliasExecution, resolveMaintenanceRemoteContext, } from './dataset-maintenance-remote.js';
10
+ import { CliError } from './errors.js';
11
+ import { withStateFileLock } from './state-lock.js';
12
+ const DEFAULT_POLL_MS = 10_000;
13
+ const SHA256 = /^[a-f0-9]{64}$/u;
14
+ function normalizeWaitSeconds(value) {
15
+ const normalized = value ?? 0;
16
+ if (!Number.isInteger(normalized) || normalized < 0 || normalized > 86_400) {
17
+ throw new CliError('waitSeconds must be an integer between 0 and 86400.', {
18
+ code: 'DATASET_MAINTENANCE_PROTECTED_WAIT_INVALID',
19
+ exitCode: 2,
20
+ });
21
+ }
22
+ return normalized;
23
+ }
24
+ function normalizePollMs(value) {
25
+ const normalized = value ?? DEFAULT_POLL_MS;
26
+ if (!Number.isInteger(normalized) || normalized < 100 || normalized > 60_000) {
27
+ throw new CliError('pollMs must be an integer between 100 and 60000.', {
28
+ code: 'DATASET_MAINTENANCE_PROTECTED_POLL_INVALID',
29
+ exitCode: 2,
30
+ });
31
+ }
32
+ return normalized;
33
+ }
34
+ function clock(options) {
35
+ return options.now ?? new Date();
36
+ }
37
+ function errorDetails(error) {
38
+ return {
39
+ name: error instanceof Error ? error.name : 'Error',
40
+ message: error instanceof Error ? error.message : String(error),
41
+ code: error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
42
+ ? error.code
43
+ : null,
44
+ };
45
+ }
46
+ function readArtifact(options) {
47
+ const resolved = path.resolve(options.filePath);
48
+ const text = readFileSync(resolved, 'utf8');
49
+ return {
50
+ resolved,
51
+ value: readJsonFile(resolved, options.label),
52
+ file_sha256: sha256Text(text),
53
+ };
54
+ }
55
+ function ensurePrivateDirectory(directory) {
56
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
57
+ chmodSync(directory, 0o700);
58
+ }
59
+ function writePrivateImmutableJson(filePath, value) {
60
+ const resolved = path.resolve(filePath);
61
+ const text = `${stableJsonText(value)}\n`;
62
+ ensurePrivateDirectory(path.dirname(resolved));
63
+ if (existsSync(resolved)) {
64
+ if (readFileSync(resolved, 'utf8') !== text) {
65
+ throw new CliError(`Refusing to overwrite protected evidence: ${resolved}`, {
66
+ code: 'DATASET_MAINTENANCE_PROTECTED_ARTIFACT_IMMUTABLE',
67
+ exitCode: 1,
68
+ });
69
+ }
70
+ chmodSync(resolved, 0o600);
71
+ return resolved;
72
+ }
73
+ writeFileSync(resolved, text, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
74
+ return resolved;
75
+ }
76
+ function appendPrivateJsonLine(filePath, value) {
77
+ const resolved = path.resolve(filePath);
78
+ ensurePrivateDirectory(path.dirname(resolved));
79
+ appendFileSync(resolved, `${stableJsonText(value)}\n`, {
80
+ encoding: 'utf8',
81
+ flag: 'a',
82
+ mode: 0o600,
83
+ });
84
+ chmodSync(resolved, 0o600);
85
+ return resolved;
86
+ }
87
+ function nextAttemptReportPath(outDir) {
88
+ let attempt = 1;
89
+ while (attemptArtifactPaths(outDir, attempt).some((filePath) => existsSync(filePath))) {
90
+ attempt += 1;
91
+ }
92
+ return attemptArtifactPaths(outDir, attempt)[0];
93
+ }
94
+ function attemptArtifactPaths(outDir, attempt) {
95
+ const suffix = `attempt-${String(attempt).padStart(4, '0')}`;
96
+ return [
97
+ path.join(outDir, `protected-run-report.${suffix}.json`),
98
+ path.join(outDir, `protected-audit-readback.${suffix}.json`),
99
+ path.join(outDir, `protected-primary-readback.${suffix}.json`),
100
+ path.join(outDir, `protected-reference-readback.${suffix}.json`),
101
+ path.join(outDir, `protected-derivative-readback.${suffix}.json`),
102
+ ];
103
+ }
104
+ function allocateAttemptArtifacts(prepared) {
105
+ const attemptReport = nextAttemptReportPath(prepared.outDir);
106
+ const suffix = path.basename(attemptReport, '.json').replace('protected-run-report.', '');
107
+ return {
108
+ ...prepared,
109
+ artifacts: {
110
+ ...prepared.artifacts,
111
+ audit_readback: path.join(prepared.outDir, `protected-audit-readback.${suffix}.json`),
112
+ primary_readback: path.join(prepared.outDir, `protected-primary-readback.${suffix}.json`),
113
+ reference_readback: path.join(prepared.outDir, `protected-reference-readback.${suffix}.json`),
114
+ derivative_readback: path.join(prepared.outDir, `protected-derivative-readback.${suffix}.json`),
115
+ attempt_report: attemptReport,
116
+ },
117
+ };
118
+ }
119
+ function prepareProtectedExecutionWithDependencies(options, dependencies) {
120
+ if (options.commit === options.statusOnly) {
121
+ throw new CliError('Choose exactly one of commit or statusOnly.', {
122
+ code: 'DATASET_MAINTENANCE_PROTECTED_MODE_INVALID',
123
+ exitCode: 2,
124
+ });
125
+ }
126
+ if (options.commit &&
127
+ (!options.approveExecution ||
128
+ !SHA256.test(options.approveExecution) ||
129
+ typeof options.confirm !== 'string' ||
130
+ !options.confirm.trim())) {
131
+ throw new CliError('Protected commit requires the exact approval hash and account email.', {
132
+ code: 'DATASET_MAINTENANCE_PROTECTED_APPROVAL_REQUIRED',
133
+ exitCode: 2,
134
+ });
135
+ }
136
+ normalizeWaitSeconds(options.waitSeconds);
137
+ normalizePollMs(options.pollMs);
138
+ normalizeMaintenancePageSize(options.pageSize);
139
+ const planArtifact = dependencies.readArtifact({
140
+ filePath: options.planPath,
141
+ label: 'Maintenance plan',
142
+ });
143
+ const freezeArtifact = dependencies.readArtifact({
144
+ filePath: options.freezePath,
145
+ label: 'Protected execution freeze',
146
+ });
147
+ const approvalArtifact = dependencies.readArtifact({
148
+ filePath: options.approvalPath,
149
+ label: 'Protected execution approval',
150
+ });
151
+ const plan = dependencies.parsePlan(planArtifact.value);
152
+ const planPath = planArtifact.resolved;
153
+ const planDir = path.dirname(planPath);
154
+ const aliasPlanRequest = dependencies.buildAliasPlan({ plan, planDir });
155
+ const freeze = dependencies.parseFreeze(freezeArtifact.value);
156
+ const approval = dependencies.parseApproval(approvalArtifact.value);
157
+ dependencies.assertFreezeMatchesPlan({
158
+ plan,
159
+ planFileSha256: planArtifact.file_sha256,
160
+ aliasPlanRequestSha256: sha256Json(aliasPlanRequest),
161
+ freeze,
162
+ });
163
+ dependencies.assertApprovalBindings({
164
+ approval,
165
+ freeze,
166
+ freezeFileSha256: freezeArtifact.file_sha256,
167
+ approvalFileSha256: approvalArtifact.file_sha256,
168
+ approveExecution: options.statusOnly
169
+ ? approval.approval_identity_sha256
170
+ : options.approveExecution,
171
+ });
172
+ const identity = dependencies.buildIdentity({
173
+ freeze,
174
+ approval,
175
+ freezeFileSha256: freezeArtifact.file_sha256,
176
+ approvalFileSha256: approvalArtifact.file_sha256,
177
+ });
178
+ const outDir = path.resolve(options.outDir);
179
+ const artifacts = {
180
+ execution_seal: path.join(outDir, 'protected-execution-seal.json'),
181
+ preflight_evidence: path.join(outDir, 'protected-preflight-evidence.json'),
182
+ gate_receipts: path.join(outDir, 'protected-gate-receipts.jsonl'),
183
+ submission_attempt: path.join(outDir, 'protected-submission-attempt.json'),
184
+ admission_response: path.join(outDir, 'protected-admission-response.json'),
185
+ admission_transport_error: path.join(outDir, 'protected-admission-transport-error.json'),
186
+ status_progress: path.join(outDir, 'protected-status-progress.jsonl'),
187
+ primary_readback: path.join(outDir, 'protected-primary-readback.json'),
188
+ reference_readback: path.join(outDir, 'protected-reference-readback.json'),
189
+ audit_readback: path.join(outDir, 'protected-audit-readback.json'),
190
+ derivative_readback: path.join(outDir, 'protected-derivative-readback.json'),
191
+ terminal_report: path.join(outDir, 'protected-terminal-report.json'),
192
+ attempt_report: path.join(outDir, 'protected-run-report.unallocated.json'),
193
+ };
194
+ return {
195
+ planPath,
196
+ planDir,
197
+ freezePath: freezeArtifact.resolved,
198
+ approvalPath: approvalArtifact.resolved,
199
+ outDir,
200
+ plan,
201
+ freeze,
202
+ approval,
203
+ identity,
204
+ aliasPlanRequest,
205
+ artifacts,
206
+ };
207
+ }
208
+ function prepareProtectedExecution(options) {
209
+ return prepareProtectedExecutionWithDependencies(options, {
210
+ readArtifact,
211
+ parsePlan: parseMaintenancePlan,
212
+ buildAliasPlan: buildAliasPlanRequest,
213
+ parseFreeze: parseProtectedFreeze,
214
+ parseApproval: parseProtectedApproval,
215
+ assertFreezeMatchesPlan: assertProtectedFreezeMatchesPlan,
216
+ assertApprovalBindings: assertProtectedApprovalBindings,
217
+ buildIdentity: buildProtectedExecutionIdentity,
218
+ });
219
+ }
220
+ function assertContextBindings(options) {
221
+ if (options.context.project_ref !== options.prepared.identity.project_ref ||
222
+ options.context.account.user_id !== options.prepared.identity.actor.user_id ||
223
+ options.context.account.email !== options.prepared.identity.actor.email ||
224
+ (options.commit && options.confirm !== options.context.account.email)) {
225
+ throw new CliError('Authenticated RLS context does not match the sealed production project, actor, and confirmation.', {
226
+ code: 'DATASET_MAINTENANCE_PROTECTED_CONTEXT_MISMATCH',
227
+ exitCode: 1,
228
+ details: {
229
+ expected_project_ref: options.prepared.identity.project_ref,
230
+ observed_project_ref: options.context.project_ref,
231
+ expected_user_id: options.prepared.identity.actor.user_id,
232
+ observed_user_id: options.context.account.user_id,
233
+ },
234
+ });
235
+ }
236
+ }
237
+ function projectedRows(options) {
238
+ const projected = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), { ...row }]));
239
+ for (const action of options.plan.actions) {
240
+ const row = projected.get(maintenanceRowKey(action));
241
+ if (row) {
242
+ projected.set(maintenanceRowKey(action), {
243
+ ...row,
244
+ json_ordered: loadMaintenanceDesiredPayload(options.planDir, action),
245
+ });
246
+ }
247
+ }
248
+ return [...projected.values()].sort((left, right) => maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)));
249
+ }
250
+ function assertStrictBeforeState(options) {
251
+ const { plan } = options.prepared;
252
+ if (!plan.snapshot_completeness ||
253
+ !isSnapshotCompletenessCompatible(options.completeness, plan.snapshot_completeness, MAINTENANCE_SCAN_TABLES)) {
254
+ throw new CliError('Production RLS census does not match the frozen complete snapshot.', {
255
+ code: 'DATASET_MAINTENANCE_PROTECTED_SNAPSHOT_INCOMPLETE',
256
+ exitCode: 1,
257
+ });
258
+ }
259
+ const snapshots = options.currentRows
260
+ .map(snapshotRemoteRow)
261
+ .sort((left, right) => maintenanceRowKey(left).localeCompare(maintenanceRowKey(right)));
262
+ if (sha256Json(snapshots) !== plan.visible_snapshot_sha256) {
263
+ throw new CliError('Production RLS visible snapshot drifted after the freeze.', {
264
+ code: 'DATASET_MAINTENANCE_PROTECTED_VISIBLE_SNAPSHOT_DRIFT',
265
+ exitCode: 1,
266
+ });
267
+ }
268
+ const expectedKeys = new Set([
269
+ ...plan.actions.map(maintenanceRowKey),
270
+ ...plan.protected_rows.map(maintenanceRowKey),
271
+ ]);
272
+ if (expectedKeys.size !== options.currentRows.length ||
273
+ options.currentRows.some((row) => !expectedKeys.has(maintenanceRowKey(row)))) {
274
+ throw new CliError('Production owner account contains missing or unexpected rows.', {
275
+ code: 'DATASET_MAINTENANCE_PROTECTED_ACCOUNT_CENSUS_DRIFT',
276
+ exitCode: 1,
277
+ });
278
+ }
279
+ const current = new Map(options.currentRows.map((row) => [maintenanceRowKey(row), row]));
280
+ for (const row of plan.protected_rows) {
281
+ const observed = current.get(maintenanceRowKey(row));
282
+ if (!observed || snapshotRemoteRow(observed).row_sha256 !== row.row_sha256) {
283
+ throw new CliError(`Protected row drifted: ${row.id}`, {
284
+ code: 'DATASET_MAINTENANCE_PROTECTED_ROW_DRIFT',
285
+ exitCode: 1,
286
+ details: row,
287
+ });
288
+ }
289
+ }
290
+ for (const action of plan.actions) {
291
+ const observed = current.get(maintenanceRowKey(action));
292
+ if (!action.before ||
293
+ !observed ||
294
+ observed.user_id !== action.expected_user_id ||
295
+ observed.state_code !== 0 ||
296
+ snapshotRemoteRow(observed).row_sha256 !== action.before.row_sha256) {
297
+ throw new CliError(`Action row is no longer in the exact frozen before state: ${action.action_id}`, {
298
+ code: 'DATASET_MAINTENANCE_PROTECTED_ACTION_DRIFT',
299
+ exitCode: 1,
300
+ });
301
+ }
302
+ }
303
+ const finalRows = projectedRows({
304
+ plan,
305
+ planDir: options.prepared.planDir,
306
+ currentRows: options.currentRows,
307
+ });
308
+ if (sha256Json(maintenanceProjectedReferenceFingerprint(finalRows)) !==
309
+ plan.projected_reference_sha256) {
310
+ throw new CliError('Projected reference closure drifted before protected execution.', {
311
+ code: 'DATASET_MAINTENANCE_PROTECTED_REFERENCE_DRIFT',
312
+ exitCode: 1,
313
+ });
314
+ }
315
+ }
316
+ async function assertSupportSnapshots(options) {
317
+ for (const batch of options.prepared.plan.alias_batches ?? []) {
318
+ for (const snapshot of [
319
+ batch.target_snapshots.unitgroup,
320
+ batch.target_snapshots.flowproperty,
321
+ batch.target_snapshots.source_unitgroup,
322
+ ]) {
323
+ if (!snapshot) {
324
+ throw new CliError(`Alias support snapshot is absent for ${batch.batch_id}.`, {
325
+ code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',
326
+ exitCode: 1,
327
+ });
328
+ }
329
+ const exact = await fetchMaintenanceExactRows({
330
+ context: options.context,
331
+ table: snapshot.table,
332
+ id: snapshot.id,
333
+ version: snapshot.version,
334
+ });
335
+ const row = exact.rows.length === 1 ? exact.rows[0] : null;
336
+ if (!row ||
337
+ row.user_id !== options.prepared.identity.actor.user_id ||
338
+ row.state_code !== 0 ||
339
+ snapshotRemoteRow(row).row_sha256 !== snapshot.row_sha256) {
340
+ throw new CliError(`Alias support row drifted for ${batch.batch_id}.`, {
341
+ code: 'DATASET_MAINTENANCE_PROTECTED_SUPPORT_DRIFT',
342
+ exitCode: 1,
343
+ details: { table: snapshot.table, id: snapshot.id, version: snapshot.version },
344
+ });
345
+ }
346
+ }
347
+ }
348
+ }
349
+ async function assertDerivativeBaselines(options) {
350
+ const targets = options.prepared.identity.derivative_targets;
351
+ for (let offset = 0; offset < targets.length; offset += 5) {
352
+ const chunk = targets.slice(offset, offset + 5);
353
+ const snapshots = await Promise.all(chunk.map(async (target) => parseProtectedDerivativeSnapshot(await fetchMaintenanceDerivativeSnapshot({
354
+ context: options.context,
355
+ table: target.table,
356
+ id: target.id,
357
+ version: target.version,
358
+ }), {
359
+ table: target.table,
360
+ id: target.id,
361
+ version: target.version,
362
+ userId: target.user_id,
363
+ })));
364
+ for (const [index, snapshot] of snapshots.entries()) {
365
+ const target = chunk[index];
366
+ if (snapshot.snapshot_sha256 !== target.baseline_snapshot_sha256) {
367
+ throw new CliError('A protected derivative baseline drifted before preflight.', {
368
+ code: 'DATASET_MAINTENANCE_PROTECTED_DERIVATIVE_BASELINE_DRIFT',
369
+ exitCode: 1,
370
+ details: { table: target.table, id: target.id, version: target.version },
371
+ });
372
+ }
373
+ }
374
+ }
375
+ }
376
+ async function captureProtectedGatesWithDependencies(options, dependencies) {
377
+ const gateNames = ['primary_support_plan', 'execution_unused', 'derivative_quiescence'];
378
+ const proofs = [];
379
+ for (const gate of gateNames) {
380
+ const raw = await dependencies.captureGate({
381
+ context: options.context,
382
+ requestId: options.prepared.identity.request_id,
383
+ preflightToken: options.preflight.preflight_token,
384
+ gateName: gate,
385
+ });
386
+ const proof = dependencies.parseGate(raw, {
387
+ identity: options.prepared.identity,
388
+ preflight: options.preflight,
389
+ gate,
390
+ });
391
+ dependencies.appendReceipt(options.receiptPath, {
392
+ observed_at_utc: dependencies.nowIso(),
393
+ proof,
394
+ raw_response_sha256: sha256Json(raw),
395
+ });
396
+ proofs.push(proof);
397
+ }
398
+ return {
399
+ proofs,
400
+ results: {
401
+ primary_support_plan: proofs[0].result,
402
+ execution_unused: proofs[1].result,
403
+ derivative_quiescence: proofs[2].result,
404
+ },
405
+ };
406
+ }
407
+ async function captureProtectedGates(options) {
408
+ return captureProtectedGatesWithDependencies(options, {
409
+ captureGate: captureMaintenanceAliasExecutionGate,
410
+ parseGate: parseProtectedGateProof,
411
+ appendReceipt: appendPrivateJsonLine,
412
+ nowIso: () => new Date().toISOString(),
413
+ });
414
+ }
415
+ function validateExistingMarker(markerPath, identity) {
416
+ if (!existsSync(markerPath))
417
+ return null;
418
+ const marker = readJsonFile(markerPath, 'Protected submission attempt');
419
+ if (!isJsonObject(marker) ||
420
+ marker.schema_version !== PROTECTED_EXECUTION_CONTRACT.marker_schema ||
421
+ marker.request_id !== identity.request_id ||
422
+ marker.identity_sha256 !== identity.identity_sha256 ||
423
+ marker.plan_sha256 !== identity.plan_sha256 ||
424
+ marker.operation_id !== identity.operation_id ||
425
+ marker.max_admit_posts !== 1 ||
426
+ marker.automatic_retry !== false) {
427
+ throw new CliError('Existing protected submission marker is foreign or malformed.', {
428
+ code: 'DATASET_MAINTENANCE_PROTECTED_MARKER_INVALID',
429
+ exitCode: 1,
430
+ });
431
+ }
432
+ return marker;
433
+ }
434
+ async function readAndVerifyWithDependencies(options, dependencies) {
435
+ const waitSeconds = normalizeWaitSeconds(options.command.waitSeconds);
436
+ const pollMs = normalizePollMs(options.command.pollMs);
437
+ const deadline = dependencies.nowMs() + waitSeconds * 1_000;
438
+ const sleep = options.command.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
439
+ while (true) {
440
+ let raw;
441
+ try {
442
+ raw = await dependencies.readExecution({
443
+ context: options.context,
444
+ requestId: options.prepared.identity.request_id,
445
+ });
446
+ }
447
+ catch (error) {
448
+ appendPrivateJsonLine(options.prepared.artifacts.status_progress, {
449
+ observed_at_utc: new Date().toISOString(),
450
+ result: 'read_error',
451
+ retry_scope: 'status_read_only',
452
+ error: errorDetails(error),
453
+ });
454
+ if (dependencies.nowMs() < deadline) {
455
+ await sleep(Math.min(pollMs, Math.max(deadline - dependencies.nowMs(), 0)));
456
+ continue;
457
+ }
458
+ return {
459
+ proof: null,
460
+ verification: {
461
+ status: 'indeterminate',
462
+ issues: [
463
+ {
464
+ code: 'PROTECTED_STATUS_READ_UNAVAILABLE',
465
+ message: 'Protected execution status could not be read; admission must not be retried.',
466
+ details: errorDetails(error),
467
+ },
468
+ ],
469
+ account_readback: null,
470
+ derivative_readback: null,
471
+ },
472
+ };
473
+ }
474
+ const proof = dependencies.parseStatus(raw, options.prepared.identity);
475
+ appendPrivateJsonLine(options.prepared.artifacts.status_progress, {
476
+ observed_at_utc: new Date().toISOString(),
477
+ proof,
478
+ raw_response_sha256: sha256Json(raw),
479
+ });
480
+ const waitForAdmissionVisibility = proof.execution_status === 'not_admitted' && dependencies.nowMs() < deadline;
481
+ if ((proof.status !== 'pending' && !waitForAdmissionVisibility) ||
482
+ dependencies.nowMs() >= deadline) {
483
+ try {
484
+ return {
485
+ proof,
486
+ verification: await dependencies.verifyExecution({
487
+ plan: options.prepared.plan,
488
+ planDir: options.prepared.planDir,
489
+ identity: options.prepared.identity,
490
+ proof,
491
+ context: options.context,
492
+ pageSize: options.command.pageSize,
493
+ }),
494
+ };
495
+ }
496
+ catch (error) {
497
+ appendPrivateJsonLine(options.prepared.artifacts.status_progress, {
498
+ observed_at_utc: new Date().toISOString(),
499
+ result: 'verification_error',
500
+ retry_scope: 'independent_readback_only',
501
+ request_id: proof.request_id,
502
+ error: errorDetails(error),
503
+ });
504
+ if (dependencies.nowMs() < deadline) {
505
+ await sleep(Math.min(pollMs, Math.max(deadline - dependencies.nowMs(), 0)));
506
+ continue;
507
+ }
508
+ return {
509
+ proof,
510
+ verification: {
511
+ status: 'indeterminate',
512
+ issues: [
513
+ {
514
+ code: 'PROTECTED_TERMINAL_READBACK_UNAVAILABLE',
515
+ message: 'Terminal proof was returned but independent RLS readback was unavailable.',
516
+ details: errorDetails(error),
517
+ },
518
+ ],
519
+ account_readback: null,
520
+ derivative_readback: null,
521
+ },
522
+ };
523
+ }
524
+ }
525
+ await sleep(Math.min(pollMs, Math.max(deadline - dependencies.nowMs(), 0)));
526
+ }
527
+ }
528
+ async function readAndVerify(options) {
529
+ return readAndVerifyWithDependencies(options, {
530
+ readExecution: readMaintenanceAliasExecution,
531
+ parseStatus: parseProtectedStatusProof,
532
+ verifyExecution: verifyProtectedExecution,
533
+ nowMs: Date.now,
534
+ });
535
+ }
536
+ function buildReport(options) {
537
+ return {
538
+ schema_version: PROTECTED_EXECUTION_CONTRACT.report_schema,
539
+ generated_at_utc: new Date().toISOString(),
540
+ mode: options.command.statusOnly ? 'status_only' : 'commit',
541
+ status: options.verification.status,
542
+ request_id: options.prepared.identity.request_id,
543
+ identity_sha256: options.prepared.identity.identity_sha256,
544
+ plan_sha256: options.prepared.plan.plan_sha256,
545
+ operation_id: options.prepared.plan.operation_id,
546
+ actor: options.prepared.identity.actor,
547
+ project_ref: options.prepared.identity.project_ref,
548
+ admission: options.admission,
549
+ database_status: options.proof,
550
+ issues: options.verification.issues,
551
+ artifacts: options.prepared.artifacts,
552
+ };
553
+ }
554
+ function canonicalTerminalReport(report) {
555
+ return {
556
+ schema_version: report.schema_version,
557
+ artifact_kind: 'protected_terminal_canonical',
558
+ status: report.status,
559
+ request_id: report.request_id,
560
+ identity_sha256: report.identity_sha256,
561
+ plan_sha256: report.plan_sha256,
562
+ operation_id: report.operation_id,
563
+ actor: report.actor,
564
+ project_ref: report.project_ref,
565
+ database_status: report.database_status,
566
+ issues: report.issues,
567
+ };
568
+ }
569
+ function persistVerificationArtifacts(options) {
570
+ if (options.proof?.primary_readback) {
571
+ writePrivateImmutableJson(options.prepared.artifacts.audit_readback, options.proof.primary_readback);
572
+ }
573
+ if (options.verification.account_readback) {
574
+ writePrivateImmutableJson(options.prepared.artifacts.primary_readback, options.verification.account_readback);
575
+ writePrivateImmutableJson(options.prepared.artifacts.reference_readback, {
576
+ projected_reference_sha256: sha256Json(maintenanceProjectedReferenceFingerprint(options.verification.account_readback.rows)),
577
+ expected_projected_reference_sha256: options.prepared.plan.projected_reference_sha256,
578
+ });
579
+ }
580
+ if (options.verification.derivative_readback) {
581
+ writePrivateImmutableJson(options.prepared.artifacts.derivative_readback, options.verification.derivative_readback);
582
+ }
583
+ const terminalReadbackRetryable = options.verification.issues.some((entry) => entry.code === 'PROTECTED_TERMINAL_READBACK_UNAVAILABLE');
584
+ writePrivateImmutableJson(options.prepared.artifacts.attempt_report, options.report);
585
+ if (options.proof &&
586
+ options.proof.status !== 'pending' &&
587
+ options.proof.execution_status !== 'not_admitted' &&
588
+ !terminalReadbackRetryable &&
589
+ !existsSync(options.prepared.artifacts.terminal_report)) {
590
+ writePrivateImmutableJson(options.prepared.artifacts.terminal_report, canonicalTerminalReport(options.report));
591
+ }
592
+ }
593
+ async function runPreparedProtectedExecution(options, basePrepared, dependencies) {
594
+ ensurePrivateDirectory(basePrepared.outDir);
595
+ return dependencies.withStateLock(basePrepared.artifacts.submission_attempt, { reason: `dataset_maintenance_protected_${basePrepared.identity.request_id}` }, async () => {
596
+ writePrivateImmutableJson(basePrepared.artifacts.execution_seal, {
597
+ schema_version: PROTECTED_EXECUTION_CONTRACT.freeze_schema,
598
+ request_id: basePrepared.identity.request_id,
599
+ identity_sha256: basePrepared.identity.identity_sha256,
600
+ plan_path: basePrepared.planPath,
601
+ freeze_path: basePrepared.freezePath,
602
+ approval_path: basePrepared.approvalPath,
603
+ plan_sha256: basePrepared.plan.plan_sha256,
604
+ operation_id: basePrepared.plan.operation_id,
605
+ project_ref: basePrepared.identity.project_ref,
606
+ actor: basePrepared.identity.actor,
607
+ bindings: basePrepared.identity.bindings,
608
+ });
609
+ const prepared = allocateAttemptArtifacts(basePrepared);
610
+ const existingMarker = validateExistingMarker(prepared.artifacts.submission_attempt, prepared.identity);
611
+ if (options.commit && existingMarker) {
612
+ throw new CliError('A protected submission marker already exists. Use status-only; admission cannot be retried.', {
613
+ code: 'DATASET_MAINTENANCE_PROTECTED_ATTEMPT_EXISTS',
614
+ exitCode: 1,
615
+ });
616
+ }
617
+ const context = await dependencies.resolveContext({
618
+ env: options.env,
619
+ fetchImpl: options.fetchImpl,
620
+ timeoutMs: options.timeoutMs,
621
+ now: options.now,
622
+ });
623
+ assertContextBindings({
624
+ prepared,
625
+ context,
626
+ confirm: options.confirm,
627
+ commit: options.commit,
628
+ });
629
+ let admission = null;
630
+ if (options.commit) {
631
+ const current = await dependencies.fetchAccountRows({
632
+ context,
633
+ userId: prepared.identity.actor.user_id,
634
+ pageSize: options.pageSize,
635
+ });
636
+ assertStrictBeforeState({
637
+ prepared,
638
+ currentRows: current.rows,
639
+ completeness: current.completeness,
640
+ });
641
+ await dependencies.assertSupport({ prepared, context });
642
+ await dependencies.assertBaselines({ prepared, context });
643
+ const preflightRequest = dependencies.buildPreflightRequest({
644
+ identity: prepared.identity,
645
+ plan: prepared.aliasPlanRequest,
646
+ freeze: prepared.freeze,
647
+ approval: prepared.approval,
648
+ });
649
+ const preflightRaw = await dependencies.preflightExecution({
650
+ context,
651
+ request: preflightRequest,
652
+ });
653
+ const preflight = dependencies.parsePreflight(preflightRaw, prepared.identity, clock(options));
654
+ const { preflight_token: preflightToken, ...preflightEvidence } = preflight;
655
+ writePrivateImmutableJson(prepared.artifacts.preflight_evidence, {
656
+ proof: preflightEvidence,
657
+ preflight_token_sha256: sha256Text(preflightToken),
658
+ raw_response_sha256: sha256Json(preflightRaw),
659
+ });
660
+ const gates = await dependencies.captureGates({
661
+ prepared,
662
+ context,
663
+ preflight,
664
+ receiptPath: prepared.artifacts.gate_receipts,
665
+ });
666
+ writePrivateImmutableJson(prepared.artifacts.submission_attempt, {
667
+ schema_version: PROTECTED_EXECUTION_CONTRACT.marker_schema,
668
+ prepared_at_utc: clock(options).toISOString(),
669
+ request_id: prepared.identity.request_id,
670
+ identity_sha256: prepared.identity.identity_sha256,
671
+ plan_sha256: prepared.identity.plan_sha256,
672
+ operation_id: prepared.identity.operation_id,
673
+ actor: prepared.identity.actor,
674
+ project_ref: prepared.identity.project_ref,
675
+ preflight_proof_sha256: preflight.preflight_proof_sha256,
676
+ preflight_token_sha256: sha256Text(preflight.preflight_token),
677
+ preflight_completed_at: preflight.completed_at,
678
+ preflight_expires_at: preflight.expires_at,
679
+ gate_results: gates.results,
680
+ gate_receipt_sha256: Object.fromEntries(gates.proofs.map((proof) => [proof.gate, proof.receipt_sha256])),
681
+ max_admit_posts: 1,
682
+ automatic_retry: false,
683
+ });
684
+ const admitRequest = dependencies.buildAdmitRequest({
685
+ preflight,
686
+ gateResults: gates.results,
687
+ });
688
+ try {
689
+ const admissionRaw = await dependencies.admitExecution({
690
+ context,
691
+ request: admitRequest,
692
+ });
693
+ admission = dependencies.parseAdmission(admissionRaw, prepared.identity, preflight);
694
+ writePrivateImmutableJson(prepared.artifacts.admission_response, {
695
+ proof: admission,
696
+ raw_response_sha256: sha256Json(admissionRaw),
697
+ });
698
+ }
699
+ catch (error) {
700
+ writePrivateImmutableJson(prepared.artifacts.admission_transport_error, {
701
+ schema_version: 1,
702
+ request_id: prepared.identity.request_id,
703
+ observed_at_utc: new Date().toISOString(),
704
+ classification: 'ambiguous_consumed_attempt',
705
+ error: errorDetails(error),
706
+ });
707
+ }
708
+ }
709
+ const readback = await dependencies.readAndVerify({ command: options, prepared, context });
710
+ const report = buildReport({
711
+ command: options,
712
+ prepared,
713
+ admission,
714
+ proof: readback.proof,
715
+ verification: readback.verification,
716
+ });
717
+ persistVerificationArtifacts({
718
+ prepared,
719
+ proof: readback.proof,
720
+ verification: readback.verification,
721
+ report,
722
+ });
723
+ return report;
724
+ });
725
+ }
726
+ export async function runDatasetMaintenanceProtected(options) {
727
+ const prepared = prepareProtectedExecution(options);
728
+ return runPreparedProtectedExecution(options, prepared, {
729
+ withStateLock: withStateFileLock,
730
+ resolveContext: resolveMaintenanceRemoteContext,
731
+ fetchAccountRows: fetchMaintenanceAccountRows,
732
+ assertSupport: assertSupportSnapshots,
733
+ assertBaselines: assertDerivativeBaselines,
734
+ buildPreflightRequest: buildProtectedPreflightRequest,
735
+ preflightExecution: preflightMaintenanceAliasExecution,
736
+ parsePreflight: parseProtectedPreflightProof,
737
+ captureGates: captureProtectedGates,
738
+ buildAdmitRequest: buildProtectedAdmitRequest,
739
+ admitExecution: admitMaintenanceAliasExecution,
740
+ parseAdmission: parseProtectedAdmissionProof,
741
+ readAndVerify,
742
+ });
743
+ }
744
+ export const __testInternals = {
745
+ allocateAttemptArtifacts,
746
+ appendPrivateJsonLine,
747
+ assertContextBindings,
748
+ assertDerivativeBaselines,
749
+ assertStrictBeforeState,
750
+ assertSupportSnapshots,
751
+ buildReport,
752
+ canonicalTerminalReport,
753
+ clock,
754
+ errorDetails,
755
+ nextAttemptReportPath,
756
+ normalizePollMs,
757
+ normalizeWaitSeconds,
758
+ prepareProtectedExecution,
759
+ prepareProtectedExecutionWithDependencies,
760
+ projectedRows,
761
+ readArtifact,
762
+ readAndVerify,
763
+ readAndVerifyWithDependencies,
764
+ runPreparedProtectedExecution,
765
+ captureProtectedGates,
766
+ captureProtectedGatesWithDependencies,
767
+ persistVerificationArtifacts,
768
+ validateExistingMarker,
769
+ writePrivateImmutableJson,
770
+ };
771
+ //# sourceMappingURL=dataset-maintenance-protected-run.js.map