@cdot65/prisma-airs-cli 5.9.1 → 5.10.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 +46 -13
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -10178,7 +10178,8 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
10178
10178
  if (prefix.length > 64 || [...prefix].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
10179
10179
  throw new Error("Invalid --name-prefix");
10180
10180
  const conflict = options.onConflict ?? "error";
10181
- if (!["error", "verify", "skip"].includes(conflict)) throw new Error("Invalid conflict policy");
10181
+ if (!["error", "verify", "skip", "reconcile"].includes(conflict))
10182
+ throw new Error("Invalid conflict policy");
10182
10183
  const patternMap2 = options.patternMap ?? {};
10183
10184
  uniqueNames(backup.dictionaries, "dictionary");
10184
10185
  uniqueNames(backup.patterns, "data pattern");
@@ -10406,6 +10407,7 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
10406
10407
  patterns: patternPlans,
10407
10408
  profiles: profilePlans,
10408
10409
  unresolved,
10410
+ suffixOnConflict: conflict === "reconcile",
10409
10411
  maxPages: maxPages3
10410
10412
  };
10411
10413
  const bindings = planBindings(plan);
@@ -10530,6 +10532,15 @@ function assertUnchanged(current, prior, label) {
10530
10532
  if (canonical(current ?? null) !== canonical(prior ?? null))
10531
10533
  throw new Error(`Destination ${label} changed after planning`);
10532
10534
  }
