@postman-cse/onboarding-insights 1.0.4 → 2.0.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/README.md CHANGED
@@ -16,6 +16,8 @@ Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman
16
16
 
17
17
  This action does **not** deploy the Insights agent, create workspaces, create environments, upload OpenAPI specs, or sync repo artifacts. It only links a service that Insights has already discovered.
18
18
 
19
+ > **Credential requirement for the Insights linking steps.** The service discovery and git-link calls run on Postman's `api-catalog` service and accept a service-account access token. The Insights acknowledgment and application-binding steps run on the `akita` (Insights) service, which authenticates against a **Postman user identity** and answers `401 "Postman User not found"` for a service-account token. To complete the full link, supply a credential pair that carries a Postman user identity (a user's access token and user PMAK). A service-account-only pair from `postman-resolve-service-token-action` is sufficient for discovery and git linking but cannot finish the Insights acknowledgment.
20
+
19
21
  ## Which action should I use?
20
22
 
21
23
  | Need | Action |
@@ -40,12 +42,12 @@ jobs:
40
42
  # ... deploy your service to Kubernetes ...
41
43
 
42
44
  - id: postman_token
43
- uses: postman-cs/postman-resolve-service-token-action@v1
45
+ uses: postman-cs/postman-resolve-service-token-action@v2
44
46
  with:
45
47
  postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
46
48
  postman-region: us
47
49
 
48
- - uses: postman-cs/postman-insights-onboarding-action@v1
50
+ - uses: postman-cs/postman-insights-onboarding-action@v2
49
51
  with:
50
52
  project-name: core-payments
51
53
  workspace-id: ${{ vars.POSTMAN_WORKSPACE_ID }}
@@ -70,12 +72,12 @@ jobs:
70
72
  # ... deploy your service to Kubernetes ...
71
73
 
72
74
  - id: postman_token
73
- uses: postman-cs/postman-resolve-service-token-action@v1
75
+ uses: postman-cs/postman-resolve-service-token-action@v2
74
76
  with:
75
77
  postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
76
78
  postman-region: us
77
79
 
78
- - uses: postman-cs/postman-insights-onboarding-action@v1
80
+ - uses: postman-cs/postman-insights-onboarding-action@v2
79
81
  with:
80
82
  project-name: core-payments
81
83
  workspace-id: ${{ vars.POSTMAN_WORKSPACE_ID }}
@@ -99,12 +101,12 @@ jobs:
99
101
  - uses: actions/checkout@v5
100
102
 
101
103
  - id: postman_token
102
- uses: postman-cs/postman-resolve-service-token-action@v1
104
+ uses: postman-cs/postman-resolve-service-token-action@v2
103
105
  with:
104
106
  postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
105
107
  postman-region: us
106
108
 
107
- - uses: postman-cs/postman-bootstrap-action@v1
109
+ - uses: postman-cs/postman-bootstrap-action@v2
108
110
  id: bootstrap
109
111
  with:
110
112
  project-name: core-payments
@@ -115,7 +117,7 @@ jobs:
115
117
 
116
118
  # ... deploy service to Kubernetes ...
117
119
 
118
- - uses: postman-cs/postman-repo-sync-action@v1
120
+ - uses: postman-cs/postman-repo-sync-action@v2
119
121
  id: sync
120
122
  with:
121
123
  project-name: core-payments
@@ -129,7 +131,7 @@ jobs:
129
131
  postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
130
132
  postman-access-token: ${{ steps.postman_token.outputs.token }}
131
133
 
132
- - uses: postman-cs/postman-insights-onboarding-action@v1
134
+ - uses: postman-cs/postman-insights-onboarding-action@v2
133
135
  with:
134
136
  project-name: core-payments
135
137
  workspace-id: ${{ steps.bootstrap.outputs.workspace-id }}
