@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/index.cjs CHANGED
@@ -18676,12 +18676,15 @@ var index_exports = {};
18676
18676
  __export(index_exports, {
18677
18677
  DEFAULT_POSTMAN_API_BASE: () => DEFAULT_POSTMAN_API_BASE,
18678
18678
  DEFAULT_POSTMAN_BIFROST_BASE: () => DEFAULT_POSTMAN_BIFROST_BASE,
18679
+ DEFAULT_POSTMAN_IAPUB_BASE: () => DEFAULT_POSTMAN_IAPUB_BASE,
18679
18680
  DEFAULT_POSTMAN_OBSERVABILITY_BASE: () => DEFAULT_POSTMAN_OBSERVABILITY_BASE,
18680
18681
  createPlannedOutputs: () => createPlannedOutputs,
18681
18682
  getTeams: () => getTeams,
18683
+ parsePreflightMode: () => parsePreflightMode,
18682
18684
  resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
18683
18685
  resolveInputs: () => resolveInputs,
18684
18686
  runAction: () => runAction,
18687
+ runCredentialPreflightForInputs: () => runCredentialPreflightForInputs,
18685
18688
  runOnboarding: () => runOnboarding,
18686
18689
  validateApiKey: () => validateApiKey
18687
18690
  });
@@ -20960,6 +20963,310 @@ function getIDToken(aud) {
20960
20963
  });
20961
20964
  }
20962
20965
 
