@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/action.yml CHANGED
@@ -31,6 +31,10 @@ inputs:
31
31
  postman-api-key:
32
32
  description: 'Postman API key (PMAK-*) for the application binding call. Auto-created from postman-access-token when omitted or invalid after a clear 401/403 validation failure.'
33
33
  required: false
34
+ credential-preflight:
35
+ description: 'Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any onboarding write; off skips the identity probes entirely (the reactive error guidance still applies). A rejected or auto-created postman-api-key is never failed on.'
36
+ required: false
37
+ default: 'warn'
34
38
  poll-timeout-seconds:
35
39
  description: 'Maximum seconds to wait for the service to appear in the discovered list'
36
40
  required: false
package/dist/action.cjs CHANGED
@@ -20943,6 +20943,312 @@ function getIDToken(aud) {
20943
20943
  });
20944
20944
  }
20945
20945
 
20946
+ // src/lib/credential-identity.ts
20947
+ var sessionPath = "/api/sessions/current";
20948
+ var pmakMemo = /* @__PURE__ */ new Map();
20949
+ var sessionMemo = /* @__PURE__ */ new Map();
20950
+ var memoizedSessionIdentity;
20951
+ function getMemoizedSessionIdentity() {
20952
+ return memoizedSessionIdentity;
20953
+ }
20954
+ function asRecord(value) {
20955
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20956
+ return void 0;
20957
+ }
20958
+ return value;
20959
+ }
20960
+ function coerceId(raw) {
20961
+ return raw ? String(raw) : void 0;
20962
+ }
20963
+ function coerceText(raw) {
20964
+ if (typeof raw !== "string") {
20965
+ return void 0;
20966
+ }
20967
+ const trimmed = raw.trim();
20968
+ return trimmed ? trimmed : void 0;
20969
+ }
20970
+ function normalizeBaseUrl(raw) {
20971
+ return String(raw || "").replace(/\/+$/, "");
20972
+ }
20973
+ async function resolvePmakIdentity(opts) {
20974
+ const apiKey = String(opts.apiKey || "").trim();
20975
+ if (!apiKey) {
20976
+ return void 0;
20977
+ }
20978
+ const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
20979
+ const memoKey = `${baseUrl}::${apiKey}`;
20980
+ let pending = pmakMemo.get(memoKey);
20981
+ if (!pending) {
20982
+ pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
20983
+ pmakMemo.set(memoKey, pending);
20984
+ }
20985
+ return pending;
20986
+ }
20987
+ async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
20988
+ try {
20989
+ const response = await fetchImpl(`${baseUrl}/me`, {
20990
+ method: "GET",
20991
+ headers: { "X-Api-Key": apiKey }
20992
+ });
20993
+ if (!response.ok) {
20994
+ return void 0;
20995
+ }
20996
+ const payload = asRecord(await response.json());
20997
+ const user = asRecord(payload?.user);
20998
+ if (!user) {
20999
+ return void 0;
21000
+ }
21001
+ return {
21002
+ source: "pmak/me",
21003
+ userId: coerceId(user.id),
21004
+ fullName: coerceText(user.fullName) ?? coerceText(user.username),
21005
+ teamId: coerceId(user.teamId),
21006
+ teamName: coerceText(user.teamName),
21007
+ teamDomain: coerceText(user.teamDomain)
21008
+ };
21009
+ } catch {
21010
+ return void 0;
21011
+ }
21012
+ }
21013
+ async function resolveSessionIdentity(opts) {
21014
+ const accessToken = String(opts.accessToken || "").trim();
21015
+ if (!accessToken) {
21016
+ return void 0;
21017
+ }
21018
+ const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
21019
+ const memoKey = `${baseUrl}::${accessToken}`;
21020
+ let pending = sessionMemo.get(memoKey);
21021
+ if (!pending) {
21022
+ pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21023
+ sessionMemo.set(memoKey, pending);
21024
+ }
21025
+ return pending;
21026
+ }
21027
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21028
+ try {
21029
+ const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21030
+ method: "GET",
21031
+ headers: { "x-access-token": accessToken }
21032
+ });
21033
+ if (!response.ok) {
21034
+ return void 0;
21035
+ }
21036
+ const payload = asRecord(await response.json());
21037
+ if (!payload) {
21038
+ return void 0;
21039
+ }
21040
+ const root = asRecord(payload.session) ?? payload;
21041
+ const identity = asRecord(root.identity);
21042
+ const data = asRecord(root.data);
21043
+ const user = asRecord(data?.user);
21044
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21045
+ const singleRole = coerceText(user?.role);
21046
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21047
+ const resolved = {
21048
+ source: "iapub/sessions",
21049
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
21050
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21051
+ teamId: coerceId(identity?.team),
21052
+ teamName: coerceText(user?.teamName),
21053
+ teamDomain: coerceText(identity?.domain),
21054
+ ...roles ? { roles } : {},
21055
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21056
+ };
21057
+ memoizedSessionIdentity = resolved;
21058
+ return resolved;
21059
+ } catch {
21060
+ return void 0;
21061
+ }
21062
+ }
21063
+ function describeTeam(id) {
21064
+ const label = id?.teamName ?? id?.teamDomain;
21065
+ return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
21066
+ }
21067
+ function formatIdentityLine(id, mask) {
21068
+ const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
21069
+ const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
21070
+ if (id.source === "pmak/me") {
21071
+ const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
21072
+ return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
21073
+ }
21074
+ return mask(
21075
+ `postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
21076
+ );
21077
+ }
21078
+ function crossCheckIdentities(args) {
21079
+ if (args.mode === "off") {
21080
+ return { ok: true, level: "ok", message: "" };
21081
+ }
21082
+ const pmakTeamId = args.pmak?.teamId;
21083
+ const sessionTeamId = args.session?.teamId;
21084
+ if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
21085
+ const level = args.mode === "enforce" ? "fail" : "note";
21086
+ const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
21087
+ 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.";
21088
+ return {
21089
+ ok: false,
21090
+ level,
21091
+ message: args.mask(
21092
+ `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
21093
+ )
21094
+ };
21095
+ }
21096
+ if (pmakTeamId && sessionTeamId) {
21097
+ const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
21098
+ const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
21099
+ return {
21100
+ ok: true,
21101
+ level: "ok",
21102
+ message: args.mask(
21103
+ `postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
21104
+ )
21105
+ };
21106
+ }
21107
+ const missing = [
21108
+ !pmakTeamId ? "PMAK identity" : void 0,
21109
+ !sessionTeamId ? "access-token session identity" : void 0
21110
+ ].filter(Boolean).join(" and ");
21111
+ return {
21112
+ ok: false,
21113
+ level: "note",
21114
+ message: args.mask(
21115
+ `postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
21116
+ )
21117
+ };
21118
+ }
21119
+ async function runCredentialPreflight(args) {
21120
+ if (args.mode === "off") {
21121
+ return;
21122
+ }
21123
+ const mask = args.mask;
21124
+ const apiKey = String(args.postmanApiKey || "").trim();
21125
+ const accessToken = String(args.postmanAccessToken || "").trim();
21126
+ let pmak = args.pmak;
21127
+ if (pmak) {
21128
+ args.log.info(formatIdentityLine(pmak, mask));
21129
+ } else if (apiKey) {
21130
+ try {
21131
+ pmak = await resolvePmakIdentity({
21132
+ apiBaseUrl: args.apiBaseUrl,
21133
+ apiKey,
21134
+ fetchImpl: args.fetchImpl
21135
+ });
21136
+ } catch (error2) {
21137
+ args.log.warning(
21138
+ mask(
21139
+ `postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21140
+ )
21141
+ );
21142
+ }
21143
+ if (pmak) {
21144
+ args.log.info(formatIdentityLine(pmak, mask));
21145
+ } else {
21146
+ args.log.warning(
21147
+ mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
21148
+ );
21149
+ }
21150
+ }
21151
+ if (!accessToken) {
21152
+ args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
21153
+ return;
21154
+ }
21155
+ let session;
21156
+ try {
21157
+ session = await resolveSessionIdentity({
21158
+ iapubBaseUrl: args.iapubBaseUrl,
21159
+ accessToken,
21160
+ fetchImpl: args.fetchImpl
21161
+ });
21162
+ } catch (error2) {
21163
+ args.log.warning(
21164
+ mask(
21165
+ `postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21166
+ )
21167
+ );
21168
+ }
21169
+ if (session) {
21170
+ args.log.info(formatIdentityLine(session, mask));
21171
+ } else {
21172
+ args.log.warning(
21173
+ mask(
21174
+ "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21175
+ )
21176
+ );
21177
+ }
21178
+ const result = crossCheckIdentities({
21179
+ pmak,
21180
+ session,
21181
+ workspaceTeamId: args.workspaceTeamId,
21182
+ explicitTeamId: args.explicitTeamId,
21183
+ mode: args.mode,
21184
+ mask
21185
+ });
21186
+ if (!result.message) {
21187
+ return;
21188
+ }
21189
+ if (result.level === "fail") {
21190
+ throw new Error(result.message);
21191
+ }
21192
+ if (result.level === "note") {
21193
+ args.log.warning(result.message);
21194
+ return;
21195
+ }
21196
+ args.log.info(result.message);
21197
+ }
21198
+
21199
+ // src/lib/error-advice.ts
21200
+ 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.";
21201
+ function expiryAdvice(code) {
21202
+ 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.`;
21203
+ }
21204
+ function forbiddenAdvice(ctx) {
21205
+ 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)` : "";
21206
+ const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
21207
+ 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";
21208
+ 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.`;
21209
+ }
21210
+ function buildAdvice(status, body, ctx) {
21211
+ if (body.includes("UNAUTHENTICATED")) {
21212
+ return expiryAdvice("UNAUTHENTICATED");
21213
+ }
21214
+ if (body.includes("authenticationError")) {
21215
+ return expiryAdvice("authenticationError");
21216
+ }
21217
+ if (body.includes("Only personal workspaces")) {
21218
+ return WORKSPACE_PERSONAL_ONLY_ADVICE;
21219
+ }
21220
+ if (body.includes("projectAlreadyConnected")) {
21221
+ 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.`;
21222
+ }
21223
+ if (body.includes("invalidParamError") && body.includes("already exists")) {
21224
+ 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.`;
21225
+ }
21226
+ if (body.includes("Team feature is not available for your organization")) {
21227
+ 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.`;
21228
+ }
21229
+ if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
21230
+ return forbiddenAdvice(ctx);
21231
+ }
21232
+ return void 0;
21233
+ }
21234
+ function adviseFromHttpError(err, ctx) {
21235
+ const body = err.responseBody || err.message || "";
21236
+ const advice = buildAdvice(err.status, body, ctx);
21237
+ if (!advice) {
21238
+ return void 0;
21239
+ }
21240
+ return new Error(ctx.mask(advice), { cause: err });
21241
+ }
21242
+ function adviseFromBifrostBody(status, body, ctx) {
21243
+ const advice = buildAdvice(status, String(body || ""), ctx);
21244
+ if (!advice) {
21245
+ return void 0;
21246
+ }
21247
+ return new Error(ctx.mask(advice), {
21248
+ cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
21249
+ });
21250
+ }
21251
+
20946
21252
  // src/lib/secrets.ts