@@ -147,7 +149,7 @@ jobs:
147
149
  The Insights agent takes time to discover services after pods start. The action polls the [API Catalog](https://learning.postman.com/docs/api-catalog/connect/insights/) discovered-services list until the service appears or the timeout is reached. For services that take longer to appear (cold cluster, large pod startup time), raise the timeout:
148
150
 
149
151
  ```yaml
150
- - uses: postman-cs/postman-insights-onboarding-action@v1
152
+ - uses: postman-cs/postman-insights-onboarding-action@v2
151
153
  with:
152
154
  project-name: core-payments
153
155
  workspace-id: ${{ vars.POSTMAN_WORKSPACE_ID }}
@@ -165,7 +167,7 @@ The Insights agent takes time to discover services after pods start. The action
165
167
  Before any onboarding write, the action can verify that `postman-api-key` and `postman-access-token` resolve to the same parent organization. The default `warn` mode logs a note and continues. Set `credential-preflight` to `enforce` to fail fast on mismatched credentials:
166
168
 
167
169
  ```yaml
168
- - uses: postman-cs/postman-insights-onboarding-action@v1
170
+ - uses: postman-cs/postman-insights-onboarding-action@v2
169
171
  with:
170
172
  project-name: core-payments
171
173
  workspace-id: ${{ vars.POSTMAN_WORKSPACE_ID }}
@@ -201,13 +203,13 @@ See [CLI Usage](docs/cli.md) for provider auto-detection, output formats, and Gi
201
203
  <!-- inputs-table:start -->
202
204
  | Name | Description | Required | Default |
203
205
  | --- | --- | --- | --- |
204
- | `project-name` | Service name or spec ID to match against discovered service names | Yes | |
206
+ | `project-name` | Service name or Jira/Xray project key to match against the final discovered-service segment | Yes | |
205
207
  | `workspace-id` | Postman workspace ID to link the discovered service to | Yes | |
206
208
  | `environment-id` | Postman environment UID for the onboarding association | Yes | |
207
209
  | `system-environment-id` | Postman system environment UUID for service-level Insights acknowledgment | No | |
208
- | `cluster-name` | Insights cluster name. Matches {cluster}/{project-name} in discovered services | No | |
210
+ | `cluster-name` | Insights cluster name. When set, matches {cluster-name}/{project-name} exactly in discovered services | No | |
209
211
  | `repo-url` | Repository URL for Git onboarding. Auto-detected from CI context when omitted. | No | |
210
- | `postman-access-token` | Service-account Postman access token for integration API calls. Prefer minting it with postman-resolve-service-token-action. | Yes | |
212
+ | `postman-access-token` | Postman access token (x-access-token) for the Bifrost linking calls. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there. | Yes | |
211
213
  | `postman-team-id` | Explicit Postman team ID for org-mode integration request headers. When omitted, x-entity-team-id is not sent. | No | |
212
214
  | `github-token` | Optional GitHub token passed as git_api_key when repository auth is required by onboarding/git | No | |
213
215
  | `postman-api-key` | Service-account Postman API key (PMAK-*) for the application binding call. Auto-created from postman-access-token when omitted or invalid after a clear 401/403 validation failure. | No | |
package/action.yml CHANGED
@@ -6,7 +6,7 @@ branding:
6
6
  color: orange
7
7
  inputs:
8
8
  project-name:
9
- description: 'Service name or spec ID to match against discovered service names'
9
+ description: 'Service name or Jira/Xray project key to match against the final discovered-service segment'
10
10
  required: true
11
11
  workspace-id:
12
12
  description: 'Postman workspace ID to link the discovered service to'
@@ -18,13 +18,13 @@ inputs:
18
18
  description: 'Postman system environment UUID for service-level Insights acknowledgment'
19
19
  required: false
20
20
  cluster-name:
21
- description: 'Insights cluster name. Matches {cluster}/{project-name} in discovered services'
21
+ description: 'Insights cluster name. When set, matches {cluster-name}/{project-name} exactly in discovered services'
22
22
  required: false
23
23
  repo-url:
24
24
  description: 'Repository URL for Git onboarding. Auto-detected from CI context when omitted.'
25
25
  required: false
26
26
  postman-access-token:
27
- description: 'Service-account Postman access token for integration API calls. Prefer minting it with postman-resolve-service-token-action.'
27
+ description: 'Postman access token (x-access-token) for the Bifrost linking calls. The api-catalog discovery and git-link steps accept a service-account token (mint it with postman-resolve-service-token-action); the Insights (akita) acknowledgment and application-binding steps require a token carrying a Postman user identity — a service-account token answers 401 "Postman User not found" there.'
28
28
  required: true
29
29
  postman-team-id:
30
30
  description: 'Explicit Postman team ID for org-mode integration request headers. When omitted, x-entity-team-id is not sent.'
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
  );
@@ -21481,6 +21481,117 @@ function resolvePostmanEndpointProfile(stack, region = "us") {
21481
21481
  return profile;
21482
21482
  }