20966
+ // src/lib/credential-identity.ts
20967
+ var sessionPath = "/api/sessions/current";
20968
+ var pmakMemo = /* @__PURE__ */ new Map();
20969
+ var sessionMemo = /* @__PURE__ */ new Map();
20970
+ var memoizedSessionIdentity;
20971
+ function getMemoizedSessionIdentity() {
20972
+ return memoizedSessionIdentity;
20973
+ }
20974
+ function asRecord(value) {
20975
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20976
+ return void 0;
20977
+ }
20978
+ return value;
20979
+ }
20980
+ function coerceId(raw) {
20981
+ return raw ? String(raw) : void 0;
20982
+ }
20983
+ function coerceText(raw) {
20984
+ if (typeof raw !== "string") {
20985
+ return void 0;
20986
+ }
20987
+ const trimmed = raw.trim();
20988
+ return trimmed ? trimmed : void 0;
20989
+ }
20990
+ function normalizeBaseUrl(raw) {
20991
+ return String(raw || "").replace(/\/+$/, "");
20992
+ }
20993
+ async function resolvePmakIdentity(opts) {
20994
+ const apiKey = String(opts.apiKey || "").trim();
20995
+ if (!apiKey) {
20996
+ return void 0;
20997
+ }
20998
+ const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
20999
+ const memoKey = `${baseUrl}::${apiKey}`;
21000
+ let pending = pmakMemo.get(memoKey);
21001
+ if (!pending) {
21002
+ pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
21003
+ pmakMemo.set(memoKey, pending);
21004
+ }
21005
+ return pending;
21006
+ }
21007
+ async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
21008
+ try {
21009
+ const response = await fetchImpl(`${baseUrl}/me`, {
21010
+ method: "GET",
21011
+ headers: { "X-Api-Key": apiKey }
21012
+ });
21013
+ if (!response.ok) {
21014
+ return void 0;
21015
+ }
21016
+ const payload = asRecord(await response.json());
21017
+ const user = asRecord(payload?.user);
21018
+ if (!user) {
21019
+ return void 0;
21020
+ }
21021
+ return {
21022
+ source: "pmak/me",
21023
+ userId: coerceId(user.id),
21024
+ fullName: coerceText(user.fullName) ?? coerceText(user.username),
21025
+ teamId: coerceId(user.teamId),
21026
+ teamName: coerceText(user.teamName),
21027
+ teamDomain: coerceText(user.teamDomain)
21028
+ };
21029
+ } catch {
21030
+ return void 0;
21031
+ }
21032
+ }
21033
+ async function resolveSessionIdentity(opts) {
21034
+ const accessToken = String(opts.accessToken || "").trim();
21035
+ if (!accessToken) {
21036
+ return void 0;
21037
+ }
21038
+ const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
21039
+ const memoKey = `${baseUrl}::${accessToken}`;
21040
+ let pending = sessionMemo.get(memoKey);
21041
+ if (!pending) {
21042
+ pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21043
+ sessionMemo.set(memoKey, pending);
21044
+ }
21045
+ return pending;
21046
+ }
21047
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21048
+ try {
21049
+ const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21050
+ method: "GET",
21051
+ headers: { "x-access-token": accessToken }
21052
+ });
21053
+ if (!response.ok) {
21054
+ return void 0;
21055
+ }
21056
+ const payload = asRecord(await response.json());
21057
+ if (!payload) {
21058
+ return void 0;
21059
+ }
21060
+ const identity = asRecord(payload.identity);
21061
+ const data = asRecord(payload.data);
21062
+ const user = asRecord(data?.user);
21063
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21064
+ const singleRole = coerceText(user?.role);
21065
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21066
+ const resolved = {
21067
+ source: "iapub/sessions",
21068
+ userId: coerceId(user?.id),
21069
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21070
+ teamId: coerceId(identity?.team),
21071
+ teamDomain: coerceText(identity?.domain),
21072
+ ...roles ? { roles } : {},
21073
+ consumerType: coerceText(payload.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21074
+ };
21075
+ memoizedSessionIdentity = resolved;
21076
+ return resolved;
21077
+ } catch {
21078
+ return void 0;
21079
+ }
21080
+ }
21081
+ function describeTeam(id) {
21082
+ const label = id?.teamName ?? id?.teamDomain;
21083
+ return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
21084
+ }
21085
+ function formatIdentityLine(id, mask) {
21086
+ const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
21087
+ const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
21088
+ if (id.source === "pmak/me") {
21089
+ const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
21090
+ return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
21091
+ }
21092
+ return mask(
21093
+ `postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
21094
+ );
21095
+ }
21096
+ function crossCheckIdentities(args) {
21097
+ if (args.mode === "off") {
21098
+ return { ok: true, level: "ok", message: "" };
21099
+ }
21100
+ const pmakTeamId = args.pmak?.teamId;
21101
+ const sessionTeamId = args.session?.teamId;
21102
+ if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
21103
+ const level = args.mode === "enforce" ? "fail" : "note";
21104
+ const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
21105
+ 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.";
21106
+ return {
21107
+ ok: false,
21108
+ level,
21109
+ message: args.mask(
21110
+ `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
21111
+ )
21112
+ };
21113
+ }
21114
+ if (pmakTeamId && sessionTeamId) {
21115
+ const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
21116
+ const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
21117
+ return {
21118
+ ok: true,
21119
+ level: "ok",
21120
+ message: args.mask(
21121
+ `postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
21122
+ )
21123
+ };
21124
+ }
21125
+ const missing = [
21126
+ !pmakTeamId ? "PMAK identity" : void 0,
21127
+ !sessionTeamId ? "access-token session identity" : void 0
21128
+ ].filter(Boolean).join(" and ");
21129
+ return {
21130
+ ok: false,
21131
+ level: "note",
21132
+ message: args.mask(
21133
+ `postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
21134
+ )
21135
+ };
21136
+ }
21137
+ async function runCredentialPreflight(args) {
21138
+ if (args.mode === "off") {
21139
+ return;
21140
+ }
21141
+ const mask = args.mask;
21142
+ const apiKey = String(args.postmanApiKey || "").trim();
21143
+ const accessToken = String(args.postmanAccessToken || "").trim();
21144
+ let pmak = args.pmak;
21145
+ if (pmak) {
21146
+ args.log.info(formatIdentityLine(pmak, mask));
21147
+ } else if (apiKey) {
21148
+ try {
21149
+ pmak = await resolvePmakIdentity({
21150
+ apiBaseUrl: args.apiBaseUrl,
21151
+ apiKey,
21152
+ fetchImpl: args.fetchImpl
21153
+ });
21154
+ } catch (error2) {
21155
+ args.log.warning(
21156
+ mask(
21157
+ `postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21158
+ )
21159
+ );
21160
+ }
21161
+ if (pmak) {
21162
+ args.log.info(formatIdentityLine(pmak, mask));
21163
+ } else {
21164
+ args.log.warning(
21165
+ mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
21166
+ );
21167
+ }
21168
+ }
21169
+ if (!accessToken) {
21170
+ args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
21171
+ return;
21172
+ }
21173
+ let session;
21174
+ try {
21175
+ session = await resolveSessionIdentity({
21176
+ iapubBaseUrl: args.iapubBaseUrl,
21177
+ accessToken,
21178
+ fetchImpl: args.fetchImpl
21179
+ });
21180
+ } catch (error2) {
21181
+ args.log.warning(
21182
+ mask(
21183
+ `postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
21184
+ )
21185
+ );
21186
+ }
21187
+ if (session) {
21188
+ args.log.info(formatIdentityLine(session, mask));
21189
+ } else {
21190
+ args.log.warning(
21191
+ mask(
21192
+ "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21193
+ )
21194
+ );
21195
+ }
21196
+ const result = crossCheckIdentities({
21197
+ pmak,
21198
+ session,
21199
+ workspaceTeamId: args.workspaceTeamId,
21200
+ explicitTeamId: args.explicitTeamId,
21201
+ mode: args.mode,
21202
+ mask
21203
+ });
21204
+ if (!result.message) {
21205
+ return;
21206
+ }
21207
+ if (result.level === "fail") {
21208
+ throw new Error(result.message);
21209
+ }
21210
+ if (result.level === "note") {
21211
+ args.log.warning(result.message);
21212
+ return;
21213
+ }
21214
+ args.log.info(result.message);
21215
+ }
21216
+
21217
+ // src/lib/error-advice.ts
21218
+ 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.";
21219
+ function expiryAdvice(code) {
21220
+ 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.`;
21221
+ }
21222
+ function forbiddenAdvice(ctx) {
21223
+ 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)` : "";
21224
+ const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
21225
+ 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";
21226
+ 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.`;
21227
+ }
21228
+ function buildAdvice(status, body, ctx) {
21229
+ if (body.includes("UNAUTHENTICATED")) {
21230
+ return expiryAdvice("UNAUTHENTICATED");
21231
+ }
21232
+ if (body.includes("authenticationError")) {
21233
+ return expiryAdvice("authenticationError");
21234
+ }
21235
+ if (body.includes("Only personal workspaces")) {
21236
+ return WORKSPACE_PERSONAL_ONLY_ADVICE;
21237
+ }
21238
+ if (body.includes("projectAlreadyConnected")) {
21239
+ 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.`;
21240
+ }
21241
+ if (body.includes("invalidParamError") && body.includes("already exists")) {
21242
+ 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.`;
21243
+ }
21244
+ if (body.includes("Team feature is not available for your organization")) {
21245
+ 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.`;
21246
+ }
21247
+ if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
21248
+ return forbiddenAdvice(ctx);
21249
+ }
21250
+ return void 0;
21251
+ }
21252
+ function adviseFromHttpError(err, ctx) {
21253
+ const body = err.responseBody || err.message || "";
21254
+ const advice = buildAdvice(err.status, body, ctx);
21255
+ if (!advice) {
21256
+ return void 0;
21257
+ }
21258
+ return new Error(ctx.mask(advice), { cause: err });
21259
+ }
21260
+ function adviseFromBifrostBody(status, body, ctx) {
21261
+ const advice = buildAdvice(status, String(body || ""), ctx);
21262
+ if (!advice) {
21263
+ return void 0;
21264
+ }
21265
+ return new Error(ctx.mask(advice), {
21266
+ cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
21267
+ });
21268
+ }
21269
+
20963
21270
  // src/lib/secrets.ts
