@postman-cse/onboarding-insights 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -20958,6 +20958,312 @@ function getIDToken(aud) {
20958
20958
  });
20959
20959
  }
20960
20960
 
20961
+ // src/lib/credential-identity.ts
20962
+ var sessionPath = "/api/sessions/current";
20963
+ var pmakMemo = /* @__PURE__ */ new Map();
20964
+ var sessionMemo = /* @__PURE__ */ new Map();
20965
+ var memoizedSessionIdentity;
20966
+ function getMemoizedSessionIdentity() {
20967
+ return memoizedSessionIdentity;
20968
+ }
20969
+ function asRecord(value) {
20970
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20971
+ return void 0;
20972
+ }
20973
+ return value;
20974
+ }
20975
+ function coerceId(raw) {
20976
+ return raw ? String(raw) : void 0;
20977
+ }
20978
+ function coerceText(raw) {
20979
+ if (typeof raw !== "string") {
20980
+ return void 0;
20981
+ }
20982
+ const trimmed = raw.trim();
20983
+ return trimmed ? trimmed : void 0;
20984
+ }
20985
+ function normalizeBaseUrl(raw) {
20986
+ return String(raw || "").replace(/\/+$/, "");
20987
+ }
20988
+ async function resolvePmakIdentity(opts) {
20989
+ const apiKey = String(opts.apiKey || "").trim();
20990
+ if (!apiKey) {
20991
+ return void 0;
20992
+ }
20993
+ const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
20994
+ const memoKey = `${baseUrl}::${apiKey}`;
20995
+ let pending = pmakMemo.get(memoKey);
20996
+ if (!pending) {
20997
+ pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
20998
+ pmakMemo.set(memoKey, pending);
20999
+ }
21000
+ return pending;
21001
+ }
21002
+ async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
21003
+ try {
21004
+ const response = await fetchImpl(`${baseUrl}/me`, {
21005
+ method: "GET",
21006
+ headers: { "X-Api-Key": apiKey }
21007
+ });
21008
+ if (!response.ok) {
21009
+ return void 0;
21010
+ }
21011
+ const payload = asRecord(await response.json());
21012
+ const user = asRecord(payload?.user);
21013
+ if (!user) {
21014
+ return void 0;
21015
+ }
21016
+ return {
21017
+ source: "pmak/me",
21018
+ userId: coerceId(user.id),
21019
+ fullName: coerceText(user.fullName) ?? coerceText(user.username),
21020
+ teamId: coerceId(user.teamId),
21021
+ teamName: coerceText(user.teamName),
21022
+ teamDomain: coerceText(user.teamDomain)
21023
+ };
21024
+ } catch {
21025
+ return void 0;
21026
+ }
21027
+ }
21028
+ async function resolveSessionIdentity(opts) {
21029
+ const accessToken = String(opts.accessToken || "").trim();
21030
+ if (!accessToken) {
21031
+ return void 0;
21032
+ }
21033
+ const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
21034
+ const memoKey = `${baseUrl}::${accessToken}`;
21035
+ let pending = sessionMemo.get(memoKey);
21036
+ if (!pending) {
21037
+ pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21038
+ sessionMemo.set(memoKey, pending);
21039
+ }
21040
+ return pending;
21041
+ }
21042
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21043
+ try {
21044
+ const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21045
+ method: "GET",
21046
+ headers: { "x-access-token": accessToken }
21047
+ });
21048
+ if (!response.ok) {
21049
+ return void 0;
21050
+ }
21051
+ const payload = asRecord(await response.json());
21052
+ if (!payload) {
21053
+ return void 0;
21054
+ }
21055
+ const root = asRecord(payload.session) ?? payload;
21056
+ const identity = asRecord(root.identity);
21057
+ const data = asRecord(root.data);
21058
+ const user = asRecord(data?.user);
21059
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21060
+ const singleRole = coerceText(user?.role);
21061
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21062
+ const resolved = {
21063
+ source: "iapub/sessions",
21064
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
21065
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21066
+ teamId: coerceId(identity?.team),
21067
+ teamName: coerceText(user?.teamName),
21068
+ teamDomain: coerceText(identity?.domain),
21069
+ ...roles ? { roles } : {},
21070
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21071
+ };
21072
+ memoizedSessionIdentity = resolved;
21073
+ return resolved;
21074
+ } catch {
21075
+ return void 0;
21076
+ }
21077
+ }
21078
+ function describeTeam(id) {
21079
+ const label = id?.teamName ?? id?.teamDomain;
21080
+ return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
21081
+ }
21082
+ function formatIdentityLine(id, mask) {
21083
+ const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
21084
+ const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
21085
+ if (id.source === "pmak/me") {
21086
+ const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
21087
+ return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
21088
+ }
21089
+ return mask(
21090
+ `postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
21091
+ );
21092
+ }
21093
+ function crossCheckIdentities(args) {
21094
+ if (args.mode === "off") {
21095
+ return { ok: true, level: "ok", message: "" };
21096
+ }
21097
+ const pmakTeamId = args.pmak?.teamId;
21098
+ const sessionTeamId = args.session?.teamId;
21099
+ if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
21100
+ const level = args.mode === "enforce" ? "fail" : "note";
21101
+ const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
21102
+ const fix = level === "fail" ? "Use one credential pair from a single parent org: re-mint the access token from the same parent org as postman-api-key (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens with that team's PMAK), or set postman-api-key to the matching parent org." : "Use one credential pair from a single parent org. Set credential-preflight: enforce to fail the run on this condition.";
21103
+ return {
21104
+ ok: false,
21105
+ level,
21106
+ message: args.mask(
21107
+ `postman: ${lead} - PMAK belongs to ${describeTeam(args.pmak)} but the access token's session belongs to a different parent org, ${describeTeam(args.session)}. Assets would be created against one team while Bifrost linking and governance act under the other, producing duplicate-link 400s and workspaces not visible to the other credential. ` + fix
21108
+ )
21109
+ };
21110
+ }
21111
+ if (pmakTeamId && sessionTeamId) {
21112
+ const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
21113
+ const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
21114
+ return {
21115
+ ok: true,
21116
+ level: "ok",
21117
+ message: args.mask(
21118
+ `postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
21119
+ )
21120
+ };
21121
+ }
21122
+ const missing = [
21123
+ !pmakTeamId ? "PMAK identity" : void 0,
21124
+ !sessionTeamId ? "access-token session identity" : void 0
21125
+ ].filter(Boolean).join(" and ");
21126
+ return {
21127
+ ok: false,
21128
+ level: "note",
21129
+ message: args.mask(
21130
+ `postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
21131
+ )
21132
+ };
21133
+ }
21134
+ async function runCredentialPreflight(args) {
21135
+ if (args.mode === "off") {
21136
+ return;
21137
+ }
21138
+ const mask = args.mask;
21139
+ const apiKey = String(args.postmanApiKey || "").trim();
21140
+ const accessToken = String(args.postmanAccessToken || "").trim();
21141
+ let pmak = args.pmak;
21142
+ if (pmak) {
21143
+ args.log.info(formatIdentityLine(pmak, mask));
21144
+ } else if (apiKey) {
21145
+ try {
21146
+ pmak = await resolvePmakIdentity({
21147
+ apiBaseUrl: args.apiBaseUrl,
21148
+ apiKey,
21149
+ fetchImpl: args.fetchImpl
21150
+ });
21151
+ } catch (error2) {
21152
+ args.log.warning(
21153
+ mask(
21154
+ `postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21155
+ )
21156
+ );
21157
+ }
21158
+ if (pmak) {
21159
+ args.log.info(formatIdentityLine(pmak, mask));
21160
+ } else {
21161
+ args.log.warning(
21162
+ mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
21163
+ );
21164
+ }
21165
+ }
21166
+ if (!accessToken) {
21167
+ args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
21168
+ return;
21169
+ }
21170
+ let session;
21171
+ try {
21172
+ session = await resolveSessionIdentity({
21173
+ iapubBaseUrl: args.iapubBaseUrl,
21174
+ accessToken,
21175
+ fetchImpl: args.fetchImpl
21176
+ });
21177
+ } catch (error2) {
21178
+ args.log.warning(
21179
+ mask(
21180
+ `postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21181
+ )
21182
+ );
21183
+ }
21184
+ if (session) {
21185
+ args.log.info(formatIdentityLine(session, mask));
21186
+ } else {
21187
+ args.log.warning(
21188
+ mask(
21189
+ "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21190
+ )
21191
+ );
21192
+ }
21193
+ const result = crossCheckIdentities({
21194
+ pmak,
21195
+ session,
21196
+ workspaceTeamId: args.workspaceTeamId,
21197
+ explicitTeamId: args.explicitTeamId,
21198
+ mode: args.mode,
21199
+ mask
21200
+ });
21201
+ if (!result.message) {
21202
+ return;
21203
+ }
21204
+ if (result.level === "fail") {
21205
+ throw new Error(result.message);
21206
+ }
21207
+ if (result.level === "note") {
21208
+ args.log.warning(result.message);
21209
+ return;
21210
+ }
21211
+ args.log.info(result.message);
21212
+ }
21213
+
21214
+ // src/lib/error-advice.ts
21215
+ var WORKSPACE_PERSONAL_ONLY_ADVICE = "Workspace creation failed: This may be an Org-mode account that requires a workspace-team-id input. The Postman API does not allow creating team workspaces at the organization level. Use the workspace-team-id input to specify which sub-team should own this workspace.";
21216
+ function expiryAdvice(code) {
21217
+ return `postman: Bifrost rejected the access token (${code}). Service-account access tokens expire after about 1 to 1.5 hours; this run likely outlived its token. Re-mint a fresh token (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens) and re-run. If it was just minted, confirm postman-access-token is the token for the same parent org as postman-api-key.`;
21218
+ }
21219
+ function forbiddenAdvice(ctx) {
21220
+ const sessionDetail = ctx.sessionTeamId ? ` while the access token is valid (it resolved to team ${ctx.sessionTeamId}${ctx.sessionRoles && ctx.sessionRoles.length > 0 ? `, roles [${ctx.sessionRoles.join(", ")}]` : ""}${ctx.sessionConsumerType ? `, consumerType ${ctx.sessionConsumerType}` : ""} at preflight)` : "";
21221
+ const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
21222
+ const teamClause = scopedTeamId ? `, or workspace-team-id ${scopedTeamId} names a sub-team it cannot act in` : ", or the workspace-team-id / POSTMAN_TEAM_ID in use names a sub-team it cannot act in";
21223
+ return `postman: Bifrost refused ${ctx.operation || "this operation"} with 403${sessionDetail}. The token's identity lacks permission for this endpoint${teamClause}. Verify the token's role and that workspace-team-id / POSTMAN_TEAM_ID matches a sub-team from GET https://api.getpostman.com/teams.`;
21224
+ }
21225
+ function buildAdvice(status, body, ctx) {
21226
+ if (body.includes("UNAUTHENTICATED")) {
21227
+ return expiryAdvice("UNAUTHENTICATED");
21228
+ }
21229
+ if (body.includes("authenticationError")) {
21230
+ return expiryAdvice("authenticationError");
21231
+ }
21232
+ if (body.includes("Only personal workspaces")) {
21233
+ return WORKSPACE_PERSONAL_ONLY_ADVICE;
21234
+ }
21235
+ if (body.includes("projectAlreadyConnected")) {
21236
+ return `postman: ${ctx.operation || "this operation"} reports projectAlreadyConnected with no workspace id in the error body. The repository is already linked to a workspace this credential cannot see, usually one created by a different credential pair or sub-team. Delete the stale link or its workspace, then re-run with one credential pair from a single parent org.`;
21237
+ }
21238
+ if (body.includes("invalidParamError") && body.includes("already exists")) {
21239
+ return `postman: ${ctx.operation || "this operation"} hit a duplicate resource error (invalidParamError: already exists). A matching resource already exists, possibly under another credential pair or sub-team where this credential cannot see it. Identify which workspace holds the existing resource and re-run with one credential pair from a single parent org.`;
21240
+ }
21241
+ if (body.includes("Team feature is not available for your organization")) {
21242
+ return `postman: ${ctx.operation || "this operation"} failed because the team feature is not available for this organization. The credential belongs to an account whose plan lacks team features; use credentials from the intended team and confirm the plan supports this operation.`;
21243
+ }
21244
+ if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
21245
+ return forbiddenAdvice(ctx);
21246
+ }
21247
+ return void 0;
21248
+ }
21249
+ function adviseFromHttpError(err, ctx) {
21250
+ const body = err.responseBody || err.message || "";
21251
+ const advice = buildAdvice(err.status, body, ctx);
21252
+ if (!advice) {
21253
+ return void 0;
21254
+ }
21255
+ return new Error(ctx.mask(advice), { cause: err });
21256
+ }
21257
+ function adviseFromBifrostBody(status, body, ctx) {
21258
+ const advice = buildAdvice(status, String(body || ""), ctx);
21259
+ if (!advice) {
21260
+ return void 0;
21261
+ }
21262
+ return new Error(ctx.mask(advice), {
21263
+ cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
21264
+ });
21265
+ }
21266
+
20961
21267
  // src/lib/secrets.ts