21483
21483
 
21484
+ // src/lib/postman/token-provider.ts
21485
+ var MintError = class extends Error {
21486
+ permanent;
21487
+ constructor(message, permanent) {
21488
+ super(message);
21489
+ this.name = "MintError";
21490
+ this.permanent = permanent;
21491
+ }
21492
+ };
21493
+ function extractAccessToken(payload) {
21494
+ if (!payload || typeof payload !== "object") return void 0;
21495
+ const record = payload;
21496
+ const direct = record.access_token;
21497
+ if (typeof direct === "string" && direct.trim()) return direct.trim();
21498
+ const session = record.session;
21499
+ if (session && typeof session === "object") {
21500
+ const token = session.token;
21501
+ if (typeof token === "string" && token.trim()) return token.trim();
21502
+ }
21503
+ return void 0;
21504
+ }
21505
+ var AccessTokenProvider = class {
21506
+ token;
21507
+ apiKey;
21508
+ apiBaseUrl;
21509
+ fetchImpl;
21510
+ maxAttempts;
21511
+ onToken;
21512
+ sleep;
21513
+ inflight;
21514
+ constructor(options) {
21515
+ this.token = String(options.accessToken || "").trim();
21516
+ this.apiKey = String(options.apiKey || "").trim();
21517
+ this.apiBaseUrl = String(
21518
+ options.apiBaseUrl || POSTMAN_ENDPOINT_PROFILES.prod.apiBaseUrl
21519
+ ).replace(/\/+$/, "");
21520
+ this.fetchImpl = options.fetchImpl ?? fetch;
21521
+ this.maxAttempts = Math.max(1, options.maxAttempts ?? 2);
21522
+ this.onToken = options.onToken;
21523
+ this.sleep = options.sleep;
21524
+ }
21525
+ current() {
21526
+ return this.token;
21527
+ }
21528
+ /** True when a PMAK is present, so an expired token can be re-minted. */
21529
+ canRefresh() {
21530
+ return Boolean(this.apiKey);
21531
+ }
21532
+ refresh() {
21533
+ this.inflight ??= this.mintWithRetry().finally(() => {
21534
+ this.inflight = void 0;
21535
+ });
21536
+ return this.inflight;
21537
+ }
21538
+ async mintWithRetry() {
21539
+ if (!this.apiKey) {
21540
+ throw new Error(
21541
+ "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."
21542
+ );
21543
+ }
21544
+ const token = await retry(() => this.mintOnce(), {
21545
+ maxAttempts: this.maxAttempts,
21546
+ delayMs: 1e3,
21547
+ backoffMultiplier: 2,
21548
+ ...this.sleep ? { sleep: this.sleep } : {},
21549
+ shouldRetry: (error2) => !(error2 instanceof MintError && error2.permanent)
21550
+ });
21551
+ this.token = token;
21552
+ this.onToken?.(token);
21553
+ return token;
21554
+ }
21555
+ async mintOnce() {
21556
+ const response = await this.fetchImpl(`${this.apiBaseUrl}/service-account-tokens`, {
21557
+ method: "POST",
21558
+ headers: {
21559
+ "Content-Type": "application/json",
21560
+ "x-api-key": this.apiKey
21561
+ },
21562
+ body: JSON.stringify({ apiKey: this.apiKey })
21563
+ });
21564
+ const body = await response.text().catch(() => "");
21565
+ if (!response.ok) {
21566
+ const status = response.status;
21567
+ if (status === 401 || status === 403) {
21568
+ throw new MintError(
21569
+ `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.`,
21570
+ true
21571
+ );
21572
+ }
21573
+ if (status === 400 && body.toLowerCase().includes("service accounts not enabled")) {
21574
+ throw new MintError(
21575
+ "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.",
21576
+ true
21577
+ );
21578
+ }
21579
+ throw new MintError(`postman: re-mint failed (service-account-tokens HTTP ${status}).`, false);
21580
+ }
21581
+ let parsed;
21582
+ try {
21583
+ parsed = JSON.parse(body);
21584
+ } catch {
21585
+ parsed = void 0;
21586
+ }
21587
+ const token = extractAccessToken(parsed);
21588
+ if (!token) {
21589
+ throw new MintError("postman: re-mint succeeded but no access token was returned.", false);
21590
+ }
21591
+ return token;
21592
+ }
21593
+ };
21594
+
21484
21595
  // src/lib/bifrost-client.ts
