@cdot65/prisma-airs-cli 5.7.1 → 5.8.0

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 +1135 -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,1015 @@ 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
+ if (!options.skipUnsupported)
9774
+ throw new Error(`Profile cannot be exported: ${name} ${reason}`);
9775
+ skipped.push({ profile: name, reason });
9776
+ }
9777
+ }
9778
+ }
9779
+ const byName = (a, b) => (a.name ?? "").localeCompare(b.name ?? "");
9780
+ const dictionaryRecords = [...dictionaries.values()].sort(byName);
9781
+ const patternRecords = [...patterns.values()].sort(byName);
9782
+ profiles2.sort(byName);
9783
+ uniqueNames(dictionaryRecords, "dictionary");
9784
+ uniqueNames(patternRecords, "data pattern");
9785
+ uniqueNames(profiles2, "data profile");
9786
+ if (!dictionaryRecords.length && !patternRecords.length && !profiles2.length)
9787
+ throw new Error("No matching DLP resources to back up");
9788
+ const backup = DlpResourcesBackupSchema.parse({
9789
+ version: 1,
9790
+ resourceType: "dlp-resources",
9791
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
9792
+ source: { tsgId },
9793
+ dictionaries: dictionaryRecords,
9794
+ patterns: patternRecords,
9795
+ profiles: profiles2
9796
+ });
9797
+ return { backup, skipped };
9798
+ }
9799
+ function compareEcho(request, readBack) {
9800
+ const differences = [];
9801
+ const serverAdded = [];
9802
+ const visit = (source, target, path3, top) => {
9803
+ const a = source ?? null;
9804
+ const b = target ?? null;
9805
+ if (a === null && b === null) return;
9806
+ if (a === null) {
9807
+ serverAdded.push(path3);
9808
+ return;
9809
+ }
9810
+ if (b === null) {
9811
+ differences.push(path3);
9812
+ return;
9813
+ }
9814
+ if (Array.isArray(a) || Array.isArray(b)) {
9815
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9816
+ differences.push(path3);
9817
+ return;
9818
+ }
9819
+ a.forEach((item, index) => {
9820
+ visit(item, b[index], `${path3}[${index}]`, false);
9821
+ });
9822
+ return;
9823
+ }
9824
+ if (object(a) && object(b)) {
9825
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
9826
+ if (top && !(key in a) && SERVER_METADATA_KEYS.has(key)) continue;
9827
+ visit(a[key], b[key], `${path3}.${key}`, false);
9828
+ }
9829
+ return;
9830
+ }
9831
+ if (a !== b) differences.push(path3);
9832
+ };
9833
+ visit(request, readBack, "body", true);
9834
+ return { matches: differences.length === 0, differences, serverAdded };
9835
+ }
9836
+ function keywordSet(value) {
9837
+ if (!Array.isArray(value) || value.some((keyword) => typeof keyword !== "string"))
9838
+ return void 0;
9839
+ return [...value].sort();
9840
+ }
9841
+ function dictionaryEcho(metadata, record, keywords) {
9842
+ const { original_file_name, ...projection } = metadata;
9843
+ const echo = compareEcho(projection, record);
9844
+ const echoedFile = record?.dictionary_metadata?.original_file_name;
9845
+ const echoedKeywords = keywordSet(record?.keywords);
9846
+ const matches = echo.matches && (echoedFile == null || echoedFile === original_file_name) && echoedKeywords !== void 0 && canonical(echoedKeywords) === canonical(keywords);
9847
+ return { matches, serverAdded: echo.serverAdded };
9848
+ }
9849
+ function dictionaryRequest(source, name) {
9850
+ const metadata = {
9851
+ category: source.category,
9852
+ name,
9853
+ original_file_name: source.dictionary_metadata?.original_file_name ?? "keywords.txt",
9854
+ region_name: source.region_name,
9855
+ ...source.description != null ? { description: source.description } : {},
9856
+ ...source.is_case_sensitive != null ? { is_case_sensitive: source.is_case_sensitive } : {}
9857
+ };
9858
+ const parsed = DictionaryRequestSchema.safeParse(metadata);
9859
+ if (!parsed.success)
9860
+ throw new Error(`Dictionary cannot be rebuilt from the backup: ${source.name}`);
9861
+ return parsed.data;
9862
+ }
9863
+ function patternRequest(source, name) {
9864
+ const body = {
9865
+ name,
9866
+ type: source.type,
9867
+ detection_config: source.detection_config,
9868
+ ...source.matching_rules != null ? { matching_rules: source.matching_rules } : {},
9869
+ ...source.description != null ? { description: source.description } : {},
9870
+ ...source.tags != null ? { tags: source.tags } : {}
9871
+ };
9872
+ const parsed = DataPatternRequestSchema.safeParse(structuredClone(body));
9873
+ if (!parsed.success)
9874
+ throw new Error(`Data pattern cannot be rebuilt from the backup: ${source.name}`);
9875
+ return parsed.data;
9876
+ }
9877
+ function portablePattern(record) {
9878
+ return PORTABLE_TECHNIQUES.has(record.detection_config?.technique ?? "") && record.detection_config?.technique !== void 0;
9879
+ }
9880
+ function matchByName(records, name, retired, label) {
9881
+ const matches = records.filter((record) => record.name === name && !retired(record));
9882
+ if (matches.length > 1) throw new Error(`Ambiguous destination ${label} name: ${name}`);
9883
+ return matches[0];
9884
+ }
9885
+ async function planDlpResourcesRestore(api, input2, destinationTsgId, options = {}) {
9886
+ const backup = DlpResourcesBackupSchema.parse(input2);
9887
+ if (!destinationTsgId) throw new Error("Destination TSG ID is required");
9888
+ const maxPages3 = options.maxPages ?? 100;
9889
+ pagesLimit(maxPages3);
9890
+ const prefix = options.namePrefix ?? "";
9891
+ if (prefix.length > 64 || [...prefix].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9892
+ throw new Error("Invalid --name-prefix");
9893
+ const conflict = options.onConflict ?? "error";
9894
+ if (!["error", "verify", "skip"].includes(conflict)) throw new Error("Invalid conflict policy");
9895
+ const patternMap2 = options.patternMap ?? {};
9896
+ uniqueNames(backup.dictionaries, "dictionary");
9897
+ uniqueNames(backup.patterns, "data pattern");
9898
+ uniqueNames(backup.profiles, "data profile");
9899
+ const unknownMapped = Object.keys(patternMap2).filter(
9900
+ (name) => !backup.patterns.some((p) => p.name === name)
9901
+ );
9902
+ if (unknownMapped.length)
9903
+ throw new Error(`--pattern-map names absent from the backup: ${unknownMapped.join(", ")}`);
9904
+ const crossTenant = backup.source.tsgId !== destinationTsgId;
9905
+ const destination = await inventories(api, maxPages3);
9906
+ const sourceTenant = tenantIdentity([...backup.patterns, ...backup.profiles]);
9907
+ const destinationTenant = tenantIdentity([...destination.patterns, ...destination.profiles]);
9908
+ if (sourceTenant !== void 0 && destinationTenant !== void 0) {
9909
+ if (crossTenant && sourceTenant === destinationTenant)
9910
+ throw new Error(
9911
+ "Destination credentials report the source DLP tenant; check the selected tenant before restoring"
9912
+ );
9913
+ if (!crossTenant && sourceTenant !== destinationTenant)
9914
+ throw new Error(
9915
+ "Destination DLP tenant does not match the backup source; use distinct tenant credentials deliberately"
9916
+ );
9917
+ }
9918
+ const dictionaryPlans = [];
9919
+ for (const source of backup.dictionaries) {
9920
+ if (!source.id || !source.name) throw new Error("Backup dictionary lacks identity");
9921
+ if (source.type === "predefined") {
9922
+ const match2 = matchByName(destination.dictionaries, source.name, () => false, "dictionary");
9923
+ if (!match2?.id || match2.type !== "predefined")
9924
+ throw new Error(`Missing predefined destination dictionary: ${source.name}`);
9925
+ dictionaryPlans.push({
9926
+ sourceId: source.id,
9927
+ name: source.name,
9928
+ action: "resolve",
9929
+ existing: structuredClone(match2)
9930
+ });
9931
+ continue;
9932
+ }
9933
+ const name = `${prefix}${source.name}`;
9934
+ assertName(name);
9935
+ const metadata = dictionaryRequest(source, name);
9936
+ const keywords = keywordSet(source.keywords);
9937
+ if (!keywords) throw new Error(`Backup dictionary lacks keywords: ${source.name}`);
9938
+ const match = matchByName(destination.dictionaries, name, () => false, "dictionary");
9939
+ if (!match) {
9940
+ dictionaryPlans.push({ sourceId: source.id, name, action: "create", metadata, keywords });
9941
+ continue;
9942
+ }
9943
+ if (!match.id) throw new Error(`Destination dictionary has no identity: ${name}`);
9944
+ if (!dictionaryEcho(metadata, match, keywords).matches)
9945
+ throw new Error(
9946
+ `Destination dictionary differs: ${name}; use --name-prefix to avoid changing a shared dictionary`
9947
+ );
9948
+ dictionaryPlans.push({
9949
+ sourceId: source.id,
9950
+ name,
9951
+ action: "reuse",
9952
+ metadata,
9953
+ keywords,
9954
+ existing: structuredClone(match)
9955
+ });
9956
+ }
9957
+ const patternPlans = [];
9958
+ for (const source of backup.patterns) {
9959
+ if (!source.id || !source.name) throw new Error("Backup data pattern lacks identity");
9960
+ const retired = (record) => RETIRED_PATTERN.has(record.status ?? "active");
9961
+ if (source.type === "predefined") {
9962
+ const match2 = matchByName(destination.patterns, source.name, retired, "data pattern");
9963
+ if (!match2?.id || match2.type !== "predefined")
9964
+ throw new Error(`Missing predefined destination data pattern: ${source.name}`);
9965
+ patternPlans.push({
9966
+ sourceId: source.id,
9967
+ name: source.name,
9968
+ action: "resolve",
9969
+ existing: structuredClone(match2)
9970
+ });
9971
+ continue;
9972
+ }
9973
+ if (Object.hasOwn(patternMap2, source.name)) {
9974
+ const target = patternMap2[source.name];
9975
+ const match2 = matchByName(destination.patterns, target, retired, "data pattern");
9976
+ if (!match2?.id) throw new Error(`Missing mapped destination data pattern: ${target}`);
9977
+ patternPlans.push({
9978
+ sourceId: source.id,
9979
+ name: target,
9980
+ action: "map",
9981
+ existing: structuredClone(match2)
9982
+ });
9983
+ continue;
9984
+ }
9985
+ if (!portablePattern(source))
9986
+ throw new Error(
9987
+ `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>"`
9988
+ );
9989
+ const name = `${prefix}${source.name}`;
9990
+ assertName(name);
9991
+ const body = patternRequest(source, name);
9992
+ if (crossTenant) assertKnownReferences(body, /* @__PURE__ */ new Set(), `pattern ${source.name}`);
9993
+ const match = matchByName(destination.patterns, name, retired, "data pattern");
9994
+ if (!match) {
9995
+ patternPlans.push({ sourceId: source.id, name, action: "create", body });
9996
+ continue;
9997
+ }
9998
+ if (!match.id) throw new Error(`Destination data pattern has no identity: ${name}`);
9999
+ if (!compareEcho(body, match).matches)
10000
+ throw new Error(
10001
+ `Destination data pattern differs: ${name}; use --name-prefix to avoid changing a shared pattern`
10002
+ );
10003
+ patternPlans.push({
10004
+ sourceId: source.id,
10005
+ name,
10006
+ action: "reuse",
10007
+ body,
10008
+ existing: structuredClone(match)
10009
+ });
10010
+ }
10011
+ const profilePlans = [];
10012
+ for (const source of backup.profiles) {
10013
+ if (!source.name) throw new Error("Backup data profile lacks identity");
10014
+ if (source.type === "predefined") throw new Error("Backup contains a predefined data profile");
10015
+ const name = `${prefix}${source.name}`;
10016
+ assertName(name);
10017
+ const match = matchByName(
10018
+ destination.profiles,
10019
+ name,
10020
+ (record) => RETIRED_PROFILE.has(record.profile_status ?? "active"),
10021
+ "data profile"
10022
+ );
10023
+ if (match && conflict === "error")
10024
+ throw new Error(
10025
+ `Destination data profile already exists: ${name}; choose --on-conflict verify or skip`
10026
+ );
10027
+ if (match && !match.id) throw new Error(`Destination data profile has no identity: ${name}`);
10028
+ profilePlans.push({
10029
+ name,
10030
+ source: structuredClone(source),
10031
+ existing: match ? structuredClone(match) : void 0,
10032
+ action: match ? conflict === "skip" ? "skip" : "verify" : "create"
10033
+ });
10034
+ }
10035
+ const plan = {
10036
+ sourceTsgId: backup.source.tsgId,
10037
+ destinationTsgId,
10038
+ dictionaries: dictionaryPlans,
10039
+ patterns: patternPlans,
10040
+ profiles: profilePlans,
10041
+ maxPages: maxPages3
10042
+ };
10043
+ const bindings = planBindings(plan);
10044
+ const planned = /* @__PURE__ */ new Set([
10045
+ ...dictionaryPlans.map((d) => d.sourceId),
10046
+ ...patternPlans.map((p) => p.sourceId)
10047
+ ]);
10048
+ for (const item of profilePlans) {
10049
+ if (item.action === "skip") continue;
10050
+ for (const leaf of ruleLeaves(item.source)) {
10051
+ const id = leaf.value.id;
10052
+ if (id != null && id !== "" && !planned.has(String(id)))
10053
+ throw new Error(
10054
+ `Backup data profile references a resource absent from the backup: ${item.name}`
10055
+ );
10056
+ }
10057
+ const body = profileRequest(item, bindings, crossTenant, item.action === "verify");
10058
+ if (item.action === "verify" && body && !compareEcho(body, item.existing).matches)
10059
+ throw new Error(
10060
+ `Destination data profile does not match restore plan: ${item.name}; verification made no changes`
10061
+ );
10062
+ }
10063
+ return plan;
10064
+ }
10065
+ function planBindings(plan) {
10066
+ const bindings = /* @__PURE__ */ new Map();
10067
+ for (const item of plan.dictionaries)
10068
+ if (item.existing?.id) bindings.set(item.sourceId, { id: item.existing.id, name: item.name });
10069
+ for (const item of plan.patterns)
10070
+ if (item.existing?.id)
10071
+ bindings.set(item.sourceId, {
10072
+ id: item.existing.id,
10073
+ name: item.name,
10074
+ version: item.existing.version ?? void 0
10075
+ });
10076
+ return bindings;
10077
+ }
10078
+ function profileRequest(item, bindings, crossTenant, requireBindings) {
10079
+ const detectionRules = structuredClone(item.source.detection_rules ?? []);
10080
+ const known = /* @__PURE__ */ new Set();
10081
+ let deferred = false;
10082
+ const remapped = { ...structuredClone(item.source), detection_rules: detectionRules };
10083
+ for (const leaf of ruleLeaves(remapped)) {
10084
+ known.add(leaf.value);
10085
+ const id = leaf.value.id;
10086
+ if (id == null || id === "") continue;
10087
+ const binding = bindings.get(String(id));
10088
+ if (!binding) {
10089
+ if (requireBindings)
10090
+ throw new Error(
10091
+ `Missing planned dependency binding; an existing data profile cannot be verified without its destination dependencies: ${item.name}`
10092
+ );
10093
+ deferred = true;
10094
+ continue;
10095
+ }
10096
+ leaf.value.id = binding.id;
10097
+ if (leaf.value.name != null) leaf.value.name = binding.name;
10098
+ if (leaf.value.version != null) {
10099
+ if (binding.version === void 0)
10100
+ throw new Error(`Destination data pattern version is unknown: ${binding.name}`);
10101
+ leaf.value.version = binding.version;
10102
+ }
10103
+ }
10104
+ const body = {
10105
+ name: item.name,
10106
+ detection_rules: detectionRules,
10107
+ ...item.source.profile_type != null ? { profile_type: item.source.profile_type } : {},
10108
+ ...item.source.description != null ? { description: item.source.description } : {},
10109
+ ...item.source.is_granular_data_profile != null ? { is_granular_data_profile: item.source.is_granular_data_profile } : {}
10110
+ };
10111
+ if (crossTenant) assertKnownReferences(body, known, `profile ${item.name}`);
10112
+ const parsed = AdvancedDataProfileRequestSchema.parse(body);
10113
+ return deferred ? void 0 : parsed;
10114
+ }
10115
+ var DEPENDENCY_ACTIONS = {
10116
+ create: "created",
10117
+ reuse: "reused",
10118
+ resolve: "resolved",
10119
+ map: "mapped"
10120
+ };
10121
+ function assertUnchanged(current, prior, label) {
10122
+ if (canonical(current ?? null) !== canonical(prior ?? null))
10123
+ throw new Error(`Destination ${label} changed after planning`);
10124
+ }
10125
+ async function restoreDlpResources(api, plan) {
10126
+ const result = {
10127
+ sourceTsgId: plan.sourceTsgId,
10128
+ destinationTsgId: plan.destinationTsgId,
10129
+ complete: false,
10130
+ dictionaries: [],
10131
+ patterns: [],
10132
+ profiles: [],
10133
+ serverAdded: []
10134
+ };
10135
+ const crossTenant = plan.sourceTsgId !== plan.destinationTsgId;
10136
+ try {
10137
+ const current = await inventories(api, plan.maxPages);
10138
+ for (const item of plan.dictionaries) {
10139
+ const match = matchByName(current.dictionaries, item.name, () => false, "dictionary");
10140
+ if (item.existing) assertUnchanged(match, item.existing, `dictionary: ${item.name}`);
10141
+ else if (match)
10142
+ throw new Error(`Destination dictionary changed after planning: ${item.name}`);
10143
+ }
10144
+ const retiredPattern = (record) => RETIRED_PATTERN.has(record.status ?? "active");
10145
+ for (const item of plan.patterns) {
10146
+ const match = matchByName(current.patterns, item.name, retiredPattern, "data pattern");
10147
+ if (item.existing) assertUnchanged(match, item.existing, `data pattern: ${item.name}`);
10148
+ else if (match)
10149
+ throw new Error(`Destination data pattern changed after planning: ${item.name}`);
10150
+ }
10151
+ for (const item of plan.profiles) {
10152
+ const match = matchByName(
10153
+ current.profiles,
10154
+ item.name,
10155
+ (record) => RETIRED_PROFILE.has(record.profile_status ?? "active"),
10156
+ "data profile"
10157
+ );
10158
+ if (item.existing) assertUnchanged(match, item.existing, `data profile: ${item.name}`);
10159
+ else if (match)
10160
+ throw new Error(`Destination data profile changed after planning: ${item.name}`);
10161
+ }
10162
+ const bindings = planBindings(plan);
10163
+ for (const item of plan.dictionaries) {
10164
+ if (item.action !== "create") {
10165
+ const id = item.existing?.id;
10166
+ if (!id) throw new Error(`Destination dictionary has no identity: ${item.name}`);
10167
+ result.dictionaries.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
10168
+ continue;
10169
+ }
10170
+ if (!item.metadata || !item.keywords) throw new Error("Dictionary plan is incomplete");
10171
+ const created = await api.dictionaries.create({
10172
+ metadata: item.metadata,
10173
+ file: `${item.keywords.join("\n")}
10174
+ `,
10175
+ includeKeywords: true
10176
+ });
10177
+ if (!created.id) throw new Error(`Restored dictionary did not verify: ${item.name}`);
10178
+ const readBack = await api.dictionaries.get(created.id, { includeKeywords: true });
10179
+ const echo = dictionaryEcho(item.metadata, readBack, item.keywords);
10180
+ if (!echo.matches || readBack.name !== item.name)
10181
+ throw new Error(`Restored dictionary did not verify: ${item.name}`);
10182
+ if (echo.serverAdded.length)
10183
+ result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
10184
+ bindings.set(item.sourceId, { id: created.id, name: item.name });
10185
+ result.dictionaries.push({ name: item.name, action: "created", id: created.id });
10186
+ }
10187
+ for (const item of plan.patterns) {
10188
+ if (item.action !== "create") {
10189
+ const id = item.existing?.id;
10190
+ if (!id) throw new Error(`Destination data pattern has no identity: ${item.name}`);
10191
+ result.patterns.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
10192
+ continue;
10193
+ }
10194
+ if (!item.body) throw new Error("Data pattern plan is incomplete");
10195
+ const created = await api.patterns.create(item.body);
10196
+ if (!created.id) throw new Error(`Restored data pattern did not verify: ${item.name}`);
10197
+ const readBack = await api.patterns.get(created.id);
10198
+ const echo = compareEcho(item.body, readBack);
10199
+ if (!echo.matches || readBack.name !== item.name || RETIRED_PATTERN.has(readBack.status ?? "active"))
10200
+ throw new Error(`Restored data pattern did not verify: ${item.name}`);
10201
+ if (echo.serverAdded.length)
10202
+ result.serverAdded.push({
10203
+ resource: `data pattern ${item.name}`,
10204
+ fields: echo.serverAdded
10205
+ });
10206
+ bindings.set(item.sourceId, {
10207
+ id: created.id,
10208
+ name: item.name,
10209
+ version: readBack.version ?? void 0
10210
+ });
10211
+ result.patterns.push({ name: item.name, action: "created", id: created.id });
10212
+ }
10213
+ for (const item of plan.profiles) {
10214
+ if (item.action === "skip") {
10215
+ result.profiles.push({
10216
+ name: item.name,
10217
+ action: "skipped",
10218
+ id: item.existing?.id ?? void 0
10219
+ });
10220
+ continue;
10221
+ }
10222
+ const body = profileRequest(item, bindings, crossTenant, true);
10223
+ if (!body) throw new Error(`Missing planned dependency binding: ${item.name}`);
10224
+ if (item.action === "verify") {
10225
+ const echo2 = compareEcho(body, item.existing);
10226
+ if (!echo2.matches)
10227
+ throw new Error(`Destination data profile does not match restore plan: ${item.name}`);
10228
+ if (echo2.serverAdded.length)
10229
+ result.serverAdded.push({
10230
+ resource: `data profile ${item.name}`,
10231
+ fields: echo2.serverAdded
10232
+ });
10233
+ result.profiles.push({
10234
+ name: item.name,
10235
+ action: "verified",
10236
+ id: item.existing?.id ?? void 0
10237
+ });
10238
+ continue;
10239
+ }
10240
+ const created = await api.profiles.create(body);
10241
+ if (!created.id) throw new Error(`Restored data profile did not verify: ${item.name}`);
10242
+ const readBack = await api.profiles.get(created.id);
10243
+ const echo = compareEcho(body, readBack);
10244
+ if (!echo.matches || readBack.name !== item.name || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
10245
+ throw new Error(`Restored data profile did not verify: ${item.name}`);
10246
+ if (echo.serverAdded.length)
10247
+ result.serverAdded.push({
10248
+ resource: `data profile ${item.name}`,
10249
+ fields: echo.serverAdded
10250
+ });
10251
+ result.profiles.push({ name: item.name, action: "created", id: created.id });
10252
+ }
10253
+ result.complete = true;
10254
+ } catch (error) {
10255
+ const status = error?.statusCode;
10256
+ 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(
10257
+ error.message
10258
+ ) ? error.message : "Restore stopped; verify destination state before retrying";
10259
+ }
10260
+ return result;
10261
+ }
10262
+
10263
+ // src/cli/commands/dlp/transfer.ts
10264
+ var MAX_BACKUP_BYTES = 20 * 1024 * 1024;
10265
+ var FORMATS = "Output format: pretty, table, markdown, csv, json, yaml";
10266
+ function maxPages(value) {
10267
+ if (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 1e3)
10268
+ usageError("--max-pages must be an integer from 1 to 1000");
10269
+ return Number(value);
10270
+ }
10271
+ function resourceKinds(value) {
10272
+ const kinds = value.split(",").map((kind) => kind.trim());
10273
+ if (!kinds.length || kinds.some((kind) => !DLP_RESOURCE_KINDS.includes(kind)) || new Set(kinds).size !== kinds.length)
10274
+ throw new CliUsageError("--resources takes a unique list of dictionaries, patterns, profiles");
10275
+ return kinds;
10276
+ }
10277
+ function patternMap(values) {
10278
+ const entries = values.map((value) => {
10279
+ const at = value.indexOf("=");
10280
+ if (at < 1 || at === value.length - 1)
10281
+ throw new CliUsageError("--pattern-map requires source-name=destination-name");
10282
+ return [value.slice(0, at), value.slice(at + 1)];
10283
+ });
10284
+ if (new Set(entries.map(([key]) => key)).size !== entries.length)
10285
+ throw new CliUsageError("Duplicate source names in --pattern-map");
10286
+ return Object.fromEntries(entries);
10287
+ }
10288
+ async function context() {
10289
+ const config = await loadConfig();
10290
+ if (!config.mgmtTsgId || !config.mgmtClientId || !config.mgmtClientSecret)
10291
+ throw new CliUsageError(
10292
+ "DLP transfer requires configured Management client ID, secret and TSG ID"
10293
+ );
10294
+ const client = new ManagementClient({ ...managementClientOptions(config), numRetries: 0 });
10295
+ return {
10296
+ api: {
10297
+ dictionaries: client.dlp.dictionaries,
10298
+ patterns: client.dlp.dataPatterns,
10299
+ profiles: client.dlp.dataProfiles
10300
+ },
10301
+ tsgId: config.mgmtTsgId
10302
+ };
10303
+ }
10304
+ function failure(error) {
10305
+ if (error instanceof CliUsageError) usageError(error.message);
10306
+ if (error instanceof ZodError) {
10307
+ ui.error("Invalid DLP backup/request schema; content is not logged.");
10308
+ } else if (error instanceof AISecSDKException2) {
10309
+ ui.error(
10310
+ `DLP transfer API request failed${error.statusCode ? ` (HTTP ${error.statusCode})` : ""}; no automatic retry was made.`
10311
+ );
10312
+ } else if (error?.code) {
10313
+ ui.error(
10314
+ `DLP transfer file operation failed (${error.code}); existing files are not overwritten.`
10315
+ );
10316
+ } else {
10317
+ ui.error(error instanceof Error ? error.message : "DLP transfer failed");
10318
+ }
10319
+ process.exitCode = 1;
10320
+ }
10321
+ function renderRestore(value, columns, format) {
10322
+ if (format !== "pretty" && format !== "table" && format !== "markdown")
10323
+ return formatOutput([value], columns, format);
10324
+ const markdown = format === "markdown";
10325
+ const rows = (key) => Array.isArray(value[key]) ? value[key] : [];
10326
+ const heading = (text2) => markdown ? `### ${text2}
10327
+ ` : text2;
10328
+ const blocks = [
10329
+ heading(
10330
+ value.dryRun ? "Restore preview \u2014 no changes made" : value.complete ? "Restore complete" : "Restore incomplete \u2014 completed writes remain"
10331
+ ),
10332
+ `Source TSG: ${value.sourceTsgId}
10333
+ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
10334
+ ];
10335
+ for (const [key, title] of [
10336
+ ["dictionaries", "Dictionaries"],
10337
+ ["patterns", "Data patterns"],
10338
+ ["profiles", "Data profiles"]
10339
+ ]) {
10340
+ blocks.push(heading(`${title} (${rows(key).length})`));
10341
+ blocks.push(
10342
+ formatOutput(
10343
+ rows(key),
10344
+ [
10345
+ { key: "action", label: "Action" },
10346
+ { key: "name", label: "Name" }
10347
+ ],
10348
+ markdown ? "markdown" : "table"
10349
+ ) || "(none)"
10350
+ );
10351
+ }
10352
+ if (rows("serverAdded").length) {
10353
+ blocks.push(heading("Verified server-added fields (source omitted these)"));
10354
+ for (const item of rows("serverAdded"))
10355
+ blocks.push(
10356
+ `${item.resource}: ${Array.isArray(item.fields) ? item.fields.length : 0} fields; use --output json for paths`
10357
+ );
10358
+ }
10359
+ if (value.error) blocks.push(`Error: ${value.error}`);
10360
+ return blocks.join("\n\n");
10361
+ }
10362
+ function register6(dlp) {
10363
+ const backup = dlp.command("backup").description(
10364
+ "Back up custom DLP dictionaries (with keywords), data patterns and data profiles with their referenced dependencies"
10365
+ ).option(
10366
+ "--resources <list>",
10367
+ "Comma-separated primary resources: dictionaries, patterns, profiles (default: all)",
10368
+ resourceKinds
10369
+ ).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(
10370
+ "--skip-unsupported",
10371
+ "Exclude profiles with non-transferable rules (multi-profile, direct EDM) instead of failing"
10372
+ ).option(
10373
+ "--max-pages <n>",
10374
+ "Maximum pages per inventory; incomplete exports fail",
10375
+ maxPages,
10376
+ 100
10377
+ ).option("--output <format>", FORMATS).addHelpText(
10378
+ "after",
10379
+ examples(
10380
+ "airs runtime dlp backup --output-file ./dlp-backup.json",
10381
+ "airs runtime dlp backup --resources profiles --skip-unsupported --output-file ./profiles.yaml --file-format yaml"
10382
+ )
10383
+ ).action(async (opts) => {
10384
+ try {
10385
+ if (!["json", "yaml"].includes(opts.fileFormat))
10386
+ throw new CliUsageError("--file-format must be json or yaml");
10387
+ const format = await resolveOutput(backup, opts);
10388
+ const extension = opts.fileFormat === "yaml" ? ".yaml" : ".json";
10389
+ if (opts.outputFile === "-")
10390
+ throw new CliUsageError(
10391
+ "Backups contain dictionary keywords and require a private file destination, not stdout"
10392
+ );
10393
+ const path3 = resolve5(
10394
+ opts.outputFile ?? `airs-dlp-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID7().slice(0, 8)}${extension}`
10395
+ );
10396
+ if (!(opts.fileFormat === "json" ? [".json"] : [".yaml", ".yml"]).includes(
10397
+ extname(path3).toLowerCase()
10398
+ ))
10399
+ throw new CliUsageError("Backup filename extension must match --file-format");
10400
+ const { api, tsgId } = await context();
10401
+ const { backup: data, skipped } = await backupDlpResources(api, tsgId, {
10402
+ resources: opts.resources,
10403
+ maxPages: opts.maxPages,
10404
+ skipUnsupported: Boolean(opts.skipUnsupported)
10405
+ });
10406
+ for (const item of skipped)
10407
+ ui.warning(`Skipped unsupported data profile: ${item.profile} (${item.reason})`);
10408
+ const content = opts.fileFormat === "yaml" ? dump6(data, { schema: JSON_SCHEMA, noRefs: true, lineWidth: -1 }) : `${JSON.stringify(data, null, 2)}
10409
+ `;
10410
+ if (Buffer.byteLength(content) > MAX_BACKUP_BYTES)
10411
+ throw new Error("Backup exceeds the 20 MiB size limit; select fewer --resources");
10412
+ await writeReportFile(path3, content);
10413
+ console.log(
10414
+ formatOutput(
10415
+ [
10416
+ {
10417
+ file: path3,
10418
+ sourceTsgId: tsgId,
10419
+ dictionaries: data.dictionaries.length,
10420
+ patterns: data.patterns.length,
10421
+ profiles: data.profiles.length,
10422
+ skipped: skipped.length
10423
+ }
10424
+ ],
10425
+ [
10426
+ { key: "file", label: "Backup file" },
10427
+ { key: "sourceTsgId", label: "Source TSG" },
10428
+ { key: "dictionaries", label: "Dictionaries" },
10429
+ { key: "patterns", label: "Patterns" },
10430
+ { key: "profiles", label: "Profiles" },
10431
+ { key: "skipped", label: "Skipped" }
10432
+ ],
10433
+ format === "pretty" ? "table" : format
10434
+ )
10435
+ );
10436
+ } catch (error) {
10437
+ failure(error);
10438
+ }
10439
+ });
10440
+ const restore = dlp.command("restore <file>").description(
10441
+ "Restore DLP resources into the selected tenant, staged dictionaries \u2192 patterns \u2192 profiles; validate all dependencies before changes"
10442
+ ).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(
10443
+ "--on-conflict <policy>",
10444
+ "Existing data profile names: error, verify (resume without writes) or skip. Updates are unsupported until the DLP profile write path is live-verified.",
10445
+ "error"
10446
+ ).option(
10447
+ "--pattern-map <source=destination>",
10448
+ "Bind a tenant-bound source pattern to an existing destination pattern (repeatable)",
10449
+ (value, prior) => [...prior, value],
10450
+ []
10451
+ ).option(
10452
+ "--max-pages <n>",
10453
+ "Maximum pages per inventory; incomplete inventories fail",
10454
+ maxPages,
10455
+ 100
10456
+ ).option("--expect-tsg <id>", "Assert destination tenant ID; required with --force").option(
10457
+ "--force",
10458
+ "Execute without a prompt; requires --expect-tsg and does not override conflicts"
10459
+ ).option("--output <format>", FORMATS).addHelpText(
10460
+ "after",
10461
+ examples(
10462
+ "airs runtime dlp restore ./dlp-backup.json --dry-run --output json",
10463
+ 'airs runtime dlp restore ./dlp-backup.json --pattern-map "EDM SSN=Dest SSN" --dry-run',
10464
+ "airs runtime dlp restore ./dlp-backup.json --name-prefix migrated- --expect-tsg <destination-tsg> --force"
10465
+ )
10466
+ ).action(async (file, opts) => {
10467
+ try {
10468
+ if (!["error", "verify", "skip"].includes(opts.onConflict))
10469
+ throw new CliUsageError("--on-conflict must be error, verify or skip");
10470
+ if (opts.force && !opts.expectTsg)
10471
+ throw new CliUsageError("--force requires --expect-tsg <destination-tsg>");
10472
+ const mappings = patternMap(opts.patternMap);
10473
+ const path3 = resolve5(file);
10474
+ const extension = extname(path3).toLowerCase();
10475
+ if (![".json", ".yaml", ".yml"].includes(extension))
10476
+ throw new CliUsageError("Backup file must use .json, .yaml or .yml");
10477
+ const info = await stat(path3);
10478
+ if (!info.isFile() || info.size > MAX_BACKUP_BYTES)
10479
+ throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
10480
+ let input2;
10481
+ try {
10482
+ const text2 = await readFile9(path3, "utf8");
10483
+ if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES) throw new Error("Backup too large");
10484
+ input2 = extension === ".json" ? JSON.parse(text2) : load2(text2, { schema: JSON_SCHEMA });
10485
+ } catch {
10486
+ throw new CliUsageError("Backup is not valid JSON/YAML; contents are not logged");
10487
+ }
10488
+ const data = DlpResourcesBackupSchema.parse(input2);
10489
+ const format = await resolveOutput(restore, opts);
10490
+ const { api, tsgId } = await context();
10491
+ if (opts.expectTsg && opts.expectTsg !== tsgId)
10492
+ throw new CliUsageError(
10493
+ "--expect-tsg does not match the selected destination tenant; no API request was made"
10494
+ );
10495
+ const plan = await planDlpResourcesRestore(api, data, tsgId, {
10496
+ namePrefix: opts.namePrefix,
10497
+ onConflict: opts.onConflict,
10498
+ patternMap: mappings,
10499
+ maxPages: opts.maxPages
10500
+ });
10501
+ const summary = {
10502
+ sourceTsgId: plan.sourceTsgId,
10503
+ destinationTsgId: plan.destinationTsgId,
10504
+ dryRun: Boolean(opts.dryRun),
10505
+ dictionaries: plan.dictionaries.map((d) => ({ name: d.name, action: d.action })),
10506
+ patterns: plan.patterns.map((p) => ({ name: p.name, action: p.action })),
10507
+ profiles: plan.profiles.map((p) => ({ name: p.name, action: p.action }))
10508
+ };
10509
+ const columns = [
10510
+ { key: "sourceTsgId", label: "Source TSG" },
10511
+ { key: "destinationTsgId", label: "Destination TSG" },
10512
+ { key: "dictionaries", label: "Dictionaries" },
10513
+ { key: "patterns", label: "Data patterns" },
10514
+ { key: "profiles", label: "Data profiles" },
10515
+ { key: "serverAdded", label: "Verified server-added fields" },
10516
+ { key: "dryRun", label: "Dry run" },
10517
+ { key: "complete", label: "Complete" }
10518
+ ];
10519
+ if (opts.dryRun) {
10520
+ console.log(renderRestore(summary, columns, format));
10521
+ return;
10522
+ }
10523
+ 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;
10524
+ if (writes)
10525
+ await confirmOrAbort(
10526
+ `Restore ${writes} DLP resources from TSG ${plan.sourceTsgId} to TSG ${tsgId}?`,
10527
+ Boolean(opts.force),
10528
+ { action: `restore DLP resources into TSG ${tsgId}` }
10529
+ );
10530
+ const result = await restoreDlpResources(api, plan);
10531
+ console.log(renderRestore({ ...result }, columns, format));
10532
+ if (!result.complete) {
10533
+ ui.error(result.error ?? "Restore incomplete");
10534
+ process.exitCode = 1;
10535
+ }
10536
+ } catch (error) {
10537
+ failure(error);
10538
+ }
10539
+ });
10540
+ }
10541
+
9553
10542
  // src/cli/commands/dlp/index.ts
9554
10543
  function registerDlpCommands(runtime) {
9555
- const dlp = runtime.command("dlp").description("DLP management (filtering-profiles, patterns, profiles, dictionaries, generate)");
10544
+ const dlp = runtime.command("dlp").description(
10545
+ "DLP management (filtering-profiles, patterns, profiles, dictionaries, generate, backup/restore)"
10546
+ );
9556
10547
  register2(dlp);
9557
10548
  register4(dlp);
9558
10549
  register5(dlp);
9559
10550
  register(dlp);
9560
10551
  register3(dlp);
10552
+ register6(dlp);
9561
10553
  }
9562
10554
 
9563
10555
  // 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";
10556
+ import { randomUUID as randomUUID8 } from "crypto";
10557
+ import { readFile as readFile10, stat as stat2 } from "fs/promises";
10558
+ import { extname as extname2, resolve as resolve6 } from "path";
10559
+ import { AISecSDKException as AISecSDKException3, ManagementClient as ManagementClient2 } from "@cdot65/prisma-airs-sdk";
10560
+ import { dump as dump7, JSON_SCHEMA as JSON_SCHEMA2, load as load3 } from "js-yaml";
10561
+ import { ZodError as ZodError2 } from "zod";
9570
10562
 
9571
10563
  // src/backup/runtime-profiles.ts
9572
10564
  import {
@@ -9575,10 +10567,10 @@ import {
9575
10567
  PolicySchema,
9576
10568
  SecurityProfileSchema
9577
10569
  } from "@cdot65/prisma-airs-sdk";
9578
- import { z as z5 } from "zod";
10570
+ import { z as z6 } from "zod";
9579
10571
 
9580
10572
  // src/backup/runtime-policy.ts
9581
- function object(value) {
10573
+ function object2(value) {
9582
10574
  return value !== null && typeof value === "object" && !Array.isArray(value);
9583
10575
  }
9584
10576
  function compareRuntimePolicies(expected, actual) {
@@ -9599,7 +10591,7 @@ function compareRuntimePolicies(expected, actual) {
9599
10591
  });
9600
10592
  return;
9601
10593
  }
9602
- if (object(source) && object(target)) {
10594
+ if (object2(source) && object2(target)) {
9603
10595
  for (const key of /* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(target)])) {
9604
10596
  const field = `${path3}.${key}`;
9605
10597
  if (!Object.hasOwn(source, key) && Object.hasOwn(target, key)) {
@@ -9630,14 +10622,14 @@ function compareRuntimePolicies(expected, actual) {
9630
10622
  }
9631
10623
  if (relative === "model.app-protection" && key === "url-detected-severity")
9632
10624
  allowed = "low";
9633
- if (relative === "model.app-protection" && key === "default-url-category" && object(target[key])) {
10625
+ if (relative === "model.app-protection" && key === "default-url-category" && object2(target[key])) {
9634
10626
  const value = target[key];
9635
10627
  if (Object.keys(value).length === 1 && value.member === null) {
9636
10628
  serverDefaults.push(field);
9637
10629
  continue;
9638
10630
  }
9639
10631
  }
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])) {
10632
+ 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
10633
  const value = target[key];
9642
10634
  if (Object.keys(value).length === 2 && value.high === "medium" && value.moderate === "low") {
9643
10635
  serverDefaults.push(field);
@@ -9672,26 +10664,26 @@ function compareRuntimePolicies(expected, actual) {
9672
10664
  }
9673
10665
 
9674
10666
  // 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)
10667
+ var RuntimeProfilesBackupSchema = z6.object({
10668
+ version: z6.literal(1),
10669
+ resourceType: z6.literal("runtime-security-profiles"),
10670
+ exportedAt: z6.string().datetime({ offset: true }),
10671
+ source: z6.object({ tsgId: z6.string().min(1) }).strict(),
10672
+ profiles: z6.array(SecurityProfileSchema).min(1).max(1e4),
10673
+ topics: z6.array(CustomTopicSchema).max(1e4)
9682
10674
  }).strict();
9683
- function object2(value) {
10675
+ function object3(value) {
9684
10676
  return value !== null && typeof value === "object" && !Array.isArray(value);
9685
10677
  }
9686
- function pagesLimit(value) {
10678
+ function pagesLimit2(value) {
9687
10679
  if (!Number.isSafeInteger(value) || value < 1 || value > 1e3)
9688
10680
  throw new Error("maxPages must be an integer from 1 to 1000");
9689
10681
  }
9690
- async function offsetPages(fetch, maxPages2) {
9691
- pagesLimit(maxPages2);
10682
+ async function offsetPages(fetch, maxPages3) {
10683
+ pagesLimit2(maxPages3);
9692
10684
  const items = [];
9693
10685
  let offset = 0;
9694
- for (let page = 0; page < maxPages2; page++) {
10686
+ for (let page = 0; page < maxPages3; page++) {
9695
10687
  const result = await fetch(offset);
9696
10688
  items.push(...result.items);
9697
10689
  if (items.length > 1e4) throw new Error("Inventory exceeds the 10000-record safety limit");
@@ -9706,7 +10698,7 @@ function latestProfiles(profiles2) {
9706
10698
  const names = /* @__PURE__ */ new Map();
9707
10699
  const revisions = /* @__PURE__ */ new Set();
9708
10700
  for (const profile of profiles2) {
9709
- assertName(profile.profile_name);
10701
+ assertName2(profile.profile_name);
9710
10702
  const identity2 = JSON.stringify([profile.profile_name, profile.profile_id, profile.revision]);
9711
10703
  if (revisions.has(identity2)) throw new Error("Inventory contains repeated profile records");
9712
10704
  revisions.add(identity2);
@@ -9718,7 +10710,7 @@ function latestProfiles(profiles2) {
9718
10710
  }
9719
10711
  return [...names.values()].sort((a, b) => a.profile_name.localeCompare(b.profile_name));
9720
10712
  }
9721
- function assertName(name) {
10713
+ function assertName2(name) {
9722
10714
  if (!name.trim() || [...name].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9723
10715
  throw new Error("Profile/topic names must be nonempty and contain no control characters");
9724
10716
  }
@@ -9726,19 +10718,19 @@ function assertTenant(profiles2, tsgId) {
9726
10718
  if (profiles2.some((profile) => profile.tsg_id != null && String(profile.tsg_id) !== tsgId))
9727
10719
  throw new Error("Destination/source profile tenant identity does not match the selected TSG");
9728
10720
  }
9729
- async function profiles(api, maxPages2) {
10721
+ async function profiles(api, maxPages3) {
9730
10722
  return latestProfiles(
9731
10723
  await offsetPages(async (offset) => {
9732
10724
  const page = await api.profiles.list({ offset, limit: 100, latest: true });
9733
10725
  return { items: page.ai_profiles, next: page.next_offset };
9734
- }, maxPages2)
10726
+ }, maxPages3)
9735
10727
  );
9736
10728
  }
9737
- async function topics(api, maxPages2) {
10729
+ async function topics(api, maxPages3) {
9738
10730
  return offsetPages(async (offset) => {
9739
10731
  const page = await api.topics.list({ offset, limit: 100 });
9740
10732
  return { items: page.custom_topics, next: page.next_offset };
9741
- }, maxPages2);
10733
+ }, maxPages3);
9742
10734
  }
9743
10735
  function topicReferences(policy) {
9744
10736
  const refs = [];
@@ -9747,7 +10739,7 @@ function topicReferences(policy) {
9747
10739
  value.forEach(visit);
9748
10740
  return;
9749
10741
  }
9750
- if (!object2(value)) return;
10742
+ if (!object3(value)) return;
9751
10743
  if ("topic_id" in value) {
9752
10744
  if (typeof value.topic_id !== "string" || typeof value.topic_name !== "string" || !Number.isSafeInteger(value.revision) || Number(value.revision) < 0)
9753
10745
  throw new Error("Invalid topic identity in profile policy");
@@ -9763,8 +10755,8 @@ function topicKey(ref) {
9763
10755
  }
9764
10756
  async function backupRuntimeProfiles(api, tsgId, options = {}) {
9765
10757
  if (!tsgId) throw new Error("Source TSG ID is required");
9766
- const maxPages2 = options.maxPages ?? 100;
9767
- let selected = await profiles(api, maxPages2);
10758
+ const maxPages3 = options.maxPages ?? 100;
10759
+ let selected = await profiles(api, maxPages3);
9768
10760
  assertTenant(selected, tsgId);
9769
10761
  if (options.profile)
9770
10762
  selected = selected.filter(
@@ -9777,7 +10769,7 @@ async function backupRuntimeProfiles(api, tsgId, options = {}) {
9777
10769
  PolicySchema.parse(profile.policy);
9778
10770
  }
9779
10771
  const refs = selected.flatMap((profile) => topicReferences(profile.policy));
9780
- const definitions = refs.length ? await topics(api, maxPages2) : [];
10772
+ const definitions = refs.length ? await topics(api, maxPages3) : [];
9781
10773
  const needed = /* @__PURE__ */ new Map();
9782
10774
  for (const ref of refs) {
9783
10775
  const matches = definitions.filter((topic) => topicKey(topic) === topicKey(ref));
@@ -9800,21 +10792,21 @@ function sameTopic(a, b) {
9800
10792
  return a.description === b.description && JSON.stringify(a.examples) === JSON.stringify(b.examples) && (a.active ?? true) === (b.active ?? true);
9801
10793
  }
9802
10794
  function dlpReferences(policy) {
9803
- if (!object2(policy)) return [];
10795
+ if (!object3(policy)) return [];
9804
10796
  const refs = [];
9805
10797
  for (const entry of Array.isArray(policy["dlp-data-profiles"]) ? policy["dlp-data-profiles"] : []) {
9806
- if (!object2(entry) || typeof entry.name !== "string")
10798
+ if (!object3(entry) || typeof entry.name !== "string")
9807
10799
  throw new Error("Invalid embedded DLP reference");
9808
10800
  refs.push({ value: entry, name: entry.name, kind: "embedded" });
9809
10801
  }
9810
10802
  for (const entry of Array.isArray(policy["ai-security-profiles"]) ? policy["ai-security-profiles"] : []) {
9811
- if (!object2(entry) || !object2(entry["model-configuration"])) continue;
10803
+ if (!object3(entry) || !object3(entry["model-configuration"])) continue;
9812
10804
  const protection = entry["model-configuration"]["data-protection"];
9813
- if (!object2(protection) || !object2(protection["data-leak-detection"])) continue;
10805
+ if (!object3(protection) || !object3(protection["data-leak-detection"])) continue;
9814
10806
  const members = protection["data-leak-detection"].member;
9815
10807
  if (!Array.isArray(members)) continue;
9816
10808
  for (const member of members) {
9817
- if (!object2(member) || typeof member.text !== "string") throw new Error("Invalid DLP member");
10809
+ if (!object3(member) || typeof member.text !== "string") throw new Error("Invalid DLP member");
9818
10810
  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
10811
  continue;
9820
10812
  refs.push({ value: member, name: member.text, kind: "member" });
@@ -9857,7 +10849,7 @@ function assertKnownPolicyReferences(policy) {
9857
10849
  });
9858
10850
  return;
9859
10851
  }
9860
- if (!object2(value)) return;
10852
+ if (!object3(value)) return;
9861
10853
  for (const [key, child] of Object.entries(value)) {
9862
10854
  if (child !== null && child !== "" && /(?:^|[-_])(id|uuid)$|(?:Id|ID|Uuid|UUID)$/.test(key) && !known.get(value)?.includes(key))
9863
10855
  throw new Error(`Unsupported cross-tenant policy reference at ${path3}.${key}`);
@@ -9866,9 +10858,9 @@ function assertKnownPolicyReferences(policy) {
9866
10858
  };
9867
10859
  visit(policy, "policy");
9868
10860
  }
9869
- async function dataProfiles(api, maxPages2) {
10861
+ async function dataProfiles(api, maxPages3) {
9870
10862
  const items = [];
9871
- for (let page = 0; page < maxPages2; page++) {
10863
+ for (let page = 0; page < maxPages3; page++) {
9872
10864
  const result = await api.dataProfiles.list({ page, size: 100 });
9873
10865
  items.push(...result.content);
9874
10866
  if (items.length > 1e4) throw new Error("DLP inventory exceeds the safety limit");
@@ -9881,8 +10873,8 @@ async function dataProfiles(api, maxPages2) {
9881
10873
  async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options = {}) {
9882
10874
  const backup = RuntimeProfilesBackupSchema.parse(input2);
9883
10875
  if (!destinationTsgId) throw new Error("Destination TSG ID is required");
9884
- const maxPages2 = options.maxPages ?? 100;
9885
- pagesLimit(maxPages2);
10876
+ const maxPages3 = options.maxPages ?? 100;
10877
+ pagesLimit2(maxPages3);
9886
10878
  const prefix = options.namePrefix ?? "";
9887
10879
  if (prefix.length > 64 || [...prefix].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
9888
10880
  throw new Error("Invalid --name-prefix");
@@ -9905,7 +10897,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9905
10897
  });
9906
10898
  if (crossTenant) assertKnownPolicyReferences(profile.policy);
9907
10899
  }
9908
- const existing = await profiles(api, maxPages2);
10900
+ const existing = await profiles(api, maxPages3);
9909
10901
  assertTenant(existing, destinationTsgId);
9910
10902
  const profilePlans = selected.map((source) => {
9911
10903
  const name = `${prefix}${source.profile_name}`;
@@ -9924,7 +10916,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9924
10916
  });
9925
10917
  const changing = profilePlans.filter((p) => p.action !== "skip");
9926
10918
  const refs = changing.flatMap((p) => topicReferences(p.source.policy));
9927
- const targetTopics = refs.length ? await topics(api, maxPages2) : [];
10919
+ const targetTopics = refs.length ? await topics(api, maxPages3) : [];
9928
10920
  const topicPlans = /* @__PURE__ */ new Map();
9929
10921
  const topicNames = /* @__PURE__ */ new Map();
9930
10922
  for (const ref of refs) {
@@ -9937,7 +10929,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9937
10929
  throw new Error(`Backup lacks an unambiguous topic definition: ${ref.topic_name}`);
9938
10930
  const source = definitions[0];
9939
10931
  const name = `${prefix}${source.topic_name}`;
9940
- assertName(name);
10932
+ assertName2(name);
9941
10933
  if (topicNames.has(name) && topicNames.get(name) !== key)
9942
10934
  throw new Error(
9943
10935
  `Multiple source revisions use topic name ${name}; split the backup by profile/revision before restoring`
@@ -9964,7 +10956,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
9964
10956
  );
9965
10957
  if (!targetFor(name)) unresolved.add(name);
9966
10958
  }
9967
- const catalog = names.some((name) => targetFor(name)) ? await dataProfiles(api, maxPages2) : [];
10959
+ const catalog = names.some((name) => targetFor(name)) ? await dataProfiles(api, maxPages3) : [];
9968
10960
  const resolved = [];
9969
10961
  for (const name of names) {
9970
10962
  const target = targetFor(name);
@@ -10015,7 +11007,7 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
10015
11007
  dlpMappings,
10016
11008
  dlpFallbacks,
10017
11009
  serverDefaults: [],
10018
- maxPages: maxPages2
11010
+ maxPages: maxPages3
10019
11011
  };
10020
11012
  const existingTopics = new Map(
10021
11013
  plan.topics.filter((t) => t.existing).map((t) => [t.key, t.existing])
@@ -10032,8 +11024,8 @@ async function planRuntimeProfilesRestore(api, input2, destinationTsgId, options
10032
11024
  }
10033
11025
  return plan;
10034
11026
  }
10035
- function canonical(value) {
10036
- const order = (v) => Array.isArray(v) ? v.map(order) : object2(v) ? Object.fromEntries(
11027
+ function canonical2(value) {
11028
+ const order = (v) => Array.isArray(v) ? v.map(order) : object3(v) ? Object.fromEntries(
10037
11029
  Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).map(([k, child]) => [k, order(child)])
10038
11030
  ) : v;
10039
11031
  return JSON.stringify(order(value));
@@ -10044,7 +11036,7 @@ function canonicalPolicy(policy) {
10044
11036
  const protection = entry["model-configuration"]?.["data-protection"];
10045
11037
  if (protection && !("database-security" in protection)) protection["database-security"] = null;
10046
11038
  }
10047
- return canonical(value);
11039
+ return canonical2(value);
10048
11040
  }
10049
11041
  function unchanged(current, prior) {
10050
11042
  return Boolean(
@@ -10187,9 +11179,9 @@ function mappedPolicy(item, plan, mappedTopics) {
10187
11179
  }
10188
11180
 
10189
11181
  // 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) {
11182
+ var MAX_BACKUP_BYTES2 = 20 * 1024 * 1024;
11183
+ var FORMATS2 = "Output format: pretty, table, markdown, csv, json, yaml";
11184
+ function renderRestore2(value, columns, format) {
10193
11185
  if (format !== "pretty" && format !== "table" && format !== "markdown")
10194
11186
  return formatOutput([value], columns, format);
10195
11187
  const markdown = format === "markdown";
@@ -10249,7 +11241,7 @@ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
10249
11241
  if (value.error) blocks.push(`Error: ${value.error}`);
10250
11242
  return blocks.join("\n\n");
10251
11243
  }
10252
- function maxPages(value) {
11244
+ function maxPages2(value) {
10253
11245
  if (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 1e3)
10254
11246
  usageError("--max-pages must be an integer from 1 to 1000");
10255
11247
  return Number(value);
@@ -10265,13 +11257,13 @@ function dlpMap(values) {
10265
11257
  throw new CliUsageError("Duplicate source names in --dlp-map");
10266
11258
  return Object.fromEntries(entries);
10267
11259
  }
10268
- async function context() {
11260
+ async function context2() {
10269
11261
  const config = await loadConfig();
10270
11262
  if (!config.mgmtTsgId || !config.mgmtClientId || !config.mgmtClientSecret)
10271
11263
  throw new CliUsageError(
10272
11264
  "Profile transfer requires configured Management client ID, secret and TSG ID"
10273
11265
  );
10274
- const client = new ManagementClient({ ...managementClientOptions(config), numRetries: 0 });
11266
+ const client = new ManagementClient2({ ...managementClientOptions(config), numRetries: 0 });
10275
11267
  return {
10276
11268
  api: {
10277
11269
  profiles: client.profiles,
@@ -10281,11 +11273,11 @@ async function context() {
10281
11273
  tsgId: config.mgmtTsgId
10282
11274
  };
10283
11275
  }
10284
- function failure(error) {
11276
+ function failure2(error) {
10285
11277
  if (error instanceof CliUsageError) usageError(error.message);
10286
- if (error instanceof ZodError) {
11278
+ if (error instanceof ZodError2) {
10287
11279
  ui.error("Invalid profile backup/request schema; content is not logged.");
10288
- } else if (error instanceof AISecSDKException2) {
11280
+ } else if (error instanceof AISecSDKException3) {
10289
11281
  ui.error(
10290
11282
  `Profile transfer API request failed${error.statusCode ? ` (HTTP ${error.statusCode})` : ""}; no automatic retry was made.`
10291
11283
  );
@@ -10304,9 +11296,9 @@ function registerProfileTransferCommands(profiles2) {
10304
11296
  ).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
11297
  "--max-pages <n>",
10306
11298
  "Maximum pages per inventory; incomplete exports fail",
10307
- maxPages,
11299
+ maxPages2,
10308
11300
  100
10309
- ).option("--output <format>", FORMATS).addHelpText(
11301
+ ).option("--output <format>", FORMATS2).addHelpText(
10310
11302
  "after",
10311
11303
  examples(
10312
11304
  "airs runtime profiles backup --all --output-file ./profiles.json",
@@ -10322,18 +11314,18 @@ function registerProfileTransferCommands(profiles2) {
10322
11314
  const extension = opts.fileFormat === "yaml" ? ".yaml" : ".json";
10323
11315
  if (opts.outputFile === "-")
10324
11316
  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}`
11317
+ const path3 = resolve6(
11318
+ opts.outputFile ?? `airs-runtime-profiles-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID8().slice(0, 8)}${extension}`
10327
11319
  );
10328
11320
  if (!(opts.fileFormat === "json" ? [".json"] : [".yaml", ".yml"]).includes(
10329
- extname(path3).toLowerCase()
11321
+ extname2(path3).toLowerCase()
10330
11322
  ))
10331
11323
  throw new CliUsageError("Backup filename extension must match --file-format");
10332
- const { api, tsgId } = await context();
11324
+ const { api, tsgId } = await context2();
10333
11325
  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)}
11326
+ const content = opts.fileFormat === "yaml" ? dump7(data, { schema: JSON_SCHEMA2, noRefs: true, lineWidth: -1 }) : `${JSON.stringify(data, null, 2)}
10335
11327
  `;
10336
- if (Buffer.byteLength(content) > MAX_BACKUP_BYTES)
11328
+ if (Buffer.byteLength(content) > MAX_BACKUP_BYTES2)
10337
11329
  throw new Error("Backup exceeds the 20 MiB size limit; select individual profiles");
10338
11330
  await writeReportFile(path3, content);
10339
11331
  console.log(
@@ -10356,7 +11348,7 @@ function registerProfileTransferCommands(profiles2) {
10356
11348
  )
10357
11349
  );
10358
11350
  } catch (error) {
10359
- failure(error);
11351
+ failure2(error);
10360
11352
  }
