@postman-cse/onboarding-insights 0.9.1 → 0.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.
package/dist/cli.cjs CHANGED
@@ -20958,6 +20958,310 @@ 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 identity = asRecord(payload.identity);
21056
+ const data = asRecord(payload.data);
21057
+ const user = asRecord(data?.user);
21058
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21059
+ const singleRole = coerceText(user?.role);
21060
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21061
+ const resolved = {
21062
+ source: "iapub/sessions",
21063
+ userId: coerceId(user?.id),
21064
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21065
+ teamId: coerceId(identity?.team),
21066
+ teamDomain: coerceText(identity?.domain),
21067
+ ...roles ? { roles } : {},
21068
+ consumerType: coerceText(payload.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21069
+ };
21070
+ memoizedSessionIdentity = resolved;
21071
+ return resolved;
21072
+ } catch {
21073
+ return void 0;
21074
+ }
21075
+ }
21076
+ function describeTeam(id) {
21077
+ const label = id?.teamName ?? id?.teamDomain;
21078
+ return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
21079
+ }
21080
+ function formatIdentityLine(id, mask) {
21081
+ const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
21082
+ const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
21083
+ if (id.source === "pmak/me") {
21084
+ const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
21085
+ return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
21086
+ }
21087
+ return mask(
21088
+ `postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
21089
+ );
21090
+ }
21091
+ function crossCheckIdentities(args) {
21092
+ if (args.mode === "off") {
21093
+ return { ok: true, level: "ok", message: "" };
21094
+ }
21095
+ const pmakTeamId = args.pmak?.teamId;
21096
+ const sessionTeamId = args.session?.teamId;
21097
+ if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
21098
+ const level = args.mode === "enforce" ? "fail" : "note";
21099
+ const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
21100
+ 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.";
21101
+ return {
21102
+ ok: false,
21103
+ level,
21104
+ message: args.mask(
21105
+ `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
21106
+ )
21107
+ };
21108
+ }
21109
+ if (pmakTeamId && sessionTeamId) {
21110
+ const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
21111
+ const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
21112
+ return {
21113
+ ok: true,
21114
+ level: "ok",
21115
+ message: args.mask(
21116
+ `postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
21117
+ )
21118
+ };
21119
+ }
21120
+ const missing = [
21121
+ !pmakTeamId ? "PMAK identity" : void 0,
21122
+ !sessionTeamId ? "access-token session identity" : void 0
21123
+ ].filter(Boolean).join(" and ");
21124
+ return {
21125
+ ok: false,
21126
+ level: "note",
21127
+ message: args.mask(
21128
+ `postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
21129
+ )
21130
+ };
21131
+ }
21132
+ async function runCredentialPreflight(args) {
21133
+ if (args.mode === "off") {
21134
+ return;
21135
+ }
21136
+ const mask = args.mask;
21137
+ const apiKey = String(args.postmanApiKey || "").trim();
21138
+ const accessToken = String(args.postmanAccessToken || "").trim();
21139
+ let pmak = args.pmak;
21140
+ if (pmak) {
21141
+ args.log.info(formatIdentityLine(pmak, mask));
21142
+ } else if (apiKey) {
21143
+ try {
21144
+ pmak = await resolvePmakIdentity({
21145
+ apiBaseUrl: args.apiBaseUrl,
21146
+ apiKey,
21147
+ fetchImpl: args.fetchImpl
21148
+ });
21149
+ } catch (error2) {
21150
+ args.log.warning(
21151
+ mask(
21152
+ `postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21153
+ )
21154
+ );
21155
+ }
21156
+ if (pmak) {
21157
+ args.log.info(formatIdentityLine(pmak, mask));
21158
+ } else {
21159
+ args.log.warning(
21160
+ mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
21161
+ );
21162
+ }
21163
+ }
21164
+ if (!accessToken) {
21165
+ args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
21166
+ return;
21167
+ }
21168
+ let session;
21169
+ try {
21170
+ session = await resolveSessionIdentity({
21171
+ iapubBaseUrl: args.iapubBaseUrl,
21172
+ accessToken,
21173
+ fetchImpl: args.fetchImpl
21174
+ });
21175
+ } catch (error2) {
21176
+ args.log.warning(
21177
+ mask(
21178
+ `postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21179
+ )
21180
+ );
21181
+ }
21182
+ if (session) {
21183
+ args.log.info(formatIdentityLine(session, mask));
21184
+ } else {
21185
+ args.log.warning(
21186
+ mask(
21187
+ "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21188
+ )
21189
+ );
21190
+ }
21191
+ const result = crossCheckIdentities({
21192
+ pmak,
21193
+ session,
21194
+ workspaceTeamId: args.workspaceTeamId,
21195
+ explicitTeamId: args.explicitTeamId,
21196
+ mode: args.mode,
21197
+ mask
21198
+ });
21199
+ if (!result.message) {
21200
+ return;
21201
+ }
21202
+ if (result.level === "fail") {
21203
+ throw new Error(result.message);
21204
+ }
21205
+ if (result.level === "note") {
21206
+ args.log.warning(result.message);
21207
+ return;
21208
+ }
21209
+ args.log.info(result.message);
21210
+ }
21211
+
21212
+ // src/lib/error-advice.ts
21213
+ 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.";
21214
+ function expiryAdvice(code) {
21215
+ 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.`;
21216
+ }
21217
+ function forbiddenAdvice(ctx) {
21218
+ 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)` : "";
21219
+ const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
21220
+ 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";
21221
+ 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.`;
21222
+ }
21223
+ function buildAdvice(status, body, ctx) {
21224
+ if (body.includes("UNAUTHENTICATED")) {
21225
+ return expiryAdvice("UNAUTHENTICATED");
21226
+ }
21227
+ if (body.includes("authenticationError")) {
21228
+ return expiryAdvice("authenticationError");
21229
+ }
21230
+ if (body.includes("Only personal workspaces")) {
21231
+ return WORKSPACE_PERSONAL_ONLY_ADVICE;
21232
+ }
21233
+ if (body.includes("projectAlreadyConnected")) {
21234
+ 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.`;
21235
+ }
21236
+ if (body.includes("invalidParamError") && body.includes("already exists")) {
21237
+ 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.`;
21238
+ }
21239
+ if (body.includes("Team feature is not available for your organization")) {
21240
+ 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.`;
21241
+ }
21242
+ if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
21243
+ return forbiddenAdvice(ctx);
21244
+ }
21245
+ return void 0;
21246
+ }
21247
+ function adviseFromHttpError(err, ctx) {
21248
+ const body = err.responseBody || err.message || "";
21249
+ const advice = buildAdvice(err.status, body, ctx);
21250
+ if (!advice) {
21251
+ return void 0;
21252
+ }
21253
+ return new Error(ctx.mask(advice), { cause: err });
21254
+ }
21255
+ function adviseFromBifrostBody(status, body, ctx) {
21256
+ const advice = buildAdvice(status, String(body || ""), ctx);
21257
+ if (!advice) {
21258
+ return void 0;
21259
+ }
21260
+ return new Error(ctx.mask(advice), {
21261
+ cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
21262
+ });
21263
+ }
21264
+
20961
21265
  // src/lib/secrets.ts