21485
21596
  var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
21486
21597
  var BIFROST_PROXY_PATH = "/ws/proxy";
@@ -21488,8 +21599,11 @@ var DEFAULT_OBSERVABILITY_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.observabilit
21488
21599
  var DEFAULT_OBSERVABILITY_ENV = POSTMAN_ENDPOINT_PROFILES.prod.observabilityEnv;
21489
21600
  var MAX_DISCOVERED_SERVICE_PAGES = 100;
21490
21601
  var MAX_PROVIDER_SERVICE_PAGES = 100;
21602
+ function isExpiredAuthError(status, body) {
21603
+ return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
21604
+ }
21491
21605
  var BifrostCatalogClient = class {
21492
- accessToken;
21606
+ tokenProvider;
21493
21607
  teamId;
21494
21608
  apiKey;
21495
21609
  fetchFn;
@@ -21498,11 +21612,14 @@ var BifrostCatalogClient = class {
21498
21612
  observabilityBaseUrl;
21499
21613
  observabilityEnv;
21500
21614
  constructor(options) {
21501
- this.accessToken = options.accessToken;
21615
+ this.tokenProvider = options.tokenProvider ?? new AccessTokenProvider({
21616
+ accessToken: options.accessToken,
21617
+ apiKey: options.apiKey
21618
+ });
21502
21619
  this.teamId = options.teamId;
21503
21620
  this.apiKey = options.apiKey;
21504
21621
  this.fetchFn = options.fetchFn ?? globalThis.fetch;
21505
- this.secretValues = [options.accessToken, options.apiKey].filter(Boolean);
21622
+ this.secretValues = [this.tokenProvider.current(), options.apiKey].filter(Boolean);
21506
21623
  const base = (options.bifrostBaseUrl || DEFAULT_BIFROST_BASE_URL).replace(/\/+$/, "");
21507
21624
  this.bifrostProxyUrl = `${base}${BIFROST_PROXY_PATH}`;
21508
21625
  this.observabilityBaseUrl = (options.observabilityBaseUrl || DEFAULT_OBSERVABILITY_BASE_URL).replace(/\/+$/, "");
@@ -21514,6 +21631,11 @@ var BifrostCatalogClient = class {
21514
21631
  this.secretValues.push(apiKey);
21515
21632
  }
21516
21633
  }
21634
+ registerAccessToken(token) {
21635
+ if (token && !this.secretValues.includes(token)) {
21636
+ this.secretValues.push(token);
21637
+ }
21638
+ }
21517
21639
  /**
21518
21640
  * Build Bifrost proxy headers.
21519
21641
  * x-entity-team-id is ONLY included when teamId is present (org-mode tokens).
@@ -21521,7 +21643,7 @@ var BifrostCatalogClient = class {
21521
21643
  */
21522
21644
  headers() {
21523
21645
  const h = {
21524
- "x-access-token": this.accessToken,
21646
+ "x-access-token": this.tokenProvider.current(),
21525
21647
  "Content-Type": "application/json"
21526
21648
  };
21527
21649
  if (this.teamId) {
@@ -21537,7 +21659,7 @@ var BifrostCatalogClient = class {
21537
21659
  const session = getMemoizedSessionIdentity();
21538
21660
  return {
21539
21661
  operation,
21540
- hasAccessToken: Boolean(this.accessToken),
21662
+ hasAccessToken: Boolean(this.tokenProvider.current()),
21541
21663
  sessionTeamId: session?.teamId,
21542
21664
  sessionRoles: session?.roles,
21543
21665
  sessionConsumerType: session?.consumerType,
@@ -21546,7 +21668,7 @@ var BifrostCatalogClient = class {
21546
21668
  };
21547
21669
  }
21548
21670
  async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
21549
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21671
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21550
21672
  method: "POST",
21551
21673
  headers: this.headers(),
21552
21674
  body: JSON.stringify({
@@ -21556,6 +21678,39 @@ var BifrostCatalogClient = class {
21556
21678
  body
21557
21679
  })
21558
21680
  });
21681
+ let response = await send2();
21682
+ if (!response.ok) {
21683
+ const bodyText = await response.text().catch(() => "");
21684
+ if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
21685
+ try {
21686
+ const refreshed = await this.tokenProvider.refresh();
21687
+ this.registerAccessToken(refreshed);
21688
+ response = await send2();
21689
+ } catch {
21690
+ const httpErr = await HttpError.fromResponse(
21691
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21692
+ {
21693
+ method: "POST",
21694
+ url: `bifrost:api-catalog:${method} ${path6}`,
21695
+ secretValues: this.secretValues
21696
+ }
21697
+ );
21698
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21699
+ throw advised ?? httpErr;
21700
+ }
21701
+ } else {
21702
+ const httpErr = await HttpError.fromResponse(
21703
+ new Response(bodyText, { status: response.status, headers: response.headers }),
21704
+ {
21705
+ method: "POST",
21706
+ url: `bifrost:api-catalog:${method} ${path6}`,
21707
+ secretValues: this.secretValues
21708
+ }
21709
+ );
21710
+ const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
21711
+ throw advised ?? httpErr;
21712
+ }
21713
+ }
21559
21714
  if (!response.ok) {
21560
21715
  const httpErr = await HttpError.fromResponse(response, {
21561
21716
  method: "POST",
@@ -21578,7 +21733,7 @@ var BifrostCatalogClient = class {
21578
21733
  return data;
21579
21734
  }
21580
21735
  async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
21581
- const response = await this.fetchFn(this.bifrostProxyUrl, {
21736
+ const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
21582
21737
  method: "POST",
21583
21738
  headers: this.headers(),
21584
21739
  body: JSON.stringify({
@@ -21588,8 +21743,30 @@ var BifrostCatalogClient = class {
21588
21743
  body
21589
21744
  })
21590
21745
  });
21746
+ let response = await send2();
21591
21747
  if (!response.ok) {
21592
21748
  const text = await response.text().catch(() => "");
21749
+ if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
21750
+ try {
21751
+ const refreshed = await this.tokenProvider.refresh();
21752
+ this.registerAccessToken(refreshed);
21753
+ response = await send2();
21754
+ if (response.ok) {
21755
+ const data2 = await response.json();
21756
+ return { ok: true, status: response.status, data: data2, errorText: "" };
21757
+ }
21758
+ const retryText = await response.text().catch(() => "");
21759
+ const advised2 = adviseFromBifrostBody(response.status, retryText, this.adviceContext(operation));
21760
+ const errorText2 = advised2 ? retryText ? `${retryText}
21761
+ ${advised2.message}` : advised2.message : retryText;
21762
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21763
+ } catch {
21764
+ const advised2 = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21765
+ const errorText2 = advised2 ? text ? `${text}
21766
+ ${advised2.message}` : advised2.message : text;
21767
+ return { ok: false, status: response.status, data: null, errorText: errorText2 };
21768
+ }
21769
+ }
21593
21770
  const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
21594
21771
  const errorText = advised ? text ? `${text}
21595
21772
  ${advised.message}` : advised.message : text;
@@ -21688,12 +21865,19 @@ ${advised.message}` : advised.message : text;
21688
21865
  }
21689
21866
  page++;
21690
21867
  }
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;
21868
+ if (clusterName) {
21869
+ const fullName = `${clusterName}/${projectName}`;
21870
+ const exactMatch = allServices.find((s) => s.name === fullName);
21871
+ return exactMatch?.id || null;
21872
+ }
21873
+ const finalSegmentMatch = allServices.find(
21874
+ (s) => getFinalServiceSegment(s.name) === projectName
21875
+ );
21876
+ if (finalSegmentMatch) return finalSegmentMatch.id;
21877
+ const bracketedMatch = allServices.find(
21878
+ (s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
21879
+ );
21880
+ return bracketedMatch?.id || null;
21697
21881
  }
21698
21882
  async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
21699
21883
  const result = await this.akitaProxyRequest(
@@ -21723,6 +21907,14 @@ ${advised.message}` : advised.message : text;
21723
21907
  throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
21724
21908
  }
21725
21909
  }
21910
+ // PMAK-only by proven exception. A live probe against the observability
21911
+ // application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
21912
+ // applications) showed x-access-token is rejected identically to the x-api-key
21913
+ // control: both return 401 {"message":"Postman User not found"} for a
21914
+ // service-account credential, because the observability service has no
21915
+ // "Postman User" for a service account. The access token offers no improvement
21916
+ // over the API key here, so this route is not migrated to access-token-primary
21917
+ // (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
21726
21918
  async createApplication(workspaceId, systemEnv) {
21727
21919
  const response = await this.fetchFn(
21728
21920
  `${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
@@ -21790,7 +21982,17 @@ function findDiscoveredService(services, projectName, clusterName) {
21790
21982
  const fullName = `${clusterName}/${projectName}`;
21791
21983
  return services.find((s) => s.name === fullName);
21792
21984
  }
21793
- return services.find((s) => s.name.endsWith(`/${projectName}`));
21985
+ const finalSegmentMatch = services.find(
21986
+ (service) => getFinalServiceSegment(service.name) === projectName
21987
+ );
21988
+ if (finalSegmentMatch) return finalSegmentMatch;
21989
+ return services.find(
21990
+ (service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
21991
+ );
21992
+ }
21993
+ function getFinalServiceSegment(serviceName) {
21994
+ const lastSlash = serviceName.lastIndexOf("/");
21995
+ return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
21794
21996
  }
21795
21997
 
21796
21998
  // node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
@@ -22047,7 +22249,7 @@ function resolveActionVersion(explicit) {
22047
22249
  if (explicit) {
22048
22250
  return explicit;
22049
22251
  }
22050
- return "1.0.4" ? "1.0.4" : "unknown";
22252
+ return "2.0.0" ? "2.0.0" : "unknown";
22051
22253
  }
22052
22254
  function telemetryDisabled(env) {
22053
22255
  const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
@@ -22436,19 +22638,39 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
22436
22638
  }
22437
22639
  return { apiKey, teamId: resolvedTeamId, pmakIdentity };
22438
22640
  }
22439
- async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
22641
+ async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
22642
+ const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
22440
22643
  await runCredentialPreflight({
22441
22644
  apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22442
22645
  iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
22443
22646
  pmak,
22444
- postmanAccessToken: inputs.postmanAccessToken,
22647
+ postmanAccessToken: accessToken,
22445
22648
  explicitTeamId: inputs.postmanTeamId || void 0,
22446
22649
  mode: inputs.credentialPreflight,
22447
- mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
22650
+ mask: createSecretMasker([inputs.postmanApiKey, accessToken]),
22448
22651
  log: reporter,
22449
22652
  fetchImpl
22450
22653
  });
22451
22654
  }
22655
+ function createInsightsTokenProvider(inputs, reporter, apiKey = inputs.postmanApiKey) {
22656
+ return new AccessTokenProvider({
22657
+ accessToken: inputs.postmanAccessToken,
22658
+ apiKey,
22659
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22660
+ onToken: (token) => reporter.setSecret(token)
22661
+ });
22662
+ }
22663
+ function createInsightsBifrostClient(inputs, tokenProvider, teamId, apiKey) {
22664
+ return new BifrostCatalogClient({
22665
+ tokenProvider,
22666
+ accessToken: tokenProvider.current(),
22667
+ teamId,
22668
+ apiKey,
22669
+ bifrostBaseUrl: inputs.postmanBifrostBase,
22670
+ observabilityBaseUrl: inputs.postmanObservabilityBase,
22671
+ observabilityEnv: inputs.postmanObservabilityEnv
22672
+ });
22673
+ }
22452
22674
  async function runAction() {
22453
22675
  const inputs = resolveInputs();
22454
22676
  const planned = createPlannedOutputs(inputs);
@@ -22458,28 +22680,32 @@ async function runAction() {
22458
22680
  setSecret(inputs.postmanAccessToken);
22459
22681
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
22460
22682
  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
- });
22683
+ const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
22684
+ const preliminaryClient = createInsightsBifrostClient(
22685
+ inputs,
22686
+ tokenProvider,
22687
+ inputs.postmanTeamId,
22688
+ inputs.postmanApiKey
22689
+ );
22469
22690
  const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
22691
+ const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
22692
+ accessToken: tokenProvider.current(),
22693
+ apiKey,
22694
+ apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22695
+ onToken: (token) => setSecret(token)
22696
+ }) : tokenProvider;
22470
22697
  const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
22471
22698
  telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
22472
22699
  let result;
22473
22700
  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
- });
22701
+ await runCredentialPreflightForInputs(
22702
+ inputs,
22703
+ pmakIdentity,
22704
+ core_exports,
22705
+ void 0,
22706
+ activeTokenProvider.current()
22707
+ );
22708
+ const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
22483
22709
  result = await runOnboarding(inputs, client, sleep, core_exports);
22484
22710
  } catch (error2) {
22485
22711
  const message = error2 instanceof Error ? error2.message : String(error2);