20964
21271
  var REDACTED = "[REDACTED]";
20965
21272
  var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -21012,6 +21319,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
21012
21319
  return sanitized.split(secret).join(replacement);
21013
21320
  }, source);
21014
21321
  }
21322
+ function createSecretMasker(secretValues, replacement = REDACTED) {
21323
+ return (input) => redactSecrets(input, secretValues, replacement);
21324
+ }
21015
21325
  function headerEntries(headers) {
21016
21326
  if (headers instanceof Headers) {
21017
21327
  return Array.from(headers.entries());
@@ -21143,12 +21453,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
21143
21453
  prod: {
21144
21454
  apiBaseUrl: "https://api.getpostman.com",
21145
21455
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
21456
+ iapubBaseUrl: "https://iapub.postman.co",
21146
21457
  observabilityBaseUrl: "https://api.observability.postman.com",
21147
21458
  observabilityEnv: "production"
21148
21459
  },
21149
21460
  beta: {
21150
21461
  apiBaseUrl: "https://api.getpostman-beta.com",
21151
21462
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
21463
+ iapubBaseUrl: "https://iapub.postman.co",
21152
21464
  observabilityBaseUrl: "https://api.observability.postman-beta.com",
21153
21465
  observabilityEnv: "beta"
21154
21466
  }
@@ -21212,7 +21524,23 @@ var BifrostCatalogClient = class {
21212
21524
  }
21213
21525
  return h;
21214
21526
  }
21215
- async proxyRequest(method, path6, body = {}) {
21527
+ /**
21528
+ * Reactive error-advice context. The session identity comes from the credential
21529
+ * preflight's in-process memo when it ran; the advice degrades gracefully without it.
21530
+ */
21531
+ adviceContext(operation) {
21532
+ const session = getMemoizedSessionIdentity();
21533
+ return {
21534
+ operation,
21535
+ hasAccessToken: Boolean(this.accessToken),
21536
+ sessionTeamId: session?.teamId,
21537
+ sessionRoles: session?.roles,
21538
+ sessionConsumerType: session?.consumerType,
21539
+ explicitTeamId: this.teamId || void 0,
21540
+ mask: createSecretMasker(this.secretValues)
21541
+ };
21542
+ }
21543
+ async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
21216
21544
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21217
21545
  method: "POST",
21218
21546
  headers: this.headers(),
@@ -21224,20 +21552,27 @@ var BifrostCatalogClient = class {
21224
21552
  })
21225
21553
  });