10361
11353
  });
10362
11354
  const restore = profiles2.command("restore <file>").description(
@@ -10377,12 +11369,12 @@ function registerProfileTransferCommands(profiles2) {
10377
11369
  ).option(
10378
11370
  "--max-pages <n>",
10379
11371
  "Maximum pages per inventory; incomplete inventories fail",
10380
- maxPages,
11372
+ maxPages2,
10381
11373
  100
10382
11374
  ).option("--expect-tsg <id>", "Assert destination tenant ID; required with --force").option(
10383
11375
  "--force",
10384
11376
  "Execute without a prompt; requires --expect-tsg and does not override conflicts"
10385
- ).option("--output <format>", FORMATS).addHelpText(
11377
+ ).option("--output <format>", FORMATS2).addHelpText(
10386
11378
  "after",
10387
11379
  examples(
10388
11380
  "airs runtime profiles restore ./profiles.json --dry-run --output json",
@@ -10398,24 +11390,24 @@ function registerProfileTransferCommands(profiles2) {
10398
11390
  if (opts.force && !opts.expectTsg)
10399
11391
  throw new CliUsageError("--force requires --expect-tsg <destination-tsg>");
10400
11392
  const mappings = dlpMap(opts.dlpMap);
10401
- const path3 = resolve5(file);
10402
- const extension = extname(path3).toLowerCase();
11393
+ const path3 = resolve6(file);
11394
+ const extension = extname2(path3).toLowerCase();
10403
11395
  if (![".json", ".yaml", ".yml"].includes(extension))
10404
11396
  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)
11397
+ const info = await stat2(path3);
11398
+ if (!info.isFile() || info.size > MAX_BACKUP_BYTES2)
10407
11399
  throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
10408
11400
  let input2;
10409
11401
  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 });
11402
+ const text2 = await readFile10(path3, "utf8");
11403
+ if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES2) throw new Error("Backup too large");
11404
+ input2 = extension === ".json" ? JSON.parse(text2) : load3(text2, { schema: JSON_SCHEMA2 });
10413
11405
  } catch {
10414
11406
  throw new CliUsageError("Backup is not valid JSON/YAML; contents are not logged");
10415
11407
  }
