@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/action.cjs CHANGED
@@ -12166,7 +12166,7 @@ var require_response = __commonJS({
12166
12166
  var assert = require("node:assert");
12167
12167
  var { types } = require("node:util");
12168
12168
  var textEncoder = new TextEncoder("utf-8");
12169
- var Response = class _Response {
12169
+ var Response2 = class _Response {
12170
12170
  // Creates network error Response.
12171
12171
  static error() {
12172
12172
  const responseObject = fromInnerResponse(makeNetworkError(), "immutable");
@@ -12309,8 +12309,8 @@ var require_response = __commonJS({
12309
12309
  return `Response ${nodeUtil.formatWithOptions(options, properties)}`;
12310
12310
  }
12311
12311
  };
12312
- mixinBody(Response);
12313
- Object.defineProperties(Response.prototype, {
12312
+ mixinBody(Response2);
12313
+ Object.defineProperties(Response2.prototype, {
12314
12314
  type: kEnumerableProperty,
12315
12315
  url: kEnumerableProperty,
12316
12316
  status: kEnumerableProperty,
@@ -12326,7 +12326,7 @@ var require_response = __commonJS({
12326
12326
  configurable: true
12327
12327
  }
12328
12328
  });
12329
- Object.defineProperties(Response, {
12329
+ Object.defineProperties(Response2, {
12330
12330
  json: kEnumerableProperty,
12331
12331
  redirect: kEnumerableProperty,
12332
12332
  error: kEnumerableProperty
@@ -12459,7 +12459,7 @@ var require_response = __commonJS({
12459
12459
  }
12460
12460
  }
12461
12461
  function fromInnerResponse(innerResponse, guard) {
12462
- const response = new Response(kConstruct);
12462
+ const response = new Response2(kConstruct);
12463
12463
  response[kState] = innerResponse;
12464
12464
  response[kHeaders] = new Headers3(kConstruct);
12465
12465
  setHeadersList(response[kHeaders], innerResponse.headersList);
@@ -12527,7 +12527,7 @@ var require_response = __commonJS({
12527
12527
  makeResponse,
12528
12528
  makeAppropriateNetworkError,
12529
12529
  filterResponse,
12530
- Response,
12530
+ Response: Response2,
12531
12531
  cloneResponse,
12532
12532
  fromInnerResponse
12533
12533
  };
@@ -15199,7 +15199,7 @@ var require_cache = __commonJS({
15199
15199
  var { urlEquals, getFieldValues } = require_util5();
15200
15200
  var { kEnumerableProperty, isDisturbed } = require_util();
15201
15201
  var { webidl } = require_webidl();
15202
- var { Response, cloneResponse, fromInnerResponse } = require_response();
15202
+ var { Response: Response2, cloneResponse, fromInnerResponse } = require_response();
15203
15203
  var { Request, fromInnerRequest } = require_request2();
15204
15204
  var { kState } = require_symbols2();
15205
15205
  var { fetching } = require_fetch();
@@ -15726,7 +15726,7 @@ var require_cache = __commonJS({
15726
15726
  converter: webidl.converters.DOMString
15727
15727
  }
15728
15728
  ]);
15729
- webidl.converters.Response = webidl.interfaceConverter(Response);
15729
+ webidl.converters.Response = webidl.interfaceConverter(Response2);
15730
15730
  webidl.converters["sequence<RequestInfo>"] = webidl.sequenceConverter(
15731
15731
  webidl.converters.RequestInfo
15732
15732
  );
@@ -20949,12 +20949,62 @@ function getIDToken(aud) {
20949
20949
 
20950
20950
  // src/lib/credential-identity.ts
20951
20951
  var sessionPath = "/api/sessions/current";
20952
+ var SESSION_MAX_ATTEMPTS = 3;
20953
+ var SESSION_RETRY_BASE_DELAY_MS = 500;
20954
+ var SESSION_RETRY_MAX_DELAY_MS = 8e3;
20952
20955
  var pmakMemo = /* @__PURE__ */ new Map();
20953
20956
  var sessionMemo = /* @__PURE__ */ new Map();
20954
20957
  var memoizedSessionIdentity;
20958
+ var memoizedSessionFailure;
20959
+ function defaultSessionSleep(ms) {
20960
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
20961
+ }
20962
+ function defaultRandom() {
20963
+ return Math.random();
20964
+ }
20965
+ function parseRetryAfterMs(value) {
20966
+ const trimmed = value?.trim();
20967
+ if (!trimmed) {
20968
+ return void 0;
20969
+ }
20970
+ if (/^\d+$/.test(trimmed)) {
20971
+ return Number(trimmed) * 1e3;
20972
+ }
20973
+ const dateMs = Date.parse(trimmed);
20974
+ if (!Number.isNaN(dateMs)) {
20975
+ return Math.max(0, dateMs - Date.now());
20976
+ }
20977
+ return void 0;
20978
+ }
20979
+ function parseRateLimitResetMs(value) {
20980
+ const trimmed = value?.trim();
20981
+ if (!trimmed || !/^\d+$/.test(trimmed)) {
20982
+ return void 0;
20983
+ }
20984
+ const seconds = Number(trimmed);
20985
+ const nowSeconds = Date.now() / 1e3;
20986
+ if (seconds > nowSeconds) {
20987
+ return Math.max(0, (seconds - nowSeconds) * 1e3);
20988
+ }
20989
+ return seconds * 1e3;
20990
+ }
20991
+ function computeSessionRetryDelayMs(response, attempt, random) {
20992
+ const headers = response?.headers;
20993
+ const signal = parseRetryAfterMs(headers?.get("retry-after") ?? null) ?? parseRateLimitResetMs(
20994
+ headers?.get("ratelimit-reset") ?? headers?.get("x-ratelimit-reset") ?? null
20995
+ );
20996
+ if (signal !== void 0) {
20997
+ return Math.min(Math.max(0, signal), SESSION_RETRY_MAX_DELAY_MS);
20998
+ }
20999
+ const ceiling = Math.min(SESSION_RETRY_MAX_DELAY_MS, SESSION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1));
21000
+ return Math.round(random() * ceiling);
21001
+ }
20955
21002
  function getMemoizedSessionIdentity() {
20956
21003
  return memoizedSessionIdentity;
20957
21004
  }
21005
+ function getSessionResolutionFailure() {
21006
+ return memoizedSessionFailure;
21007
+ }
20958
21008
  function asRecord(value) {
20959
21009
  if (!value || typeof value !== "object" || Array.isArray(value)) {
20960
21010
  return void 0;
@@ -21023,46 +21073,90 @@ async function resolveSessionIdentity(opts) {
21023
21073
  const memoKey = `${baseUrl}::${accessToken}`;
21024
21074
  let pending = sessionMemo.get(memoKey);
21025
21075
  if (!pending) {
21026
- pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
21076
+ pending = probeSessionIdentity(
21077
+ baseUrl,
21078
+ accessToken,
21079
+ opts.fetchImpl ?? fetch,
21080
+ Math.max(1, opts.maxAttempts ?? SESSION_MAX_ATTEMPTS),
21081
+ opts.sleepImpl ?? defaultSessionSleep,
21082
+ opts.randomImpl ?? defaultRandom
21083
+ );
21027
21084
  sessionMemo.set(memoKey, pending);
21028
21085
  }
21029
21086
  return pending;
21030
21087
  }
21031
- async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
21088
+ async function parseSessionResponse(response) {
21089
+ let payload;
21032
21090
  try {
21033
- const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21034
- method: "GET",
21035
- headers: { "x-access-token": accessToken }
21036
- });
21037
- if (!response.ok) {
21038
- return void 0;
21039
- }
21040
- const payload = asRecord(await response.json());
21041
- if (!payload) {
21042
- return void 0;
21043
- }
21044
- const root = asRecord(payload.session) ?? payload;
21045
- const identity = asRecord(root.identity);
21046
- const data = asRecord(root.data);
21047
- const user = asRecord(data?.user);
21048
- const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21049
- const singleRole = coerceText(user?.role);
21050
- const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21051
- const resolved = {
21052
- source: "iapub/sessions",
21053
- userId: coerceId(identity?.user) ?? coerceId(user?.id),
21054
- fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21055
- teamId: coerceId(identity?.team),
21056
- teamName: coerceText(user?.teamName),
21057
- teamDomain: coerceText(identity?.domain),
21058
- ...roles ? { roles } : {},
21059
- consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21060
- };
21061
- memoizedSessionIdentity = resolved;
21062
- return resolved;
21091
+ payload = asRecord(await response.json());
21063
21092
  } catch {
21064
21093
  return void 0;
21065
21094
  }
21095
+ if (!payload) {
21096
+ return void 0;
21097
+ }
21098
+ const root = asRecord(payload.session) ?? payload;
21099
+ const identity = asRecord(root.identity);
21100
+ const data = asRecord(root.data);
21101
+ const user = asRecord(data?.user);
21102
+ const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
21103
+ const singleRole = coerceText(user?.role);
21104
+ const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
21105
+ return {
21106
+ source: "iapub/sessions",
21107
+ userId: coerceId(identity?.user) ?? coerceId(user?.id),
21108
+ fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
21109
+ teamId: coerceId(identity?.team),
21110
+ teamName: coerceText(user?.teamName),
21111
+ teamDomain: coerceText(identity?.domain),
21112
+ ...roles ? { roles } : {},
21113
+ consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
21114
+ };
21115
+ }
21116
+ async function probeSessionIdentity(baseUrl, accessToken, fetchImpl, maxAttempts, sleepImpl, random) {
21117
+ let failure = "unavailable";
21118
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
21119
+ let response;
21120
+ try {
21121
+ response = await fetchImpl(`${baseUrl}${sessionPath}`, {
21122
+ method: "GET",
21123
+ headers: { "x-access-token": accessToken }
21124
+ });
21125
+ } catch {
21126
+ failure = "unavailable";
21127
+ if (attempt < maxAttempts) {
21128
+ await sleepImpl(computeSessionRetryDelayMs(void 0, attempt, random));
21129
+ continue;
21130
+ }
21131
+ break;
21132
+ }
21133
+ if (response.ok) {
21134
+ const resolved = await parseSessionResponse(response);
21135
+ if (resolved) {
21136
+ memoizedSessionIdentity = resolved;
21137
+ memoizedSessionFailure = void 0;
21138
+ return resolved;
21139
+ }
21140
+ failure = "unavailable";
21141
+ break;
21142
+ }
21143
+ if (response.status === 401 || response.status === 403) {
21144
+ failure = "auth";
21145
+ break;
21146
+ }
21147
+ if (response.status === 429 || response.status >= 500) {
21148
+ failure = "unavailable";
21149
+ if (attempt < maxAttempts) {
21150
+ await sleepImpl(computeSessionRetryDelayMs(response, attempt, random));
21151
+ continue;
21152
+ }
21153
+ break;
21154
+ }
21155
+ failure = "unavailable";
21156
+ break;
21157
+ }
21158
+ memoizedSessionFailure = failure;
21159
+ return void 0;
21066
21160
  }
21067
21161
  function describeTeam(id) {
21068
21162
  const label = id?.teamName ?? id?.teamDomain;
@@ -21155,7 +21249,9 @@ async function runCredentialPreflight(args) {
21155
21249
  session = await resolveSessionIdentity({
21156
21250
  iapubBaseUrl: args.iapubBaseUrl,
21157
21251
  accessToken,
21158
- fetchImpl: args.fetchImpl
21252
+ fetchImpl: args.fetchImpl,
21253
+ ...args.sleepImpl ? { sleepImpl: args.sleepImpl } : {},
21254
+ ...args.randomImpl ? { randomImpl: args.randomImpl } : {}
21159
21255
  });
21160
21256
  } catch (error2) {
21161
21257
  args.log.warning(
@@ -21175,11 +21271,20 @@ async function runCredentialPreflight(args) {
21175
21271
  );
21176
21272
  }
21177
21273
  } else {
21274
+ const failure = getSessionResolutionFailure();
21275
+ 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.";
21276
+ const base = "postman: credential preflight could not resolve the access-token session identity from iapub: " + detail;
21277
+ if (args.mode === "enforce") {
21278
+ throw new Error(
21279
+ mask(
21280
+ `${base} (credential-preflight: enforce requires a resolvable session identity; use credential-preflight: warn to continue with reactive error guidance only.)`
21281
+ )
21282
+ );
21283
+ }
21178
21284
  args.log.warning(
21179
- mask(
21180
- "postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
21181
- )
21285
+ mask(`${base} Continuing with reactive error guidance only (credential-preflight: warn).`)
21182
21286
  );
21287
+ return;
21183
21288
  }
21184
21289
  const result = crossCheckIdentities({
21185
21290
  pmak,
@@ -21481,6 +21586,164 @@ function resolvePostmanEndpointProfile(stack, region = "us") {
21481
21586
  return profile;
21482
21587
  }
21483
21588
 
21589
+ // src/lib/postman/token-provider.ts
21590
+ var MintError = class extends Error {
21591
+ permanent;
21592
+ constructor(message, permanent) {
21593
+ super(message);
21594
+ this.name = "MintError";
21595
+ this.permanent = permanent;
21596
+ }
21597
+ };
21598
+ function extractAccessToken(payload) {
21599
+ if (!payload || typeof payload !== "object") return void 0;
21600
+ const record = payload;
21601
+ const direct = record.access_token;
21602
+ if (typeof direct === "string" && direct.trim()) return direct.trim();
21603
+ const session = record.session;
21604
+ if (session && typeof session === "object") {
21605
+ const token = session.token;
21606
+ if (typeof token === "string" && token.trim()) return token.trim();
21607
+ }
21608
+ return void 0;
21609
+ }
21610
+ var AccessTokenProvider = class {
21611
+ token;
21612
+ apiKey;
21613
+ apiBaseUrl;
21614
+ fetchImpl;
21615
+ maxAttempts;
21616
+ onToken;
21617
+ sleep;
21618
+ inflight;
21619
+ constructor(options) {
21620
+ this.token = String(options.accessToken || "").trim();
21621
+ this.apiKey = String(options.apiKey || "").trim();
21622
+ this.apiBaseUrl = String(
21623
+ options.apiBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
21624
+ ).replace(/\/+$/, "");
21625
+ this.fetchImpl = options.fetchImpl ?? fetch;
21626
+ this.maxAttempts = Math.max(1, options.maxAttempts ?? 2);
21627
+ this.onToken = options.onToken;
21628
+ this.sleep = options.sleep;
21629
+ }
21630
+ current() {
21631
+ return this.token;
21632
+ }
21633
+ /** True when a PMAK is present, so an expired token can be re-minted. */
21634
+ canRefresh() {
21635
+ return Boolean(this.apiKey);
21636
+ }
21637
+ refresh() {
21638
+ this.inflight ??= this.mintWithRetry().finally(() => {
21639
+ this.inflight = void 0;
21640
+ });
21641
+ return this.inflight;
21642
+ }
21643
+ async mintWithRetry() {
21644
+ if (!this.apiKey) {
21645
+ throw new Error(
21646
+ "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."
21647
+ );
21648
+ }
21649
+ const token = await retry(() => this.mintOnce(), {
21650
+ maxAttempts: this.maxAttempts,
21651
+ delayMs: 1e3,
21652
+ backoffMultiplier: 2,
21653
+ ...this.sleep ? { sleep: this.sleep } : {},
21654
+ shouldRetry: (error2) => !(error2 instanceof MintError && error2.permanent)
21655
+ });
21656
+ this.token = token;
21657
+ this.onToken?.(token);
21658
+ return token;
21659
+ }
21660
+ async mintOnce() {
21661
+ const response = await this.fetchImpl(`${this.apiBaseUrl}/service-account-tokens`, {
21662
+ method: "POST",
21663
+ headers: {
21664
+ "Content-Type": "application/json",
21665
+ "x-api-key": this.apiKey
21666
+ },
21667
+ body: JSON.stringify({ apiKey: this.apiKey })
21668
+ });
21669
+ const body = await response.text().catch(() => "");
21670
+ if (!response.ok) {
21671
+ const status = response.status;
21672
+ if (status === 401 || status === 403) {
21673
+ throw new MintError(
21674
+ `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.`,
21675
+ true
21676
+ );
21677
+ }
21678
+ if (status === 400 && body.toLowerCase().includes("service accounts not enabled")) {
21679
+ throw new MintError(
21680
+ "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.",
21681
+ true
21682
+ );
21683
+ }
21684
+ throw new MintError(`postman: re-mint failed (service-account-tokens HTTP ${status}).`, false);
21685
+ }
21686
+ let parsed;
21687
+ try {
21688
+ parsed = JSON.parse(body);
21689
+ } catch {
21690
+ parsed = void 0;
21691
+ }
21692
+ const token = extractAccessToken(parsed);
21693
+ if (!token) {
21694
+ throw new MintError("postman: re-mint succeeded but no access token was returned.", false);
21695
+ }
21696
+ return token;
21697
+ }
21698
+ };
21699
+ async function describeMintFailure(mintError, apiKey, apiBaseUrl, fetchImpl) {
21700
+ const raw = mintError instanceof Error ? mintError.message : String(mintError);
21701
+ const rejected = /HTTP 40[13]|PMAK rejected/.test(raw);
21702
+ if (!rejected) {
21703
+ return raw;
21704
+ }
21705
+ try {
21706
+ const me = await fetchImpl(`${apiBaseUrl}/me`, { headers: { "x-api-key": apiKey } });
21707
+ if (me.ok) {
21708
+ const body = await me.json().catch(() => void 0);
21709
+ const user = body?.user;
21710
+ const looksPersonal = Boolean(user && (user.username || user.email));
21711
+ if (looksPersonal) {
21712
+ 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.";
21713
+ }
21714
+ 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.";
21715
+ }
21716
+ 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.";
21717
+ } catch {
21718
+ return raw;
21719
+ }
21720
+ }
21721
+ async function mintAccessTokenIfNeeded(inputs, log, setSecret2, fetchImpl = fetch) {
21722
+ if (inputs.postmanAccessToken || !inputs.postmanApiKey) {
21723
+ return;
21724
+ }
21725
+ const apiBaseUrl = String(
21726
+ inputs.postmanApiBase || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
21727
+ ).replace(/\/+$/, "");
21728
+ const provider = new AccessTokenProvider({
21729
+ apiKey: inputs.postmanApiKey,
21730
+ apiBaseUrl,
21731
+ fetchImpl,
21732
+ onToken: (token) => setSecret2?.(token)
21733
+ });
21734
+ try {
21735
+ inputs.postmanAccessToken = await provider.refresh();
21736
+ log.info(
21737
+ "postman: no postman-access-token configured - minted a short-lived service-account access token from the postman-api-key."
21738
+ );
21739
+ } catch (error2) {
21740
+ const diagnosis = await describeMintFailure(error2, inputs.postmanApiKey, apiBaseUrl, fetchImpl);
21741
+ log.warning(
21742
+ "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."
21743
+ );
21744
+ }
21745
+ }
21746
+
21484
21747
  // src/lib/bifrost-client.ts
21485
21748
  var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
21486
21749
  var BIFROST_PROXY_PATH = "/ws/proxy";
@@ -21488,8 +21751,11 @@ var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilit
21488
21751
  var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
21489
21752
  var MAX_DISCOVERED_SERVICE_PAGES = 100;
21490
21753
  var MAX_PROVIDER_SERVICE_PAGES = 100;
21754
+ function isExpiredAuthError(status, body) {
21755
+ return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
21756
+ }
21491
21757
  var BifrostCatalogClient = class {
21492
- accessToken;
21758
+ tokenProvider;
21493
21759
  teamId;
21494
21760
  apiKey;
21495
21761
  fetchFn;
@@ -21498,11 +21764,14 @@ var BifrostCatalogClient = class {
21498
21764
  observabilityBaseUrl;
21499
21765
  observabilityEnv;
21500
21766
  constructor(options) {
21501
- this.accessToken = options.accessToken;
21767
+ this.tokenProvider = options.tokenProvider ?? new AccessTokenProvider({
21768
+ accessToken: options.accessToken,
21769
+ apiKey: options.apiKey
21770
+ });
21502
21771
  this.teamId = options.teamId;
21503
21772
  this.apiKey = options.apiKey;
21504
21773
  this.fetchFn = options.fetchFn ?? globalThis.fetch;
21505
- this.secretValues = [options.accessToken, options.apiKey].filter(Boolean);
21774
+ this.secretValues = [this.tokenProvider.current(), options.apiKey].filter(Boolean);
21506
21775
  const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
21507
21776
  this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
21508
21777
  this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
@@ -21514,6 +21783,11 @@ var BifrostCatalogClient = class {
21514
21783
  this.secretValues.push(apiKey);
21515
21784
  }
21516
21785
  }
21786
+ registerAccessToken(token) {
21787
+ if (token && !this.secretValues.includes(token)) {
21788
+ this.secretValues.push(token);
21789
+ }
21790
+ }
21517
21791
  /**
21518
21792
  * Build Bifrost proxy headers.
21519
21793
  * x-entity-team-id is ONLY included when teamId is present (org-mode tokens).
@@ -21521,7 +21795,7 @@ var BifrostCatalogClient = class {
21521
21795
  */
21522
21796
  headers() {
21523
21797
  const h = {
21524
- "x-access-token": this.accessToken,
21798
+ "x-access-token": this.tokenProvider.current(),
21525
21799
  "Content-Type": "application/json"
21526
21800
  };
21527
21801
  if (this.teamId) {
@@ -21537,7 +21811,7 @@ var BifrostCatalogClient = class {
21537
21811
  const session = getMemoizedSessionIdentity();
21538
21812
  return {
21539
21813
  operation,
21540
- hasAccessToken: Boolean(this.accessToken),
21814
+ hasAccessToken: Boolean(this.tokenProvider.current()),
21541
21815
  sessionTeamId: session?.teamId,
21542
21816
  sessionRoles: session?.roles,
21543
21817
  sessionConsumerType: session?.consumerType,
@@ -21546,7 +21820,7 @@ var BifrostCatalogClient = class {
21546
21820
  };
21547
21821
  }
21548
21822
  async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
21549
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21823
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21550
21824
  method: "POST",
21551
21825
  headers: this.headers(),
21552
21826
  body: JSON.stringify({
@@ -21556,6 +21830,39 @@ var BifrostCatalogClient = class {
21556
21830
  body
21557
21831
  })
21558
21832
  });
21833
+ let response = await send2();
21834
+ if (!response.ok) {
21835
+ const bodyText = await response.text().catch(() => "");
21836
+ if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
21837
+ try {
21838
+ const refreshed = await this.tokenProvider.refresh();
21839
+ this.registerAccessToken(refreshed);
21840
+ response = await send2();
21841
+ } catch {
21842
+ const httpErr = await HttpError.fromResponse(
21843
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21844
+ {
21845
+ method: "POST",
21846
+ url: `bifrost:api-catalog:${method} ${path6}`,
21847
+ secretValues: this.secretValues
21848
+ }
21849
+ );
21850
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21851
+ throw advised ?? httpErr;
21852
+ }
21853
+ } else {
21854
+ const httpErr = await HttpError.fromResponse(
21855
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21856
+ {
21857
+ method: "POST",
21858
+ url: `bifrost:api-catalog:${method} ${path6}`,
21859
+ secretValues: this.secretValues
21860
+ }
21861
+ );
21862
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21863
+ throw advised ?? httpErr;
21864
+ }
21865
+ }
21559
21866
  if (!response.ok) {
21560
21867
  const httpErr = await HttpError.fromResponse(response, {
21561
21868
  method: "POST",
@@ -21578,7 +21885,7 @@ var BifrostCatalogClient = class {
21578
21885
  return data;
21579
21886
  }
21580
21887
  async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
21581
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21888
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21582
21889
  method: "POST",
21583
21890
  headers: this.headers(),
21584
21891
  body: JSON.stringify({
@@ -21588,8 +21895,30 @@ var BifrostCatalogClient = class {
21588
21895
  body
21589
21896
  })
21590
21897
  });
21898
+ let response = await send2();
21591
21899
  if (!response.ok) {
21592
21900
  const text = await response.text().catch(() => "");
21901
+ if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
21902
+ try {
21903
+ const refreshed = await this.tokenProvider.refresh();
21904
+ this.registerAccessToken(refreshed);
21905
+ response = await send2();
21906
+ if (response.ok) {
21907
+ const data2 = await response.json();
21908
+ return { ok: true, status: response.status, data: data2, errorText: "" };
21909
+ }
21910
+ const retryText = await response.text().catch(() => "");
21911
+ const advised2 = adviseFromBifrostBody(response.status, retryText, this.adviceContext(operation));
21912
+ const errorText2 = advised2 ? retryText ? `${retryText}
21913
+ ${advised2.message}` : advised2.message : retryText;
21914
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21915
+ } catch {
21916
+ const advised2 = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21917
+ const errorText2 = advised2 ? text ? `${text}
21918
+ ${advised2.message}` : advised2.message : text;
21919
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21920
+ }
21921
+ }
21593
21922
  const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21594
21923
  const errorText = advised ? text ? `${text}
21595
21924
  ${advised.message}` : advised.message : text;
@@ -21688,12 +22017,19 @@ ${advised.message}` : advised.message : text;
21688
22017
  }
21689
22018
  page++;
21690
22019
  }
21691
- const fullName = clusterName ? `${clusterName}/${projectName}` : projectName;
21692
- const exactMatch = allServices.find((s) => s.name === fullName);
21693
- if (exactMatch) return exactMatch.id;
21694
- if (clusterName) return null;
21695
- const suffixMatch = allServices.find((s) => s.name.endsWith(`/${projectName}`));
21696
- return suffixMatch?.id || null;
22020
+ if (clusterName) {
22021
+ const fullName = `${clusterName}/${projectName}`;
22022
+ const exactMatch = allServices.find((s) => s.name === fullName);
22023
+ return exactMatch?.id || null;
22024
+ }
22025
+ const finalSegmentMatch = allServices.find(
22026
+ (s) => getFinalServiceSegment(s.name) === projectName
22027
+ );
22028
+ if (finalSegmentMatch) return finalSegmentMatch.id;
22029
+ const bracketedMatch = allServices.find(
22030
+ (s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
22031
+ );
22032
+ return bracketedMatch?.id || null;
21697
22033
  }
21698
22034
  async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
21699
22035
  const result = await this.akitaProxyRequest(
@@ -21723,6 +22059,14 @@ ${advised.message}` : advised.message : text;
21723
22059
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
21724
22060
  }
21725
22061
  }
22062
+ // PMAK-only by proven exception. A live probe against the observability
22063
+ // application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
22064
+ // applications) showed x-access-token is rejected identically to the x-api-key
22065
+ // control: both return 401 {"message":"Postman User not found"} for a
22066
+ // service-account credential, because the observability service has no
22067
+ // "Postman User" for a service account. The access token offers no improvement
22068
+ // over the API key here, so this route is not migrated to access-token-primary
22069
+ // (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
21726
22070
  async createApplication(workspaceId, systemEnv) {
21727
22071
  const response = await this.fetchFn(
21728
22072
  `${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
@@ -21790,7 +22134,17 @@ function findDiscoveredService(services, projectName, clusterName) {
21790
22134
  const fullName = `${clusterName}/${projectName}`;
21791
22135
  return services.find((s) => s.name === fullName);
21792
22136
  }
21793
- return services.find((s) => s.name.endsWith(`/${projectName}`));
22137
+ const finalSegmentMatch = services.find(
22138
+ (service) => getFinalServiceSegment(service.name) === projectName
22139
+ );
22140
+ if (finalSegmentMatch) return finalSegmentMatch;
22141
+ return services.find(
22142
+ (service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
22143
+ );
22144
+ }
22145
+ function getFinalServiceSegment(serviceName) {
22146
+ const lastSlash = serviceName.lastIndexOf("/");
22147
+ return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
21794
22148
  }
21795
22149
 
21796
22150
  // node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
@@ -22047,7 +22401,7 @@ function resolveActionVersion(explicit) {
22047
22401
  if (explicit) {
22048
22402
  return explicit;
22049
22403
  }
22050
- return "1.0.4" ? "1.0.4" : "unknown";
22404
+ return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
22051
22405
  }
22052
22406
  function telemetryDisabled(env) {
22053
22407
  const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
@@ -22169,6 +22523,18 @@ function createTelemetryContext(options) {
22169
22523
  };
22170
22524
  }
22171
22525
 
22526
+ // src/action-version.ts
22527
+ var import_node_fs = require("node:fs");
22528
+ var import_node_path = require("node:path");
22529
+ function resolveActionVersion2() {
22530
+ try {
22531
+ const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
22532
+ return JSON.parse(raw).version ?? "unknown";
22533
+ } catch {
22534
+ return "unknown";
22535
+ }
22536
+ }
22537
+
22172
22538
  // src/index.ts
22173
22539
  var POLL_TIMEOUT_MIN = 10;
22174
22540
  var POLL_TIMEOUT_MAX = 600;
@@ -22234,8 +22600,12 @@ function resolveInputs(env = process.env) {
22234
22600
  const projectName = get("project-name");
22235
22601
  if (!projectName) throw new Error("project-name is required");
22236
22602
  const postmanAccessToken = get("postman-access-token");
22237
- if (!postmanAccessToken) throw new Error("postman-access-token is required");
22238
22603
  const postmanApiKey = get("postman-api-key");
22604
+ if (!postmanAccessToken && !postmanApiKey) {
22605
+ throw new Error(
22606
+ "postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
22607
+ );
22608
+ }
22239
22609
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
22240
22610
  const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
22241
22611
  if (!workspaceId) {
@@ -22436,50 +22806,81 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
22436
22806
  }
22437
22807
  return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22438
22808
  }
22439
- async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22809
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
22810
+ const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
22440
22811
  await runCredentialPreflight({
22441
22812
  apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22442
22813
  iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22443
22814
  pmak,
22444
- postmanAccessToken: inputs.postmanAccessToken,
22815
+ postmanAccessToken: accessToken,
22445
22816
  explicitTeamId: inputs.postmanTeamId || void 0,
22446
22817
  mode: inputs.credentialPreflight,
22447
- mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22818
+ mask: createSecretMasker([inputs.postmanApiKey, accessToken]),
22448
22819
  log: reporter,
22449
22820
  fetchImpl
22450
22821
  });
22451
22822
  }
22823
+ function createInsightsTokenProvider(inputs, reporter, apiKey = inputs.postmanApiKey) {
22824
+ return new AccessTokenProvider({
22825
+ accessToken: inputs.postmanAccessToken,
22826
+ apiKey,
22827
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22828
+ onToken: (token) => reporter.setSecret(token)
22829
+ });
22830
+ }
22831
+ function createInsightsBifrostClient(inputs, tokenProvider, teamId, apiKey) {
22832
+ return new BifrostCatalogClient({
22833
+ tokenProvider,
22834
+ accessToken: tokenProvider.current(),
22835
+ teamId,
22836
+ apiKey,
22837
+ bifrostBaseUrl: inputs.postmanBifrostBase,
22838
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
22839
+ observabilityEnv: inputs.postmanObservabilityEnv
22840
+ });
22841
+ }
22452
22842
  async function runAction() {
22453
22843
  const inputs = resolveInputs();
22454
22844
  const planned = createPlannedOutputs(inputs);
22455
22845
  for (const [key, value] of Object.entries(planned)) {
22456
22846
  setOutput(key, value);
22457
22847
  }
22458
- setSecret(inputs.postmanAccessToken);
22848
+ const mintHolder = {
22849
+ postmanAccessToken: inputs.postmanAccessToken,
22850
+ postmanApiKey: inputs.postmanApiKey,
22851
+ postmanApiBase: inputs.postmanApiBase
22852
+ };
22853
+ await mintAccessTokenIfNeeded(mintHolder, core_exports, (secret) => setSecret(secret));
22854
+ inputs.postmanAccessToken = mintHolder.postmanAccessToken;
22855
+ if (inputs.postmanAccessToken) setSecret(inputs.postmanAccessToken);
22459
22856
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
22460
22857
  if (inputs.githubToken) setSecret(inputs.githubToken);
22461
- const preliminaryClient = new BifrostCatalogClient({
22462
- accessToken: inputs.postmanAccessToken,
22463
- teamId: inputs.postmanTeamId,
22464
- apiKey: inputs.postmanApiKey,
22465
- bifrostBaseUrl: inputs.postmanBifrostBase,
22466
- observabilityBaseUrl: inputs.postmanObservabilityBase,
22467
- observabilityEnv: inputs.postmanObservabilityEnv
22468
- });
22858
+ const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
22859
+ const preliminaryClient = createInsightsBifrostClient(
22860
+ inputs,
22861
+ tokenProvider,
22862
+ inputs.postmanTeamId,
22863
+ inputs.postmanApiKey
22864
+ );
22469
22865
  const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22470
- const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
22866
+ const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
22867
+ accessToken: tokenProvider.current(),
22868
+ apiKey,
22869
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22870
+ onToken: (token) => setSecret(token)
22871
+ }) : tokenProvider;
22872
+ const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
22471
22873
  telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
22472
22874
  let result;
22473
22875
  try {
22474
- await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
22475
- const client = new BifrostCatalogClient({
22476
- accessToken: inputs.postmanAccessToken,
22477
- teamId,
22478
- apiKey,
22479
- bifrostBaseUrl: inputs.postmanBifrostBase,
22480
- observabilityBaseUrl: inputs.postmanObservabilityBase,
22481
- observabilityEnv: inputs.postmanObservabilityEnv
22482
- });
22876
+ await runCredentialPreflightForInputs(
22877
+ inputs,
22878
+ pmakIdentity,
22879
+ core_exports,
22880
+ void 0,
22881
+ activeTokenProvider.current()
22882
+ );
22883
+ const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
22483
22884
  result = await runOnboarding(inputs, client, sleep, core_exports);
22484
22885
  } catch (error2) {
22485
22886
  const message = error2 instanceof Error ? error2.message : String(error2);