@cdot65/prisma-airs-cli 5.7.1 → 5.8.1

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 (2) hide show
  1. package/dist/cli/index.js +1137 -143
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -59,7 +59,7 @@ function installProcessGuards() {
59
59
  }
60
60
 
61
61
  // src/cli/program.ts
62
- import { randomUUID as randomUUID11 } from "crypto";
62
+ import { randomUUID as randomUUID12 } from "crypto";
63
63
  import { readFileSync as readFileSync4 } from "fs";
64
64
  import { dirname as dirname5, join as join5 } from "path";
65
65
  import { fileURLToPath } from "url";
@@ -142,8 +142,8 @@ function modelSecurityClientOptions(config) {
142
142
 
143
143
  // src/reports/agentguard.ts
144
144
  async function collectAgentGuardReport(client, options = {}) {
145
- const maxPages2 = options.maxPages ?? 40;
146
- if (!Number.isSafeInteger(maxPages2) || maxPages2 < 1 || maxPages2 > 100)
145
+ const maxPages3 = options.maxPages ?? 40;
146
+ if (!Number.isSafeInteger(maxPages3) || maxPages3 < 1 || maxPages3 > 100)
147
147
  throw new Error("maxPages must be between 1 and 100");
148
148
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
149
149
  const end = now();
@@ -205,7 +205,7 @@ async function collectAgentGuardReport(client, options = {}) {
205
205
  const seen = /* @__PURE__ */ new Set();
206
206
  let total;
207
207
  try {
208
- for (let page = 0; page < maxPages2; page++) {
208
+ for (let page = 0; page < maxPages3; page++) {
209
209
  const r = await read(rows.length);
210
210
  s.pages++;
211
211
  if (!Number.isSafeInteger(r.total) || r.total < 0 || !Array.isArray(r.rows) || r.rows.length > 10)
@@ -222,7 +222,7 @@ async function collectAgentGuardReport(client, options = {}) {
222
222
  if (pageCount ? r.rows.length < 10 : rows.length === total) return rows;
223
223
  if (!r.rows.length) throw new Error();
224
224
  }
225
- s.notes.push(`Reached the ${maxPages2}-page budget.`);
225
+ s.notes.push(`Reached the ${maxPages3}-page budget.`);
226
226
  throw new Error();
227
227
  } catch (error) {
228
228
  if (!rows.length) throw error;
@@ -3494,7 +3494,7 @@ function registerAiGatewayInference(parent) {
3494
3494
  const outputError = (error) => controller.abort(error);
3495
3495
  let stream;
3496
3496
  let failed = false;
3497
- let failure2;
3497
+ let failure3;
3498
3498
  try {
3499
3499
  const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3500
3500
  const config = await loadConfig({ aiGwInferenceEndpoint: opts.endpoint });
@@ -3521,8 +3521,8 @@ function registerAiGatewayInference(parent) {
3521
3521
  process.once("SIGINT", interrupt);
3522
3522
  process.once("SIGTERM", interrupt);
3523
3523
  process.stdout.on("error", outputError);
3524
- const write = (text2) => new Promise((resolve8, reject) => {
3525
- process.stdout.write(text2, (error) => error ? reject(error) : resolve8());
3524
+ const write = (text2) => new Promise((resolve9, reject) => {
3525
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve9());
3526
3526
  });
3527
3527
  const requestOptions = { signal: controller.signal };
3528
3528
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -3557,7 +3557,7 @@ function registerAiGatewayInference(parent) {
3557
3557
  }
3558
3558
  } catch (error) {
3559
3559
  failed = true;
3560
- failure2 = error;
3560
+ failure3 = error;
3561
3561
  } finally {
3562
3562
  await stream?.cancel();
3563
3563
  controller.abort();
@@ -3565,10 +3565,10 @@ function registerAiGatewayInference(parent) {
3565
3565
  process.removeListener("SIGTERM", interrupt);
3566
3566
  process.stdout.removeListener("error", outputError);
3567
3567
  }
3568
- if (failed && failure2?.code !== "EPIPE") {
3569
- if (failure2?.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR)
3570
- usageError(failure2 instanceof Error ? failure2.message : "Invalid inference options");
3571
- fail(failure2);
3568
+ if (failed && failure3?.code !== "EPIPE") {
3569
+ if (failure3?.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR)
3570
+ usageError(failure3 instanceof Error ? failure3.message : "Invalid inference options");
3571
+ fail(failure3);
3572
3572
  }
3573
3573
  });
3574
3574
  }
@@ -3896,20 +3896,20 @@ function renderGatewayDetail(value, format) {
3896
3896
  );
3897
3897
  if (rendered) console.log(rendered);
3898
3898
  }
3899
- async function runList(command, opts, label, load3) {
3899
+ async function runList(command, opts, label, load4) {
3900
3900
  try {
3901
3901
  const format = await resolveOutput(command, opts);
3902
3902
  const client = await createAiGatewayClient();
3903
- renderGatewayList(responseItems(await load3(client)), format, label);
3903
+ renderGatewayList(responseItems(await load4(client)), format, label);
3904
3904
  } catch (error) {
3905
3905
  failAiGateway(error);
3906
3906
  }
3907
3907
  }
3908
- async function runDetail(command, opts, load3) {
3908
+ async function runDetail(command, opts, load4) {
3909
3909
  try {
3910
3910
  const format = await resolveOutput(command, opts);
3911
3911
  const client = await createAiGatewayClient();
3912
- renderGatewayDetail(await load3(client), format);
3912
+ renderGatewayDetail(await load4(client), format);
3913
3913
  } catch (error) {
3914
3914
  failAiGateway(error);
3915
3915
  }
@@ -5244,8 +5244,8 @@ function registerAiGatewayReportCommand(aigateway) {
5244
5244
  const iso = z2.string().datetime({ offset: true });
5245
5245
  if (opts.start && (!iso.safeParse(opts.start).success || !iso.safeParse(opts.end).success) || !Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start >= end)
5246
5246
  usageError("Provide a valid increasing ISO-8601 window with timezone");
5247
- const maxPages2 = Number(opts.maxPages);
5248
- if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages2) || maxPages2 > 100)
5247
+ const maxPages3 = Number(opts.maxPages);
5248
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages3) || maxPages3 > 100)
5249
5249
  usageError("--max-pages must be an integer from 1 to 100");
5250
5250
  if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
5251
5251
  usageError("--title must contain 1\u2013240 characters");
@@ -5269,7 +5269,7 @@ function registerAiGatewayReportCommand(aigateway) {
5269
5269
  ui.status("Collecting AI Gateway statistics and current configuration (read-only)...");
5270
5270
  const report = await collectGatewayEnvironmentReport(client, {
5271
5271
  title: opts.title,
5272
- maxPages: maxPages2,
5272
+ maxPages: maxPages3,
5273
5273
  workspace: opts.workspace,
5274
5274
  tsgId: config.mgmtTsgId ?? process.env.PANW_AI_GW_TSG_ID ?? "",
5275
5275
  start,
@@ -5900,8 +5900,8 @@ async function withTimeout(promise, ms) {
5900
5900
  try {
5901
5901
  return await Promise.race([
5902
5902
  promise,
5903
- new Promise((resolve8) => {
5904
- timer = setTimeout(() => resolve8(TIMED_OUT), ms);
5903
+ new Promise((resolve9) => {
5904
+ timer = setTimeout(() => resolve9(TIMED_OUT), ms);
5905
5905
  })
5906
5906
  ]);
5907
5907
  } finally {
@@ -6186,12 +6186,12 @@ function hasBin(bin) {
6186
6186
  }
6187
6187
  }
6188
6188
  function run2(bin, args, label) {
6189
- return new Promise((resolve8, reject) => {
6189
+ return new Promise((resolve9, reject) => {
6190
6190
  const child = spawn(bin, args, { stdio: "inherit" });
6191
6191
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
6192
6192
  child.on("close", (code) => {
6193
6193
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
6194
- else resolve8();
6194
+ else resolve9();
6195
6195
  });
6196
6196
  });
6197
6197
  }
@@ -6801,8 +6801,8 @@ function registerRedTeamReportCommand(redteam, scanReport) {
6801
6801
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
6802
6802
  if (format !== "html" && format !== "markdown")
6803
6803
  usageError("Report output must be html or markdown");
6804
- const maxPages2 = Number(opts.maxPages);
6805
- if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages2) || maxPages2 > 100)
6804
+ const maxPages3 = Number(opts.maxPages);
6805
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages3) || maxPages3 > 100)
6806
6806
  usageError("--max-pages must be an integer from 1 to 100");
6807
6807
  if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
6808
6808
  usageError("--title must contain 1\u2013240 characters");
@@ -6826,7 +6826,7 @@ function registerRedTeamReportCommand(redteam, scanReport) {
6826
6826
  ui.status("Collecting Red Team statistics and current configuration (read-only)...");
6827
6827
  const report = await collectRedTeamEnvironmentReport(client, {
6828
6828
  title: opts.title,
6829
- maxPages: maxPages2
6829
+ maxPages: maxPages3
6830
6830
  });
6831
6831
  const rendered = format === "html" ? renderRedTeamReportHtml(report) : renderRedTeamReportMarkdown(report);
6832
6832
  if (outputPath) {
@@ -8085,9 +8085,9 @@ function registerRedteamCommand(program) {
8085
8085
  }
8086
8086
 
8087
8087
  // src/cli/commands/runtime.ts
8088
- import { randomUUID as randomUUID9 } from "crypto";
8088
+ import { randomUUID as randomUUID10 } from "crypto";
8089
8089
  import * as fs5 from "fs";
8090
- import { readFile as readFile11 } from "fs/promises";
8090
+ import { readFile as readFile12 } from "fs/promises";
8091
8091
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
8092
8092
  import chalk11 from "chalk";
8093
8093
 
@@ -9550,23 +9550,1017 @@ function register5(dlp) {
9550
9550
  });
9551
9551
  }
9552
9552
 
9553
+ // src/cli/commands/dlp/transfer.ts
9554
+ import { randomUUID as randomUUID7 } from "crypto";
9555
+ import { readFile as readFile9, stat } from "fs/promises";
9556
+ import { extname, resolve as resolve5 } from "path";
9557
+ import { AISecSDKException as AISecSDKException2, ManagementClient } from "@cdot65/prisma-airs-sdk";
9558
+ import { dump as dump6, JSON_SCHEMA, load as load2 } from "js-yaml";
9559
+ import { ZodError } from "zod";
9560
+
9561
+ // src/backup/dlp-resources.ts
9562
+ import {
9563
+ AdvancedDataProfileRequestSchema,
9564
+ DataPatternRequestSchema,
9565
+ DataPatternResponseSchema,
9566
+ DataProfileResponseSchema,
9567
+ DictionaryRequestSchema,
9568
+ DictionaryResponseSchema
9569
+ } from "@cdot65/prisma-airs-sdk";
9570
+ import { z as z5 } from "zod";
9571
+ var DLP_RESOURCE_KINDS = ["dictionaries", "patterns", "profiles"];
9572
+ var DlpResourcesBackupSchema = z5.object({
9573
+ version: z5.literal(1),
9574
+ resourceType: z5.literal("dlp-resources"),
9575
+ exportedAt: z5.string().datetime({ offset: true }),
9576
+ source: z5.object({ tsgId: z5.string().min(1) }).strict(),
9577
+ dictionaries: z5.array(DictionaryResponseSchema).max(1e4),
9578
+ patterns: z5.array(DataPatternResponseSchema).max(1e4),
9579
+ profiles: z5.array(DataProfileResponseSchema).max(1e4)
9580
+ }).strict();
9581
+ function object(value) {
9582
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9583
+ }
9584
+ var PORTABLE_TECHNIQUES = /* @__PURE__ */ new Set(["regex", "weighted_regex", "file_property"]);
9585
+ var RETIRED_PATTERN = /* @__PURE__ */ new Set(["deleted", "disabled"]);
9586
+ var RETIRED_PROFILE = /* @__PURE__ */ new Set(["deleted", "disabled"]);
9587
+ var SERVER_METADATA_KEYS = /* @__PURE__ */ new Set([
9588
+ "id",
9589
+ "version",
9590
+ "status",
9591
+ "profile_status",
9592
+ "tenant_id",
9593
+ "license_type",
9594
+ "is_parent_managed",
9595
+ "audit_metadata",
9596
+ "type",
9597
+ "advance_data_patterns_rule_request",
9598
+ "dictionary_metadata",
9599
+ "detection_technique",
9600
+ "detection_sub_technique",
9601
+ "attributes",
9602
+ "keywords",
9603
+ "tags"
9604
+ ]);
9605
+ function pagesLimit(value) {
9606
+ if (!Number.isSafeInteger(value) || value < 1 || value > 1e3)
9607
+ throw new Error("maxPages must be an integer from 1 to 1000");
9608
+ }
9609
+ function assertName(name) {
9610
+ if (!name.trim() || [...name].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9611
+ throw new Error("DLP resource names must be nonempty and contain no control characters");
9612
+ }
9613
+ async function springPages(fetch, maxPages3, label) {
9614
+ pagesLimit(maxPages3);
9615
+ const items = [];
9616
+ for (let page = 0; page < maxPages3; page++) {
9617
+ const result = await fetch(page);
9618
+ items.push(...result.content);
9619
+ if (items.length > 1e4)
9620
+ throw new Error(`Inventory of ${label} exceeds the 10000-record safety limit`);
9621
+ const last = result.last ?? (result.totalPages !== void 0 ? page + 1 >= result.totalPages : result.content.length < 100);
9622
+ if (last) return items;
9623
+ if (!result.content.length) throw new Error(`Inventory of ${label} did not advance`);
9624
+ }
9625
+ throw new Error(`Inventory of ${label} is incomplete: increase --max-pages`);
9626
+ }
9627
+ async function inventories(api, maxPages3) {
9628
+ return {
9629
+ dictionaries: await springPages(
9630
+ (page) => api.dictionaries.list({ page, size: 100, keywords: true }),
9631
+ maxPages3,
9632
+ "dictionaries"
9633
+ ),
9634
+ patterns: await springPages(
9635
+ (page) => api.patterns.list({ page, size: 100 }),
9636
+ maxPages3,
9637
+ "data patterns"
9638
+ ),
9639
+ profiles: await springPages(
9640
+ (page) => api.profiles.list({ page, size: 100 }),
9641
+ maxPages3,
9642
+ "data profiles"
9643
+ )
9644
+ };
9645
+ }
9646
+ function canonical(value) {
9647
+ const order = (v) => Array.isArray(v) ? v.map(order) : object(v) ? Object.fromEntries(
9648
+ Object.entries(v).filter(([, child]) => child !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([k, child]) => [k, order(child)])
9649
+ ) : v;
9650
+ return JSON.stringify(order(value));
9651
+ }
9652
+ function tenantIdentity(records) {
9653
+ const values = new Set(
9654
+ records.map((record) => record.tenant_id).filter((value) => value != null)
9655
+ );
9656
+ if (values.size > 1) throw new Error("Inventory reports mixed DLP tenant identities");
9657
+ return [...values][0];
9658
+ }
9659
+ function ruleLeaves(profile) {
9660
+ const leaves = [];
9661
+ const visit = (node, depth) => {
9662
+ if (depth > 100) throw new Error("Unsupported detection rule: expression tree too deep");
9663
+ if (!object(node)) return;
9664
+ if (object(node.rule_item)) {
9665
+ if (typeof node.rule_item.detection_technique !== "string")
9666
+ throw new Error("Unsupported detection rule: leaf without a detection technique");
9667
+ leaves.push({ value: node.rule_item, technique: node.rule_item.detection_technique });
9668
+ }
9669
+ if (Array.isArray(node.sub_expressions))
9670
+ for (const child of node.sub_expressions) visit(child, depth + 1);
9671
+ };
9672
+ for (const rule of profile.detection_rules ?? []) {
9673
+ if (!object(rule)) throw new Error("Unsupported detection rule shape");
9674
+ if (rule.rule_type !== "expression_tree")
9675
+ throw new Error(`Unsupported detection rule type: ${String(rule.rule_type)}`);
9676
+ visit(rule.expression_tree, 0);
9677
+ }
9678
+ return leaves;
9679
+ }
9680
+ function assertKnownReferences(body, known, label) {
9681
+ const visit = (value, path3) => {
9682
+ if (Array.isArray(value)) {
9683
+ value.forEach((v, i) => {
9684
+ visit(v, `${path3}[${i}]`);
9685
+ });
9686
+ return;
9687
+ }
9688
+ if (!object(value)) return;
9689
+ for (const [key, child] of Object.entries(value)) {
9690
+ if (child !== null && child !== "" && /(?:^|[-_])(id|uuid)$|(?:Id|ID|Uuid|UUID)$/.test(key) && !(known.has(value) && key === "id"))
9691
+ throw new Error(`Unsupported cross-tenant reference in ${label} at ${path3}.${key}`);
9692
+ visit(child, `${path3}.${key}`);
9693
+ }
9694
+ };
9695
+ visit(body, label);
9696
+ }
9697
+ function uniqueNames(records, label) {
9698
+ const names = /* @__PURE__ */ new Set();
9699
+ for (const record of records) {
9700
+ const name = record.name;
9701
+ if (!name) throw new Error(`Backup contains a ${label} without a name`);
9702
+ assertName(name);
9703
+ if (names.has(name)) throw new Error(`Backup contains duplicate ${label} names: ${name}`);
9704
+ names.add(name);
9705
+ }
9706
+ }
9707
+ async function backupDlpResources(api, tsgId, options = {}) {
9708
+ if (!tsgId) throw new Error("Source TSG ID is required");
9709
+ const maxPages3 = options.maxPages ?? 100;
9710
+ const kinds = options.resources ?? [...DLP_RESOURCE_KINDS];
9711
+ if (!kinds.length || kinds.some((kind) => !DLP_RESOURCE_KINDS.includes(kind)))
9712
+ throw new Error("resources must name dictionaries, patterns and/or profiles");
9713
+ const inventory2 = await inventories(api, maxPages3);
9714
+ tenantIdentity([...inventory2.patterns, ...inventory2.profiles]);
9715
+ const dictionaries = /* @__PURE__ */ new Map();
9716
+ const patterns = /* @__PURE__ */ new Map();
9717
+ const addDictionary = (record) => {
9718
+ if (!record.id) throw new Error("Inventory dictionary has no id");
9719
+ if (record.type !== "predefined" && !Array.isArray(record.keywords))
9720
+ throw new Error(`Dictionary keywords were not returned by the region: ${record.name}`);
9721
+ dictionaries.set(record.id, structuredClone(record));
9722
+ };
9723
+ const addPattern = (record) => {
9724
+ if (!record.id) throw new Error("Inventory data pattern has no id");
9725
+ patterns.set(record.id, structuredClone(record));
9726
+ };
9727
+ if (kinds.includes("dictionaries")) {
9728
+ for (const record of inventory2.dictionaries)
9729
+ if (record.type !== "predefined") addDictionary(record);
9730
+ }
9731
+ if (kinds.includes("patterns")) {
9732
+ for (const record of inventory2.patterns)
9733
+ if (record.type !== "predefined" && !RETIRED_PATTERN.has(record.status ?? "active"))
9734
+ addPattern(record);
9735
+ }
9736
+ const profiles2 = [];
9737
+ const skipped = [];
9738
+ if (kinds.includes("profiles")) {
9739
+ const patternById = new Map(inventory2.patterns.map((p) => [p.id ?? "", p]));
9740
+ const dictionaryById = new Map(inventory2.dictionaries.map((d) => [d.id ?? "", d]));
9741
+ for (const profile of inventory2.profiles) {
9742
+ if (profile.type === "predefined") continue;
9743
+ if (RETIRED_PROFILE.has(profile.profile_status ?? "active")) continue;
9744
+ const name = profile.name ?? "(unnamed)";
9745
+ try {
9746
+ for (const leaf of ruleLeaves(profile)) {
9747
+ const id = leaf.value.id;
9748
+ if (id == null || id === "") {
9749
+ if (leaf.value.edm_dataset_id != null)
9750
+ throw new Error("references an EDM dataset directly");
9751
+ continue;
9752
+ }
9753
+ if (typeof id !== "string") throw new Error("has a non-string rule reference id");
9754
+ if (leaf.technique === "dictionary") {
9755
+ const record = dictionaryById.get(id);
9756
+ if (!record) throw new Error(`references an unknown dictionary: ${id}`);
9757
+ if (leaf.value.version != null)
9758
+ throw new Error(`references a version-pinned dictionary: ${id}`);
9759
+ addDictionary(record);
9760
+ } else {
9761
+ const record = patternById.get(id);
9762
+ if (!record) throw new Error(`references an unknown data pattern: ${id}`);
9763
+ if (leaf.value.version != null && record.version !== leaf.value.version)
9764
+ throw new Error(
9765
+ `references data pattern version ${leaf.value.version}, but the catalog holds version ${record.version ?? "unknown"}: ${id}`
9766
+ );
9767
+ addPattern(record);
9768
+ }
9769
+ }
9770
+ profiles2.push(structuredClone(profile));
9771
+ } catch (error) {
9772
+ const reason = error instanceof Error ? error.message : "unsupported rule";
9773
+ skipped.push({ profile: name, reason });
9774
+ }
9775
+ }
9776
+ if (skipped.length && !options.skipUnsupported)
9777
+ throw new Error(
9778
+ `Profiles cannot be exported: ${skipped.map((item) => `${item.profile} (${item.reason})`).join("; ")}; exclude unsupported profiles explicitly with --skip-unsupported`
9779
+ );
9780
+ }
9781
+ const byName = (a, b) => (a.name ?? "").localeCompare(b.name ?? "");
9782
+ const dictionaryRecords = [...dictionaries.values()].sort(byName);
9783
+ const patternRecords = [...patterns.values()].sort(byName);
9784
+ profiles2.sort(byName);
9785
+ uniqueNames(dictionaryRecords, "dictionary");
9786
+ uniqueNames(patternRecords, "data pattern");
9787
+ uniqueNames(profiles2, "data profile");
9788
+ if (!dictionaryRecords.length && !patternRecords.length && !profiles2.length)
9789
+ throw new Error("No matching DLP resources to back up");
9790
+ const backup = DlpResourcesBackupSchema.parse({
9791
+ version: 1,
9792
+ resourceType: "dlp-resources",
9793
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
9794
+ source: { tsgId },
9795
+ dictionaries: dictionaryRecords,
9796
+ patterns: patternRecords,
9797
+ profiles: profiles2
9798
+ });
9799
+ return { backup, skipped };
9800
+ }
9801
+ function compareEcho(request, readBack) {
9802
+ const differences = [];
9803
+ const serverAdded = [];
9804
+ const visit = (source, target, path3, top) => {
9805
+ const a = source ?? null;
9806
+ const b = target ?? null;
9807
+ if (a === null && b === null) return;
9808
+ if (a === null) {
9809
+ serverAdded.push(path3);
9810
+ return;
9811
+ }
9812
+ if (b === null) {
9813
+ differences.push(path3);
9814
+ return;
9815
+ }
9816
+ if (Array.isArray(a) || Array.isArray(b)) {
9817
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9818
+ differences.push(path3);
9819
+ return;
9820
+ }
9821
+ a.forEach((item, index) => {
9822
+ visit(item, b[index], `${path3}[${index}]`, false);
9823
+ });
9824
+ return;
9825
+ }
9826
+ if (object(a) && object(b)) {
9827
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
9828
+ if (top && !(key in a) && SERVER_METADATA_KEYS.has(key)) continue;
9829
+ visit(a[key], b[key], `${path3}.${key}`, false);
9830
+ }
9831
+ return;
9832
+ }
9833
+ if (a !== b) differences.push(path3);
9834
+ };
9835
+ visit(request, readBack, "body", true);
9836
+ return { matches: differences.length === 0, differences, serverAdded };
9837
+ }
9838
+ function keywordSet(value) {
9839
+ if (!Array.isArray(value) || value.some((keyword) => typeof keyword !== "string"))
9840
+ return void 0;
9841
+ return [...value].sort();
9842
+ }
9843
+ function dictionaryEcho(metadata, record, keywords) {
9844
+ const { original_file_name, ...projection } = metadata;
9845
+ const echo = compareEcho(projection, record);
9846
+ const echoedFile = record?.dictionary_metadata?.original_file_name;
9847
+ const echoedKeywords = keywordSet(record?.keywords);
9848
+ const matches = echo.matches && (echoedFile == null || echoedFile === original_file_name) && echoedKeywords !== void 0 && canonical(echoedKeywords) === canonical(keywords);
9849
+ return { matches, serverAdded: echo.serverAdded };
9850
+ }
9851
+ function dictionaryRequest(source, name) {
9852
+ const metadata = {
9853
+ category: source.category,
9854
+ name,
9855
+ original_file_name: source.dictionary_metadata?.original_file_name ?? "keywords.txt",
9856
+ region_name: source.region_name,
9857
+ ...source.description != null ? { description: source.description } : {},
9858
+ ...source.is_case_sensitive != null ? { is_case_sensitive: source.is_case_sensitive } : {}
9859
+ };
9860
+ const parsed = DictionaryRequestSchema.safeParse(metadata);
9861
+ if (!parsed.success)
9862
+ throw new Error(`Dictionary cannot be rebuilt from the backup: ${source.name}`);
9863
+ return parsed.data;
9864
+ }
9865
+ function patternRequest(source, name) {
9866
+ const body = {
9867
+ name,
9868
+ type: source.type,
9869
+ detection_config: source.detection_config,
9870
+ ...source.matching_rules != null ? { matching_rules: source.matching_rules } : {},
9871
+ ...source.description != null ? { description: source.description } : {},
9872
+ ...source.tags != null ? { tags: source.tags } : {}
9873
+ };
9874
+ const parsed = DataPatternRequestSchema.safeParse(structuredClone(body));
9875
+ if (!parsed.success)
9876
+ throw new Error(`Data pattern cannot be rebuilt from the backup: ${source.name}`);
9877
+ return parsed.data;
9878
+ }
9879
+ function portablePattern(record) {
9880
+ return PORTABLE_TECHNIQUES.has(record.detection_config?.technique ?? "") && record.detection_config?.technique !== void 0;
9881
+ }
9882
+ function matchByName(records, name, retired, label) {
9883
+ const matches = records.filter((record) => record.name === name && !retired(record));
9884
+ if (matches.length > 1) throw new Error(`Ambiguous destination ${label} name: ${name}`);
9885
+ return matches[0];
9886
+ }
9887
+ async function planDlpResourcesRestore(api, input2, destinationTsgId, options = {}) {
9888
+ const backup = DlpResourcesBackupSchema.parse(input2);
9889
+ if (!destinationTsgId) throw new Error("Destination TSG ID is required");
9890
+ const maxPages3 = options.maxPages ?? 100;
9891
+ pagesLimit(maxPages3);
9892
+ const prefix = options.namePrefix ?? "";
9893
+ if (prefix.length > 64 || [...prefix].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9894
+ throw new Error("Invalid --name-prefix");
9895
+ const conflict = options.onConflict ?? "error";
9896
+ if (!["error", "verify", "skip"].includes(conflict)) throw new Error("Invalid conflict policy");
9897
+ const patternMap2 = options.patternMap ?? {};
9898
+ uniqueNames(backup.dictionaries, "dictionary");
9899
+ uniqueNames(backup.patterns, "data pattern");
9900
+ uniqueNames(backup.profiles, "data profile");
9901
+ const unknownMapped = Object.keys(patternMap2).filter(
9902
+ (name) => !backup.patterns.some((p) => p.name === name)
9903
+ );
9904
+ if (unknownMapped.length)
9905
+ throw new Error(`--pattern-map names absent from the backup: ${unknownMapped.join(", ")}`);
9906
+ const crossTenant = backup.source.tsgId !== destinationTsgId;
9907
+ const destination = await inventories(api, maxPages3);
9908
+ const sourceTenant = tenantIdentity([...backup.patterns, ...backup.profiles]);
9909
+ const destinationTenant = tenantIdentity([...destination.patterns, ...destination.profiles]);
9910
+ if (sourceTenant !== void 0 && destinationTenant !== void 0) {
9911
+ if (crossTenant && sourceTenant === destinationTenant)
9912
+ throw new Error(
9913
+ "Destination credentials report the source DLP tenant; check the selected tenant before restoring"
9914
+ );
9915
+ if (!crossTenant && sourceTenant !== destinationTenant)
9916
+ throw new Error(
9917
+ "Destination DLP tenant does not match the backup source; use distinct tenant credentials deliberately"
9918
+ );
9919
+ }
9920
+ const dictionaryPlans = [];
9921
+ for (const source of backup.dictionaries) {
9922
+ if (!source.id || !source.name) throw new Error("Backup dictionary lacks identity");
9923
+ if (source.type === "predefined") {
9924
+ const match2 = matchByName(destination.dictionaries, source.name, () => false, "dictionary");
9925
+ if (!match2?.id || match2.type !== "predefined")
9926
+ throw new Error(`Missing predefined destination dictionary: ${source.name}`);
9927
+ dictionaryPlans.push({
9928
+ sourceId: source.id,
9929
+ name: source.name,
9930
+ action: "resolve",
9931
+ existing: structuredClone(match2)
9932
+ });
9933
+ continue;
9934
+ }
9935
+ const name = `${prefix}${source.name}`;
9936
+ assertName(name);
9937
+ const metadata = dictionaryRequest(source, name);
9938
+ const keywords = keywordSet(source.keywords);
9939
+ if (!keywords) throw new Error(`Backup dictionary lacks keywords: ${source.name}`);
9940
+ const match = matchByName(destination.dictionaries, name, () => false, "dictionary");
9941
+ if (!match) {
9942
+ dictionaryPlans.push({ sourceId: source.id, name, action: "create", metadata, keywords });
9943
+ continue;
9944
+ }
9945
+ if (!match.id) throw new Error(`Destination dictionary has no identity: ${name}`);
9946
+ if (!dictionaryEcho(metadata, match, keywords).matches)
9947
+ throw new Error(
9948
+ `Destination dictionary differs: ${name}; use --name-prefix to avoid changing a shared dictionary`
9949
+ );
9950
+ dictionaryPlans.push({
9951
+ sourceId: source.id,
9952
+ name,
9953
+ action: "reuse",
9954
+ metadata,
9955
+ keywords,
9956
+ existing: structuredClone(match)
9957
+ });
9958
+ }
9959
+ const patternPlans = [];
9960
+ for (const source of backup.patterns) {
9961
+ if (!source.id || !source.name) throw new Error("Backup data pattern lacks identity");
9962
+ const retired = (record) => RETIRED_PATTERN.has(record.status ?? "active");
9963
+ if (source.type === "predefined") {
9964
+ const match2 = matchByName(destination.patterns, source.name, retired, "data pattern");
9965
+ if (!match2?.id || match2.type !== "predefined")
9966
+ throw new Error(`Missing predefined destination data pattern: ${source.name}`);
9967
+ patternPlans.push({
9968
+ sourceId: source.id,
9969
+ name: source.name,
9970
+ action: "resolve",
9971
+ existing: structuredClone(match2)
9972
+ });
9973
+ continue;
9974
+ }
9975
+ if (Object.hasOwn(patternMap2, source.name)) {
9976
+ const target = patternMap2[source.name];
9977
+ const match2 = matchByName(destination.patterns, target, retired, "data pattern");
9978
+ if (!match2?.id) throw new Error(`Missing mapped destination data pattern: ${target}`);
9979
+ patternPlans.push({
9980
+ sourceId: source.id,
9981
+ name: target,
9982
+ action: "map",
9983
+ existing: structuredClone(match2)
9984
+ });
9985
+ continue;
9986
+ }
9987
+ if (!portablePattern(source))
9988
+ throw new Error(
9989
+ `Data pattern uses a tenant-bound detection technique: ${source.name}; provision it in the destination and bind it with --pattern-map "${source.name}=<destination-name>"`
9990
+ );
9991
+ const name = `${prefix}${source.name}`;
9992
+ assertName(name);
9993
+ const body = patternRequest(source, name);
9994
+ if (crossTenant) assertKnownReferences(body, /* @__PURE__ */ new Set(), `pattern ${source.name}`);
9995
+ const match = matchByName(destination.patterns, name, retired, "data pattern");
9996
+ if (!match) {
9997
+ patternPlans.push({ sourceId: source.id, name, action: "create", body });
9998
+ continue;
9999
+ }
10000
+ if (!match.id) throw new Error(`Destination data pattern has no identity: ${name}`);
10001
+ if (!compareEcho(body, match).matches)
10002
+ throw new Error(
10003
+ `Destination data pattern differs: ${name}; use --name-prefix to avoid changing a shared pattern`
10004
+ );
10005
+ patternPlans.push({
10006
+ sourceId: source.id,
10007
+ name,
10008
+ action: "reuse",
10009
+ body,
10010
+ existing: structuredClone(match)
10011
+ });
10012
+ }
10013
+ const profilePlans = [];
10014
+ for (const source of backup.profiles) {
10015
+ if (!source.name) throw new Error("Backup data profile lacks identity");
10016
+ if (source.type === "predefined") throw new Error("Backup contains a predefined data profile");
10017
+ const name = `${prefix}${source.name}`;
10018
+ assertName(name);
10019
+ const match = matchByName(
10020
+ destination.profiles,
10021
+ name,
10022
+ (record) => RETIRED_PROFILE.has(record.profile_status ?? "active"),
10023
+ "data profile"
10024
+ );
10025
+ if (match && conflict === "error")
10026
+ throw new Error(
10027
+ `Destination data profile already exists: ${name}; choose --on-conflict verify or skip`
10028
+ );
10029
+ if (match && !match.id) throw new Error(`Destination data profile has no identity: ${name}`);
10030
+ profilePlans.push({
10031
+ name,
10032
+ source: structuredClone(source),
10033
+ existing: match ? structuredClone(match) : void 0,
10034
+ action: match ? conflict === "skip" ? "skip" : "verify" : "create"
10035
+ });
10036
+ }
10037
+ const plan = {
10038
+ sourceTsgId: backup.source.tsgId,
10039
+ destinationTsgId,
10040
+ dictionaries: dictionaryPlans,
10041
+ patterns: patternPlans,
10042
+ profiles: profilePlans,
10043
+ maxPages: maxPages3
10044
+ };
10045
+ const bindings = planBindings(plan);
10046
+ const planned = /* @__PURE__ */ new Set([
10047
+ ...dictionaryPlans.map((d) => d.sourceId),
10048
+ ...patternPlans.map((p) => p.sourceId)
10049
+ ]);
10050
+ for (const item of profilePlans) {
10051
+ if (item.action === "skip") continue;
10052
+ for (const leaf of ruleLeaves(item.source)) {
10053
+ const id = leaf.value.id;
10054
+ if (id != null && id !== "" && !planned.has(String(id)))
10055
+ throw new Error(
10056
+ `Backup data profile references a resource absent from the backup: ${item.name}`
10057
+ );
10058
+ }
10059
+ const body = profileRequest(item, bindings, crossTenant, item.action === "verify");
10060
+ if (item.action === "verify" && body && !compareEcho(body, item.existing).matches)
10061
+ throw new Error(
10062
+ `Destination data profile does not match restore plan: ${item.name}; verification made no changes`
10063
+ );
10064
+ }
10065
+ return plan;
10066
+ }
10067
+ function planBindings(plan) {
10068
+ const bindings = /* @__PURE__ */ new Map();
10069
+ for (const item of plan.dictionaries)
10070
+ if (item.existing?.id) bindings.set(item.sourceId, { id: item.existing.id, name: item.name });
10071
+ for (const item of plan.patterns)
10072
+ if (item.existing?.id)
10073
+ bindings.set(item.sourceId, {
10074
+ id: item.existing.id,
10075
+ name: item.name,
10076
+ version: item.existing.version ?? void 0
10077
+ });
10078
+ return bindings;
10079
+ }
10080
+ function profileRequest(item, bindings, crossTenant, requireBindings) {
10081
+ const detectionRules = structuredClone(item.source.detection_rules ?? []);
10082
+ const known = /* @__PURE__ */ new Set();
10083
+ let deferred = false;
10084
+ const remapped = { ...structuredClone(item.source), detection_rules: detectionRules };
10085
+ for (const leaf of ruleLeaves(remapped)) {
10086
+ known.add(leaf.value);
10087
+ const id = leaf.value.id;
10088
+ if (id == null || id === "") continue;
10089
+ const binding = bindings.get(String(id));
10090
+ if (!binding) {
10091
+ if (requireBindings)
10092
+ throw new Error(
10093
+ `Missing planned dependency binding; an existing data profile cannot be verified without its destination dependencies: ${item.name}`
10094
+ );
10095
+ deferred = true;
10096
+ continue;
10097
+ }
10098
+ leaf.value.id = binding.id;
10099
+ if (leaf.value.name != null) leaf.value.name = binding.name;
10100
+ if (leaf.value.version != null) {
10101
+ if (binding.version === void 0)
10102
+ throw new Error(`Destination data pattern version is unknown: ${binding.name}`);
10103
+ leaf.value.version = binding.version;
10104
+ }
10105
+ }
10106
+ const body = {
10107
+ name: item.name,
10108
+ detection_rules: detectionRules,
10109
+ ...item.source.profile_type != null ? { profile_type: item.source.profile_type } : {},
10110
+ ...item.source.description != null ? { description: item.source.description } : {},
10111
+ ...item.source.is_granular_data_profile != null ? { is_granular_data_profile: item.source.is_granular_data_profile } : {}
10112
+ };
10113
+ if (crossTenant) assertKnownReferences(body, known, `profile ${item.name}`);
10114
+ const parsed = AdvancedDataProfileRequestSchema.parse(body);
10115
+ return deferred ? void 0 : parsed;
10116
+ }
10117
+ var DEPENDENCY_ACTIONS = {
10118
+ create: "created",
10119
+ reuse: "reused",
10120
+ resolve: "resolved",
10121
+ map: "mapped"
10122
+ };
10123
+ function assertUnchanged(current, prior, label) {
10124
+ if (canonical(current ?? null) !== canonical(prior ?? null))
10125
+ throw new Error(`Destination ${label} changed after planning`);
10126
+ }
10127
+ async function restoreDlpResources(api, plan) {
10128
+ const result = {
10129
+ sourceTsgId: plan.sourceTsgId,
10130
+ destinationTsgId: plan.destinationTsgId,
10131
+ complete: false,
10132
+ dictionaries: [],
10133
+ patterns: [],
10134
+ profiles: [],
10135
+ serverAdded: []
10136
+ };
10137
+ const crossTenant = plan.sourceTsgId !== plan.destinationTsgId;
10138
+ try {
10139
+ const current = await inventories(api, plan.maxPages);
10140
+ for (const item of plan.dictionaries) {
10141
+ const match = matchByName(current.dictionaries, item.name, () => false, "dictionary");
10142
+ if (item.existing) assertUnchanged(match, item.existing, `dictionary: ${item.name}`);
10143
+ else if (match)
10144
+ throw new Error(`Destination dictionary changed after planning: ${item.name}`);
10145
+ }
10146
+ const retiredPattern = (record) => RETIRED_PATTERN.has(record.status ?? "active");
10147
+ for (const item of plan.patterns) {
10148
+ const match = matchByName(current.patterns, item.name, retiredPattern, "data pattern");
10149
+ if (item.existing) assertUnchanged(match, item.existing, `data pattern: ${item.name}`);
10150
+ else if (match)
10151
+ throw new Error(`Destination data pattern changed after planning: ${item.name}`);
10152
+ }
10153
+ for (const item of plan.profiles) {
10154
+ const match = matchByName(
10155
+ current.profiles,
10156
+ item.name,
10157
+ (record) => RETIRED_PROFILE.has(record.profile_status ?? "active"),
10158
+ "data profile"
10159
+ );
10160
+ if (item.existing) assertUnchanged(match, item.existing, `data profile: ${item.name}`);
10161
+ else if (match)
10162
+ throw new Error(`Destination data profile changed after planning: ${item.name}`);
10163
+ }
10164
+ const bindings = planBindings(plan);
10165
+ for (const item of plan.dictionaries) {
10166
+ if (item.action !== "create") {
10167
+ const id = item.existing?.id;
10168
+ if (!id) throw new Error(`Destination dictionary has no identity: ${item.name}`);
10169
+ result.dictionaries.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
10170
+ continue;
10171
+ }
10172
+ if (!item.metadata || !item.keywords) throw new Error("Dictionary plan is incomplete");
10173
+ const created = await api.dictionaries.create({
10174
+ metadata: item.metadata,
10175
+ file: `${item.keywords.join("\n")}
10176
+ `,
10177
+ includeKeywords: true
10178
+ });
10179
+ if (!created.id) throw new Error(`Restored dictionary did not verify: ${item.name}`);
10180
+ const readBack = await api.dictionaries.get(created.id, { includeKeywords: true });
10181
+ const echo = dictionaryEcho(item.metadata, readBack, item.keywords);
10182
+ if (!echo.matches || readBack.name !== item.name)
10183
+ throw new Error(`Restored dictionary did not verify: ${item.name}`);
10184
+ if (echo.serverAdded.length)
10185
+ result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
10186
+ bindings.set(item.sourceId, { id: created.id, name: item.name });
10187
+ result.dictionaries.push({ name: item.name, action: "created", id: created.id });
10188
+ }
10189
+ for (const item of plan.patterns) {
10190
+ if (item.action !== "create") {
10191
+ const id = item.existing?.id;
10192
+ if (!id) throw new Error(`Destination data pattern has no identity: ${item.name}`);
10193
+ result.patterns.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
10194
+ continue;
10195
+ }
10196
+ if (!item.body) throw new Error("Data pattern plan is incomplete");
10197
+ const created = await api.patterns.create(item.body);
10198
+ if (!created.id) throw new Error(`Restored data pattern did not verify: ${item.name}`);
10199
+ const readBack = await api.patterns.get(created.id);
10200
+ const echo = compareEcho(item.body, readBack);
10201
+ if (!echo.matches || readBack.name !== item.name || RETIRED_PATTERN.has(readBack.status ?? "active"))
10202
+ throw new Error(`Restored data pattern did not verify: ${item.name}`);
10203
+ if (echo.serverAdded.length)
10204
+ result.serverAdded.push({
10205
+ resource: `data pattern ${item.name}`,
10206
+ fields: echo.serverAdded
10207
+ });
10208
+ bindings.set(item.sourceId, {
10209
+ id: created.id,
10210
+ name: item.name,
10211
+ version: readBack.version ?? void 0
10212
+ });
10213
+ result.patterns.push({ name: item.name, action: "created", id: created.id });
10214
+ }
10215
+ for (const item of plan.profiles) {
10216
+ if (item.action === "skip") {
10217
+ result.profiles.push({
10218
+ name: item.name,
10219
+ action: "skipped",
10220
+ id: item.existing?.id ?? void 0
10221
+ });
10222
+ continue;
10223
+ }
10224
+ const body = profileRequest(item, bindings, crossTenant, true);
10225
+ if (!body) throw new Error(`Missing planned dependency binding: ${item.name}`);
10226
+ if (item.action === "verify") {
10227
+ const echo2 = compareEcho(body, item.existing);
10228
+ if (!echo2.matches)
10229
+ throw new Error(`Destination data profile does not match restore plan: ${item.name}`);
10230
+ if (echo2.serverAdded.length)
10231
+ result.serverAdded.push({
10232
+ resource: `data profile ${item.name}`,
10233
+ fields: echo2.serverAdded
10234
+ });
10235
+ result.profiles.push({
10236
+ name: item.name,
10237
+ action: "verified",
10238
+ id: item.existing?.id ?? void 0
10239
+ });
10240
+ continue;
10241
+ }
10242
+ const created = await api.profiles.create(body);
10243
+ if (!created.id) throw new Error(`Restored data profile did not verify: ${item.name}`);
10244
+ const readBack = await api.profiles.get(created.id);
10245
+ const echo = compareEcho(body, readBack);
10246
+ if (!echo.matches || readBack.name !== item.name || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
10247
+ throw new Error(`Restored data profile did not verify: ${item.name}`);
10248
+ if (echo.serverAdded.length)
10249
+ result.serverAdded.push({
10250
+ resource: `data profile ${item.name}`,
10251
+ fields: echo.serverAdded
10252
+ });
10253
+ result.profiles.push({ name: item.name, action: "created", id: created.id });
10254
+ }
10255
+ result.complete = true;
10256
+ } catch (error) {
10257
+ const status = error?.statusCode;
10258
+ result.error = Number.isInteger(status) ? `API request failed (HTTP ${status}); verify destination state before retrying` : error instanceof Error && /^(Destination|Restored|Missing|Inventory|Ambiguous|Dictionary|Data pattern|Data profile)/.test(
10259
+ error.message
10260
+ ) ? error.message : "Restore stopped; verify destination state before retrying";
10261
+ }
10262
+ return result;
10263
+ }
10264
+
10265
+ // src/cli/commands/dlp/transfer.ts
10266
+ var MAX_BACKUP_BYTES = 20 * 1024 * 1024;
10267
+ var FORMATS = "Output format: pretty, table, markdown, csv, json, yaml";
10268
+ function maxPages(value) {
10269
+ if (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 1e3)
10270
+ usageError("--max-pages must be an integer from 1 to 1000");
10271
+ return Number(value);
10272
+ }
10273
+ function resourceKinds(value) {
10274
+ const kinds = value.split(",").map((kind) => kind.trim());
10275
+ if (!kinds.length || kinds.some((kind) => !DLP_RESOURCE_KINDS.includes(kind)) || new Set(kinds).size !== kinds.length)
10276
+ throw new CliUsageError("--resources takes a unique list of dictionaries, patterns, profiles");
10277
+ return kinds;
10278
+ }
10279
+ function patternMap(values) {
10280
+ const entries = values.map((value) => {
10281
+ const at = value.indexOf("=");
10282
+ if (at < 1 || at === value.length - 1)
10283
+ throw new CliUsageError("--pattern-map requires source-name=destination-name");
10284
+ return [value.slice(0, at), value.slice(at + 1)];
10285
+ });
10286
+ if (new Set(entries.map(([key]) => key)).size !== entries.length)
10287
+ throw new CliUsageError("Duplicate source names in --pattern-map");
10288
+ return Object.fromEntries(entries);
10289
+ }
10290
+ async function context() {
10291
+ const config = await loadConfig();
10292
+ if (!config.mgmtTsgId || !config.mgmtClientId || !config.mgmtClientSecret)
10293
+ throw new CliUsageError(
10294
+ "DLP transfer requires configured Management client ID, secret and TSG ID"
10295
+ );
10296
+ const client = new ManagementClient({ ...managementClientOptions(config), numRetries: 0 });
10297
+ return {
10298
+ api: {
10299
+ dictionaries: client.dlp.dictionaries,
10300
+ patterns: client.dlp.dataPatterns,
10301
+ profiles: client.dlp.dataProfiles
10302
+ },
10303
+ tsgId: config.mgmtTsgId
10304
+ };
10305
+ }
10306
+ function failure(error) {
10307
+ if (error instanceof CliUsageError) usageError(error.message);
10308
+ if (error instanceof ZodError) {
10309
+ ui.error("Invalid DLP backup/request schema; content is not logged.");
10310
+ } else if (error instanceof AISecSDKException2) {
10311
+ ui.error(
10312
+ `DLP transfer API request failed${error.statusCode ? ` (HTTP ${error.statusCode})` : ""}; no automatic retry was made.`
10313
+ );
10314
+ } else if (error?.code) {
10315
+ ui.error(
10316
+ `DLP transfer file operation failed (${error.code}); existing files are not overwritten.`
10317
+ );
10318
+ } else {
10319
+ ui.error(error instanceof Error ? error.message : "DLP transfer failed");
10320
+ }
10321
+ process.exitCode = 1;
10322
+ }
10323
+ function renderRestore(value, columns, format) {
10324
+ if (format !== "pretty" && format !== "table" && format !== "markdown")
10325
+ return formatOutput([value], columns, format);
10326
+ const markdown = format === "markdown";
10327
+ const rows = (key) => Array.isArray(value[key]) ? value[key] : [];
10328
+ const heading = (text2) => markdown ? `### ${text2}
10329
+ ` : text2;
10330
+ const blocks = [
10331
+ heading(
10332
+ value.dryRun ? "Restore preview \u2014 no changes made" : value.complete ? "Restore complete" : "Restore incomplete \u2014 completed writes remain"
10333
+ ),
10334
+ `Source TSG: ${value.sourceTsgId}
10335
+ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
10336
+ ];
10337
+ for (const [key, title] of [
10338
+ ["dictionaries", "Dictionaries"],
10339
+ ["patterns", "Data patterns"],
10340
+ ["profiles", "Data profiles"]
10341
+ ]) {
10342
+ blocks.push(heading(`${title} (${rows(key).length})`));
10343
+ blocks.push(
10344
+ formatOutput(
10345
+ rows(key),
10346
+ [
10347
+ { key: "action", label: "Action" },
10348
+ { key: "name", label: "Name" }
10349
+ ],
10350
+ markdown ? "markdown" : "table"
10351
+ ) || "(none)"
10352
+ );
10353
+ }
10354
+ if (rows("serverAdded").length) {
10355
+ blocks.push(heading("Verified server-added fields (source omitted these)"));
10356
+ for (const item of rows("serverAdded"))
10357
+ blocks.push(
10358
+ `${item.resource}: ${Array.isArray(item.fields) ? item.fields.length : 0} fields; use --output json for paths`
10359
+ );
10360
+ }
10361
+ if (value.error) blocks.push(`Error: ${value.error}`);
10362
+ return blocks.join("\n\n");
10363
+ }
10364
+ function register6(dlp) {
10365
+ const backup = dlp.command("backup").description(
10366
+ "Back up custom DLP dictionaries (with keywords), data patterns and data profiles with their referenced dependencies"
10367
+ ).option(
10368
+ "--resources <list>",
10369
+ "Comma-separated primary resources: dictionaries, patterns, profiles (default: all)",
10370
+ resourceKinds
10371
+ ).option("--file-format <format>", "Backup file format: json or yaml", "json").option("--output-file <path>", "New private backup file; defaults to the current directory").option(
10372
+ "--skip-unsupported",
10373
+ "Exclude profiles with non-transferable rules (multi-profile, direct EDM) instead of failing"
10374
+ ).option(
10375
+ "--max-pages <n>",
10376
+ "Maximum pages per inventory; incomplete exports fail",
10377
+ maxPages,
10378
+ 100
10379
+ ).option("--output <format>", FORMATS).addHelpText(
10380
+ "after",
10381
+ examples(
10382
+ "airs runtime dlp backup --output-file ./dlp-backup.json",
10383
+ "airs runtime dlp backup --resources profiles --skip-unsupported --output-file ./profiles.yaml --file-format yaml"
10384
+ )
10385
+ ).action(async (opts) => {
10386
+ try {
10387
+ if (!["json", "yaml"].includes(opts.fileFormat))
10388
+ throw new CliUsageError("--file-format must be json or yaml");
10389
+ const format = await resolveOutput(backup, opts);
10390
+ const extension = opts.fileFormat === "yaml" ? ".yaml" : ".json";
10391
+ if (opts.outputFile === "-")
10392
+ throw new CliUsageError(
10393
+ "Backups contain dictionary keywords and require a private file destination, not stdout"
10394
+ );
10395
+ const path3 = resolve5(
10396
+ opts.outputFile ?? `airs-dlp-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID7().slice(0, 8)}${extension}`
10397
+ );
10398
+ if (!(opts.fileFormat === "json" ? [".json"] : [".yaml", ".yml"]).includes(
10399
+ extname(path3).toLowerCase()
10400
+ ))
10401
+ throw new CliUsageError("Backup filename extension must match --file-format");
10402
+ const { api, tsgId } = await context();
10403
+ const { backup: data, skipped } = await backupDlpResources(api, tsgId, {
10404
+ resources: opts.resources,
10405
+ maxPages: opts.maxPages,
10406
+ skipUnsupported: Boolean(opts.skipUnsupported)
10407
+ });
10408
+ for (const item of skipped)
10409
+ ui.warning(`Skipped unsupported data profile: ${item.profile} (${item.reason})`);
10410
+ const content = opts.fileFormat === "yaml" ? dump6(data, { schema: JSON_SCHEMA, noRefs: true, lineWidth: -1 }) : `${JSON.stringify(data, null, 2)}
10411
+ `;
10412
+ if (Buffer.byteLength(content) > MAX_BACKUP_BYTES)
10413
+ throw new Error("Backup exceeds the 20 MiB size limit; select fewer --resources");
10414
+ await writeReportFile(path3, content);
10415
+ console.log(
10416
+ formatOutput(
10417
+ [
10418
+ {
10419
+ file: path3,
10420
+ sourceTsgId: tsgId,
10421
+ dictionaries: data.dictionaries.length,
10422
+ patterns: data.patterns.length,
10423
+ profiles: data.profiles.length,
10424
+ skipped: skipped.length
10425
+ }
10426
+ ],
10427
+ [
10428
+ { key: "file", label: "Backup file" },
10429
+ { key: "sourceTsgId", label: "Source TSG" },
10430
+ { key: "dictionaries", label: "Dictionaries" },
10431
+ { key: "patterns", label: "Patterns" },
10432
+ { key: "profiles", label: "Profiles" },
10433
+ { key: "skipped", label: "Skipped" }
10434
+ ],
10435
+ format === "pretty" ? "table" : format
10436
+ )
10437
+ );
10438
+ } catch (error) {
10439
+ failure(error);
10440
+ }
10441
+ });
10442
+ const restore = dlp.command("restore <file>").description(
10443
+ "Restore DLP resources into the selected tenant, staged dictionaries \u2192 patterns \u2192 profiles; validate all dependencies before changes"
10444
+ ).option("--dry-run", "Read and validate a restore plan without changing destination resources").option("--name-prefix <prefix>", "Prefix restored custom resource names to avoid collisions").option(
10445
+ "--on-conflict <policy>",
10446
+ "Existing data profile names: error, verify (resume without writes) or skip. Updates are unsupported until the DLP profile write path is live-verified.",
10447
+ "error"
10448
+ ).option(
10449
+ "--pattern-map <source=destination>",
10450
+ "Bind a tenant-bound source pattern to an existing destination pattern (repeatable)",
10451
+ (value, prior) => [...prior, value],
10452
+ []
10453
+ ).option(
10454
+ "--max-pages <n>",
10455
+ "Maximum pages per inventory; incomplete inventories fail",
10456
+ maxPages,
10457
+ 100
10458
+ ).option("--expect-tsg <id>", "Assert destination tenant ID; required with --force").option(
10459
+ "--force",
10460
+ "Execute without a prompt; requires --expect-tsg and does not override conflicts"
10461
+ ).option("--output <format>", FORMATS).addHelpText(
10462
+ "after",
10463
+ examples(
10464
+ "airs runtime dlp restore ./dlp-backup.json --dry-run --output json",
10465
+ 'airs runtime dlp restore ./dlp-backup.json --pattern-map "EDM SSN=Dest SSN" --dry-run',
10466
+ "airs runtime dlp restore ./dlp-backup.json --name-prefix migrated- --expect-tsg <destination-tsg> --force"
10467
+ )
10468
+ ).action(async (file, opts) => {
10469
+ try {
10470
+ if (!["error", "verify", "skip"].includes(opts.onConflict))
10471
+ throw new CliUsageError("--on-conflict must be error, verify or skip");
10472
+ if (opts.force && !opts.expectTsg)
10473
+ throw new CliUsageError("--force requires --expect-tsg <destination-tsg>");
10474
+ const mappings = patternMap(opts.patternMap);
10475
+ const path3 = resolve5(file);
10476
+ const extension = extname(path3).toLowerCase();
10477
+ if (![".json", ".yaml", ".yml"].includes(extension))
10478
+ throw new CliUsageError("Backup file must use .json, .yaml or .yml");
10479
+ const info = await stat(path3);
10480
+ if (!info.isFile() || info.size > MAX_BACKUP_BYTES)
10481
+ throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
10482
+ let input2;
10483
+ try {
10484
+ const text2 = await readFile9(path3, "utf8");
10485
+ if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES) throw new Error("Backup too large");
10486
+ input2 = extension === ".json" ? JSON.parse(text2) : load2(text2, { schema: JSON_SCHEMA });
10487
+ } catch {
10488
+ throw new CliUsageError("Backup is not valid JSON/YAML; contents are not logged");
10489
+ }
10490
+ const data = DlpResourcesBackupSchema.parse(input2);
10491
+ const format = await resolveOutput(restore, opts);
10492
+ const { api, tsgId } = await context();
10493
+ if (opts.expectTsg && opts.expectTsg !== tsgId)
10494
+ throw new CliUsageError(
10495
+ "--expect-tsg does not match the selected destination tenant; no API request was made"
10496
+ );
10497
+ const plan = await planDlpResourcesRestore(api, data, tsgId, {
10498
+ namePrefix: opts.namePrefix,
10499
+ onConflict: opts.onConflict,
10500
+ patternMap: mappings,
10501
+ maxPages: opts.maxPages
10502
+ });
10503
+ const summary = {
10504
+ sourceTsgId: plan.sourceTsgId,
10505
+ destinationTsgId: plan.destinationTsgId,
10506
+ dryRun: Boolean(opts.dryRun),
10507
+ dictionaries: plan.dictionaries.map((d) => ({ name: d.name, action: d.action })),
10508
+ patterns: plan.patterns.map((p) => ({ name: p.name, action: p.action })),
10509
+ profiles: plan.profiles.map((p) => ({ name: p.name, action: p.action }))
10510
+ };
10511
+ const columns = [
10512
+ { key: "sourceTsgId", label: "Source TSG" },
10513
+ { key: "destinationTsgId", label: "Destination TSG" },
10514
+ { key: "dictionaries", label: "Dictionaries" },
10515
+ { key: "patterns", label: "Data patterns" },
10516
+ { key: "profiles", label: "Data profiles" },
10517
+ { key: "serverAdded", label: "Verified server-added fields" },
10518
+ { key: "dryRun", label: "Dry run" },
10519
+ { key: "complete", label: "Complete" }
10520
+ ];
10521
+ if (opts.dryRun) {
10522
+ console.log(renderRestore(summary, columns, format));
10523
+ return;
10524
+ }
10525
+ const writes = plan.dictionaries.filter((d) => d.action === "create").length + plan.patterns.filter((p) => p.action === "create").length + plan.profiles.filter((p) => p.action === "create").length;
10526
+ if (writes)
10527
+ await confirmOrAbort(
10528
+ `Restore ${writes} DLP resources from TSG ${plan.sourceTsgId} to TSG ${tsgId}?`,
10529
+ Boolean(opts.force),
10530
+ { action: `restore DLP resources into TSG ${tsgId}` }
10531
+ );
10532
+ const result = await restoreDlpResources(api, plan);
10533
+ console.log(renderRestore({ ...result }, columns, format));
10534
+ if (!result.complete) {
10535
+ ui.error(result.error ?? "Restore incomplete");
10536
+ process.exitCode = 1;
10537
+ }
10538
+ } catch (error) {
10539
+ failure(error);
10540
+ }
10541
+ });
10542
+ }
10543
+
9553
10544
  // src/cli/commands/dlp/index.ts
9554
10545
  function registerDlpCommands(runtime) {
9555
- const dlp = runtime.command("dlp").description("DLP management (filtering-profiles, patterns, profiles, dictionaries, generate)");
10546
+ const dlp = runtime.command("dlp").description(
10547
+ "DLP management (filtering-profiles, patterns, profiles, dictionaries, generate, backup/restore)"
10548
+ );
9556
10549
  register2(dlp);
9557
10550
  register4(dlp);
9558
10551
  register5(dlp);
9559
10552
  register(dlp);
9560
10553
  register3(dlp);
10554
+ register6(dlp);
9561
10555
  }
9562
10556
 
9563
10557
  // src/cli/commands/profile-transfer.ts
9564
- import { randomUUID as randomUUID7 } from "crypto";
9565
- import { readFile as readFile9, stat } from "fs/promises";
9566
- import { extname, resolve as resolve5 } from "path";
9567
- import { AISecSDKException as AISecSDKException2, ManagementClient } from "@cdot65/prisma-airs-sdk";
9568
- import { dump as dump6, JSON_SCHEMA, load as load2 } from "js-yaml";
9569
- import { ZodError } from "zod";
10558
+ import { randomUUID as randomUUID8 } from "crypto";
10559
+ import { readFile as readFile10, stat as stat2 } from "fs/promises";
10560
+ import { extname as extname2, resolve as resolve6 } from "path";
10561
+ import { AISecSDKException as AISecSDKException3, ManagementClient as ManagementClient2 } from "@cdot65/prisma-airs-sdk";
10562
+ import { dump as dump7, JSON_SCHEMA as JSON_SCHEMA2, load as load3 } from "js-yaml";
10563
+ import { ZodError as ZodError2 } from "zod";
9570
10564
 
9571
10565
  // src/backup/runtime-profiles.ts
9572
10566
  import {
@@ -9575,10 +10569,10 @@ import {
9575
10569
  PolicySchema,
9576
10570
  SecurityProfileSchema
9577
10571
  } from "@cdot65/prisma-airs-sdk";
9578
- import { z as z5 } from "zod";
10572
+ import { z as z6 } from "zod";
9579
10573
 
9580
10574
  // src/backup/runtime-policy.ts
9581
- function object(value) {
10575
+ function object2(value) {
9582
10576
  return value !== null && typeof value === "object" && !Array.isArray(value);
9583
10577
  }
9584
10578
  function compareRuntimePolicies(expected, actual) {
@@ -9599,7 +10593,7 @@ function compareRuntimePolicies(expected, actual) {
9599
10593
  });
9600
10594
  return;
9601
10595
  }
9602
- if (object(source) && object(target)) {
10596
+ if (object2(source) && object2(target)) {
9603
10597
  for (const key of /* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(target)])) {
9604
10598
  const field = `${path3}.${key}`;
9605
10599
  if (!Object.hasOwn(source, key) && Object.hasOwn(target, key)) {
@@ -9630,14 +10624,14 @@ function compareRuntimePolicies(expected, actual) {
9630
10624
  }
9631
10625
  if (relative === "model.app-protection" && key === "url-detected-severity")
9632
10626
  allowed = "low";
9633
- if (relative === "model.app-protection" && key === "default-url-category" && object(target[key])) {
10627
+ if (relative === "model.app-protection" && key === "default-url-category" && object2(target[key])) {
9634
10628
  const value = target[key];
9635
10629
  if (Object.keys(value).length === 1 && value.member === null) {
9636
10630
  serverDefaults.push(field);
9637
10631
  continue;
9638
10632
  }
9639
10633
  }
9640
- if ((/^model\.model-protection\[\d+\]$/.test(relative) && source.name === "toxic-content" && target.name === source.name || insideToxicCategories && /^model\.model-protection\[\d+\]\.toxic-category-list\[\d+\]$/.test(relative) && typeof source.category === "string" && source.category.length > 0 && source.category === target.category) && key === "severity-by-confidence" && object(target[key])) {
10634
+ if ((/^model\.model-protection\[\d+\]$/.test(relative) && source.name === "toxic-content" && target.name === source.name || insideToxicCategories && /^model\.model-protection\[\d+\]\.toxic-category-list\[\d+\]$/.test(relative) && typeof source.category === "string" && source.category.length > 0 && source.category === target.category) && key === "severity-by-confidence" && object2(target[key])) {
9641
10635
  const value = target[key];
9642
10636
  if (Object.keys(value).length === 2 && value.high === "medium" && value.moderate === "low") {
9643
10637
  serverDefaults.push(field);
@@ -9672,26 +10666,26 @@ function compareRuntimePolicies(expected, actual) {
9672
10666
  }
9673
10667
 
9674
10668
  // src/backup/runtime-profiles.ts
9675
- var RuntimeProfilesBackupSchema = z5.object({
9676
- version: z5.literal(1),
9677
- resourceType: z5.literal("runtime-security-profiles"),
9678
- exportedAt: z5.string().datetime({ offset: true }),
9679
- source: z5.object({ tsgId: z5.string().min(1) }).strict(),
9680
- profiles: z5.array(SecurityProfileSchema).min(1).max(1e4),
9681
- topics: z5.array(CustomTopicSchema).max(1e4)
10669
+ var RuntimeProfilesBackupSchema = z6.object({
10670
+ version: z6.literal(1),
10671
+ resourceType: z6.literal("runtime-security-profiles"),
10672
+ exportedAt: z6.string().datetime({ offset: true }),
10673
+ source: z6.object({ tsgId: z6.string().min(1) }).strict(),
10674
+ profiles: z6.array(SecurityProfileSchema).min(1).max(1e4),
10675
+ topics: z6.array(CustomTopicSchema).max(1e4)
9682
10676
  }).strict();
9683
- function object2(value) {
10677
+ function object3(value) {
9684
10678
  return value !== null && typeof value === "object" && !Array.isArray(value);
9685
10679
  }
9686
- function pagesLimit(value) {
10680
+ function pagesLimit2(value) {
9687
10681
  if (!Number.isSafeInteger(value) || value < 1 || value > 1e3)
9688
10682
  throw new Error("maxPages must be an integer from 1 to 1000");
9689
10683
  }
9690
- async function offsetPages(fetch, maxPages2) {
9691
- pagesLimit(maxPages2);
10684
+ async function offsetPages(fetch, maxPages3) {
10685
+ pagesLimit2(maxPages3);
9692
10686
  const items = [];
9693
10687
  let offset = 0;
9694
- for (let page = 0; page < maxPages2; page++) {
10688
+ for (let page = 0; page < maxPages3; page++) {
9695
10689
  const result = await fetch(offset);
9696
10690
  items.push(...result.items);
9697
10691
  if (items.length > 1e4) throw new Error("Inventory exceeds the 10000-record safety limit");
@@ -9706,7 +10700,7 @@ function latestProfiles(profiles2) {
9706
10700
  const names = /* @__PURE__ */ new Map();
9707
10701
  const revisions = /* @__PURE__ */ new Set();
9708
10702
  for (const profile of profiles2) {
9709
- assertName(profile.profile_name);
10703
+ assertName2(profile.profile_name);
9710
10704
  const identity2 = JSON.stringify([profile.profile_name, profile.profile_id, profile.revision]);
9711
10705
  if (revisions.has(identity2)) throw new Error("Inventory contains repeated profile records");
9712
10706
  revisions.add(identity2);
@@ -9718,7 +10712,7 @@ function latestProfiles(profiles2) {
9718
10712
  }
9719
10713
  return [...names.values()].sort((a, b) => a.profile_name.localeCompare(b.profile_name));
9720
10714
  }
9721
- function assertName(name) {
10715
+ function assertName2(name) {
9722
10716
  if (!name.trim() || [...name].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9723
10717
  throw new Error("Profile/topic names must be nonempty and contain no control characters");
9724
10718
  }
@@ -9726,19 +10720,19 @@ function assertTenant(profiles2, tsgId) {
9726
10720
  if (profiles2.some((profile) => profile.tsg_id != null && String(profile.tsg_id) !== tsgId))
9727
10721
  throw new Error("Destination/source profile tenant identity does not match the selected TSG");
9728
10722
  }
9729
- async function profiles(api, maxPages2) {
10723
+ async function profiles(api, maxPages3) {
9730
10724
  return latestProfiles(
9731
10725
  await offsetPages(async (offset) => {
9732
10726
  const page = await api.profiles.list({ offset, limit: 100, latest: true });
9733
10727
  return { items: page.ai_profiles, next: page.next_offset };
9734
- }, maxPages2)
10728
+ }, maxPages3)
9735
10729
  );
9736
10730
  }
9737
- async function topics(api, maxPages2) {
10731
+ async function topics(api, maxPages3) {
9738
10732
  return offsetPages(async (offset) => {
9739
10733
  const page = await api.topics.list({ offset, limit: 100 });
9740
10734
  return { items: page.custom_topics, next: page.next_offset };
9741
- }, maxPages2);
10735
+ }, maxPages3);
9742
10736
  }
9743
10737
  function topicReferences(policy) {
9744
10738
  const refs = [];
@@ -9747,7 +10741,7 @@ function topicReferences(policy) {
9747
10741
  value.forEach(visit);
9748
10742
  return;
9749
10743
  }
9750
- if (!object2(value)) return;
10744
+ if (!object3(value)) return;
9751
10745
  if ("topic_id" in value) {
9752
10746
  if (typeof value.topic_id !== "string" || typeof value.topic_name !== "string" || !Number.isSafeInteger(value.revision) || Number(value.revision) < 0)
9753
10747
  throw new Error("Invalid topic identity in profile policy");
@@ -9763,8 +10757,8 @@ function topicKey(ref) {
9763
10757
  }
9764
10758
  async function backupRuntimeProfiles(api, tsgId, options = {}) {
9765
10759
  if (!tsgId) throw new Error("Source TSG ID is required");
9766
- const maxPages2 = options.maxPages ?? 100;
9767
- let selected = await profiles(api, maxPages2);
10760
+ const maxPages3 = options.maxPages ?? 100;
10761
+ let selected = await profiles(api, maxPages3);
9768
10762
  assertTenant(selected, tsgId);
9769
10763
  if (options.profile)
9770
10764
  selected = selected.filter(
@@ -9777,7 +10771,7 @@ async function backupRuntimeProfiles(api, tsgId, options = {}) {
9777
10771
  PolicySchema.parse(profile.policy);
9778
10772
  }
9779
10773
  const refs = selected.flatMap((profile) => topicReferences(profile.policy));
9780
- const definitions = refs.length ? await topics(api, maxPages2) : [];
10774
+ const definitions = refs.length ? await topics(api, maxPages3) : [];
9781
10775
  const needed = /* @__PURE__ */ new Map();
9782
10776
  for (const ref of refs) {
9783
10777
  const matches = definitions.filter((topic) => topicKey(topic) === topicKey(ref));
@@ -9800,21 +10794,21 @@ function sameTopic(a, b) {
9800
10794
  return a.description === b.description && JSON.stringify(a.examples) === JSON.stringify(b.examples) && (a.active ?? true) === (b.active ?? true);
9801
10795
  }
9802
10796
  function dlpReferences(policy) {
9803
- if (!object2(policy)) return [];
10797
+ if (!object3(policy)) return [];
9804
10798
  const refs = [];
9805
10799
  for (const entry of Array.isArray(policy["dlp-data-profiles"]) ? policy["dlp-data-profiles"] : []) {
9806
- if (!object2(entry) || typeof entry.name !== "string")
10800
+ if (!object3(entry) || typeof entry.name !== "string")
9807
10801
  throw new Error("Invalid embedded DLP reference");
9808
10802
  refs.push({ value: entry, name: entry.name, kind: "embedded" });
9809
10803
  }
9810
10804
  for (const entry of Array.isArray(policy["ai-security-profiles"]) ? policy["ai-security-profiles"] : []) {
9811
- if (!object2(entry) || !object2(entry["model-configuration"])) continue;
10805
+ if (!object3(entry) || !object3(entry["model-configuration"])) continue;
9812
10806
  const protection = entry["model-configuration"]["data-protection"];
9813
- if (!object2(protection) || !object2(protection["data-leak-detection"])) continue;
10807
+ if (!object3(protection) || !object3(protection["data-leak-detection"])) continue;
9814
10808
  const members = protection["data-leak-detection"].member;
9815
10809
  if (!Array.isArray(members)) continue;
9816
10810
  for (const member of members) {
9817
- if (!object2(member) || typeof member.text !== "string") throw new Error("Invalid DLP member");
10811
+ if (!object3(member) || typeof member.text !== "string") throw new Error("Invalid DLP member");
9818
10812
  if (member.text.toLowerCase() === "sensitive content" && (member.id === "" || member.id === void 0) && (member.version === "2" || member.version === void 0) && !refs.some((ref) => ref.kind === "embedded" && ref.name === member.text))
9819
10813
  continue;
9820
10814
  refs.push({ value: member, name: member.text, kind: "member" });
@@ -9857,7 +10851,7 @@ function assertKnownPolicyReferences(policy) {
9857
10851
  });
9858
10852
  return;
9859
10853
  }
9860
- if (!object2(value)) return;
10854
+ if (!object3(value)) return;
9861
10855
  for (const [key, child] of Object.entries(value)) {
9862
10856
  if (child !== null && child !== "" && /(?:^|[-_])(id|uuid)$|(?:Id|ID|Uuid|UUID)$/.test(key) && !known.get(value)?.includes(key))
9863
10857
  throw new Error(`Unsupported cross-tenant policy reference at ${path3}.${key}`);
@@ -9866,9 +10860,9 @@ function assertKnownPolicyReferences(policy) {
9866
10860
  };
9867
10861
  visit(policy, "policy");
9868
10862
  }
9869
- async function dataProfiles(api, maxPages2) {
10863
+ async function dataProfiles(api, maxPages3) {
9870
10864
  const items = [];
9871
- for (let page = 0; page < maxPages2; page++) {
10865
+ for (let page = 0; page < maxPages3; page++) {
9872
10866
  const result = await api.dataProfiles.list({ page, size: 100 });
9873
10867
  items.push(...result.content);
9874
10868
  if (items.length > 1e4) throw new Error("DLP inventory exceeds the safety limit");
@@ -9881,8 +10875,8 @@ async function dataProfiles(api, maxPages2) {
9881
10875
  async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options = {}) {
9882
10876
  const backup = RuntimeProfilesBackupSchema.parse(input2);
9883
10877
  if (!destinationTsgId) throw new Error("Destination TSG ID is required");
9884
- const maxPages2 = options.maxPages ?? 100;
9885
- pagesLimit(maxPages2);
10878
+ const maxPages3 = options.maxPages ?? 100;
10879
+ pagesLimit2(maxPages3);
9886
10880
  const prefix = options.namePrefix ?? "";
9887
10881
  if (prefix.length > 64 || [...prefix].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9888
10882
  throw new Error("Invalid --name-prefix");
@@ -9905,7 +10899,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9905
10899
  });
9906
10900
  if (crossTenant) assertKnownPolicyReferences(profile.policy);
9907
10901
  }
9908
- const existing = await profiles(api, maxPages2);
10902
+ const existing = await profiles(api, maxPages3);
9909
10903
  assertTenant(existing, destinationTsgId);
9910
10904
  const profilePlans = selected.map((source) => {
9911
10905
  const name = `${prefix}${source.profile_name}`;
@@ -9924,7 +10918,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9924
10918
  });
9925
10919
  const changing = profilePlans.filter((p) => p.action !== "skip");
9926
10920
  const refs = changing.flatMap((p) => topicReferences(p.source.policy));
9927
- const targetTopics = refs.length ? await topics(api, maxPages2) : [];
10921
+ const targetTopics = refs.length ? await topics(api, maxPages3) : [];
9928
10922
  const topicPlans = /* @__PURE__ */ new Map();
9929
10923
  const topicNames = /* @__PURE__ */ new Map();
9930
10924
  for (const ref of refs) {
@@ -9937,7 +10931,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9937
10931
  throw new Error(`Backup lacks an unambiguous topic definition: ${ref.topic_name}`);
9938
10932
  const source = definitions[0];
9939
10933
  const name = `${prefix}${source.topic_name}`;
9940
- assertName(name);
10934
+ assertName2(name);
9941
10935
  if (topicNames.has(name) && topicNames.get(name) !== key)
9942
10936
  throw new Error(
9943
10937
  `Multiple source revisions use topic name ${name}; split the backup by profile/revision before restoring`
@@ -9964,7 +10958,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9964
10958
  );
9965
10959
  if (!targetFor(name)) unresolved.add(name);
9966
10960
  }
9967
- const catalog = names.some((name) => targetFor(name)) ? await dataProfiles(api, maxPages2) : [];
10961
+ const catalog = names.some((name) => targetFor(name)) ? await dataProfiles(api, maxPages3) : [];
9968
10962
  const resolved = [];
9969
10963
  for (const name of names) {
9970
10964
  const target = targetFor(name);
@@ -10015,7 +11009,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
10015
11009
  dlpMappings,
10016
11010
  dlpFallbacks,
10017
11011
  serverDefaults: [],
10018
- maxPages: maxPages2
11012
+ maxPages: maxPages3
10019
11013
  };
10020
11014
  const existingTopics = new Map(
10021
11015
  plan.topics.filter((t) => t.existing).map((t) => [t.key, t.existing])
@@ -10032,8 +11026,8 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
10032
11026
  }
10033
11027
  return plan;
10034
11028
  }
10035
- function canonical(value) {
10036
- const order = (v) => Array.isArray(v) ? v.map(order) : object2(v) ? Object.fromEntries(
11029
+ function canonical2(value) {
11030
+ const order = (v) => Array.isArray(v) ? v.map(order) : object3(v) ? Object.fromEntries(
10037
11031
  Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).map(([k, child]) => [k, order(child)])
10038
11032
  ) : v;
10039
11033
  return JSON.stringify(order(value));
@@ -10044,7 +11038,7 @@ function canonicalPolicy(policy) {
10044
11038
  const protection = entry["model-configuration"]?.["data-protection"];
10045
11039
  if (protection && !("database-security" in protection)) protection["database-security"] = null;
10046
11040
  }
10047
- return canonical(value);
11041
+ return canonical2(value);
10048
11042
  }
10049
11043
  function unchanged(current, prior) {
10050
11044
  return Boolean(
@@ -10187,9 +11181,9 @@ function mappedPolicy(item, plan, mappedTopics) {
10187
11181
  }
10188
11182
 
10189
11183
  // src/cli/commands/profile-transfer.ts
10190
- var MAX_BACKUP_BYTES = 20 * 1024 * 1024;
10191
- var FORMATS = "Output format: pretty, table, markdown, csv, json, yaml";
10192
- function renderRestore(value, columns, format) {
11184
+ var MAX_BACKUP_BYTES2 = 20 * 1024 * 1024;
11185
+ var FORMATS2 = "Output format: pretty, table, markdown, csv, json, yaml";
11186
+ function renderRestore2(value, columns, format) {
10193
11187
  if (format !== "pretty" && format !== "table" && format !== "markdown")
10194
11188
  return formatOutput([value], columns, format);
10195
11189
  const markdown = format === "markdown";
@@ -10249,7 +11243,7 @@ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
10249
11243
  if (value.error) blocks.push(`Error: ${value.error}`);
10250
11244
  return blocks.join("\n\n");
10251
11245
  }
10252
- function maxPages(value) {
11246
+ function maxPages2(value) {
10253
11247
  if (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 1e3)
10254
11248
  usageError("--max-pages must be an integer from 1 to 1000");
10255
11249
  return Number(value);
@@ -10265,13 +11259,13 @@ function dlpMap(values) {
10265
11259
  throw new CliUsageError("Duplicate source names in --dlp-map");
10266
11260
  return Object.fromEntries(entries);
10267
11261
  }
10268
- async function context() {
11262
+ async function context2() {
10269
11263
  const config = await loadConfig();
10270
11264
  if (!config.mgmtTsgId || !config.mgmtClientId || !config.mgmtClientSecret)
10271
11265
  throw new CliUsageError(
10272
11266
  "Profile transfer requires configured Management client ID, secret and TSG ID"
10273
11267
  );
10274
- const client = new ManagementClient({ ...managementClientOptions(config), numRetries: 0 });
11268
+ const client = new ManagementClient2({ ...managementClientOptions(config), numRetries: 0 });
10275
11269
  return {
10276
11270
  api: {
10277
11271
  profiles: client.profiles,
@@ -10281,11 +11275,11 @@ async function context() {
10281
11275
  tsgId: config.mgmtTsgId
10282
11276
  };
10283
11277
  }
10284
- function failure(error) {
11278
+ function failure2(error) {
10285
11279
  if (error instanceof CliUsageError) usageError(error.message);
10286
- if (error instanceof ZodError) {
11280
+ if (error instanceof ZodError2) {
10287
11281
  ui.error("Invalid profile backup/request schema; content is not logged.");
10288
- } else if (error instanceof AISecSDKException2) {
11282
+ } else if (error instanceof AISecSDKException3) {
10289
11283
  ui.error(
10290
11284
  `Profile transfer API request failed${error.statusCode ? ` (HTTP ${error.statusCode})` : ""}; no automatic retry was made.`
10291
11285
  );
@@ -10304,9 +11298,9 @@ function registerProfileTransferCommands(profiles2) {
10304
11298
  ).option("--all", "Back up all latest profiles (cannot be combined with a profile selector)").option("--file-format <format>", "Backup file format: json or yaml", "json").option("--output-file <path>", "New private backup file; defaults to the current directory").option(
10305
11299
  "--max-pages <n>",
10306
11300
  "Maximum pages per inventory; incomplete exports fail",
10307
- maxPages,
11301
+ maxPages2,
10308
11302
  100
10309
- ).option("--output <format>", FORMATS).addHelpText(
11303
+ ).option("--output <format>", FORMATS2).addHelpText(
10310
11304
  "after",
10311
11305
  examples(
10312
11306
  "airs runtime profiles backup --all --output-file ./profiles.json",
@@ -10322,18 +11316,18 @@ function registerProfileTransferCommands(profiles2) {
10322
11316
  const extension = opts.fileFormat === "yaml" ? ".yaml" : ".json";
10323
11317
  if (opts.outputFile === "-")
10324
11318
  throw new CliUsageError("Backups require a private file destination, not stdout");
10325
- const path3 = resolve5(
10326
- opts.outputFile ?? `airs-runtime-profiles-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID7().slice(0, 8)}${extension}`
11319
+ const path3 = resolve6(
11320
+ opts.outputFile ?? `airs-runtime-profiles-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID8().slice(0, 8)}${extension}`
10327
11321
  );
10328
11322
  if (!(opts.fileFormat === "json" ? [".json"] : [".yaml", ".yml"]).includes(
10329
- extname(path3).toLowerCase()
11323
+ extname2(path3).toLowerCase()
10330
11324
  ))
10331
11325
  throw new CliUsageError("Backup filename extension must match --file-format");
10332
- const { api, tsgId } = await context();
11326
+ const { api, tsgId } = await context2();
10333
11327
  const data = await backupRuntimeProfiles(api, tsgId, { profile, maxPages: opts.maxPages });
10334
- const content = opts.fileFormat === "yaml" ? dump6(data, { schema: JSON_SCHEMA, noRefs: true, lineWidth: -1 }) : `${JSON.stringify(data, null, 2)}
11328
+ const content = opts.fileFormat === "yaml" ? dump7(data, { schema: JSON_SCHEMA2, noRefs: true, lineWidth: -1 }) : `${JSON.stringify(data, null, 2)}
10335
11329
  `;
10336
- if (Buffer.byteLength(content) > MAX_BACKUP_BYTES)
11330
+ if (Buffer.byteLength(content) > MAX_BACKUP_BYTES2)
10337
11331
  throw new Error("Backup exceeds the 20 MiB size limit; select individual profiles");
10338
11332
  await writeReportFile(path3, content);
10339
11333
  console.log(
@@ -10356,7 +11350,7 @@ function registerProfileTransferCommands(profiles2) {
10356
11350
  )
10357
11351
  );
10358
11352
  } catch (error) {
10359
- failure(error);
11353
+ failure2(error);
10360
11354
  }
10361
11355
  });
10362
11356
  const restore = profiles2.command("restore <file>").description(
@@ -10377,12 +11371,12 @@ function registerProfileTransferCommands(profiles2) {
10377
11371
  ).option(
10378
11372
  "--max-pages <n>",
10379
11373
  "Maximum pages per inventory; incomplete inventories fail",
10380
- maxPages,
11374
+ maxPages2,
10381
11375
  100
10382
11376
  ).option("--expect-tsg <id>", "Assert destination tenant ID; required with --force").option(
10383
11377
  "--force",
10384
11378
  "Execute without a prompt; requires --expect-tsg and does not override conflicts"
10385
- ).option("--output <format>", FORMATS).addHelpText(
11379
+ ).option("--output <format>", FORMATS2).addHelpText(
10386
11380
  "after",
10387
11381
  examples(
10388
11382
  "airs runtime profiles restore ./profiles.json --dry-run --output json",
@@ -10398,24 +11392,24 @@ function registerProfileTransferCommands(profiles2) {
10398
11392
  if (opts.force && !opts.expectTsg)
10399
11393
  throw new CliUsageError("--force requires --expect-tsg <destination-tsg>");
10400
11394
  const mappings = dlpMap(opts.dlpMap);
10401
- const path3 = resolve5(file);
10402
- const extension = extname(path3).toLowerCase();
11395
+ const path3 = resolve6(file);
11396
+ const extension = extname2(path3).toLowerCase();
10403
11397
  if (![".json", ".yaml", ".yml"].includes(extension))
10404
11398
  throw new CliUsageError("Backup file must use .json, .yaml or .yml");
10405
- const info = await stat(path3);
10406
- if (!info.isFile() || info.size > MAX_BACKUP_BYTES)
11399
+ const info = await stat2(path3);
11400
+ if (!info.isFile() || info.size > MAX_BACKUP_BYTES2)
10407
11401
  throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
10408
11402
  let input2;
10409
11403
  try {
10410
- const text2 = await readFile9(path3, "utf8");
10411
- if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES) throw new Error("Backup too large");
10412
- input2 = extension === ".json" ? JSON.parse(text2) : load2(text2, { schema: JSON_SCHEMA });
11404
+ const text2 = await readFile10(path3, "utf8");
11405
+ if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES2) throw new Error("Backup too large");
11406
+ input2 = extension === ".json" ? JSON.parse(text2) : load3(text2, { schema: JSON_SCHEMA2 });
10413
11407
  } catch {
10414
11408
  throw new CliUsageError("Backup is not valid JSON/YAML; contents are not logged");
10415
11409
  }
10416
11410
  const data = RuntimeProfilesBackupSchema.parse(input2);
10417
11411
  const format = await resolveOutput(restore, opts);
10418
- const { api, tsgId } = await context();
11412
+ const { api, tsgId } = await context2();
10419
11413
  if (opts.expectTsg && opts.expectTsg !== tsgId)
10420
11414
  throw new CliUsageError(
10421
11415
  "--expect-tsg does not match the selected destination tenant; no API request was made"
@@ -10459,7 +11453,7 @@ function registerProfileTransferCommands(profiles2) {
10459
11453
  `DLP protection change: ${fallback.profile} will use Basic detection instead of ${fallback.replaced.join(", ")}. Custom rules are not preserved.`
10460
11454
  );
10461
11455
  if (opts.dryRun) {
10462
- console.log(renderRestore(summary, columns, format));
11456
+ console.log(renderRestore2(summary, columns, format));
10463
11457
  return;
10464
11458
  }
10465
11459
  const changing = plan.profiles.filter(
@@ -10472,13 +11466,13 @@ function registerProfileTransferCommands(profiles2) {
10472
11466
  { action: `restore profiles into TSG ${tsgId}` }
10473
11467
  );
10474
11468
  const result = await restoreRuntimeProfiles(api, plan);
10475
- console.log(renderRestore({ ...result }, columns, format));
11469
+ console.log(renderRestore2({ ...result }, columns, format));
10476
11470
  if (!result.complete) {
10477
11471
  ui.error(result.error ?? "Restore incomplete");
10478
11472
  process.exitCode = 1;
10479
11473
  }
10480
11474
  } catch (error) {
10481
- failure(error);
11475
+ failure2(error);
10482
11476
  }
10483
11477
  });
10484
11478
  }
@@ -10586,8 +11580,8 @@ function registerCleanupCommand(parent) {
10586
11580
 
10587
11581
  // src/cli/commands/runtime-dashboard.ts
10588
11582
  import { lstat as lstat4 } from "fs/promises";
10589
- import { resolve as resolve6 } from "path";
10590
- import { dump as dump7 } from "js-yaml";
11583
+ import { resolve as resolve7 } from "path";
11584
+ import { dump as dump8 } from "js-yaml";
10591
11585
  var TIME_UNITS = ["hour", "hours", "day", "days"];
10592
11586
  function integer3(value, name, minimum = 0) {
10593
11587
  const n = Number(value);
@@ -10649,7 +11643,7 @@ function scanOptions(command) {
10649
11643
  function registerRuntimeDashboardCommands(runtime) {
10650
11644
  const dashboard = runtime.command("dashboard").description("SCM application summaries and daily telemetry (OAuth)");
10651
11645
  const sessions = runtime.command("sessions").description("Verified SCM session inventory and drill-down (OAuth)");
10652
- function register6(command, prepare) {
11646
+ function register7(command, prepare) {
10653
11647
  command.option("--output <format>", "Output format: pretty, json, yaml").action(async (opts) => {
10654
11648
  try {
10655
11649
  const read = prepare(opts);
@@ -10662,7 +11656,7 @@ function registerRuntimeDashboardCommands(runtime) {
10662
11656
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
10663
11657
  );
10664
11658
  if (opts.outputFile) {
10665
- const path3 = resolve6(text(opts.outputFile, "--output-file"));
11659
+ const path3 = resolve7(text(opts.outputFile, "--output-file"));
10666
11660
  const existing = await lstat4(path3).catch((error) => {
10667
11661
  if (error.code !== "ENOENT") throw error;
10668
11662
  return void 0;
@@ -10676,9 +11670,9 @@ function registerRuntimeDashboardCommands(runtime) {
10676
11670
  numRetries: 0
10677
11671
  });
10678
11672
  const result = await read(client.dashboard);
10679
- const output = format === "yaml" ? dump7(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
11673
+ const output = format === "yaml" ? dump8(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
10680
11674
  if (command.name() === "scan-content" && opts.outputFile) {
10681
- await writeReportFile(resolve6(String(opts.outputFile)), `${output}
11675
+ await writeReportFile(resolve7(String(opts.outputFile)), `${output}
10682
11676
  `);
10683
11677
  ui.status("Scan content exported to the requested private file (0600).");
10684
11678
  } else console.log(output);
@@ -10692,7 +11686,7 @@ function registerRuntimeDashboardCommands(runtime) {
10692
11686
  }
10693
11687
  });
10694
11688
  }
10695
- register6(
11689
+ register7(
10696
11690
  pageOptions2(
10697
11691
  timeOptions(
10698
11692
  dashboard.command("applications").description("One page of application activity"),
@@ -10721,7 +11715,7 @@ function registerRuntimeDashboardCommands(runtime) {
10721
11715
  ["application", "application"],
10722
11716
  ["application-violations", "applicationViolationBreakdown"]
10723
11717
  ]) {
10724
- register6(
11718
+ register7(
10725
11719
  timeOptions(
10726
11720
  dashboard.command(name).requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
10727
11721
  "30",
@@ -10747,7 +11741,7 @@ function registerRuntimeDashboardCommands(runtime) {
10747
11741
  ["violations-trend", "applicationsViolationsTrend", false],
10748
11742
  ["apps-list", "appsList", true]
10749
11743
  ]) {
10750
- register6(
11744
+ register7(
10751
11745
  timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
10752
11746
  (opts) => {
10753
11747
  const query2 = window(opts);
@@ -10755,11 +11749,11 @@ function registerRuntimeDashboardCommands(runtime) {
10755
11749
  }
10756
11750
  );
10757
11751
  }
10758
- register6(timeOptions(sessions.command("chart")), (opts) => {
11752
+ register7(timeOptions(sessions.command("chart")), (opts) => {
10759
11753
  const query2 = window(opts);
10760
11754
  return (client) => client.sessionsChart(query2);
10761
11755
  });
10762
- register6(
11756
+ register7(
10763
11757
  pageOptions2(
10764
11758
  timeOptions(
10765
11759
  sessions.command("list").description("Session entries; --all walks pages with completeness checks")
@@ -10825,7 +11819,7 @@ function registerRuntimeDashboardCommands(runtime) {
10825
11819
  };
10826
11820
  }
10827
11821
  );
10828
- register6(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
11822
+ register7(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
10829
11823
  const query2 = {
10830
11824
  ...identity(opts),
10831
11825
  limit: integer3(opts.limit, "--limit", 1),
@@ -10833,11 +11827,11 @@ function registerRuntimeDashboardCommands(runtime) {
10833
11827
  };
10834
11828
  return (client) => client.session(query2);
10835
11829
  });
10836
- register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
11830
+ register7(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
10837
11831
  const query2 = { ...identity(opts), ...scan(opts) };
10838
11832
  return (client) => client.sessionTransaction(query2);
10839
11833
  });
10840
- register6(
11834
+ register7(
10841
11835
  scanOptions(
10842
11836
  sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
10843
11837
  ).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
@@ -10850,8 +11844,8 @@ function registerRuntimeDashboardCommands(runtime) {
10850
11844
 
10851
11845
  // src/cli/commands/runtime-report.ts
10852
11846
  import { lstat as lstat5 } from "fs/promises";
10853
- import { resolve as resolve7 } from "path";
10854
- import { randomUUID as randomUUID8 } from "crypto";
11847
+ import { resolve as resolve8 } from "path";
11848
+ import { randomUUID as randomUUID9 } from "crypto";
10855
11849
  function registerRuntimeReportCommand(runtime) {
10856
11850
  const command = runtime.command("report").description("Generate a daily AI Runtime Security environment report (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
10857
11851
  "--output-file <path>",
@@ -10868,15 +11862,15 @@ function registerRuntimeReportCommand(runtime) {
10868
11862
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
10869
11863
  if (format !== "html" && format !== "markdown")
10870
11864
  usageError("Report output must be html or markdown");
10871
- const maxPages2 = Number(opts.maxPages);
10872
- if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages2) || maxPages2 > 100)
11865
+ const maxPages3 = Number(opts.maxPages);
11866
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages3) || maxPages3 > 100)
10873
11867
  usageError("--max-pages must be an integer from 1 to 100");
10874
11868
  if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
10875
11869
  usageError("--title must contain 1\u2013240 characters");
10876
11870
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
10877
11871
  usageError("--output-file cannot be empty");
10878
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID8().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
10879
- const outputPath = opts.outputFile === "-" ? void 0 : resolve7(opts.outputFile ?? defaultName);
11872
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID9().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
11873
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve8(opts.outputFile ?? defaultName);
10880
11874
  try {
10881
11875
  if (outputPath) {
10882
11876
  const existing = await lstat5(outputPath).catch((error) => {
@@ -10891,7 +11885,7 @@ function registerRuntimeReportCommand(runtime) {
10891
11885
  numRetries: 0
10892
11886
  });
10893
11887
  ui.status("Collecting daily runtime activity and current configuration (read-only)...");
10894
- const report = await collectRuntimeDailyReport(client, { title: opts.title, maxPages: maxPages2 });
11888
+ const report = await collectRuntimeDailyReport(client, { title: opts.title, maxPages: maxPages3 });
10895
11889
  const rendered = format === "html" ? renderRuntimeReportHtml(report) : renderRuntimeReportMarkdown(report);
10896
11890
  if (outputPath) {
10897
11891
  await writeReportFile(outputPath, rendered);
@@ -11051,7 +12045,7 @@ function registerCreateCommand(parent) {
11051
12045
  }
11052
12046
 
11053
12047
  // src/cli/commands/topics-eval.ts
11054
- import { readFile as readFile10 } from "fs/promises";
12048
+ import { readFile as readFile11 } from "fs/promises";
11055
12049
 
11056
12050
  // src/core/prompt-loader.ts
11057
12051
  function parseCsvLine(line) {
@@ -11194,7 +12188,7 @@ function registerEvalCommand(parent) {
11194
12188
  resolveDeprecatedAliases(cmd, opts);
11195
12189
  try {
11196
12190
  const config = await loadConfig();
11197
- const csvContent = await readFile10(opts.prompts, "utf-8");
12191
+ const csvContent = await readFile11(opts.prompts, "utf-8");
11198
12192
  const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
11199
12193
  if (!config.airsApiKey && !config.airsApiToken) {
11200
12194
  fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
@@ -11366,7 +12360,7 @@ function completedBulkResults(state) {
11366
12360
  }
11367
12361
  async function writeBulkResults(outputPath, results) {
11368
12362
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
11369
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID9()}`;
12363
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID10()}`;
11370
12364
  try {
11371
12365
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
11372
12366
  encoding: "utf-8",
@@ -11493,7 +12487,7 @@ function registerRuntimeCommand(program) {
11493
12487
  if (!config.airsApiKey && !config.airsApiToken) {
11494
12488
  fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
11495
12489
  }
11496
- const raw = await readFile11(opts.file, "utf-8");
12490
+ const raw = await readFile12(opts.file, "utf-8");
11497
12491
  const prompts = parseInputFile(raw, opts.file);
11498
12492
  if (prompts.length === 0) {
11499
12493
  usageError("No prompts found in input file");
@@ -12151,7 +13145,7 @@ function registerRuntimeCommand(program) {
12151
13145
  }
12152
13146
 
12153
13147
  // src/config/tenant-settings.ts
12154
- import { randomUUID as randomUUID10 } from "crypto";
13148
+ import { randomUUID as randomUUID11 } from "crypto";
12155
13149
  import { constants } from "fs";
12156
13150
  import { access, lstat as lstat6, mkdir as mkdir3, open as open2, rename as rename2, unlink as unlink2 } from "fs/promises";
12157
13151
  import { dirname as dirname4, join as join4 } from "path";
@@ -12181,7 +13175,7 @@ async function createManagedTenant(name, config) {
12181
13175
  throw new Error("Tenant name already exists");
12182
13176
  const directory = join4(dirname4(tenantStorePath()), "configs");
12183
13177
  await mkdir3(directory, { recursive: true, mode: 448 });
12184
- const path3 = join4(directory, `${name}-${randomUUID10()}.json`);
13178
+ const path3 = join4(directory, `${name}-${randomUUID11()}.json`);
12185
13179
  const file = await open2(path3, "wx", 384);
12186
13180
  try {
12187
13181
  const data = Object.fromEntries(
@@ -12211,11 +13205,11 @@ async function setTenantSetting(name, key, value) {
12211
13205
  "Cannot lock tenant config; it may be read-only or another edit is in progress"
12212
13206
  );
12213
13207
  });
12214
- const temporary = join4(dirname4(path3), `.airs-config-${randomUUID10()}.tmp`);
13208
+ const temporary = join4(dirname4(path3), `.airs-config-${randomUUID11()}.tmp`);
12215
13209
  let created = false;
12216
13210
  try {
12217
- const stat2 = await lstat6(path3);
12218
- if (!stat2.isFile() || (stat2.mode & 146) === 0)
13211
+ const stat3 = await lstat6(path3);
13212
+ if (!stat3.isFile() || (stat3.mode & 146) === 0)
12219
13213
  throw new Error("Tenant config is not a writable regular file");
12220
13214
  await access(path3, constants.W_OK);
12221
13215
  const current = readTenantConfigFile(path3, entry.tsgId);
@@ -12511,7 +13505,7 @@ function buildProgram() {
12511
13505
  if (root.debug) {
12512
13506
  const logPath = join5(
12513
13507
  process.cwd(),
12514
- `debug-api-${Date.now()}-${randomUUID11().slice(0, 8)}.jsonl`
13508
+ `debug-api-${Date.now()}-${randomUUID12().slice(0, 8)}.jsonl`
12515
13509
  );
12516
13510
  try {
12517
13511
  let ancestor = actionCommand;