@postman-cse/onboarding-insights 2.0.0 → 2.1.1

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
@@ -4,7 +4,13 @@
4
4
 
5
5
  Links [Postman Insights](https://learning.postman.com/docs/insights/overview/) discovered services to [API Catalog](https://learning.postman.com/docs/api-catalog/overview/) workspaces and git repositories after deployment, so every service the Insights agent finds lands in your catalog with a collection, a repo link, and live telemetry.
6
6
 
7
- Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman-api-onboarding-action).
7
+ Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman-api-onboarding-action); the composite action's README has the full [action-picker table](https://github.com/postman-cs/postman-api-onboarding-action#which-action-should-i-use).
8
+
9
+ - [Prerequisites](#prerequisites)
10
+ - [Usage](#usage)
11
+ - [Examples](#examples)
12
+ - [Inputs](#inputs) / [Outputs](#outputs)
13
+ - [How it works](#how-it-works)
8
14
 
9
15
  ## Prerequisites
10
16
 
@@ -16,19 +22,7 @@ Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman
16
22
 
17
23
  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
24
 
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
-
21
- ## Which action should I use?
22
-
23
- | Need | Action |
24
- | --- | --- |
25
- | Mint a service-account access token and team ID | [Postman Onboarding: Service Token](https://github.com/postman-cs/postman-resolve-service-token-action) |
26
- | Run the full workspace, collection, repo sync, and optional Insights pipeline | [Postman API Onboarding](https://github.com/postman-cs/postman-api-onboarding-action) |
27
- | Create a workspace, upload an OpenAPI spec, and generate collections | [Postman Onboarding: Workspace Bootstrap](https://github.com/postman-cs/postman-bootstrap-action) |
28
- | Sync Postman artifacts, mocks, monitors, and CI files back to the repo | [Postman Onboarding: Repo Sync](https://github.com/postman-cs/postman-repo-sync-action) |
29
- | Link an already discovered Insights service to an existing workspace | [Postman Onboarding: Insights Linking](https://github.com/postman-cs/postman-insights-onboarding-action) |
30
- | Discover OpenAPI specs from AWS services | [Postman Onboarding: AWS Spec Discovery](https://github.com/postman-cs/postman-aws-spec-discovery-action) |
31
- | Apply a curated smoke flow to an existing Smoke collection | [Postman Onboarding: Smoke Flow](https://github.com/postman-cs/postman-smoke-flow-action) |
25
+ > **Credential requirement for the Insights linking steps.** The service discovery and git-link calls run on the `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.
32
26
 
33
27
  ## Usage
34
28
 
@@ -209,7 +203,7 @@ See [CLI Usage](docs/cli.md) for provider auto-detection, output formats, and Gi
209
203
  | `system-environment-id` | Postman system environment UUID for service-level Insights acknowledgment | No | |
210
204
  | `cluster-name` | Insights cluster name. When set, matches {cluster-name}/{project-name} exactly in discovered services | No | |
211
205
  | `repo-url` | Repository URL for Git onboarding. Auto-detected from CI context when omitted. | No | |
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 | |
206
+ | `postman-access-token` | Postman access token (x-access-token) for the Bifrost linking calls. When omitted, the action mints a service-account token from postman-api-key. 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. | No | |
213
207
  | `postman-team-id` | Explicit Postman team ID for org-mode integration request headers. When omitted, x-entity-team-id is not sent. | No | |
214
208
  | `github-token` | Optional GitHub token passed as git_api_key when repository auth is required by onboarding/git | No | |
215
209
  | `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 | |
@@ -238,11 +232,19 @@ Failures set `status=error` before the action exits.
238
232
 
239
233
  ## How it works
240
234
 
235
+ ```mermaid
236
+ flowchart LR
237
+ D["Discovery poll<br/>discovered-services list"] --> P["Catalog prep<br/>prepare-collection"]
238
+ P --> G["Git link<br/>service to repository"]
239
+ G --> A["Acknowledgment<br/>onboard + workspace ack"]
240
+ A --> B["Binding<br/>application binding +<br/>team verification token"]
241
+ ```
242
+
241
243
  **Discovery poll.** The action polls the API Catalog discovered-services list at the configured interval until a service matching `{cluster-name}/{project-name}` appears (suffix matching when `cluster-name` is omitted) or the timeout is reached.
242
244
 
243
245
  **Catalog prep.** It then calls `POST /api/v1/onboarding/prepare-collection` to create the API Catalog collection entry for the discovered service in your workspace.
244
246
 
245
- **Git link.** `POST /api/v1/onboarding/git` with `via_integrations: false` links the service to the GitHub repository (`repo-url` input, auto-detected from CI context when omitted; `github-token` is passed as `git_api_key` only when the endpoint requires repository auth). Postman's [connect code](https://learning.postman.com/docs/api-catalog/connect/code/) docs cover the product workflow this binding supports.
247
+ **Git link.** `POST /api/v1/onboarding/git` with `via_integrations: false` links the service to the GitHub repository (`repo-url` input, auto-detected from CI context when omitted; `github-token` is passed as `git_api_key` only when the endpoint requires repository auth). The [connect code](https://learning.postman.com/docs/api-catalog/connect/code/) docs cover the product workflow this binding supports.
246
248
 
247
249
  **Acknowledgment.** The action resolves the `svc_*` Akita service ID, marks the service as managed (`POST /v2/api-catalog/services/onboard`), and acknowledges the workspace (`POST /v2/workspaces/{id}/onboarding/acknowledge`) to activate the Insights project.
248
250
 
@@ -252,71 +254,14 @@ For local builds, contract smoke monitoring, and release channels, see [Developm
252
254
 
253
255
  ## Resources
254
256
 
255
- ### The suite
256
-
257
- | Action | Role |
258
- | --- | --- |
259
- | [Postman API Onboarding](https://github.com/postman-cs/postman-api-onboarding-action) | Entry point: chains workspace bootstrap, repo sync, and optional Insights linking |
260
- | [Postman Onboarding: Service Token](https://github.com/postman-cs/postman-resolve-service-token-action) | Mints the service-account access token and team ID |
261
- | [Postman Onboarding: AWS Spec Discovery](https://github.com/postman-cs/postman-aws-spec-discovery-action) | Discovers and exports API specs from AWS services |
262
- | [Postman Onboarding: Workspace Bootstrap](https://github.com/postman-cs/postman-bootstrap-action) | Creates the workspace, uploads the spec, generates collections |
263
- | [Postman Onboarding: Smoke Flow](https://github.com/postman-cs/postman-smoke-flow-action) | Applies a curated flow.yaml to the Smoke collection |
264
- | [Postman Onboarding: Repo Sync](https://github.com/postman-cs/postman-repo-sync-action) | Exports artifacts into the repo and wires CI, mocks, and monitors |
265
- | [Postman Onboarding: Insights Linking](https://github.com/postman-cs/postman-insights-onboarding-action) | Links Insights discovered services to the workspace |
266
-
267
- - [postman-resolve-service-token-action](https://github.com/postman-cs/postman-resolve-service-token-action): mints a service-account access token and team ID.
268
- - [postman-api-onboarding-action](https://github.com/postman-cs/postman-api-onboarding-action): composite action that orchestrates the onboarding pipeline.
269
- - [postman-bootstrap-action](https://github.com/postman-cs/postman-bootstrap-action): workspace provisioning, spec upload, and collection generation.
270
- - [postman-smoke-flow-action](https://github.com/postman-cs/postman-smoke-flow-action): applies a curated flow.yaml to the canonical Smoke collection.
271
- - [postman-repo-sync-action](https://github.com/postman-cs/postman-repo-sync-action): artifact sync, environments, mocks, monitors, and CI templates.
272
- - [postman-aws-spec-discovery-action](https://github.com/postman-cs/postman-aws-spec-discovery-action): discovers API specs from AWS.
273
257
  - npm package: [@postman-cse/onboarding-insights](https://www.npmjs.com/package/@postman-cse/onboarding-insights)
258
+ - Docs in this repo: [Credentials and Identity](docs/credentials.md), [Development and Operations](docs/development.md), [CLI usage](docs/cli.md)
259
+ - Marketplace docs: [Support](SUPPORT.md), [Security Policy](SECURITY.md), [Release Policy](RELEASE_POLICY.md)
274
260
  - Postman Learning Center: [Insights overview](https://learning.postman.com/docs/insights/overview/), [connect Insights](https://learning.postman.com/docs/api-catalog/connect/insights/), [Insights API Catalog agent reference](https://learning.postman.com/docs/insights/reference/agent/api-catalog/), [API Catalog overview](https://learning.postman.com/docs/api-catalog/overview/), [connect code](https://learning.postman.com/docs/api-catalog/connect/code/)
275
- - [Credentials and Identity](docs/credentials.md)
276
- - [Development and Operations](docs/development.md)
277
- - [Support](SUPPORT.md)
278
- - [Security Policy](SECURITY.md)
279
- - [Release Policy](RELEASE_POLICY.md)
280
-
281
261
 
282
262
  ## Telemetry
283
263
 
284
- This action sends a single non-identifying usage event when a run completes, so the
285
- Postman team can measure adoption across CI systems. The event contains the
286
- action name and version, your Postman team ID, the detected CI provider and
287
- runner kind, the run outcome, the CI run identifier, an event timestamp, and a one-way SHA-256 hash of the repository
288
- identifier. Each event also carries a schema version and a constant event marker (always `completion`). The Postman team ID is sent in the clear on a legitimate-interest
289
- basis to measure product adoption.
290
-
291
- The `events.pm-cse.dev` endpoint is operated by the Postman Customer Success
292
- Engineering team. Postman, Inc. processes these events only to measure
293
- onboarding adoption in aggregate, retains them only as aggregated counts for
294
- product-adoption trend analysis, and includes no payload field that identifies
295
- an individual person.
296
-
297
- It never sends API keys, access tokens, spec content, workspace or repository
298
- names, or any personal data. It is fire-and-forget with a hard
299
- timeout and can never block or fail your pipeline. Corporate HTTP and HTTPS
300
- proxies are honored through the standard `HTTPS_PROXY`, `HTTP_PROXY`, and
301
- `NO_PROXY` environment variables.
302
-
303
- Disable it by setting either environment variable in your CI:
304
-
305
- ```sh
306
- POSTMAN_ACTIONS_TELEMETRY=off
307
- # or the cross-tool standard
308
- DO_NOT_TRACK=1
309
- ```
310
-
311
- Telemetry is also skipped automatically when no Postman team ID can be resolved.
312
-
313
- A run that ends in the `not-found` state (no matching Insights project) is
314
- recorded with a `failure` outcome.
315
-
316
- Events are sent over HTTPS to `https://events.pm-cse.dev/v1/events`. To
317
- allowlist this destination on a restricted network, or to route events to a
318
- collector you operate, set the `POSTMAN_ACTIONS_TELEMETRY_ENDPOINT` environment
319
- variable to your own URL.
264
+ The action sends one anonymous usage event per run (action name/version, outcome, coarse CI metadata; never secrets, spec content, or repo names). A run ending in the `not-found` state is recorded with a `failure` outcome. Disable with `POSTMAN_ACTIONS_TELEMETRY=off` or `DO_NOT_TRACK=1`; route events to your own collector with `POSTMAN_ACTIONS_TELEMETRY_ENDPOINT`.
320
265
 
321
266
  ## License
322
267
 
package/action.yml CHANGED
@@ -24,8 +24,8 @@ inputs:
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: '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
- required: true
27
+ description: 'Postman access token (x-access-token) for the Bifrost linking calls. When omitted, the action mints a service-account token from postman-api-key. 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
+ required: false
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.'
31
31
  required: false
package/dist/action.cjs CHANGED
@@ -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,
@@ -21591,6 +21696,53 @@ var AccessTokenProvider = class {
21591
21696
  return token;
21592
21697
  }
21593
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
+ }
21594
21746
 
21595
21747
  // src/lib/bifrost-client.ts
21596
21748
  var DEFAULT_BIFROST_BASE_URL = POSTMAN_ENDPOINT_PROFILES.prod.bifrostBaseUrl;
@@ -21995,6 +22147,35 @@ function getFinalServiceSegment(serviceName) {
21995
22147
  return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
21996
22148
  }
21997
22149
 
22150
+ // src/lib/input.ts
22151
+ function normalizeInputValue(value) {
22152
+ return String(value ?? "").trim();
22153
+ }
22154
+ function runnerInputEnvName(name) {
22155
+ return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
22156
+ }
22157
+ function normalizedInputEnvName(name) {
22158
+ return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
22159
+ }
22160
+ function getInput2(name, env = process.env) {
22161
+ const normalizedName = normalizedInputEnvName(name);
22162
+ const runnerName = runnerInputEnvName(name);
22163
+ const normalizedRaw = env[normalizedName];
22164
+ const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
22165
+ const hasNormalized = normalizedRaw !== void 0;
22166
+ const hasRunner = runnerRaw !== void 0;
22167
+ if (hasNormalized && hasRunner) {
22168
+ const normalizedValue = normalizeInputValue(normalizedRaw);
22169
+ const runnerValue = normalizeInputValue(runnerRaw);
22170
+ if (normalizedValue !== runnerValue) {
22171
+ throw new Error(
22172
+ `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
22173
+ );
22174
+ }
22175
+ }
22176
+ return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
22177
+ }
22178
+
21998
22179
  // node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
21999
22180
  function norm(value) {
22000
22181
  const trimmed = (value ?? "").trim();
@@ -22249,7 +22430,7 @@ function resolveActionVersion(explicit) {
22249
22430
  if (explicit) {
22250
22431
  return explicit;
22251
22432
  }
22252
- return "2.0.0" ? "2.0.0" : "unknown";
22433
+ return typeof __ACTION_VERSION__ !== "undefined" && __ACTION_VERSION__ ? __ACTION_VERSION__ : "unknown";
22253
22434
  }
22254
22435
  function telemetryDisabled(env) {
22255
22436
  const flag = String(env.POSTMAN_ACTIONS_TELEMETRY ?? "").trim().toLowerCase();
@@ -22371,6 +22552,18 @@ function createTelemetryContext(options) {
22371
22552
  };
22372
22553
  }
22373
22554
 
22555
+ // src/action-version.ts
22556
+ var import_node_fs = require("node:fs");
22557
+ var import_node_path = require("node:path");
22558
+ function resolveActionVersion2() {
22559
+ try {
22560
+ const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8");
22561
+ return JSON.parse(raw).version ?? "unknown";
22562
+ } catch {
22563
+ return "unknown";
22564
+ }
22565
+ }
22566
+
22374
22567
  // src/index.ts
22375
22568
  var POLL_TIMEOUT_MIN = 10;
22376
22569
  var POLL_TIMEOUT_MAX = 600;
@@ -22432,12 +22625,16 @@ function clamp(value, min, max, fallback) {
22432
22625
  return Math.min(max, Math.max(min, parsed));
22433
22626
  }
22434
22627
  function resolveInputs(env = process.env) {
22435
- const get = (name, fallback = "") => env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`]?.trim() || fallback;
22628
+ const get = (name, fallback = "") => getInput2(name, env) || fallback;
22436
22629
  const projectName = get("project-name");
22437
22630
  if (!projectName) throw new Error("project-name is required");
22438
22631
  const postmanAccessToken = get("postman-access-token");
22439
- if (!postmanAccessToken) throw new Error("postman-access-token is required");
22440
22632
  const postmanApiKey = get("postman-api-key");
22633
+ if (!postmanAccessToken && !postmanApiKey) {
22634
+ throw new Error(
22635
+ "postman-access-token is required (or provide a service-account postman-api-key so the action can mint one)."
22636
+ );
22637
+ }
22441
22638
  const postmanTeamId = get("postman-team-id") || env.POSTMAN_TEAM_ID?.trim() || "";
22442
22639
  const workspaceId = get("workspace-id") || env.POSTMAN_WORKSPACE_ID?.trim() || "";
22443
22640
  if (!workspaceId) {
@@ -22677,7 +22874,14 @@ async function runAction() {
22677
22874
  for (const [key, value] of Object.entries(planned)) {
22678
22875
  setOutput(key, value);
22679
22876
  }
22680
- setSecret(inputs.postmanAccessToken);
22877
+ const mintHolder = {
22878
+ postmanAccessToken: inputs.postmanAccessToken,
22879
+ postmanApiKey: inputs.postmanApiKey,
22880
+ postmanApiBase: inputs.postmanApiBase
22881
+ };
22882
+ await mintAccessTokenIfNeeded(mintHolder, core_exports, (secret) => setSecret(secret));
22883
+ inputs.postmanAccessToken = mintHolder.postmanAccessToken;
22884
+ if (inputs.postmanAccessToken) setSecret(inputs.postmanAccessToken);
22681
22885
  if (inputs.postmanApiKey) setSecret(inputs.postmanApiKey);
22682
22886
  if (inputs.githubToken) setSecret(inputs.githubToken);
22683
22887
  const tokenProvider = createInsightsTokenProvider(inputs, core_exports);
@@ -22694,7 +22898,7 @@ async function runAction() {
22694
22898
  apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
22695
22899
  onToken: (token) => setSecret(token)
22696
22900
  }) : tokenProvider;
22697
- const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", logger: core_exports });
22901
+ const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
22698
22902
  telemetry.setTeamId(inputs.postmanTeamId || pmakIdentity?.teamId);
22699
22903
  let result;
22700
22904
  try {