10416
11408
  const data = RuntimeProfilesBackupSchema.parse(input2);
10417
11409
  const format = await resolveOutput(restore, opts);
10418
- const { api, tsgId } = await context();
11410
+ const { api, tsgId } = await context2();
10419
11411
  if (opts.expectTsg && opts.expectTsg !== tsgId)
10420
11412
  throw new CliUsageError(
10421
11413
  "--expect-tsg does not match the selected destination tenant; no API request was made"
@@ -10459,7 +11451,7 @@ function registerProfileTransferCommands(profiles2) {
10459
11451
  `DLP protection change: ${fallback.profile} will use Basic detection instead of ${fallback.replaced.join(", ")}. Custom rules are not preserved.`
10460
11452
  );
10461
11453
  if (opts.dryRun) {
10462
- console.log(renderRestore(summary, columns, format));
11454
+ console.log(renderRestore2(summary, columns, format));
10463
11455
  return;
10464
11456
  }
10465
11457
  const changing = plan.profiles.filter(
@@ -10472,13 +11464,13 @@ function registerProfileTransferCommands(profiles2) {
10472
11464
  { action: `restore profiles into TSG ${tsgId}` }
10473
11465
  );
10474
11466
  const result = await restoreRuntimeProfiles(api, plan);
10475
- console.log(renderRestore({ ...result }, columns, format));
11467
+ console.log(renderRestore2({ ...result }, columns, format));
10476
11468
  if (!result.complete) {
10477
11469
  ui.error(result.error ?? "Restore incomplete");
10478
11470
  process.exitCode = 1;
10479
11471
  }
10480
11472
  } catch (error) {
10481
- failure(error);
11473
+ failure2(error);
10482
11474
  }
10483
11475
  });