20947
21253
  var REDACTED = "[REDACTED]";
20948
21254
  var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -20995,6 +21301,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
20995
21301
  return sanitized.split(secret).join(replacement);
20996
21302
  }, source);
20997
21303
  }
21304
+ function createSecretMasker(secretValues, replacement = REDACTED) {
21305
+ return (input) => redactSecrets(input, secretValues, replacement);
21306
+ }
20998
21307
  function headerEntries(headers) {
20999
21308
  if (headers instanceof Headers) {
21000
21309
  return Array.from(headers.entries());
@@ -21126,12 +21435,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
21126
21435
  prod: {
21127
21436
  apiBaseUrl: "https://api.getpostman.com",
21128
21437
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
21438
+ iapubBaseUrl: "https://iapub.postman.co",
21129
21439
  observabilityBaseUrl: "https://api.observability.postman.com",
21130
21440
  observabilityEnv: "production"
21131
21441
  },
21132
21442
  beta: {
21133
21443
  apiBaseUrl: "https://api.getpostman-beta.com",
21134
21444
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
21445
+ iapubBaseUrl: "https://iapub.postman.co",
21135
21446
  observabilityBaseUrl: "https://api.observability.postman-beta.com",
21136
21447
  observabilityEnv: "beta"
21137
21448
  }
@@ -21195,7 +21506,23 @@ var BifrostCatalogClient = class {
21195
21506
  }
21196
21507
  return h;
21197
21508
  }
21198
- async proxyRequest(method, path6, body = {}) {
21509
+ /**
21510
+ * Reactive error-advice context. The session identity comes from the credential
21511
+ * preflight's in-process memo when it ran; the advice degrades gracefully without it.
21512
+ */
21513
+ adviceContext(operation) {
21514
+ const session = getMemoizedSessionIdentity();
21515
+ return {
21516
+ operation,
21517
+ hasAccessToken: Boolean(this.accessToken),
21518
+ sessionTeamId: session?.teamId,
21519
+ sessionRoles: session?.roles,
21520
+ sessionConsumerType: session?.consumerType,
21521
+ explicitTeamId: this.teamId || void 0,
21522
+ mask: createSecretMasker(this.secretValues)
21523
+ };
21524
+ }
21525
+ async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
21199
21526
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21200
21527
  method: "POST",
21201
21528
  headers: this.headers(),
@@ -21207,20 +21534,27 @@ var BifrostCatalogClient = class {
21207
21534
  })
21208
21535
  });