21226
21554
  if (!response.ok) {
21227
- throw await HttpError.fromResponse(response, {
21555
+ const httpErr = await HttpError.fromResponse(response, {
21228
21556
  method: "POST",
21229
21557
  url: `bifrost:api-catalog:${method} ${path6}`,
21230
21558
  secretValues: this.secretValues
21231
21559
  });
21560
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21561
+ throw advised ?? httpErr;
21232
21562
  }
21233
21563
  const data = await response.json();
21234
21564
  if (data && typeof data === "object" && "error" in data && data.error) {
21235
21565
  const errObj = data.error;
21236
- throw new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21566
+ const advised = adviseFromBifrostBody(
21567
+ response.status,
21568
+ JSON.stringify({ error: errObj }),
21569
+ this.adviceContext(operation)
21570
+ );
21571
+ throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
21237
21572
  }
21238
21573
  return data;
21239
21574
  }
21240
- async akitaProxyRequest(method, path6, body = {}) {
21575
+ async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
21241
21576
  const response = await this.fetchFn(this.bifrostProxyUrl, {
21242
21577
  method: "POST",
21243
21578
  headers: this.headers(),
@@ -21250,7 +21585,10 @@ var BifrostCatalogClient = class {
21250
21585
  });
21251
21586
  if (!response.ok) {
21252
21587
  const text = await response.text().catch(() => "");
21253
- return { ok: false, status: response.status, data: null, errorText: text };
21588
+ const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21589
+ const errorText = advised ? text ? `${text}
21590
+ ${advised.message}` : advised.message : text;
21591
+ return { ok: false, status: response.status, data: null, errorText };
21254
21592
  }
21255
21593
  const data = await response.json();
21256
21594
  return { ok: true, status: response.status, data, errorText: "" };
@@ -21265,7 +21603,9 @@ var BifrostCatalogClient = class {
21265
21603
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
21266
21604
  const data = await this.proxyRequest(
21267
21605
  "GET",
21268
- `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`
21606
+ `/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
21607
+ {},
21608
+ "discovered-services listing"
21269
21609
  );
21270
21610
  allItems.push(...data.items || []);
21271
21611
  if (data.total && allItems.length >= data.total) {
@@ -21289,7 +21629,8 @@ var BifrostCatalogClient = class {
21289
21629
  const data = await this.proxyRequest(
21290
21630
  "POST",
21291
21631
  "/api/v1/onboarding/prepare-collection",
21292
- { service_id: String(serviceId), workspace_id: workspaceId }
21632
+ { service_id: String(serviceId), workspace_id: workspaceId },
21633
+ "collection preparation"
21293
21634
  );
21294
21635
  return data.id;
21295
21636
  },
@@ -21313,7 +21654,8 @@ var BifrostCatalogClient = class {
21313
21654
  await this.proxyRequest(
21314
21655
  "POST",
21315
21656
  "/api/v1/onboarding/git",
21316
- body
21657
+ body,
21658
+ "git onboarding"
21317
21659
  );
21318
21660
  },
21319
21661
  { maxAttempts: 2, delayMs: 3e3 }
@@ -21326,7 +21668,9 @@ var BifrostCatalogClient = class {
21326
21668
  for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
21327
21669
  const result = await this.akitaProxyRequest(
21328
21670
  "GET",
21329
- `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`
21671
+ `/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
21672
+ {},
21673
+ "provider service resolution"
21330
21674
  );
21331
21675
  if (!result.ok || !result.data) return null;
21332
21676
  const services = result.data.services || [];
@@ -21356,7 +21700,8 @@ var BifrostCatalogClient = class {
21356
21700
  workspace_id: workspaceId,
21357
21701
  system_env: systemEnvironmentId
21358
21702
  }]
21359
- }
21703
+ },
21704
+ "Insights onboarding acknowledgment"
21360
21705
  );
