@tiangong-lca/cli 0.0.24 → 0.0.26

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