21209
21536
  if (!response.ok) {
21210
- throw await HttpError.fromResponse(response, {
21537
+ const httpErr = await HttpError.fromResponse(response, {
21211
21538
  method: "POST",
21212
21539
  url: `bifrost:api-catalog:${method} ${path6}`,
21213
21540
  secretValues: this.secretValues
21214
21541
  });
21542
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21543
+ throw advised ?? httpErr;
21215
21544
  }
21216
21545
  const data = await response.json();
21217
21546
  if (data && typeof data === "object" && "error" in data && data.error) {
21218
21547
  const errObj = data.error;
21219
- throw new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21548
+ const advised = adviseFromBifrostBody(
21549
+ response.status,
21550
+ JSON.stringify({ error: errObj }),
21551
+ this.adviceContext(operation)
21552
+ );
21553
+ throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21220
21554
  }
21221
21555
  return data;
21222
21556
  }
21223
- async akitaProxyRequest(method, path6, body = {}) {
21557
+ async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
21224
21558
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21225
21559
  method: "POST",
21226
21560
  headers: this.headers(),
@@ -21233,7 +21567,10 @@ var BifrostCatalogClient = class {
21233
21567
  });
21234
21568
  if (!response.ok) {
21235
21569
  const text = await response.text().catch(() => "");
21236
- return { ok: false, status: response.status, data: null, errorText: text };
21570
+ const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21571
+ const errorText = advised ? text ? `${text}
21572
+ ${advised.message}` : advised.message : text;
21573
+ return { ok: false, status: response.status, data: null, errorText };
21237
21574
  }
21238
21575
  const data = await response.json();
21239
21576
  return { ok: true, status: response.status, data, errorText: "" };
@@ -21248,7 +21585,9 @@ var BifrostCatalogClient = class {
21248
21585
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
21249
21586
  const data = await this.proxyRequest(
21250
21587
  "GET",
21251
- `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
21588
+ `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
21589
+ {},
21590
+ "discovered-services listing"
21252
21591
  );
21253
21592
  allItems.push(...data.items || []);
21254
21593
  if (data.total && allItems.length >= data.total) {
@@ -21272,7 +21611,8 @@ var BifrostCatalogClient = class {
21272
21611
  const data = await this.proxyRequest(
21273
21612
  "POST",
21274
21613
  "/api/v1/onboarding/prepare-collection",
21275
- { service_id: String(serviceId), workspace_id: workspaceId }
21614
+ { service_id: String(serviceId), workspace_id: workspaceId },
21615
+ "collection preparation"
21276
21616
  );
21277
21617
  return data.id;
21278
21618
  },
@@ -21296,7 +21636,8 @@ var BifrostCatalogClient = class {
21296
21636
  await this.proxyRequest(
21297
21637
  "POST",
21298
21638
  "/api/v1/onboarding/git",
21299
- body
21639
+ body,
21640
+ "git onboarding"
21300
21641
  );
21301
21642
  },
21302
21643
  { maxAttempts: 2, delayMs: 3e3 }
@@ -21309,7 +21650,9 @@ var BifrostCatalogClient = class {
21309
21650
  for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
21310
21651
  const result = await this.akitaProxyRequest(
21311
21652
  "GET",
21312
- `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
21653
+ `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
21654
+ {},
21655
+ "provider service resolution"
21313
21656
  );
21314
21657
  if (!result.ok || !result.data) return null;
21315
21658
  const services = result.data.services || [];
@@ -21339,7 +21682,8 @@ var BifrostCatalogClient = class {
21339
21682
  workspace_id: workspaceId,
21340
21683
  system_env: systemEnvironmentId
21341
21684
  }]
21342
- }
21685
+ },
21686
+ "Insights onboarding acknowledgment"
21343
21687
  );