20962
21266
  var REDACTED = "[REDACTED]";
20963
21267
  var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -21010,6 +21314,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
21010
21314
  return sanitized.split(secret).join(replacement);
21011
21315
  }, source);
21012
21316
  }
21317
+ function createSecretMasker(secretValues, replacement = REDACTED) {
21318
+ return (input) => redactSecrets(input, secretValues, replacement);
21319
+ }
21013
21320
  function headerEntries(headers) {
21014
21321
  if (headers instanceof Headers) {
21015
21322
  return Array.from(headers.entries());
@@ -21141,12 +21448,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
21141
21448
  prod: {
21142
21449
  apiBaseUrl: "https://api.getpostman.com",
21143
21450
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
21451
+ iapubBaseUrl: "https://iapub.postman.co",
21144
21452
  observabilityBaseUrl: "https://api.observability.postman.com",
21145
21453
  observabilityEnv: "production"
21146
21454
  },
21147
21455
  beta: {
21148
21456
  apiBaseUrl: "https://api.getpostman-beta.com",
21149
21457
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
21458
+ iapubBaseUrl: "https://iapub.postman.co",
21150
21459
  observabilityBaseUrl: "https://api.observability.postman-beta.com",
21151
21460
  observabilityEnv: "beta"
21152
21461
  }
@@ -21210,7 +21519,23 @@ var BifrostCatalogClient = class {
21210
21519
  }
21211
21520
  return h;
21212
21521
  }
21213
- async proxyRequest(method, path7, body = {}) {
21522
+ /**
21523
+ * Reactive error-advice context. The session identity comes from the credential
21524
+ * preflight's in-process memo when it ran; the advice degrades gracefully without it.
21525
+ */
21526
+ adviceContext(operation) {
21527
+ const session = getMemoizedSessionIdentity();
21528
+ return {
21529
+ operation,
21530
+ hasAccessToken: Boolean(this.accessToken),
21531
+ sessionTeamId: session?.teamId,
21532
+ sessionRoles: session?.roles,
21533
+ sessionConsumerType: session?.consumerType,
21534
+ explicitTeamId: this.teamId || void 0,
21535
+ mask: createSecretMasker(this.secretValues)
21536
+ };
21537
+ }
21538
+ async proxyRequest(method, path7, body = {}, operation = "api-catalog request") {
21214
21539
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21215
21540
  method: "POST",
21216
21541
  headers: this.headers(),
@@ -21222,20 +21547,27 @@ var BifrostCatalogClient = class {
21222
21547
  })
21223
21548
  });