21361
21706
  if (!result.ok) {
21362
21707
  throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21365,7 +21710,9 @@ var BifrostCatalogClient = class {
21365
21710
  async acknowledgeWorkspace(workspaceId) {
21366
21711
  const result = await this.akitaProxyRequest(
21367
21712
  "POST",
21368
- `/v2/workspaces/${workspaceId}/onboarding/acknowledge`
21713
+ `/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
21714
+ {},
21715
+ "workspace onboarding acknowledgment"
21369
21716
  );
21370
21717
  if (!result.ok) {
21371
21718
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
@@ -21385,18 +21732,22 @@ var BifrostCatalogClient = class {
21385
21732
  }
21386
21733
  );
21387
21734
  if (!response.ok) {
21388
- throw await HttpError.fromResponse(response, {
21735
+ const httpErr = await HttpError.fromResponse(response, {
21389
21736
  method: "POST",
21390
21737
  url: `observability:createApplication(${workspaceId})`,
21391
21738
  secretValues: this.secretValues
21392
21739
  });
21740
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
21741
+ throw advised ?? httpErr;
21393
21742
  }
21394
21743
  return response.json();
21395
21744
  }
21396
21745
  async getTeamVerificationToken(workspaceId) {
21397
21746
  const result = await this.akitaProxyRequest(
21398
21747
  "GET",
21399
- `/v2/workspaces/${workspaceId}/team-verification-token`
21748
+ `/v2/workspaces/${workspaceId}/team-verification-token`,
21749
+ {},
21750
+ "team verification token retrieval"
21400
21751
  );
21401
21752
  if (!result.ok || !result.data) return null;
21402
21753
  return result.data.team_verification_token || null;
@@ -21413,11 +21764,13 @@ var BifrostCatalogClient = class {
21413
21764
  })
21414
21765
  });
21415
21766
  if (!response.ok) {
21416
- throw await HttpError.fromResponse(response, {
21767
+ const httpErr = await HttpError.fromResponse(response, {
21417
21768
  method: "POST",
21418
21769
  url: "bifrost:identity:POST /api/keys",
21419
21770
  secretValues: this.secretValues
21420
21771
  });
21772
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
21773
+ throw advised ?? httpErr;
21421
21774
  }
21422
21775
  const data = await response.json();
21423
21776
  const apikey = data?.apikey;
@@ -21445,7 +21798,17 @@ var POLL_INTERVAL_DEFAULT = 10;
21445
21798
  var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
21446
21799
  var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
21447
21800
  var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
21801
+ var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
21448
21802
  var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
21803
+ function parsePreflightMode(value) {
21804
+ const normalized = String(value || "warn").trim().toLowerCase();
21805
+ if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
21806
+ return normalized;
21807
+ }
21808
+ throw new Error(
21809
+ `Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
21810
+ );
21811
+ }
21449
21812
  function trimTrailingSlash(value) {
21450
21813
  return value.replace(/\/+$/, "");
21451
21814
  }
@@ -21522,11 +21885,13 @@ function resolveInputs(env = process.env) {
21522
21885
  postmanApiKey,
21523
21886
  postmanTeamId,
21524
21887
  githubToken: get("github-token", env.GITHUB_TOKEN || ""),
21888
+ credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
21525
21889
  pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
21526
21890
  pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
21527
21891
  postmanStack,
21528
21892
  postmanApiBase: endpointProfile.apiBaseUrl,
21529
21893
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
21894
+ postmanIapubBase: endpointProfile.iapubBaseUrl,
21530
21895
  postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
21531
21896
  postmanObservabilityEnv: endpointProfile.observabilityEnv
21532
21897
  };
@@ -21632,11 +21997,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21632
21997
  let apiKey = inputs.postmanApiKey;
21633
21998
  const teamId = inputs.postmanTeamId;
21634
21999
  let keyValid = false;
22000
+ let pmakIdentity;
21635
22001
  const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
21636
22002
  if (apiKey) {
21637
22003
  const result = await validateApiKey(apiKey, apiBase);
21638
22004
  keyValid = result.valid;
21639
- if (!keyValid) {
22005
+ if (keyValid) {
22006
+ pmakIdentity = { source: "pmak/me", teamId: result.teamId };
22007
+ } else {
21640
22008
  reporter.warning("Provided postman-api-key is invalid or expired.");
21641
22009
  }
21642
22010
  }
@@ -21683,7 +22051,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
21683
22051
  } else {
21684
22052
  reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
21685
22053
  }
21686
- return { apiKey, teamId: resolvedTeamId };
22054
+ return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22055
+ }
22056
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22057
+ await runCredentialPreflight({
22058
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22059
+ iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22060
+ pmak,
22061
+ postmanAccessToken: inputs.postmanAccessToken,
22062
+ explicitTeamId: inputs.postmanTeamId || void 0,
22063
+ mode: inputs.credentialPreflight,
22064
+ mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22065
+ log: reporter,
22066
+ fetchImpl
22067
+ });
21687
22068
  }