21344
21688
  if (!result.ok) {
21345
21689
  throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21348,7 +21692,9 @@ var BifrostCatalogClient = class {
21348
21692
  async acknowledgeWorkspace(workspaceId) {
21349
21693
  const result = await this.akitaProxyRequest(
21350
21694
  "POST",
21351
- `/v2/workspaces/${workspaceId}/onboarding/acknowledge`
21695
+ `/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
21696
+ {},
21697
+ "workspace onboarding acknowledgment"
21352
21698
  );
21353
21699
  if (!result.ok) {
21354
21700
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21368,18 +21714,22 @@ var BifrostCatalogClient = class {
21368
21714
  }
21369
21715
  );
21370
21716
  if (!response.ok) {
21371
- throw await HttpError.fromResponse(response, {
21717
+ const httpErr = await HttpError.fromResponse(response, {
21372
21718
  method: "POST",
21373
21719
  url: `observability:createApplication(${workspaceId})`,
21374
21720
  secretValues: this.secretValues
21375
21721
  });
21722
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
21723
+ throw advised ?? httpErr;
21376
21724
  }
21377
21725
  return response.json();
21378
21726
  }
21379
21727
  async getTeamVerificationToken(workspaceId) {
21380
21728
  const result = await this.akitaProxyRequest(
21381
21729
  "GET",
21382
- `/v2/workspaces/${workspaceId}/team-verification-token`
21730
+ `/v2/workspaces/${workspaceId}/team-verification-token`,
21731
+ {},
21732
+ "team verification token retrieval"
21383
21733
  );
21384
21734
  if (!result.ok || !result.data) return null;
21385
21735
  return result.data.team_verification_token || null;
@@ -21396,11 +21746,13 @@ var BifrostCatalogClient = class {
21396
21746
  })
21397
21747
  });
21398
21748
  if (!response.ok) {
21399
- throw await HttpError.fromResponse(response, {
21749
+ const httpErr = await HttpError.fromResponse(response, {
21400
21750
  method: "POST",
21401
21751
  url: "bifrost:identity:POST /api/keys",
21402
21752
  secretValues: this.secretValues
21403
21753
  });
21754
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
21755
+ throw advised ?? httpErr;
21404
21756
  }
21405
21757
  const data = await response.json();
21406
21758
  const apikey = data?.apikey;
@@ -21428,7 +21780,17 @@ var POLL_INTERVAL_DEFAULT = 10;
21428
21780
  var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
21429
21781
  var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
21430
21782
  var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
21783
+ var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
21431
21784
  var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
21785
+ function parsePreflightMode(value) {
21786
+ const normalized = String(value || "warn").trim().toLowerCase();
21787
+ if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
21788
+ return normalized;
21789
+ }
21790
+ throw new Error(
21791
+ `Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
21792
+ );
21793
+ }
21432
21794
  function trimTrailingSlash(value) {
21433
21795
  return value.replace(/\/+$/, "");
21434
21796
  }
@@ -21505,11 +21867,13 @@ function resolveInputs(env = process.env) {
21505
21867
  postmanApiKey,
21506
21868
  postmanTeamId,
21507
21869
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
21870
+ credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
21508
21871
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
21509
21872
  pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
21510
21873
  postmanStack,
21511
21874
  postmanApiBase: endpointProfile.apiBaseUrl,
21512
21875
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
21876
+ postmanIapubBase: endpointProfile.iapubBaseUrl,
21513
21877
  postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
21514
21878
  postmanObservabilityEnv: endpointProfile.observabilityEnv
21515
21879
  };
@@ -21615,11 +21979,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21615
21979
  let apiKey = inputs.postmanApiKey;
21616
21980
  const teamId = inputs.postmanTeamId;
21617
21981
  let keyValid = false;
21982
+ let pmakIdentity;
21618
21983
  const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
21619
21984
  if (apiKey) {
21620
21985
  const result = await validateApiKey(apiKey, apiBase);
21621
21986
  keyValid = result.valid;
21622
- if (!keyValid) {
21987
+ if (keyValid) {
21988
+ pmakIdentity = { source: "pmak/me", teamId: result.teamId };
21989
+ } else {
21623
21990
  reporter.warning("Provided postman-api-key is invalid or expired.");
21624
21991
  }
21625
21992
  }
@@ -21666,7 +22033,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21666
22033
  } else {
21667
22034
  reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
21668
22035
  }
21669
- return { apiKey, teamId: resolvedTeamId };
22036
+ return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22037
+ }
22038
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22039
+ await runCredentialPreflight({
22040
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22041
+ iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22042
+ pmak,
22043
+ postmanAccessToken: inputs.postmanAccessToken,
22044
+ explicitTeamId: inputs.postmanTeamId || void 0,
22045
+ mode: inputs.credentialPreflight,
22046
+ mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22047
+ log: reporter,
22048
+ fetchImpl
22049
+ });
21670
22050
  }
21671
22051
  async function runAction() {
21672
22052
  const inputs = resolveInputs();
@@ -21685,7 +22065,8 @@ async function runAction() {
21685
22065
  observabilityBaseUrl: inputs.postmanObservabilityBase,
21686
22066
  observabilityEnv: inputs.postmanObservabilityEnv
21687
22067
  });
21688
- const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22068
+ const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22069
+ await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
21689
22070
  const client = new BifrostCatalogClient({
21690
22071
  accessToken: inputs.postmanAccessToken,
21691
22072
  teamId,