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