10535
+ function defaultProfileSuffix() {
10536
+ return Math.floor(Math.random() * 16777216).toString(16).padStart(6, "0");
10537
+ }
10538
+ function suffixedProfileName(name, suffix) {
10539
+ const tail = `-${suffix}`;
10540
+ const room = 32 - tail.length;
10541
+ const base = name.length > room ? name.slice(0, room) : name;
10542
+ return `${base}${tail}`;
10543
+ }
10533
10544
  async function restoreDlpResources(api, plan, options = {}) {
10534
10545
  const result = {
10535
10546
  sourceTsgId: plan.sourceTsgId,
@@ -10544,6 +10555,7 @@ async function restoreDlpResources(api, plan, options = {}) {
10544
10555
  };
10545
10556
  const crossTenant = plan.sourceTsgId !== plan.destinationTsgId;
10546
10557
  const emit2 = options.onProgress;
10558
+ const suffixGenerator = options.suffixGenerator ?? defaultProfileSuffix;
10547
10559
  const emitItem = (stage, name, action, index, total) => emit2?.({ phase: "item", stage, name, action, index, total });
10548
10560
  try {
10549
10561
  emit2?.({ phase: "recheck" });
@@ -10696,23 +10708,39 @@ async function restoreDlpResources(api, plan, options = {}) {
10696
10708
  emitItem("profiles", item.name, "verified", result.profiles.length, plan.profiles.length);
10697
10709
  continue;
10698
10710
  }
10699
- const created = await api.profiles.create(body);
10700
- if (!created.id) throw new Error(`Restored data profile did not verify: ${item.name}`);
10701
- result.unverifiedCreates.push({ kind: "profiles", name: item.name, id: created.id });
10711
+ let writeBody = body;
10712
+ let finalName = item.name;
10713
+ let created;
10714
+ try {
10715
+ created = await api.profiles.create(writeBody);
10716
+ } catch (error) {
10717
+ if (!plan.suffixOnConflict || error?.statusCode !== 409)
10718
+ throw error;
10719
+ finalName = suffixedProfileName(item.name, suffixGenerator());
10720
+ writeBody = { ...body, name: finalName };
10721
+ created = await api.profiles.create(writeBody);
10722
+ }
10723
+ if (!created.id) throw new Error(`Restored data profile did not verify: ${finalName}`);
10724
+ result.unverifiedCreates.push({ kind: "profiles", name: finalName, id: created.id });
10702
10725
  const readBack = await api.profiles.get(created.id);
10703
- const echo = compareEcho(body, readBack);
10704
- if (!echo.matches || readBack.id !== created.id || readBack.name !== item.name || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
10726
+ const echo = compareEcho(writeBody, readBack);
10727
+ if (!echo.matches || readBack.id !== created.id || readBack.name !== finalName || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
10705
10728
  throw new Error(
10706
- `Restored data profile did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
10729
+ `Restored data profile did not verify: ${finalName} (created id ${created.id}; inspect it before retrying)`
10707
10730
  );
10708
10731
  if (echo.serverAdded.length)
10709
10732
  result.serverAdded.push({
10710
- resource: `data profile ${item.name}`,
10733
+ resource: `data profile ${finalName}`,
10711
10734
  fields: echo.serverAdded
10712
10735
  });
10713
- result.profiles.push({ name: item.name, action: "created", id: created.id });
10736
+ result.profiles.push({
10737
+ name: finalName,
10738
+ action: "created",
10739
+ id: created.id,
10740
+ ...finalName !== item.name ? { renamedFrom: item.name } : {}
10741
+ });
10714
10742
  result.unverifiedCreates.pop();
10715
- emitItem("profiles", item.name, "created", result.profiles.length, plan.profiles.length);
10743
+ emitItem("profiles", finalName, "created", result.profiles.length, plan.profiles.length);
10716
10744
  }
10717
10745
  result.complete = true;
10718
10746
  } catch (error) {
@@ -10860,6 +10888,11 @@ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
10860
10888
  )
10861
10889
  );
10862
10890
  }
10891
+ const renamed = rows("profiles").filter((p) => p.renamedFrom);
10892
+ if (renamed.length) {
10893
+ blocks.push(heading("Renamed around archived-name collisions"));
10894
+ for (const p of renamed) blocks.push(`${p.renamedFrom} -> ${p.name}`);
10895
+ }
10863
10896
  if (rows("serverAdded").length) {
10864
10897
  blocks.push(heading("Verified server-added fields (source omitted these)"));
10865
10898
  for (const item of rows("serverAdded"))
@@ -10953,7 +10986,7 @@ function register6(dlp) {
10953
10986
  "Restore DLP resources into the selected tenant, staged dictionaries \u2192 patterns \u2192 profiles; validate all dependencies before changes"
10954
10987
  ).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(
10955
10988
  "--on-conflict <policy>",
10956
- "Existing data profile names: error, verify (resume without writes) or skip. Updates are unsupported until the DLP profile write path is live-verified.",
10989
+ "Existing data profile names: error, verify (resume without writes), skip, or reconcile (verify active duplicates, and give archived-name collisions a unique suffix). Profile update is a live server error, so a divergent active profile still fails.",
10957
10990
  "error"
10958
10991
  ).option(
10959
10992
  "--pattern-map <source=destination>",
@@ -10980,8 +11013,8 @@ function register6(dlp) {
10980
11013
  )
10981
11014
  ).action(async (file, opts) => {
10982
11015
  try {
10983
- if (!["error", "verify", "skip"].includes(opts.onConflict))
10984
- throw new CliUsageError("--on-conflict must be error, verify or skip");
11016
+ if (!["error", "verify", "skip", "reconcile"].includes(opts.onConflict))
11017
+ throw new CliUsageError("--on-conflict must be error, verify, skip or reconcile");
10985
11018
  if (opts.force && !opts.expectTsg)
10986
11019
  throw new CliUsageError("--force requires --expect-tsg <destination-tsg>");
10987
11020
  const mappings = patternMap(opts.patternMap);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs-cli",
3
3
  "packageManager": "pnpm@10.6.5",
4
- "version": "5.9.1",
4
+ "version": "5.10.0",
5
5
  "description": "CLI and library for Palo Alto Prisma AIRS — guardrail refinement, AI red teaming, model security scanning, profile audits",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",