20962
21268
  var REDACTED = "[REDACTED]";
20963
21269
  var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -21010,6 +21316,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
21010
21316
  return sanitized.split(secret).join(replacement);
21011
21317
  }, source);
21012
21318
  }
21319
+ function createSecretMasker(secretValues, replacement = REDACTED) {
21320
+ return (input) => redactSecrets(input, secretValues, replacement);
21321
+ }
21013
21322
  function headerEntries(headers) {
21014
21323
  if (headers instanceof Headers) {
21015
21324
  return Array.from(headers.entries());
@@ -21141,12 +21450,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
21141
21450
  prod: {
21142
21451
  apiBaseUrl: "https://api.getpostman.com",
21143
21452
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
21453
+ iapubBaseUrl: "https://iapub.postman.co",
21144
21454
  observabilityBaseUrl: "https://api.observability.postman.com",
21145
21455
  observabilityEnv: "production"
21146
21456
  },
21147
21457
  beta: {
21148
21458
  apiBaseUrl: "https://api.getpostman-beta.com",
21149
21459
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
21460
+ iapubBaseUrl: "https://iapub.postman.co",
21150
21461
  observabilityBaseUrl: "https://api.observability.postman-beta.com",
21151
21462
  observabilityEnv: "beta"
21152
21463
  }
@@ -21210,7 +21521,23 @@ var BifrostCatalogClient = class {
21210
21521
  }
21211
21522
  return h;
21212
21523
  }
21213
- async proxyRequest(method, path7, body = {}) {
21524
+ /**
21525
+ * Reactive error-advice context. The session identity comes from the credential
21526
+ * preflight's in-process memo when it ran; the advice degrades gracefully without it.
21527
+ */
21528
+ adviceContext(operation) {
21529
+ const session = getMemoizedSessionIdentity();
21530
+ return {
21531
+ operation,
21532
+ hasAccessToken: Boolean(this.accessToken),
21533
+ sessionTeamId: session?.teamId,
21534
+ sessionRoles: session?.roles,
21535
+ sessionConsumerType: session?.consumerType,
21536
+ explicitTeamId: this.teamId || void 0,
21537
+ mask: createSecretMasker(this.secretValues)
21538
+ };
21539
+ }
21540
+ async proxyRequest(method, path7, body = {}, operation = "api-catalog request") {
21214
21541
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21215
21542
  method: "POST",
21216
21543
  headers: this.headers(),
@@ -21222,20 +21549,27 @@ var BifrostCatalogClient = class {
21222
21549
  })
21223
21550
  });