10484
11476
  }
@@ -10586,8 +11578,8 @@ function registerCleanupCommand(parent) {
10586
11578
 
10587
11579
  // src/cli/commands/runtime-dashboard.ts
10588
11580
  import { lstat as lstat4 } from "fs/promises";
10589
- import { resolve as resolve6 } from "path";
10590
- import { dump as dump7 } from "js-yaml";
11581
+ import { resolve as resolve7 } from "path";
11582
+ import { dump as dump8 } from "js-yaml";
10591
11583
  var TIME_UNITS = ["hour", "hours", "day", "days"];
10592
11584
  function integer3(value, name, minimum = 0) {
10593
11585
  const n = Number(value);
@@ -10649,7 +11641,7 @@ function scanOptions(command) {
10649
11641
  function registerRuntimeDashboardCommands(runtime) {
10650
11642
  const dashboard = runtime.command("dashboard").description("SCM application summaries and daily telemetry (OAuth)");
10651
11643
  const sessions = runtime.command("sessions").description("Verified SCM session inventory and drill-down (OAuth)");
10652
- function register6(command, prepare) {
11644
+ function register7(command, prepare) {
10653
11645
  command.option("--output <format>", "Output format: pretty, json, yaml").action(async (opts) => {
10654
11646
  try {
10655
11647
  const read = prepare(opts);
@@ -10662,7 +11654,7 @@ function registerRuntimeDashboardCommands(runtime) {
10662
11654
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
10663
11655
  );
10664
11656
  if (opts.outputFile) {
10665
- const path3 = resolve6(text(opts.outputFile, "--output-file"));
11657
+ const path3 = resolve7(text(opts.outputFile, "--output-file"));
10666
11658
  const existing = await lstat4(path3).catch((error) => {
10667
11659
  if (error.code !== "ENOENT") throw error;
10668
11660
  return void 0;
@@ -10676,9 +11668,9 @@ function registerRuntimeDashboardCommands(runtime) {
10676
11668
  numRetries: 0
10677
11669
  });
10678
11670
  const result = await read(client.dashboard);
10679
- const output = format === "yaml" ? dump7(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
11671
+ const output = format === "yaml" ? dump8(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
10680
11672
  if (command.name() === "scan-content" && opts.outputFile) {
10681
- await writeReportFile(resolve6(String(opts.outputFile)), `${output}
11673
+ await writeReportFile(resolve7(String(opts.outputFile)), `${output}
10682
11674
  `);
10683
11675
  ui.status("Scan content exported to the requested private file (0600).");
10684
11676
  } else console.log(output);
@@ -10692,7 +11684,7 @@ function registerRuntimeDashboardCommands(runtime) {
10692
11684
  }
10693
11685
  });
10694
11686
  }
10695
- register6(
11687
+ register7(
10696
11688
  pageOptions2(
10697
11689
  timeOptions(
10698
11690
  dashboard.command("applications").description("One page of application activity"),
@@ -10721,7 +11713,7 @@ function registerRuntimeDashboardCommands(runtime) {
10721
11713
  ["application", "application"],
10722
11714
  ["application-violations", "applicationViolationBreakdown"]
10723
11715
  ]) {
10724
- register6(
11716
+ register7(
10725
11717
  timeOptions(
10726
11718
  dashboard.command(name).requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
10727
11719
  "30",
@@ -10747,7 +11739,7 @@ function registerRuntimeDashboardCommands(runtime) {
10747
11739
  ["violations-trend", "applicationsViolationsTrend", false],
10748
11740
  ["apps-list", "appsList", true]
10749
11741
  ]) {
10750
- register6(
11742
+ register7(
10751
11743
  timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
10752
11744
  (opts) => {
10753
11745
  const query2 = window(opts);
@@ -10755,11 +11747,11 @@ function registerRuntimeDashboardCommands(runtime) {
10755
11747
  }
10756
11748
  );
10757
11749
  }
10758
- register6(timeOptions(sessions.command("chart")), (opts) => {
11750
+ register7(timeOptions(sessions.command("chart")), (opts) => {
10759
11751
  const query2 = window(opts);
10760
11752
  return (client) => client.sessionsChart(query2);
10761
11753
  });
10762
- register6(
11754
+ register7(
10763
11755
  pageOptions2(
10764
11756
  timeOptions(
10765
11757
  sessions.command("list").description("Session entries; --all walks pages with completeness checks")
@@ -10825,7 +11817,7 @@ function registerRuntimeDashboardCommands(runtime) {
10825
11817
  };
10826
11818
  }
10827
11819
  );
10828
- register6(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
11820
+ register7(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
10829
11821
  const query2 = {
10830
11822
  ...identity(opts),
10831
11823
  limit: integer3(opts.limit, "--limit", 1),
@@ -10833,11 +11825,11 @@ function registerRuntimeDashboardCommands(runtime) {
10833
11825
  };
10834
11826
  return (client) => client.session(query2);
10835
11827
  });
10836
- register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
11828
+ register7(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
10837
11829
  const query2 = { ...identity(opts), ...scan(opts) };
10838
11830
  return (client) => client.sessionTransaction(query2);
10839
11831
  });
10840
- register6(
11832
+ register7(
10841
11833
  scanOptions(
10842
11834
  sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
10843
11835
  ).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
@@ -10850,8 +11842,8 @@ function registerRuntimeDashboardCommands(runtime) {
10850
11842
 
10851
11843
  // src/cli/commands/runtime-report.ts
10852
11844
  import { lstat as lstat5 } from "fs/promises";
10853
- import { resolve as resolve7 } from "path";
10854
- import { randomUUID as randomUUID8 } from "crypto";
11845
+ import { resolve as resolve8 } from "path";
11846
+ import { randomUUID as randomUUID9 } from "crypto";
10855
11847
  function registerRuntimeReportCommand(runtime) {
10856
11848
  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
11849
  "--output-file <path>",
@@ -10868,15 +11860,15 @@ function registerRuntimeReportCommand(runtime) {
10868
11860
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
10869
11861
  if (format !== "html" && format !== "markdown")
10870
11862
  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)
11863
+ const maxPages3 = Number(opts.maxPages);
11864
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages3) || maxPages3 > 100)
10873
11865
  usageError("--max-pages must be an integer from 1 to 100");
10874
11866
  if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
10875
11867
  usageError("--title must contain 1\u2013240 characters");
10876
11868
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
10877
11869
  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);
11870
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID9().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
11871
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve8(opts.outputFile ?? defaultName);
10880
11872
  try {
10881
11873
  if (outputPath) {
10882
11874
  const existing = await lstat5(outputPath).catch((error) => {
@@ -10891,7 +11883,7 @@ function registerRuntimeReportCommand(runtime) {
10891
11883
  numRetries: 0
10892
11884
  });
10893
11885
  ui.status("Collecting daily runtime activity and current configuration (read-only)...");
10894
- const report = await collectRuntimeDailyReport(client, { title: opts.title, maxPages: maxPages2 });
11886
+ const report = await collectRuntimeDailyReport(client, { title: opts.title, maxPages: maxPages3 });
10895
11887
  const rendered = format === "html" ? renderRuntimeReportHtml(report) : renderRuntimeReportMarkdown(report);
10896
11888
  if (outputPath) {
10897
11889
  await writeReportFile(outputPath, rendered);
@@ -11051,7 +12043,7 @@ function registerCreateCommand(parent) {
11051
12043
  }
11052
12044
 
11053
12045
  // src/cli/commands/topics-eval.ts
11054
- import { readFile as readFile10 } from "fs/promises";
12046
+ import { readFile as readFile11 } from "fs/promises";
11055
12047
 
11056
12048
  // src/core/prompt-loader.ts
11057
12049
  function parseCsvLine(line) {
@@ -11194,7 +12186,7 @@ function registerEvalCommand(parent) {
11194
12186
  resolveDeprecatedAliases(cmd, opts);
11195
12187
  try {
11196
12188
  const config = await loadConfig();
11197
- const csvContent = await readFile10(opts.prompts, "utf-8");
12189
+ const csvContent = await readFile11(opts.prompts, "utf-8");
11198
12190
  const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
11199
12191
  if (!config.airsApiKey && !config.airsApiToken) {
11200
12192
  fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
@@ -11366,7 +12358,7 @@ function completedBulkResults(state) {
11366
12358
  }
11367
12359
  async function writeBulkResults(outputPath, results) {
11368
12360
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
11369
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID9()}`;
12361
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID10()}`;
11370
12362
  try {
11371
12363
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
11372
12364
  encoding: "utf-8",
@@ -11493,7 +12485,7 @@ function registerRuntimeCommand(program) {
11493
12485
  if (!config.airsApiKey && !config.airsApiToken) {
11494
12486
  fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
11495
12487
  }
11496
- const raw = await readFile11(opts.file, "utf-8");
12488
+ const raw = await readFile12(opts.file, "utf-8");
11497
12489
  const prompts = parseInputFile(raw, opts.file);
11498
12490
  if (prompts.length === 0) {
11499
12491
  usageError("No prompts found in input file");
@@ -12151,7 +13143,7 @@ function registerRuntimeCommand(program) {
12151
13143
  }
12152
13144
 
12153
13145
  // src/config/tenant-settings.ts
12154
- import { randomUUID as randomUUID10 } from "crypto";
13146
+ import { randomUUID as randomUUID11 } from "crypto";
12155
13147
  import { constants } from "fs";
12156
13148
  import { access, lstat as lstat6, mkdir as mkdir3, open as open2, rename as rename2, unlink as unlink2 } from "fs/promises";
12157
13149
  import { dirname as dirname4, join as join4 } from "path";
@@ -12181,7 +13173,7 @@ async function createManagedTenant(name, config) {
12181
13173
  throw new Error("Tenant name already exists");
12182
13174
  const directory = join4(dirname4(tenantStorePath()), "configs");
12183
13175
  await mkdir3(directory, { recursive: true, mode: 448 });
12184
- const path3 = join4(directory, `${name}-${randomUUID10()}.json`);
13176
+ const path3 = join4(directory, `${name}-${randomUUID11()}.json`);
12185
13177
  const file = await open2(path3, "wx", 384);
12186
13178
  try {
12187
13179
  const data = Object.fromEntries(
@@ -12211,11 +13203,11 @@ async function setTenantSetting(name, key, value) {
12211
13203
  "Cannot lock tenant config; it may be read-only or another edit is in progress"
12212
13204
  );
12213
13205
  });
12214
- const temporary = join4(dirname4(path3), `.airs-config-${randomUUID10()}.tmp`);
13206
+ const temporary = join4(dirname4(path3), `.airs-config-${randomUUID11()}.tmp`);
12215
13207
  let created = false;
12216
13208
  try {
12217
- const stat2 = await lstat6(path3);
12218
- if (!stat2.isFile() || (stat2.mode & 146) === 0)
13209
+ const stat3 = await lstat6(path3);
13210
+ if (!stat3.isFile() || (stat3.mode & 146) === 0)
12219
13211
  throw new Error("Tenant config is not a writable regular file");
12220
13212
  await access(path3, constants.W_OK);
12221
13213
  const current = readTenantConfigFile(path3, entry.tsgId);
@@ -12511,7 +13503,7 @@ function buildProgram() {
12511
13503
  if (root.debug) {
12512
13504
  const logPath = join5(
12513
13505
  process.cwd(),
12514
- `debug-api-${Date.now()}-${randomUUID11().slice(0, 8)}.jsonl`
13506
+ `debug-api-${Date.now()}-${randomUUID12().slice(0, 8)}.jsonl`
12515
13507
  );
12516
13508
  try {
12517
13509
  let ancestor = actionCommand;