@postman-cse/onboarding-insights 1.0.4 → 2.1.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
@@ -12167,7 +12167,7 @@ var require_response = __commonJS({
12167
12167
  var assert = require("node:assert");
12168
12168
  var { types } = require("node:util");
12169
12169
  var textEncoder = new TextEncoder("utf-8");
12170
- var Response = class _Response {
12170
+ var Response2 = class _Response {
12171
12171
  // Creates network error Response.
12172
12172
  static error() {
12173
12173
  const responseObject = fromInnerResponse(makeNetworkError(), "immutable");
@@ -12310,8 +12310,8 @@ var require_response = __commonJS({
12310
12310
  return `Response ${nodeUtil.formatWithOptions(options, properties)}`;
12311
12311
  }
12312
12312
  };
12313
- mixinBody(Response);
12314
- Object.defineProperties(Response.prototype, {
12313
+ mixinBody(Response2);
12314
+ Object.defineProperties(Response2.prototype, {
12315
12315
  type: kEnumerableProperty,
12316
12316
  url: kEnumerableProperty,
12317
12317
  status: kEnumerableProperty,
@@ -12327,7 +12327,7 @@ var require_response = __commonJS({
12327
12327
  configurable: true
12328
12328
  }
12329
12329
  });
12330
- Object.defineProperties(Response, {
12330
+ Object.defineProperties(Response2, {
12331
12331
  json: kEnumerableProperty,
12332
12332
  redirect: kEnumerableProperty,
12333
12333
  error: kEnumerableProperty
@@ -12460,7 +12460,7 @@ var require_response = __commonJS({
12460
12460
  }
12461
12461
  }
12462
12462
  function fromInnerResponse(innerResponse, guard) {
12463
- const response = new Response(kConstruct);
12463
+ const response = new Response2(kConstruct);
12464
12464
  response[kState] = innerResponse;
12465
12465
  response[kHeaders] = new Headers3(kConstruct);
12466
12466
  setHeadersList(response[kHeaders], innerResponse.headersList);
@@ -12528,7 +12528,7 @@ var require_response = __commonJS({
12528
12528
  makeResponse,
12529
12529
  makeAppropriateNetworkError,
12530
12530
  filterResponse,
12531
- Response,
12531
+ Response: Response2,
12532
12532
  cloneResponse,
12533
12533
  fromInnerResponse
12534
12534
  };
@@ -15200,7 +15200,7 @@ var require_cache = __commonJS({
15200
15200
  var { urlEquals, getFieldValues } = require_util5();
15201
15201
  var { kEnumerableProperty, isDisturbed } = require_util();
15202
15202
  var { webidl } = require_webidl();
15203
- var { Response, cloneResponse, fromInnerResponse } = require_response();
15203
+ var { Response: Response2, cloneResponse, fromInnerResponse } = require_response();
15204
15204
  var { Request, fromInnerRequest } = require_request2();
15205
15205
  var { kState } = require_symbols2();
15206
15206
  var { fetching } = require_fetch();
@@ -15727,7 +15727,7 @@ var require_cache = __commonJS({
15727
15727
  converter: webidl.converters.DOMString
15728
15728
  }
15729
15729
  ]);
15730
- webidl.converters.Response = webidl.interfaceConverter(Response);
15730
+ webidl.converters.Response = webidl.interfaceConverter(Response2);
15731
15731
  webidl.converters["sequence<RequestInfo>"] = webidl.sequenceConverter(
15732
15732
  webidl.converters.RequestInfo
15733
15733
  );
@@ -18682,6 +18682,8 @@ __export(index_exports, {
18682
18682
  DEFAULT_POSTMAN_BIFROST_BASE: () => DEFAULT_POSTMAN_BIFROST_BASE,
18683
18683
  DEFAULT_POSTMAN_IAPUB_BASE: () => DEFAULT_POSTMAN_IAPUB_BASE,
18684
18684
  DEFAULT_POSTMAN_OBSERVABILITY_BASE: () => DEFAULT_POSTMAN_OBSERVABILITY_BASE,
18685
+ createInsightsBifrostClient: () => createInsightsBifrostClient,
18686
+ createInsightsTokenProvider: () => createInsightsTokenProvider,
18685
18687
  createPlannedOutputs: () => createPlannedOutputs,
18686
18688
  getTeams: () => getTeams,
18687
18689
  parsePreflightMode: () => parsePreflightMode,
@@ -20969,12 +20971,62 @@ function getIDToken(aud) {
20969
20971
 
20970
20972
  // src/lib/credential-identity.ts
20971
20973
  var sessionPath = "/api/sessions/current";
20974
+ var SESSION_MAX_ATTEMPTS = 3;
20975
+ var SESSION_RETRY_BASE_DELAY_MS = 500;
20976
+ var SESSION_RETRY_MAX_DELAY_MS = 8e3;
20972
20977
  var pmakMemo = /* @__PURE__ */ new Map();
20973
20978
  var sessionMemo = /* @__PURE__ */ new Map();
20974
20979
  var memoizedSessionIdentity;
20980
+ var memoizedSessionFailure;
20981
+ function defaultSessionSleep(ms) {
20982
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
20983
+ }
20984
+ function defaultRandom() {
20985
+ return Math.random();
20986
+ }
20987
+ function parseRetryAfterMs(value) {
20988
+ const trimmed = value?.trim();
20989
+ if (!trimmed) {
20990
+ return void 0;
20991
+ }
20992
+ if (/^\d+$/.test(trimmed)) {
20993
+ return Number(trimmed) * 1e3;
20994
+ }
20995
+ const dateMs = Date.parse(trimmed);
20996
+ if (!Number.isNaN(dateMs)) {
20997
+ return Math.max(0, dateMs - Date.now());
20998
+ }
20999
+ return void 0;
21000
+ }
21001
+ function parseRateLimitResetMs(value) {
21002
+ const trimmed = value?.trim();
21003
+ if (!trimmed || !/^\d+$/.test(trimmed)) {
21004
+ return void 0;
21005
+ }
21006
+ const seconds = Number(trimmed);
21007
+ const nowSeconds = Date.now() / 1e3;
21008
+ if (seconds > nowSeconds) {
21009
+ return Math.max(0, (seconds - nowSeconds) * 1e3);
21010
+ }
21011
+ return seconds * 1e3;
21012
+ }
21013
+ function computeSessionRetryDelayMs(response, attempt, random) {
21014
+ const headers = response?.headers;
21015
+ const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
21016
+ headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
21017
+ );
21018
+ if (signal !== void 0) {
21019
+ return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
21020
+ }
21021
+ const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
21022
+ return Math.round(random() * ceiling);
21023
+ }
20975
21024
  function getMemoizedSessionIdentity() {
20976
21025
  return memoizedSessionIdentity;
20977
21026
  }
21027
+ function getSessionResolutionFailure() {
21028
+ return memoizedSessionFailure;
21029
+ }
20978
21030
  function asRecord(value) {
20979
21031
  if (!value || typeof value !== "object" || Array.isArray(value)) {
20980
21032
  return void 0;
@@ -21043,46 +21095,90 @@ async function resolveSessionIdentity(opts) {
21043
21095
  const memoKey = `${baseUrl}::${accessToken}`;
21044
21096
  let pending = sessionMemo.get(memoKey);
21045
21097
  if (!pending) {
21046
- pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21098
+ pending = probeSessionIdentity(
21099
+ baseUrl,
21100
+ accessToken,
21101
+ opts.fetchImpl ?? fetch,
21102
+ Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
21103
+ opts.sleepImpl ?? defaultSessionSleep,
21104
+ opts.randomImpl ?? defaultRandom
21105
+ );
21047
21106
  sessionMemo.set(memoKey, pending);
21048
21107
  }
21049
21108
  return pending;
21050
21109
  }
21051
- async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21110
+ async function parseSessionResponse(response) {
21111
+ let payload;
21052
21112
  try {
21053
- const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21054
- method: "GET",
21055
- headers: { "x-access-token": accessToken }
21056
- });
21057
- if (!response.ok) {
21058
- return void 0;
21059
- }
21060
- const payload = asRecord(await response.json());
21061
- if (!payload) {
21062
- return void 0;
21063
- }
21064
- const root = asRecord(payload.session) ?? payload;
21065
- const identity = asRecord(root.identity);
21066
- const data = asRecord(root.data);
21067
- const user = asRecord(data?.user);
21068
- const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21069
- const singleRole = coerceText(user?.role);
21070
- const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21071
- const resolved = {
21072
- source: "iapub/sessions",
21073
- userId: coerceId(identity?.user) ?? coerceId(user?.id),
21074
- fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21075
- teamId: coerceId(identity?.team),
21076
- teamName: coerceText(user?.teamName),
21077
- teamDomain: coerceText(identity?.domain),
21078
- ...roles ? { roles } : {},
21079
- consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21080
- };
21081
- memoizedSessionIdentity = resolved;
21082
- return resolved;
21113
+ payload = asRecord(await response.json());
21083
21114
  } catch {
21084
21115
  return void 0;
21085
21116
  }
21117
+ if (!payload) {
21118
+ return void 0;
21119
+ }
21120
+ const root = asRecord(payload.session) ?? payload;
21121
+ const identity = asRecord(root.identity);
21122
+ const data = asRecord(root.data);
21123
+ const user = asRecord(data?.user);
21124
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21125
+ const singleRole = coerceText(user?.role);
21126
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21127
+ return {
21128
+ source: "iapub/sessions",
21129
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
21130
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21131
+ teamId: coerceId(identity?.team),
21132
+ teamName: coerceText(user?.teamName),
21133
+ teamDomain: coerceText(identity?.domain),
21134
+ ...roles ? { roles } : {},
21135
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21136
+ };
21137
+ }
21138
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
21139
+ let failure = "unavailable";
21140
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
21141
+ let response;
21142
+ try {
21143
+ response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21144
+ method: "GET",
21145
+ headers: { "x-access-token": accessToken }
21146
+ });
21147
+ } catch {
21148
+ failure = "unavailable";
21149
+ if (attempt < maxAttempts) {
21150
+ await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
21151
+ continue;
21152
+ }
21153
+ break;
21154
+ }
21155
+ if (response.ok) {
21156
+ const resolved = await parseSessionResponse(response);
21157
+ if (resolved) {
21158
+ memoizedSessionIdentity = resolved;
21159
+ memoizedSessionFailure = void 0;
21160
+ return resolved;
21161
+ }
21162
+ failure = "unavailable";
21163
+ break;
21164
+ }
21165
+ if (response.status === 401 || response.status === 403) {
21166
+ failure = "auth";
21167
+ break;
21168
+ }
21169
+ if (response.status === 429 || response.status >= 500) {
21170
+ failure = "unavailable";
21171
+ if (attempt < maxAttempts) {
21172
+ await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
21173
+ continue;
21174
+ }
21175
+ break;
21176
+ }
21177
+ failure = "unavailable";
21178
+ break;
21179
+ }
21180
+ memoizedSessionFailure = failure;
21181
+ return void 0;
21086
21182
  }
21087
21183
  function describeTeam(id) {
21088
21184
  const label = id?.teamName ?? id?.teamDomain;
@@ -21175,7 +21271,9 @@ async function runCredentialPreflight(args) {
21175
21271
  session = await resolveSessionIdentity({
21176
21272
  iapubBaseUrl: args.iapubBaseUrl,
21177
21273
  accessToken,
21178
- fetchImpl: args.fetchImpl
21274
+ fetchImpl: args.fetchImpl,
21275
+ ...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
21276
+ ...args.randomImpl ? { randomImpl: args.randomImpl } : {}
21179
21277
  });
21180
21278
  } catch (error2) {
21181
21279
  args.log.warning(
@@ -21195,11 +21293,20 @@ async function runCredentialPreflight(args) {
21195
21293
  );
21196
21294
  }
21197
21295
  } else {
21296
+ const failure = getSessionResolutionFailure();
21297
+ const detail = failure === "auth" ? "the access token was rejected by iapub (401/403), so it is invalid or expired. Re-mint it with postman-resolve-service-token-action (or POST https://api.getpostman.com/service-account-tokens) and re-run." : "iapub was unreachable after retries (network or 5xx). This is usually transient; re-run the job.";
21298
+ const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
21299
+ if (args.mode === "enforce") {
21300
+ throw new Error(
21301
+ mask(
21302
+ `${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
21303
+ )
21304
+ );
21305
+ }
21198
21306
  args.log.warning(
21199
- mask(
21200
- "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21201
- )
21307
+ mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
21202
21308
  );
21309
+ return;
21203
21310
  }
21204
21311
  const result = crossCheckIdentities({
21205
21312
  pmak,
@@ -21501,6 +21608,164 @@ function resolvePostmanEndpointProfile(stack, region = "us") {
21501
21608
  return profile;
21502
21609
  }
21503
21610
 
21611
+ // src/lib/postman/token-provider.ts
21612
+ var MintError = class extends Error {
21613
+ permanent;
21614
+ constructor(message, permanent) {
21615
+ super(message);
21616
+ this.name = "MintError";
21617
+ this.permanent = permanent;
21618
+ }
21619
+ };
21620
+ function extractAccessToken(payload) {
21621
+ if (!payload || typeof payload !== "object") return void 0;
21622
+ const record = payload;
21623
+ const direct = record.access_token;
21624
+ if (typeof direct === "string" && direct.trim()) return direct.trim();
21625
+ const session = record.session;
21626
+ if (session && typeof session === "object") {
21627
+ const token = session.token;
21628
+ if (typeof token === "string" && token.trim()) return token.trim();
21629
+ }
21630
+ return void 0;
21631
+ }
21632
+ var AccessTokenProvider = class {
21633
+ token;
21634
+ apiKey;
21635
+ apiBaseUrl;
21636
+ fetchImpl;
21637
+ maxAttempts;
21638
+ onToken;
21639
+ sleep;
21640
+ inflight;
21641
+ constructor(options) {
21642
+ this.token = String(options.accessToken || "").trim();
21643
+ this.apiKey = String(options.apiKey || "").trim();
21644
+ this.apiBaseUrl = String(
21645
+ options.apiBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
21646
+ ).replace(/\/+$/, "");
21647
+ this.fetchImpl = options.fetchImpl ?? fetch;
21648
+ this.maxAttempts = Math.max(1, options.maxAttempts ?? 2);
21649
+ this.onToken = options.onToken;
21650
+ this.sleep = options.sleep;
21651
+ }
21652
+ current() {
21653
+ return this.token;
21654
+ }
21655
+ /** True when a PMAK is present, so an expired token can be re-minted. */
21656
+ canRefresh() {
21657
+ return Boolean(this.apiKey);
21658
+ }
21659
+ refresh() {
21660
+ this.inflight ??= this.mintWithRetry().finally(() => {
21661
+ this.inflight = void 0;
21662
+ });
21663
+ return this.inflight;
21664
+ }
21665
+ async mintWithRetry() {
21666
+ if (!this.apiKey) {
21667
+ throw new Error(
21668
+ "postman: the access token expired and cannot be refreshed because no postman-api-key is present. Service-account access tokens expire after about 1 to 1.5 hours. Re-mint a fresh token (postman-resolve-service-token-action) and re-run."
21669
+ );
21670
+ }
21671
+ const token = await retry(() => this.mintOnce(), {
21672
+ maxAttempts: this.maxAttempts,
21673
+ delayMs: 1e3,
21674
+ backoffMultiplier: 2,
21675
+ ...this.sleep ? { sleep: this.sleep } : {},
21676
+ shouldRetry: (error2) => !(error2 instanceof MintError && error2.permanent)
21677
+ });
21678
+ this.token = token;
21679
+ this.onToken?.(token);
21680
+ return token;
21681
+ }
21682
+ async mintOnce() {
21683
+ const response = await this.fetchImpl(`${this.apiBaseUrl}/service-account-tokens`, {
21684
+ method: "POST",
21685
+ headers: {
21686
+ "Content-Type": "application/json",
21687
+ "x-api-key": this.apiKey
21688
+ },
21689
+ body: JSON.stringify({ apiKey: this.apiKey })
21690
+ });
21691
+ const body = await response.text().catch(() => "");
21692
+ if (!response.ok) {
21693
+ const status = response.status;
21694
+ if (status === 401 || status === 403) {
21695
+ throw new MintError(
21696
+ `postman: re-mint failed because the postman-api-key was rejected (PMAK rejected, HTTP ${status}); confirm it is a valid, enabled service-account PMAK for the intended team.`,
21697
+ true
21698
+ );
21699
+ }
21700
+ if (status === 400 && body.toLowerCase().includes("service accounts not enabled")) {
21701
+ throw new MintError(
21702
+ "postman: re-mint failed because service accounts are not enabled for this team; enable them in Team Settings or use a team where they are.",
21703
+ true
21704
+ );
21705
+ }
21706
+ throw new MintError(`postman: re-mint failed (service-account-tokens HTTP ${status}).`, false);
21707
+ }
21708
+ let parsed;
21709
+ try {
21710
+ parsed = JSON.parse(body);
21711
+ } catch {
21712
+ parsed = void 0;
21713
+ }
21714
+ const token = extractAccessToken(parsed);
21715
+ if (!token) {
21716
+ throw new MintError("postman: re-mint succeeded but no access token was returned.", false);
21717
+ }
21718
+ return token;
21719
+ }
21720
+ };
21721
+ async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
21722
+ const raw = mintError instanceof Error ? mintError.message : String(mintError);
21723
+ const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
21724
+ if (!rejected) {
21725
+ return raw;
21726
+ }
21727
+ try {
21728
+ const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
21729
+ if (me.ok) {
21730
+ const body = await me.json().catch(() => void 0);
21731
+ const user = body?.user;
21732
+ const looksPersonal = Boolean(user && (user.username || user.email));
21733
+ if (looksPersonal) {
21734
+ return "Personal API key detected, cannot mint a service-account access token. POST /service-account-tokens only accepts a SERVICE-ACCOUNT API key; this postman-api-key belongs to a user account" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". Create a service account in Team Settings and use its PMAK, or mint the token elsewhere and pass postman-access-token.";
21735
+ }
21736
+ return "The postman-api-key authenticates (GET /me OK) but was rejected by POST /service-account-tokens" + (user?.teamId ? ` (team ${user.teamId})` : "") + ". The service account likely lacks permission to mint access tokens, or service accounts are restricted for this team. Check the service account role in Team Settings, or pass a pre-minted postman-access-token.";
21737
+ }
21738
+ return "The postman-api-key is invalid, disabled, or expired (rejected by both POST /service-account-tokens and GET /me). Generate a fresh service-account PMAK in Team Settings and update the secret.";
21739
+ } catch {
21740
+ return raw;
21741
+ }
21742
+ }
21743
+ async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
21744
+ if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
21745
+ return;
21746
+ }
21747
+ const apiBaseUrl = String(
21748
+ inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
21749
+ ).replace(/\/+$/, "");
21750
+ const provider = new AccessTokenProvider({
21751
+ apiKey: inputs.postmanApiKey,
21752
+ apiBaseUrl,
21753
+ fetchImpl,
21754
+ onToken: (token) => setSecret2?.(token)
21755
+ });
21756
+ try {
21757
+ inputs.postmanAccessToken = await provider.refresh();
21758
+ log.info(
21759
+ "postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
21760
+ );
21761
+ } catch (error2) {
21762
+ const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
21763
+ log.warning(
21764
+ "postman: could not mint an access token from the postman-api-key. " + diagnosis + " Continuing without an access token - access-token-only functionality will be unavailable unless postman-access-token is provided."
21765
+ );
21766
+ }
21767
+ }
21768
+
21504
21769
  // src/lib/bifrost-client.ts
21505
21770
  var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
21506
21771
  var BIFROST_PROXY_PATH = "/ws/proxy";
@@ -21508,8 +21773,11 @@ var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilit
21508
21773
  var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
21509
21774
  var MAX_DISCOVERED_SERVICE_PAGES = 100;
21510
21775
  var MAX_PROVIDER_SERVICE_PAGES = 100;
21776
+ function isExpiredAuthError(status, body) {
21777
+ return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
21778
+ }
21511
21779
  var BifrostCatalogClient = class {
21512
- accessToken;
21780
+ tokenProvider;
21513
21781
  teamId;
21514
21782
  apiKey;
21515
21783
  fetchFn;
@@ -21518,11 +21786,14 @@ var BifrostCatalogClient = class {
21518
21786
  observabilityBaseUrl;
21519
21787
  observabilityEnv;
21520
21788
  constructor(options) {
21521
- this.accessToken = options.accessToken;
21789
+ this.tokenProvider = options.tokenProvider ?? new AccessTokenProvider({
21790
+ accessToken: options.accessToken,
21791
+ apiKey: options.apiKey
21792
+ });
21522
21793
  this.teamId = options.teamId;
21523
21794
  this.apiKey = options.apiKey;
21524
21795
  this.fetchFn = options.fetchFn ?? globalThis.fetch;
21525
- this.secretValues = [options.accessToken, options.apiKey].filter(Boolean);
21796
+ this.secretValues = [this.tokenProvider.current(), options.apiKey].filter(Boolean);
21526
21797
  const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
21527
21798
  this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
21528
21799
  this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
@@ -21534,6 +21805,11 @@ var BifrostCatalogClient = class {
21534
21805
  this.secretValues.push(apiKey);
21535
21806
  }
21536
21807
  }
21808
+ registerAccessToken(token) {
21809
+ if (token && !this.secretValues.includes(token)) {
21810
+ this.secretValues.push(token);
21811
+ }
21812
+ }
21537
21813
  /**
21538
21814
  * Build Bifrost proxy headers.
21539
21815
  * x-entity-team-id is ONLY included when teamId is present (org-mode tokens).
@@ -21541,7 +21817,7 @@ var BifrostCatalogClient = class {
21541
21817
  */
21542
21818
  headers() {
21543
21819
  const h = {
21544
- "x-access-token": this.accessToken,
21820
+ "x-access-token": this.tokenProvider.current(),
21545
21821
  "Content-Type": "application/json"
21546
21822
  };
21547
21823
  if (this.teamId) {
@@ -21557,7 +21833,7 @@ var BifrostCatalogClient = class {
21557
21833
  const session = getMemoizedSessionIdentity();
21558
21834
  return {
21559
21835
  operation,
21560
- hasAccessToken: Boolean(this.accessToken),
21836
+ hasAccessToken: Boolean(this.tokenProvider.current()),
21561
21837
  sessionTeamId: session?.teamId,
21562
21838
  sessionRoles: session?.roles,
21563
21839
  sessionConsumerType: session?.consumerType,
@@ -21566,7 +21842,7 @@ var BifrostCatalogClient = class {
21566
21842
  };
21567
21843
  }
21568
21844
  async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
21569
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21845
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21570
21846
  method: "POST",
21571
21847
  headers: this.headers(),
21572
21848
  body: JSON.stringify({
@@ -21576,6 +21852,39 @@ var BifrostCatalogClient = class {
21576
21852
  body
21577
21853
  })
21578
21854
  });
21855
+ let response = await send2();
21856
+ if (!response.ok) {
21857
+ const bodyText = await response.text().catch(() => "");
21858
+ if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
21859
+ try {
21860
+ const refreshed = await this.tokenProvider.refresh();
21861
+ this.registerAccessToken(refreshed);
21862
+ response = await send2();
21863
+ } catch {
21864
+ const httpErr = await HttpError.fromResponse(
21865
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21866
+ {
21867
+ method: "POST",
21868
+ url: `bifrost:api-catalog:${method} ${path6}`,
21869
+ secretValues: this.secretValues
21870
+ }
21871
+ );
21872
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21873
+ throw advised ?? httpErr;
21874
+ }
21875
+ } else {
21876
+ const httpErr = await HttpError.fromResponse(
21877
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21878
+ {
21879
+ method: "POST",
21880
+ url: `bifrost:api-catalog:${method} ${path6}`,
21881
+ secretValues: this.secretValues
21882
+ }
21883
+ );
21884
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21885
+ throw advised ?? httpErr;
21886
+ }
21887
+ }
21579
21888
  if (!response.ok) {
21580
21889
  const httpErr = await HttpError.fromResponse(response, {
21581
21890
  method: "POST",
@@ -21598,7 +21907,7 @@ var BifrostCatalogClient = class {
21598
21907
  return data;
21599
21908
  }
21600
21909
  async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
21601
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21910
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21602
21911
  method: "POST",
21603
21912
  headers: this.headers(),
21604
21913
  body: JSON.stringify({
@@ -21608,8 +21917,30 @@ var BifrostCatalogClient = class {
21608
21917
  body
21609
21918
  })
21610
21919
  });
21920
+ let response = await send2();
21611
21921
  if (!response.ok) {
21612
21922
  const text = await response.text().catch(() => "");
21923
+ if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
21924
+ try {
21925
+ const refreshed = await this.tokenProvider.refresh();
21926
+ this.registerAccessToken(refreshed);
21927
+ response = await send2();
21928
+ if (response.ok) {
21929
+ const data2 = await response.json();
21930
+ return { ok: true, status: response.status, data: data2, errorText: "" };
21931
+ }
21932
+ const retryText = await response.text().catch(() => "");
21933
+ const advised2 = adviseFromBifrostBody(response.status, retryText, this.adviceContext(operation));
21934
+ const errorText2 = advised2 ? retryText ? `${retryText}
21935
+ ${advised2.message}` : advised2.message : retryText;
21936
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21937
+ } catch {
21938
+ const advised2 = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21939
+ const errorText2 = advised2 ? text ? `${text}
21940
+ ${advised2.message}` : advised2.message : text;
21941
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21942
+ }
21943
+ }
21613
21944
  const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21614
21945
  const errorText = advised ? text ? `${text}
21615
21946
  ${advised.message}` : advised.message : text;
@@ -21708,12 +22039,19 @@ ${advised.message}` : advised.message : text;
21708
22039
  }
21709
22040
  page++;
21710
22041
  }
21711
- const fullName = clusterName ? `${clusterName}/${projectName}` : projectName;
21712
- const exactMatch = allServices.find((s) => s.name === fullName);
21713
- if (exactMatch) return exactMatch.id;
21714
- if (clusterName) return null;
21715
- const suffixMatch = allServices.find((s) => s.name.endsWith(`/${projectName}`));
21716
- return suffixMatch?.id || null;
22042
+ if (clusterName) {
22043
+ const fullName = `${clusterName}/${projectName}`;
22044
+ const exactMatch = allServices.find((s) => s.name === fullName);
22045
+ return exactMatch?.id || null;
22046
+ }
22047
+ const finalSegmentMatch = allServices.find(
22048
+ (s) => getFinalServiceSegment(s.name) === projectName
22049
+ );
22050
+ if (finalSegmentMatch) return finalSegmentMatch.id;
22051
+ const bracketedMatch = allServices.find(
22052
+ (s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
22053
+ );
22054
+ return bracketedMatch?.id || null;
21717
22055
  }
21718
22056
  async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
21719
22057
  const result = await this.akitaProxyRequest(
@@ -21743,6 +22081,14 @@ ${advised.message}` : advised.message : text;
21743
22081
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
21744
22082
  }
21745
22083
  }
22084
+ // PMAK-only by proven exception. A live probe against the observability
22085
+ // application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
22086
+ // applications) showed x-access-token is rejected identically to the x-api-key
22087
+ // control: both return 401 {"message":"Postman User not found"} for a
22088
+ // service-account credential, because the observability service has no
22089
+ // "Postman User" for a service account. The access token offers no improvement
22090
+ // over the API key here, so this route is not migrated to access-token-primary
22091
+ // (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
21746
22092
  async createApplication(workspaceId, systemEnv) {
21747
22093
  const response = await this.fetchFn(
21748
22094
  `${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
@@ -21810,7 +22156,17 @@ function findDiscoveredService(services, projectName, clusterName) {
21810
22156
  const fullName = `${clusterName}/${projectName}`;
21811
22157
  return services.find((s) => s.name === fullName);
21812
22158
  }
21813
- return services.find((s) => s.name.endsWith(`/${projectName}`));
22159
+ const finalSegmentMatch = services.find(
22160
+ (service) => getFinalServiceSegment(service.name) === projectName
22161
+ );
22162
+ if (finalSegmentMatch) return finalSegmentMatch;
22163
+ return services.find(
22164
+ (service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
22165
+ );
22166
+ }
22167
+ function getFinalServiceSegment(serviceName) {
22168
+ const lastSlash = serviceName.lastIndexOf("/");
22169
+ return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
21814
22170
  }
21815
22171
 
21816
22172
  // node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
@@ -22067,7 +22423,7 @@ function resolveActionVersion(explicit) {
22067
22423
  if (explicit) {
22068
22424
  return explicit;
22069
22425
  }
22070
- return "1.0.4" ? "1.0.4" : "unknown";
22426
+ return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
22071
22427
  }
22072
22428
  function telemetryDisabled(env) {
22073
22429
  const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
@@ -22189,6 +22545,18 @@ function createTelemetryContext(options) {
22189
22545
  };
22190
22546
  }
22191
22547
 
22548
+ // src/action-version.ts
22549
+ var import_node_fs = require("node:fs");
22550
+ var import_node_path = require("node:path");
22551
+ function resolveActionVersion2() {
22552
+ try {
22553
+ const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
22554
+ return JSON.parse(raw).version ?? "unknown";
22555
+ } catch {
22556
+ return "unknown";
22557
+ }
22558
+ }
22559
+
22192
22560
  // src/index.ts
22193
22561
  var POLL_TIMEOUT_MIN = 10;
22194
22562
  var POLL_TIMEOUT_MAX = 600;
@@ -22254,8 +22622,12 @@ function resolveInputs(env = process.env) {
22254
22622
  const projectName = get("project-name");
22255
22623
  if (!projectName) throw new Error("project-name is required");
22256
22624
  const postmanAccessToken = get("postman-access-token");
22257
- if (!postmanAccessToken) throw new Error("postman-access-token is required");
22258
22625
  const postmanApiKey = get("postman-api-key");
22626
+ if (!postmanAccessToken && !postmanApiKey) {
22627
+ throw new Error(
22628
+ "postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
22629
+ );
22630
+ }
22259
22631
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
22260
22632
  const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
22261
22633
  if (!workspaceId) {
@@ -22456,50 +22828,81 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
22456
22828
  }
22457
22829
  return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22458
22830
  }
22459
- async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22831
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
22832
+ const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
22460
22833
  await runCredentialPreflight({
22461
22834
  apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22462
22835
  iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22463
22836
  pmak,
22464
- postmanAccessToken: inputs.postmanAccessToken,
22837
+ postmanAccessToken: accessToken,
22465
22838
  explicitTeamId: inputs.postmanTeamId || void 0,
22466
22839
  mode: inputs.credentialPreflight,
22467
- mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22840
+ mask: createSecretMasker([inputs.postmanApiKey, accessToken]),
22468
22841
  log: reporter,
22469
22842
  fetchImpl
22470
22843
  });
22471
22844
  }
22845
+ function createInsightsTokenProvider(inputs, reporter, apiKey = inputs.postmanApiKey) {
22846
+ return new AccessTokenProvider({
22847
+ accessToken: inputs.postmanAccessToken,
22848
+ apiKey,
22849
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22850
+ onToken: (token) => reporter.setSecret(token)
22851
+ });
22852
+ }
22853
+ function createInsightsBifrostClient(inputs, tokenProvider, teamId, apiKey) {
22854
+ return new BifrostCatalogClient({
22855
+ tokenProvider,
22856
+ accessToken: tokenProvider.current(),
22857
+ teamId,
22858
+ apiKey,
22859
+ bifrostBaseUrl: inputs.postmanBifrostBase,
22860
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
22861
+ observabilityEnv: inputs.postmanObservabilityEnv
22862
+ });
22863
+ }
22472
22864
  async function runAction() {
22473
22865
  const inputs = resolveInputs();
22474
22866
  const planned = createPlannedOutputs(inputs);
22475
22867
  for (const [key, value] of Object.entries(planned)) {
22476
22868
  setOutput(key, value);
22477
22869
  }
22478
- setSecret(inputs.postmanAccessToken);
22870
+ const mintHolder = {
22871
+ postmanAccessToken: inputs.postmanAccessToken,
22872
+ postmanApiKey: inputs.postmanApiKey,
22873
+ postmanApiBase: inputs.postmanApiBase
22874
+ };
22875
+ await mintAccessTokenIfNeeded(mintHolder, core_exports, (secret) => setSecret(secret));
22876
+ inputs.postmanAccessToken = mintHolder.postmanAccessToken;
22877
+ if (inputs.postmanAccessToken) setSecret(inputs.postmanAccessToken);
22479
22878
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
22480
22879
  if (inputs.githubToken) setSecret(inputs.githubToken);
22481
- const preliminaryClient = new BifrostCatalogClient({
22482
- accessToken: inputs.postmanAccessToken,
22483
- teamId: inputs.postmanTeamId,
22484
- apiKey: inputs.postmanApiKey,
22485
- bifrostBaseUrl: inputs.postmanBifrostBase,
22486
- observabilityBaseUrl: inputs.postmanObservabilityBase,
22487
- observabilityEnv: inputs.postmanObservabilityEnv
22488
- });
22880
+ const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
22881
+ const preliminaryClient = createInsightsBifrostClient(
22882
+ inputs,
22883
+ tokenProvider,
22884
+ inputs.postmanTeamId,
22885
+ inputs.postmanApiKey
22886
+ );
22489
22887
  const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22490
- const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
22888
+ const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
22889
+ accessToken: tokenProvider.current(),
22890
+ apiKey,
22891
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22892
+ onToken: (token) => setSecret(token)
22893
+ }) : tokenProvider;
22894
+ const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
22491
22895
  telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
22492
22896
  let result;
22493
22897
  try {
22494
- await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
22495
- const client = new BifrostCatalogClient({
22496
- accessToken: inputs.postmanAccessToken,
22497
- teamId,
22498
- apiKey,
22499
- bifrostBaseUrl: inputs.postmanBifrostBase,
22500
- observabilityBaseUrl: inputs.postmanObservabilityBase,
22501
- observabilityEnv: inputs.postmanObservabilityEnv
22502
- });
22898
+ await runCredentialPreflightForInputs(
22899
+ inputs,
22900
+ pmakIdentity,
22901
+ core_exports,
22902
+ void 0,
22903
+ activeTokenProvider.current()
22904
+ );
22905
+ const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
22503
22906
  result = await runOnboarding(inputs, client, sleep, core_exports);
22504
22907
  } catch (error2) {
22505
22908
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -22530,6 +22933,8 @@ async function runAction() {
22530
22933
  DEFAULT_POSTMAN_BIFROST_BASE,
22531
22934
  DEFAULT_POSTMAN_IAPUB_BASE,
22532
22935
  DEFAULT_POSTMAN_OBSERVABILITY_BASE,
22936
+ createInsightsBifrostClient,
22937
+ createInsightsTokenProvider,
22533
22938
  createPlannedOutputs,
22534
22939
  getTeams,
22535
22940
  parsePreflightMode,