21224
21551
  if (!response.ok) {
21225
- throw await HttpError.fromResponse(response, {
21552
+ const httpErr = await HttpError.fromResponse(response, {
21226
21553
  method: "POST",
21227
21554
  url: `bifrost:api-catalog:${method} ${path7}`,
21228
21555
  secretValues: this.secretValues
21229
21556
  });
21557
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21558
+ throw advised ?? httpErr;
21230
21559
  }
21231
21560
  const data = await response.json();
21232
21561
  if (data && typeof data === "object" && "error" in data && data.error) {
21233
21562
  const errObj = data.error;
21234
- throw new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21563
+ const advised = adviseFromBifrostBody(
21564
+ response.status,
21565
+ JSON.stringify({ error: errObj }),
21566
+ this.adviceContext(operation)
21567
+ );
21568
+ throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21235
21569
  }
21236
21570
  return data;
21237
21571
  }
21238
- async akitaProxyRequest(method, path7, body = {}) {
21572
+ async akitaProxyRequest(method, path7, body = {}, operation = "Insights request") {
21239
21573
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21240
21574
  method: "POST",
21241
21575
  headers: this.headers(),
@@ -21248,7 +21582,10 @@ var BifrostCatalogClient = class {
21248
21582
  });
21249
21583
  if (!response.ok) {
21250
21584
  const text = await response.text().catch(() => "");
21251
- return { ok: false, status: response.status, data: null, errorText: text };
21585
+ const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21586
+ const errorText = advised ? text ? `${text}
21587
+ ${advised.message}` : advised.message : text;
21588
+ return { ok: false, status: response.status, data: null, errorText };
21252
21589
  }
21253
21590
  const data = await response.json();
21254
21591
  return { ok: true, status: response.status, data, errorText: "" };
@@ -21263,7 +21600,9 @@ var BifrostCatalogClient = class {
21263
21600
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
21264
21601
  const data = await this.proxyRequest(
21265
21602
  "GET",
21266
- `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
21603
+ `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
21604
+ {},
21605
+ "discovered-services listing"
21267
21606
  );
21268
21607
  allItems.push(...data.items || []);
21269
21608
  if (data.total && allItems.length >= data.total) {
@@ -21287,7 +21626,8 @@ var BifrostCatalogClient = class {
21287
21626
  const data = await this.proxyRequest(
21288
21627
  "POST",
21289
21628
  "/api/v1/onboarding/prepare-collection",
21290
- { service_id: String(serviceId), workspace_id: workspaceId }
21629
+ { service_id: String(serviceId), workspace_id: workspaceId },
21630
+ "collection preparation"
21291
21631
  );
21292
21632
  return data.id;
21293
21633
  },
@@ -21311,7 +21651,8 @@ var BifrostCatalogClient = class {
21311
21651
  await this.proxyRequest(
21312
21652
  "POST",
21313
21653
  "/api/v1/onboarding/git",
21314
- body
21654
+ body,
21655
+ "git onboarding"
21315
21656
  );
21316
21657
  },
21317
21658
  { maxAttempts: 2, delayMs: 3e3 }
@@ -21324,7 +21665,9 @@ var BifrostCatalogClient = class {
21324
21665
  for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
21325
21666
  const result = await this.akitaProxyRequest(
21326
21667
  "GET",
21327
- `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
21668
+ `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
21669
+ {},
21670
+ "provider service resolution"
21328
21671
  );
21329
21672
  if (!result.ok || !result.data) return null;
21330
21673
  const services = result.data.services || [];
@@ -21354,7 +21697,8 @@ var BifrostCatalogClient = class {
21354
21697
  workspace_id: workspaceId,
21355
21698
  system_env: systemEnvironmentId
21356
21699
  }]
21357
- }
21700
+ },
21701
+ "Insights onboarding acknowledgment"
21358
21702
  );
21359
21703
  if (!result.ok) {
21360
21704
  throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21363,7 +21707,9 @@ var BifrostCatalogClient = class {
21363
21707
  async acknowledgeWorkspace(workspaceId) {
21364
21708
  const result = await this.akitaProxyRequest(
21365
21709
  "POST",
21366
- `/v2/workspaces/${workspaceId}/onboarding/acknowledge`
21710
+ `/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
21711
+ {},
21712
+ "workspace onboarding acknowledgment"
21367
21713
  );
21368
21714
  if (!result.ok) {
21369
21715
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21383,18 +21729,22 @@ var BifrostCatalogClient = class {
21383
21729
  }
21384
21730
  );
21385
21731
  if (!response.ok) {
21386
- throw await HttpError.fromResponse(response, {
21732
+ const httpErr = await HttpError.fromResponse(response, {
21387
21733
  method: "POST",
21388
21734
  url: `observability:createApplication(${workspaceId})`,
21389
21735
  secretValues: this.secretValues
21390
21736
  });
21737
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
21738
+ throw advised ?? httpErr;
21391
21739
  }
21392
21740
  return response.json();
21393
21741
  }
21394
21742
  async getTeamVerificationToken(workspaceId) {
21395
21743
  const result = await this.akitaProxyRequest(
21396
21744
  "GET",
21397
- `/v2/workspaces/${workspaceId}/team-verification-token`
21745
+ `/v2/workspaces/${workspaceId}/team-verification-token`,
21746
+ {},
21747
+ "team verification token retrieval"
21398
21748
  );
21399
21749
  if (!result.ok || !result.data) return null;
21400
21750
  return result.data.team_verification_token || null;
@@ -21411,11 +21761,13 @@ var BifrostCatalogClient = class {
21411
21761
  })
21412
21762
  });
21413
21763
  if (!response.ok) {
21414
- throw await HttpError.fromResponse(response, {
21764
+ const httpErr = await HttpError.fromResponse(response, {
21415
21765
  method: "POST",
21416
21766
  url: "bifrost:identity:POST /api/keys",
21417
21767
  secretValues: this.secretValues
21418
21768
  });
21769
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
21770
+ throw advised ?? httpErr;
21419
21771
  }
21420
21772
  const data = await response.json();
21421
21773
  const apikey = data?.apikey;
@@ -21443,7 +21795,17 @@ var POLL_INTERVAL_DEFAULT = 10;
21443
21795
  var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
21444
21796
  var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
21445
21797
  var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
21798
+ var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
21446
21799
  var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
21800
+ function parsePreflightMode(value) {
21801
+ const normalized = String(value || "warn").trim().toLowerCase();
21802
+ if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
21803
+ return normalized;
21804
+ }
21805
+ throw new Error(
21806
+ `Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
21807
+ );
21808
+ }
21447
21809
  function trimTrailingSlash(value) {
21448
21810
  return value.replace(/\/+$/, "");
21449
21811
  }
@@ -21520,11 +21882,13 @@ function resolveInputs(env = process.env) {
21520
21882
  postmanApiKey,
21521
21883
  postmanTeamId,
21522
21884
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
21885
+ credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
21523
21886
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
21524
21887
  pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
21525
21888
  postmanStack,
21526
21889
  postmanApiBase: endpointProfile.apiBaseUrl,
21527
21890
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
21891
+ postmanIapubBase: endpointProfile.iapubBaseUrl,
21528
21892
  postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
21529
21893
  postmanObservabilityEnv: endpointProfile.observabilityEnv
21530
21894
  };
@@ -21620,11 +21984,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21620
21984
  let apiKey = inputs.postmanApiKey;
21621
21985
  const teamId = inputs.postmanTeamId;
21622
21986
  let keyValid = false;
21987
+ let pmakIdentity;
21623
21988
  const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
21624
21989
  if (apiKey) {
21625
21990
  const result = await validateApiKey(apiKey, apiBase);
21626
21991
  keyValid = result.valid;
21627
- if (!keyValid) {
21992
+ if (keyValid) {
21993
+ pmakIdentity = { source: "pmak/me", teamId: result.teamId };
21994
+ } else {
21628
21995
  reporter.warning("Provided postman-api-key is invalid or expired.");
21629
21996
  }
21630
21997
  }
@@ -21671,7 +22038,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21671
22038
  } else {
21672
22039
  reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
21673
22040
  }
21674
- return { apiKey, teamId: resolvedTeamId };
22041
+ return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22042
+ }
22043
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22044
+ await runCredentialPreflight({
22045
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22046
+ iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22047
+ pmak,
22048
+ postmanAccessToken: inputs.postmanAccessToken,
22049
+ explicitTeamId: inputs.postmanTeamId || void 0,
22050
+ mode: inputs.credentialPreflight,
22051
+ mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22052
+ log: reporter,
22053
+ fetchImpl
22054
+ });
21675
22055
  }
21676
22056
 
21677
22057
  // src/cli.ts
@@ -21722,6 +22102,7 @@ function parseCliArgs(argv, env = process.env) {
21722
22102
  "repo-url",
21723
22103
  "postman-access-token",
21724
22104
  "postman-api-key",
22105
+ "credential-preflight",
21725
22106
  "postman-team-id",
21726
22107
  "github-token",
21727
22108
  "poll-timeout-seconds",
@@ -21790,10 +22171,15 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
21790
22171
  observabilityBaseUrl: inputs.postmanObservabilityBase,
21791
22172
  observabilityEnv: inputs.postmanObservabilityEnv
21792
22173
  });
21793
- const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, reporter);
22174
+ const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(
22175
+ inputs,
22176
+ preliminaryClient,
22177
+ reporter
22178
+ );
21794
22179
  if (apiKey) {
21795
22180
  reporter.setSecret(apiKey);
21796
22181
  }
22182
+ await runCredentialPreflightForInputs(inputs, pmakIdentity, reporter);
21797
22183
  const client = new BifrostCatalogClient({
21798
22184
  accessToken: inputs.postmanAccessToken,
21799
22185
  teamId,