21688
22069
  async function runAction() {
21689
22070
  const inputs = resolveInputs();
@@ -21702,7 +22083,8 @@ async function runAction() {
21702
22083
  observabilityBaseUrl: inputs.postmanObservabilityBase,
21703
22084
  observabilityEnv: inputs.postmanObservabilityEnv
21704
22085
  });
21705
- const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22086
+ const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22087
+ await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
21706
22088
  const client = new BifrostCatalogClient({
21707
22089
  accessToken: inputs.postmanAccessToken,
21708
22090
  teamId,
@@ -21736,12 +22118,15 @@ async function runAction() {
21736
22118
  0 && (module.exports = {
21737
22119
  DEFAULT_POSTMAN_API_BASE,
21738
22120
  DEFAULT_POSTMAN_BIFROST_BASE,
22121
+ DEFAULT_POSTMAN_IAPUB_BASE,
21739
22122
  DEFAULT_POSTMAN_OBSERVABILITY_BASE,
21740
22123
  createPlannedOutputs,
21741
22124
  getTeams,
22125
+ parsePreflightMode,
21742
22126
  resolveApiKeyAndTeamId,
21743
22127
  resolveInputs,
21744
22128
  runAction,
22129
+ runCredentialPreflightForInputs,
21745
22130
  runOnboarding,
21746
22131
  validateApiKey
21747
22132
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-insights",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "GitHub Action to onboard Postman Insights discovered services to API Catalog workspaces.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",