@tiangong-lca/cli 0.0.24 → 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.
package/README.md CHANGED
@@ -17,8 +17,8 @@ checkPaths:
17
17
  - bin/**
18
18
  - src/cli.ts
19
19
  - src/main.ts
20
- lastReviewedAt: 2026-07-14
21
- lastReviewedCommit: ce8c18f270725adad789ada8f4582ca0e97e4117
20
+ lastReviewedAt: 2026-07-15
21
+ lastReviewedCommit: ca0cdd7549cad9003d08fb338223ba74682955ae
22
22
  ---
23
23
 
24
24
  # TianGong LCA CLI
@@ -31,6 +31,8 @@ Review note, 2026-07-13: maintenance scans now prove exact-count pagination even
31
31
 
32
32
  Review note, 2026-07-14: maintenance now includes the protected derivative-only `rebuild-derivatives` operation. V1 plans exactly one current-owner state-0 process with `action=rebuild_derivatives`, `target_mode=owner_draft`, and components `extracted_md` plus `embedding_ft`. Apply only proves guarded-RPC admission (`accepted`/`queued`); independent verify reports `pending`, `passed`, or `failed`.
33
33
 
34
+ Review note, 2026-07-15: `dataset maintenance run-protected` adds a production-only path for one sealed private alias execution and its exact 50-target derivative closure. The protected executor is server-dispatched and fenced by the authenticated owner plus exact actor/user_id/state_code=0 and plan-closure checks; RLS remains a defense on public and independent-read surfaces. It performs one server preflight, writes an immutable local attempt marker before one admission POST, and requires status-only recovery after any marker or ambiguous response. It has no dev, legacy-alias, publication, or state-code fallback.
35
+
34
36
  ## Run
35
37
 
36
38
  One-off published run:
@@ -283,6 +285,7 @@ tiangong-lca dataset maintenance plan --scope ./maintenance-scope.json --operati
283
285
  tiangong-lca dataset maintenance plan --scope ./derivative-rebuild-scope.json --operation rebuild-derivatives --out-dir /abs/path/to/derivative-rebuild --json
284
286
  tiangong-lca dataset maintenance apply --plan /abs/path/to/dataset-maintenance/maintenance-plan.json --commit --approve-plan <sha256> --confirm <current-account-email> --timeout-ms 10000 --json
285
287
  tiangong-lca dataset maintenance verify --plan /abs/path/to/dataset-maintenance/maintenance-plan.json --out-dir /abs/path/to/dataset-maintenance/verify --page-size 1000 --timeout-ms 10000 --json
288
+ tiangong-lca dataset maintenance run-protected --plan /abs/path/to/maintenance-plan.json --freeze /abs/path/to/protected-execution-seal.json --approval /abs/path/to/protected-approval.json --out-dir /abs/path/to/protected-run --status-only --json
286
289
  tiangong-lca lifecyclemodel auto-build --input ./examples/lifecyclemodel-auto-build.request.json --out-dir /abs/path/to/lifecyclemodel-run --json
287
290
  tiangong-lca lifecyclemodel validate-build --run-dir /abs/path/to/lifecyclemodel-run --json
288
291
  tiangong-lca lifecyclemodel publish-build --run-dir /abs/path/to/lifecyclemodel-run --json
@@ -327,12 +330,12 @@ For `dataset references rewrite`, `--commit` executes the state-aware save-draft
327
330
 
328
331
  ## Dataset Maintenance
329
332
 
330
- `dataset maintenance plan/apply/verify` is the row-level cleanup surface for bad imports, the fixed BAFU private alias rewrite, and one protected derivative-only process rebuild. It runs as the currently authenticated user and relies on RLS for visibility and ownership enforcement.
333
+ `dataset maintenance plan/apply/run-protected/verify` is the row-level cleanup surface for bad imports, the fixed BAFU private alias rewrite, and protected derivative rebuilds. Ordinary planning, apply, and independent verification use the authenticated account and RLS. The protected executor is server-dispatched and additionally enforces the sealed actor, user_id, state_code=0, exact target set, and closure hashes on every write.
331
334
 
332
335
  ```bash
333
336
  tiangong-lca dataset maintenance plan \
334
337
  --scope ./maintenance-scope.json \
335
- --operation merge-support-aliases \
338
+ --operation repair-references \
336
339
  --out-dir ./dataset-maintenance \
337
340
  --page-size 1000 \
338
341
  --timeout-ms 10000 \
@@ -352,8 +355,37 @@ tiangong-lca dataset maintenance verify \
352
355
  --page-size 1000 \
353
356
  --timeout-ms 10000 \
354
357
  --json
358
+
359
+ tiangong-lca dataset maintenance run-protected \
360
+ --plan ./protected-step2/maintenance-plan.json \
361
+ --freeze ./protected-step2/protected-execution-seal.json \
362
+ --approval ./protected-step2/protected-approval.json \
363
+ --out-dir ./protected-step2/run \
364
+ --commit \
365
+ --approve-execution <approved-execution-sha256> \
366
+ --confirm <current-account-email> \
367
+ --wait-seconds 60 \
368
+ --poll-ms 10000 \
369
+ --page-size 1000 \
370
+ --timeout-ms 10000 \
371
+ --json
372
+
373
+ tiangong-lca dataset maintenance run-protected \
374
+ --plan ./protected-step2/maintenance-plan.json \
375
+ --freeze ./protected-step2/protected-execution-seal.json \
376
+ --approval ./protected-step2/protected-approval.json \
377
+ --out-dir ./protected-step2/run \
378
+ --status-only \
379
+ --wait-seconds 60 \
380
+ --json
355
381
  ```
356
382
 
383
+ `run-protected` is a separate one-shot path for an already reviewed and sealed production execution; it does not replace ordinary planning. Both modes require the exact plan, freeze/seal, approval artifact, and private output directory. Commit mode additionally requires `--commit`, the exact approved execution identity through `--approve-execution`, and the authenticated account email through `--confirm`. `--status-only` is mutually exclusive with `--commit` and performs no preflight or admission.
384
+
385
+ Before requesting preflight, the command validates the sealed production project, full current-user RLS before-state, support closure, and exact derivative baseline. The server then returns the three expected gate digests and a token valid for at most 180 seconds; the CLI captures and compares the live gate receipts before admission. The server-dispatched write remains fenced to the authenticated actor's exact `user_id`, `state_code=0` rows and sealed plan/closure; independent readback still uses RLS. The CLI writes an immutable local submission marker and sends at most one admission POST. A marker, admission timeout, connection loss, or ambiguous admission response permanently switches that local run to status-only recovery; status-read failures may be polled only within the configured wait window and never cause a second admission or fallback to dev or the legacy whole-plan RPC. The default status polling interval is 10 seconds.
386
+
387
+ Success requires the terminal database proof and independent RLS readback to agree on the approved execution, exact row/exchange/audit closure, and exactly 50 derivative targets split into 23 flows and 27 processes. `pending`, `failed`, and `indeterminate` all return a non-zero exit status. The protected operation keeps all affected rows private to their owner, changes no `state_code`, and does not publish data.
388
+
357
389
  For the derivative-only profile, use the same three commands with `--operation rebuild-derivatives`. Its scope must contain exactly one `processes` action with `action: "rebuild_derivatives"`, `target_mode: "owner_draft"`, expected current owner, expected `state_code: 0`, and the exact component set `extracted_md` plus `embedding_ft`.
358
390
 
359
391
  `--page-size` accepts `1-5000` and is only the requested maximum. PostgREST may enforce a lower server-side cap. The CLI requests `Prefer: count=exact`, validates the exact total and returned range from each `Content-Range`, advances the next offset by the number of rows actually returned, and requires strict `id`/`version` ordering without missing or duplicate identities. Each accepted scan records per-table requested/effective page size, page count, rows fetched, exact total, and aggregate entity counts.
@@ -373,7 +405,7 @@ The scope is intentionally narrow:
373
405
 
374
406
  `plan` accepts the account scan only after its exact-count proof is complete, then writes the frozen `maintenance-scope.json`, `rls-visible-snapshot.json`, `protected-rows.jsonl`, `reference-impact-report.json`, `maintenance-plan.json`, and `dry-run-report.json`. The snapshot, dry-run report, and newly generated plan carry the aggregate completeness proof, so it is bound into the plan SHA-256. Alias plans additionally write `exchange-rewrite-plan.jsonl`, freeze current-owner state-0 target FP, target UG, and source UG snapshots for each batch, derive schema-valid desired payloads with matching embedded UUID/version, and include the exact closure, `modified_at`, hashes, conversion evidence, and postconditions in the approved plan. A derivative rebuild plan additionally obtains a database-produced snapshot for only the exact target action and binds its primary and derivative preconditions into the plan; large markdown/vector fields are not added to the account-wide scan. The plan SHA-256 is the approval identity; do not edit or recompute the plan after review.
375
407
 
376
- `apply` is write-disabled unless all three commit guards are present: `--commit`, `--approve-plan <sha256>`, and `--confirm <current-account-email>`. Before approval is persisted or any write runs, it requires a fresh complete exact-count account scan and re-checks the whole plan for drift; the current completeness proof is recorded in `approval-record.json`. Ordinary draft updates/deletes use their platform paths. The ordered `time` plus `length_time` alias request is sent once to `cmd_dataset_alias_plan_guarded` with `target_visibility=owner_draft`; the CLI has neither a per-dimension fallback nor a 52-write sequential fallback. The RPC locks and validates the complete 52-row/59-exchange closure before both dimensions commit, so a second-dimension failure rolls back the first. It checks actor ownership, state 0, exact payload/timestamp locks and embedded UUID/version, rejects missing or phantom flow/exchange references, and returns one plan summary audit id plus both batch and per-row audit proofs. The CLI writes `alias-plan-progress.jsonl` together with plan-bound per-batch, per-row, and per-exchange ledgers. A lost response or incomplete derived ledger is repaired only by replaying the same whole plan and matching every returned plan and batch proof.
408
+ `apply` is write-disabled unless all three commit guards are present: `--commit`, `--approve-plan <sha256>`, and `--confirm <current-account-email>`. Before approval is persisted or any write runs, it requires a fresh complete exact-count account scan and re-checks the whole plan for drift; the current completeness proof is recorded in `approval-record.json`. Ordinary draft updates/deletes use their platform paths. The original V1 alias adapter retains its frozen request and artifact contract for compatibility, but it is not an authorized execution or recovery fallback for a sealed production `merge-support-aliases` plan. That plan must use `run-protected`, whose database contract replaces replay with one durable attempt/admission identity.
377
409
 
378
410
  For `rebuild-derivatives`, apply submits the frozen single-action plan only to the authenticated guarded RPC. The database admission envelope must report `queued`; the CLI records that durable admission as an `accepted` action with queued proof. It does not mean markdown or embedding generation has completed. Replay must return the same durable request/proof rather than enqueueing a second rebuild. There is no fallback to a direct Edge call, `admin embedding-run`, a raw queue, SQL, service-role credentials, or raw REST mutation.
379
411
 
package/dist/src/cli.js CHANGED
@@ -56,6 +56,7 @@ import { runDatasetClassificationApply, runDatasetClassificationAudit, runDatase
56
56
  import { runDatasetMaintenanceClearAccount, } from './lib/dataset-maintenance-clear-account.js';
57
57
  import { runDatasetMaintenancePlan } from './lib/dataset-maintenance-plan.js';
58
58
  import { runDatasetMaintenanceApply } from './lib/dataset-maintenance-apply.js';
59
+ import { runDatasetMaintenanceProtected } from './lib/dataset-maintenance-protected-run.js';
59
60
  import { runDatasetMaintenanceVerify } from './lib/dataset-maintenance-verify.js';
60
61
  import { runDatasetSourceUploadAttachments, } from './lib/dataset-source-upload-attachments.js';
61
62
  function renderMainHelp(dotEnvStatus) {
@@ -131,6 +132,7 @@ Examples:
131
132
  tiangong-lca dataset references rewrite --input ./rows.jsonl --from flow:<old-id>@<old-version> --to flow:<new-id>@<new-version> --out-dir /abs/path/to/dataset-rewrite
132
133
  tiangong-lca dataset maintenance clear-account --out-dir /abs/path/to/account-clear --json
133
134
  tiangong-lca dataset maintenance plan --scope ./maintenance-scope.json --operation merge-support-aliases --out-dir /abs/path/to/dataset-maintenance
135
+ tiangong-lca dataset maintenance run-protected --plan ./maintenance-plan.json --freeze ./protected-execution-seal.json --approval ./protected-approval.json --out-dir /abs/path/to/protected-run --status-only
134
136
  tiangong-lca lifecyclemodel auto-build --input ./lifecyclemodel-auto-build.request.json --out-dir /abs/path/to/lifecyclemodel-run
135
137
  tiangong-lca lifecyclemodel validate-build --run-dir /abs/path/to/lifecyclemodel-run
136
138
  tiangong-lca lifecyclemodel publish-build --run-dir /abs/path/to/lifecyclemodel-run
@@ -283,6 +285,7 @@ Implemented Subcommands:
283
285
  maintenance clear-account Dry-run or clear current authenticated account-owned dataset rows through RLS
284
286
  maintenance plan Build an immutable, RLS-visible row-level maintenance plan
285
287
  maintenance apply Execute an explicitly approved maintenance plan through current-user RLS
288
+ maintenance run-protected Run or inspect one sealed, one-shot production maintenance execution
286
289
  maintenance verify Read back affected rows and references against the immutable plan
287
290
 
288
291
  Examples:
@@ -310,21 +313,25 @@ Examples:
310
313
  tiangong-lca dataset references refresh-remote --input ./rows.jsonl --out ./rows.refreshed.jsonl --out-dir ./dataset-reference-refresh --help
311
314
  tiangong-lca dataset maintenance clear-account --out-dir ./account-clear --json --help
312
315
  tiangong-lca dataset maintenance plan --scope ./maintenance-scope.json --operation merge-support-aliases --out-dir ./dataset-maintenance --help
316
+ tiangong-lca dataset maintenance run-protected --help
313
317
  `.trim();
314
318
  }
315
319
  function renderDatasetMaintenanceHelp() {
316
320
  return `Usage:
317
- tiangong-lca dataset maintenance <clear-account|plan|apply|verify> [options]
321
+ tiangong-lca dataset maintenance <clear-account|plan|apply|run-protected|verify> [options]
318
322
 
319
323
  Actions:
320
324
  clear-account Dry-run or delete current authenticated account-owned lifecyclemodels, processes, flows, sources, and contacts.
321
325
  plan Build an immutable maintenance plan from a scope manifest, visible remote snapshot, dependency impact report, and intended operation.
322
- apply Execute or durably admit an approved plan through current-user RLS and platform dataset command paths; never bypass RLS.
326
+ apply Execute or durably admit an ordinary approved plan through current-user RLS and platform dataset command paths.
327
+ run-protected Run or inspect one sealed production execution with one-shot admission and terminal derivative proof.
323
328
  verify Re-fetch rows, references, or derivative request state and report passed, pending, or failed independently of apply.
324
329
 
325
330
  Safety:
326
331
  plan and verify are read-only.
327
332
  apply is commit-only and requires --commit, the exact plan SHA-256 via --approve-plan, and the current account email via --confirm.
333
+ A sealed production merge-support-aliases execution must use run-protected; apply is not its fallback.
334
+ run-protected is production-only: authenticated owner context plus server-side actor/user_id/state_code=0 and exact-plan closure fences protect writes; RLS remains a defense on public and independent-read surfaces. It never falls back to dev, a legacy alias RPC, or a second admission POST.
328
335
 
329
336
  Required Artifact Contract:
330
337
  - maintenance-plan.json
@@ -334,13 +341,15 @@ Required Artifact Contract:
334
341
  - dry-run-report.json
335
342
  - commit-report.json
336
343
  - readback-verify-report.json
344
+ run-protected uses a separate immutable protected-execution seal, submission marker, append-only status ledger, and terminal report.
337
345
 
338
346
  Examples:
339
347
  tiangong-lca dataset maintenance clear-account --out-dir ./account-clear --json
340
348
  tiangong-lca dataset maintenance clear-account --commit --confirm user@example.com --out-dir ./account-clear
341
- tiangong-lca dataset maintenance plan --scope ./maintenance-scope.json --operation merge-support-aliases --out-dir ./dataset-maintenance
349
+ tiangong-lca dataset maintenance plan --scope ./maintenance-scope.json --operation repair-references --out-dir ./dataset-maintenance
342
350
  tiangong-lca dataset maintenance plan --scope ./derivative-rebuild-scope.json --operation rebuild-derivatives --out-dir ./derivative-rebuild
343
351
  tiangong-lca dataset maintenance apply --plan ./dataset-maintenance/maintenance-plan.json --commit --approve-plan <sha256> --confirm user@example.com
352
+ tiangong-lca dataset maintenance run-protected --plan ./maintenance-plan.json --freeze ./protected-execution-seal.json --approval ./protected-approval.json --out-dir ./protected-run --status-only
344
353
  tiangong-lca dataset maintenance verify --plan ./dataset-maintenance/maintenance-plan.json --out-dir ./dataset-maintenance/verify
345
354
  `.trim();
346
355
  }
@@ -373,6 +382,7 @@ Behavior:
373
382
  Commit-only. The command rejects dry-run mode, a missing --commit flag, a plan hash mismatch,
374
383
  or a confirmation email that does not match the current authenticated account.
375
384
  For rebuild-derivatives, success means guarded admission is queued; it never means derivatives completed.
385
+ Sealed production merge-support-aliases executions must use run-protected; this command is not a fallback.
376
386
 
377
387
  Options:
378
388
  --plan <file> Immutable maintenance-plan.json
@@ -386,6 +396,45 @@ Options:
386
396
  Outputs include approval-record.json and commit-report.json alongside an append-only action or admission ledger.
387
397
  `.trim();
388
398
  }
399
+ function renderDatasetMaintenanceRunProtectedHelp() {
400
+ return `Usage:
401
+ tiangong-lca dataset maintenance run-protected --plan <file> --freeze <file> --approval <file> --out-dir <dir> --commit --approve-execution <sha256> --confirm <email> [options]
402
+ tiangong-lca dataset maintenance run-protected --plan <file> --freeze <file> --approval <file> --out-dir <dir> --status-only [options]
403
+
404
+ Behavior:
405
+ Runs one sealed, server-dispatched production maintenance execution fenced to the authenticated actor's
406
+ exact owner-draft target set, or reads durable status through an auth.uid-, actor-, and plan-fenced RPC
407
+ without admitting work; only the independent table readback uses RLS.
408
+ Commit and status-only modes are mutually exclusive. The commit path validates the full private
409
+ before-state, accepts only server-derived gates within the 180-second preflight window, writes an immutable
410
+ local submission marker, and sends at most one admission POST. After a marker or ambiguous response,
411
+ use --status-only; the command never retries admission or falls back to dev or the legacy alias RPC.
412
+
413
+ Required in both modes:
414
+ --plan <file> Immutable maintenance-plan.json
415
+ --freeze <file> Protected production execution seal/freeze artifact
416
+ --approval <file> Exact human-approval binding artifact
417
+ --out-dir <dir> Private protected-run artifact directory
418
+
419
+ Commit-only guards:
420
+ --commit Submit the sealed execution exactly once
421
+ --approve-execution <sha256> Exact approved execution identity SHA-256
422
+ --confirm <email> Current authenticated account email
423
+
424
+ Recovery:
425
+ --status-only Read/poll and independently verify the sealed request; never preflight or admit
426
+
427
+ Options:
428
+ --wait-seconds <n> Maximum terminal-status wait in seconds; 0 performs one status read
429
+ --poll-ms <n> Positive polling interval in milliseconds; default 10000
430
+ --page-size <n> Requested RLS readback page size, 1-5000
431
+ --timeout-ms <n> Positive request timeout in milliseconds
432
+ --json Print compact JSON
433
+ -h, --help
434
+
435
+ Only a terminal passed proof exits successfully. pending, failed, and indeterminate return non-zero.
436
+ `.trim();
437
+ }
389
438
  function renderDatasetMaintenanceVerifyHelp() {
390
439
  return `Usage:
391
440
  tiangong-lca dataset maintenance verify --plan <file> [options]
@@ -3040,6 +3089,19 @@ function parseDatasetMaintenancePositiveInteger(value, flagName) {
3040
3089
  }
3041
3090
  return Number.parseInt(value.trim(), 10);
3042
3091
  }
3092
+ function parseDatasetMaintenanceNonNegativeInteger(value, flagName) {
3093
+ if (typeof value !== 'string') {
3094
+ return undefined;
3095
+ }
3096
+ if (!/^\d+$/u.test(value.trim())) {
3097
+ throw new CliError(`${flagName} must be a non-negative integer.`, {
3098
+ code: 'DATASET_MAINTENANCE_INTEGER_INVALID',
3099
+ exitCode: 2,
3100
+ details: { flag: flagName, value },
3101
+ });
3102
+ }
3103
+ return Number.parseInt(value.trim(), 10);
3104
+ }
3043
3105
  function parseDatasetMaintenancePlanFlags(args) {
3044
3106
  let values;
3045
3107
  try {
@@ -3126,6 +3188,54 @@ function parseDatasetMaintenanceApplyFlags(args) {
3126
3188
  dryRun: Boolean(values['dry-run']),
3127
3189
  };
3128
3190
  }
3191
+ function parseDatasetMaintenanceProtectedFlags(args) {
3192
+ let values;
3193
+ try {
3194
+ ({ values } = parseArgs({
3195
+ args,
3196
+ allowPositionals: false,
3197
+ strict: true,
3198
+ options: {
3199
+ help: { type: 'boolean', short: 'h' },
3200
+ json: { type: 'boolean' },
3201
+ plan: { type: 'string' },
3202
+ freeze: { type: 'string' },
3203
+ approval: { type: 'string' },
3204
+ 'out-dir': { type: 'string' },
3205
+ commit: { type: 'boolean' },
3206
+ 'status-only': { type: 'boolean' },
3207
+ 'approve-execution': { type: 'string' },
3208
+ confirm: { type: 'string' },
3209
+ 'wait-seconds': { type: 'string' },
3210
+ 'poll-ms': { type: 'string' },
3211
+ 'page-size': { type: 'string' },
3212
+ 'timeout-ms': { type: 'string' },
3213
+ },
3214
+ }));
3215
+ }
3216
+ catch (error) {
3217
+ throw new CliError(String(error), {
3218
+ code: 'INVALID_ARGS',
3219
+ exitCode: 2,
3220
+ });
3221
+ }
3222
+ return {
3223
+ help: Boolean(values.help),
3224
+ json: Boolean(values.json),
3225
+ planPath: typeof values.plan === 'string' ? values.plan : '',
3226
+ freezePath: typeof values.freeze === 'string' ? values.freeze : '',
3227
+ approvalPath: typeof values.approval === 'string' ? values.approval : '',
3228
+ outDir: typeof values['out-dir'] === 'string' ? values['out-dir'] : '',
3229
+ commit: Boolean(values.commit),
3230
+ statusOnly: Boolean(values['status-only']),
3231
+ approveExecution: typeof values['approve-execution'] === 'string' ? values['approve-execution'] : undefined,
3232
+ confirm: typeof values.confirm === 'string' ? values.confirm : undefined,
3233
+ waitSeconds: parseDatasetMaintenanceNonNegativeInteger(values['wait-seconds'], '--wait-seconds'),
3234
+ pollMs: parseDatasetMaintenancePositiveInteger(values['poll-ms'], '--poll-ms'),
3235
+ pageSize: parseDatasetMaintenancePositiveInteger(values['page-size'], '--page-size'),
3236
+ timeoutMs: parseDatasetMaintenancePositiveInteger(values['timeout-ms'], '--timeout-ms'),
3237
+ };
3238
+ }
3129
3239
  function parseDatasetMaintenanceVerifyFlags(args) {
3130
3240
  let values;
3131
3241
  try {
@@ -4905,6 +5015,7 @@ export async function executeCli(argv, deps) {
4905
5015
  const datasetMaintenanceClearAccountImpl = deps.runDatasetMaintenanceClearAccountImpl ?? runDatasetMaintenanceClearAccount;
4906
5016
  const datasetMaintenancePlanImpl = deps.runDatasetMaintenancePlanImpl ?? runDatasetMaintenancePlan;
4907
5017
  const datasetMaintenanceApplyImpl = deps.runDatasetMaintenanceApplyImpl ?? runDatasetMaintenanceApply;
5018
+ const datasetMaintenanceProtectedImpl = deps.runDatasetMaintenanceProtectedImpl ?? runDatasetMaintenanceProtected;
4908
5019
  const datasetMaintenanceVerifyImpl = deps.runDatasetMaintenanceVerifyImpl ?? runDatasetMaintenanceVerify;
4909
5020
  const datasetSourceUploadAttachmentsImpl = deps.runDatasetSourceUploadAttachmentsImpl ?? runDatasetSourceUploadAttachments;
4910
5021
  if (flags.version) {
@@ -5617,6 +5728,85 @@ export async function executeCli(argv, deps) {
5617
5728
  stderr: '',
5618
5729
  };
5619
5730
  }
5731
+ if (action === 'run-protected') {
5732
+ const datasetFlags = parseDatasetMaintenanceProtectedFlags(commandArgs.slice(1));
5733
+ if (datasetFlags.help) {
5734
+ return {
5735
+ exitCode: 0,
5736
+ stdout: `${renderDatasetMaintenanceRunProtectedHelp()}\n`,
5737
+ stderr: '',
5738
+ };
5739
+ }
5740
+ if (!datasetFlags.planPath) {
5741
+ throw new CliError('dataset maintenance run-protected requires --plan.', {
5742
+ code: 'DATASET_MAINTENANCE_PROTECTED_PLAN_REQUIRED',
5743
+ exitCode: 2,
5744
+ });
5745
+ }
5746
+ if (!datasetFlags.freezePath) {
5747
+ throw new CliError('dataset maintenance run-protected requires --freeze.', {
5748
+ code: 'DATASET_MAINTENANCE_PROTECTED_FREEZE_REQUIRED',
5749
+ exitCode: 2,
5750
+ });
5751
+ }
5752
+ if (!datasetFlags.approvalPath) {
5753
+ throw new CliError('dataset maintenance run-protected requires --approval.', {
5754
+ code: 'DATASET_MAINTENANCE_PROTECTED_APPROVAL_FILE_REQUIRED',
5755
+ exitCode: 2,
5756
+ });
5757
+ }
5758
+ if (!datasetFlags.outDir) {
5759
+ throw new CliError('dataset maintenance run-protected requires --out-dir.', {
5760
+ code: 'DATASET_MAINTENANCE_PROTECTED_OUT_DIR_REQUIRED',
5761
+ exitCode: 2,
5762
+ });
5763
+ }
5764
+ if (datasetFlags.commit && datasetFlags.statusOnly) {
5765
+ throw new CliError('Cannot pass both --commit and --status-only.', {
5766
+ code: 'DATASET_MAINTENANCE_PROTECTED_MODE_CONFLICT',
5767
+ exitCode: 2,
5768
+ });
5769
+ }
5770
+ if (!datasetFlags.commit && !datasetFlags.statusOnly) {
5771
+ throw new CliError('dataset maintenance run-protected requires either --commit or --status-only.', {
5772
+ code: 'DATASET_MAINTENANCE_PROTECTED_MODE_REQUIRED',
5773
+ exitCode: 2,
5774
+ });
5775
+ }
5776
+ if (datasetFlags.commit && !datasetFlags.approveExecution) {
5777
+ throw new CliError('dataset maintenance run-protected requires --approve-execution <sha256> with --commit.', {
5778
+ code: 'DATASET_MAINTENANCE_PROTECTED_APPROVAL_REQUIRED',
5779
+ exitCode: 2,
5780
+ });
5781
+ }
5782
+ if (datasetFlags.commit && !datasetFlags.confirm) {
5783
+ throw new CliError('dataset maintenance run-protected requires --confirm <email> with --commit.', {
5784
+ code: 'DATASET_MAINTENANCE_PROTECTED_CONFIRM_REQUIRED',
5785
+ exitCode: 2,
5786
+ });
5787
+ }
5788
+ const report = await datasetMaintenanceProtectedImpl({
5789
+ planPath: datasetFlags.planPath,
5790
+ freezePath: datasetFlags.freezePath,
5791
+ approvalPath: datasetFlags.approvalPath,
5792
+ outDir: datasetFlags.outDir,
5793
+ commit: datasetFlags.commit,
5794
+ statusOnly: datasetFlags.statusOnly,
5795
+ approveExecution: datasetFlags.approveExecution,
5796
+ confirm: datasetFlags.confirm,
5797
+ waitSeconds: datasetFlags.waitSeconds,
5798
+ pollMs: datasetFlags.pollMs,
5799
+ pageSize: datasetFlags.pageSize,
5800
+ timeoutMs: datasetFlags.timeoutMs,
5801
+ env: deps.env,
5802
+ fetchImpl: deps.fetchImpl,
5803
+ });
5804
+ return {
5805
+ exitCode: report.status === 'passed' ? 0 : 1,
5806
+ stdout: stringifyJson(report, datasetFlags.json),
5807
+ stderr: '',
5808
+ };
5809
+ }
5620
5810
  if (action === 'verify') {
5621
5811
  const datasetFlags = parseDatasetMaintenanceVerifyFlags(commandArgs.slice(1));
5622
5812
  if (datasetFlags.help) {
@@ -5646,7 +5836,7 @@ export async function executeCli(argv, deps) {
5646
5836
  stderr: '',
5647
5837
  };
5648
5838
  }
5649
- throw new CliError("dataset maintenance action must be 'clear-account', 'plan', 'apply', or 'verify'.", {
5839
+ throw new CliError("dataset maintenance action must be 'clear-account', 'plan', 'apply', 'run-protected', or 'verify'.", {
5650
5840
  code: 'DATASET_MAINTENANCE_ACTION_INVALID',
5651
5841
  exitCode: 2,
5652
5842
  });