21224
21549
  if (!response.ok) {
21225
- throw await HttpError.fromResponse(response, {
21550
+ const httpErr = await HttpError.fromResponse(response, {
21226
21551
  method: "POST",
21227
21552
  url: `bifrost:api-catalog:${method} ${path7}`,
21228
21553
  secretValues: this.secretValues
21229
21554
  });
21555
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21556
+ throw advised ?? httpErr;
21230
21557
  }
21231
21558
  const data = await response.json();
21232
21559
  if (data && typeof data === "object" && "error" in data && data.error) {
21233
21560
  const errObj = data.error;
21234
- throw new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21561
+ const advised = adviseFromBifrostBody(
21562
+ response.status,
21563
+ JSON.stringify({ error: errObj }),
21564
+ this.adviceContext(operation)
21565
+ );
21566
+ throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21235
21567
  }
21236
21568
  return data;
21237
21569
  }
21238
- async akitaProxyRequest(method, path7, body = {}) {
21570
+ async akitaProxyRequest(method, path7, body = {}, operation = "Insights request") {
21239
21571
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21240
21572
  method: "POST",
21241
21573
  headers: this.headers(),
@@ -21248,7 +21580,10 @@ var BifrostCatalogClient = class {
21248
21580
  });
21249
21581
  if (!response.ok) {
21250
21582
  const text = await response.text().catch(() => "");
21251
- return { ok: false, status: response.status, data: null, errorText: text };
21583
+ const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21584
+ const errorText = advised ? text ? `${text}
21585
+ ${advised.message}` : advised.message : text;
21586
+ return { ok: false, status: response.status, data: null, errorText };
21252
21587
  }
21253
21588
  const data = await response.json();
21254
21589
  return { ok: true, status: response.status, data, errorText: "" };
@@ -21263,7 +21598,9 @@ var BifrostCatalogClient = class {
21263
21598
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
21264
21599
  const data = await this.proxyRequest(
21265
21600
  "GET",
21266
- `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
21601
+ `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
21602
+ {},
21603
+ "discovered-services listing"
21267
21604
  );
21268
21605
  allItems.push(...data.items || []);
21269
21606
  if (data.total && allItems.length >= data.total) {
@@ -21287,7 +21624,8 @@ var BifrostCatalogClient = class {
21287
21624
  const data = await this.proxyRequest(
21288
21625
  "POST",
21289
21626
  "/api/v1/onboarding/prepare-collection",
21290
- { service_id: String(serviceId), workspace_id: workspaceId }
21627
+ { service_id: String(serviceId), workspace_id: workspaceId },
21628
+ "collection preparation"
21291
21629
  );
21292
21630
  return data.id;
21293
21631
  },
@@ -21311,7 +21649,8 @@ var BifrostCatalogClient = class {
21311
21649
  await this.proxyRequest(
21312
21650
  "POST",
21313
21651
  "/api/v1/onboarding/git",
21314
- body
21652
+ body,
21653
+ "git onboarding"
21315
21654
  );
21316
21655
  },
21317
21656
  { maxAttempts: 2, delayMs: 3e3 }
@@ -21324,7 +21663,9 @@ var BifrostCatalogClient = class {
21324
21663
  for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
21325
21664
  const result = await this.akitaProxyRequest(
21326
21665
  "GET",
21327
- `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
21666
+ `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
21667
+ {},
21668
+ "provider service resolution"
21328
21669
  );
21329
21670
  if (!result.ok || !result.data) return null;
21330
21671
  const services = result.data.services || [];
@@ -21354,7 +21695,8 @@ var BifrostCatalogClient = class {
21354
21695
  workspace_id: workspaceId,
21355
21696
  system_env: systemEnvironmentId
21356
21697
  }]
21357
- }
21698
+ },
21699
+ "Insights onboarding acknowledgment"
21358
21700
  );
21359
21701
  if (!result.ok) {
21360
21702
  throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21363,7 +21705,9 @@ var BifrostCatalogClient = class {
21363
21705
  async acknowledgeWorkspace(workspaceId) {
21364
21706
  const result = await this.akitaProxyRequest(
21365
21707
  "POST",
21366
- `/v2/workspaces/${workspaceId}/onboarding/acknowledge`
21708
+ `/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
21709
+ {},
21710
+ "workspace onboarding acknowledgment"
21367
21711
  );
21368
21712
  if (!result.ok) {
21369
21713
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21383,18 +21727,22 @@ var BifrostCatalogClient = class {
21383
21727
  }
21384
21728
  );
21385
21729
  if (!response.ok) {
21386
- throw await HttpError.fromResponse(response, {
21730
+ const httpErr = await HttpError.fromResponse(response, {
21387
21731
  method: "POST",
21388
21732
  url: `observability:createApplication(${workspaceId})`,
21389
21733
  secretValues: this.secretValues
21390
21734
  });
21735
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
21736
+ throw advised ?? httpErr;
21391
21737
  }
21392
21738
  return response.json();
21393
21739
  }
21394
21740
  async getTeamVerificationToken(workspaceId) {
21395
21741
  const result = await this.akitaProxyRequest(
21396
21742
  "GET",
21397
- `/v2/workspaces/${workspaceId}/team-verification-token`
21743
+ `/v2/workspaces/${workspaceId}/team-verification-token`,
21744
+ {},
21745
+ "team verification token retrieval"
21398
21746
  );
21399
21747
  if (!result.ok || !result.data) return null;
21400
21748
  return result.data.team_verification_token || null;
@@ -21411,11 +21759,13 @@ var BifrostCatalogClient = class {
21411
21759
  })
21412
21760
  });
21413
21761
  if (!response.ok) {
21414
- throw await HttpError.fromResponse(response, {
21762
+ const httpErr = await HttpError.fromResponse(response, {
21415
21763
  method: "POST",
21416
21764
  url: "bifrost:identity:POST /api/keys",
21417
21765
  secretValues: this.secretValues
21418
21766
  });
21767
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
21768
+ throw advised ?? httpErr;
21419
21769
  }
21420
21770
  const data = await response.json();
21421
21771
  const apikey = data?.apikey;
@@ -21443,7 +21793,17 @@ var POLL_INTERVAL_DEFAULT = 10;
21443
21793
  var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
21444
21794
  var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
21445
21795
  var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
21796
+ var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
21446
21797
  var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
21798
+ function parsePreflightMode(value) {
21799
+ const normalized = String(value || "warn").trim().toLowerCase();
21800
+ if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
21801
+ return normalized;
21802
+ }
21803
+ throw new Error(
21804
+ `Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
21805
+ );
21806
+ }
21447
21807
  function trimTrailingSlash(value) {
21448
21808
  return value.replace(/\/+$/, "");
21449
21809
  }
@@ -21520,11 +21880,13 @@ function resolveInputs(env = process.env) {
21520
21880
  postmanApiKey,
21521
21881
  postmanTeamId,
21522
21882
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
21883
+ credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
21523
21884
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
21524
21885
  pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
21525
21886
  postmanStack,
21526
21887
  postmanApiBase: endpointProfile.apiBaseUrl,
21527
21888
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
21889
+ postmanIapubBase: endpointProfile.iapubBaseUrl,
21528
21890
  postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
21529
21891
  postmanObservabilityEnv: endpointProfile.observabilityEnv
21530
21892
  };
@@ -21620,11 +21982,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21620
21982
  let apiKey = inputs.postmanApiKey;
21621
21983
  const teamId = inputs.postmanTeamId;
21622
21984
  let keyValid = false;
21985
+ let pmakIdentity;
21623
21986
  const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
21624
21987
  if (apiKey) {
21625
21988
  const result = await validateApiKey(apiKey, apiBase);
21626
21989
  keyValid = result.valid;
21627
- if (!keyValid) {
21990
+ if (keyValid) {
21991
+ pmakIdentity = { source: "pmak/me", teamId: result.teamId };
21992
+ } else {
21628
21993
  reporter.warning("Provided postman-api-key is invalid or expired.");
21629
21994
  }
21630
21995
  }
@@ -21671,7 +22036,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21671
22036
  } else {
21672
22037
  reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
21673
22038
  }
21674
- return { apiKey, teamId: resolvedTeamId };
22039
+ return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22040
+ }
22041
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22042
+ await runCredentialPreflight({
22043
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22044
+ iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22045
+ pmak,
22046
+ postmanAccessToken: inputs.postmanAccessToken,
22047
+ explicitTeamId: inputs.postmanTeamId || void 0,
22048
+ mode: inputs.credentialPreflight,
22049
+ mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22050
+ log: reporter,
22051
+ fetchImpl
22052
+ });
21675
22053
  }
21676
22054
 
21677
22055
  // src/cli.ts
@@ -21722,6 +22100,7 @@ function parseCliArgs(argv, env = process.env) {
21722
22100
  "repo-url",
21723
22101
  "postman-access-token",
21724
22102
  "postman-api-key",
22103
+ "credential-preflight",
21725
22104
  "postman-team-id",
21726
22105
  "github-token",
21727
22106
  "poll-timeout-seconds",
@@ -21790,10 +22169,15 @@ async function runCli(argv = process.argv.slice(2), runtime = {}) {
21790
22169
  observabilityBaseUrl: inputs.postmanObservabilityBase,
21791
22170
  observabilityEnv: inputs.postmanObservabilityEnv
21792
22171
  });
21793
- const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, reporter);
22172
+ const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(
22173
+ inputs,
22174
+ preliminaryClient,
22175
+ reporter
22176
+ );
21794
22177
  if (apiKey) {
21795
22178
  reporter.setSecret(apiKey);
21796
22179
  }
22180
+ await runCredentialPreflightForInputs(inputs, pmakIdentity, reporter);
21797
22181
  const client = new BifrostCatalogClient({
21798
22182
  accessToken: inputs.postmanAccessToken,
21799
22183
  teamId,