@postman-cse/onboarding-repo-sync 2.1.5 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -92333,11 +92333,11 @@ var require_valid = __commonJS({
92333
92333
  var require_clean = __commonJS({
92334
92334
  "node_modules/postman-collection/node_modules/semver/functions/clean.js"(exports2, module2) {
92335
92335
  var parse2 = require_parse5();
92336
- var clean = (version2, options) => {
92336
+ var clean2 = (version2, options) => {
92337
92337
  const s = parse2(version2.trim().replace(/^[=v]+/, ""), options);
92338
92338
  return s ? s.version : null;
92339
92339
  };
92340
- module2.exports = clean;
92340
+ module2.exports = clean2;
92341
92341
  }
92342
92342
  });
92343
92343
 
@@ -93648,7 +93648,7 @@ var require_semver2 = __commonJS({
93648
93648
  var identifiers = require_identifiers();
93649
93649
  var parse2 = require_parse5();
93650
93650
  var valid = require_valid();
93651
- var clean = require_clean();
93651
+ var clean2 = require_clean();
93652
93652
  var inc = require_inc();
93653
93653
  var diff = require_diff();
93654
93654
  var major = require_major();
@@ -93686,7 +93686,7 @@ var require_semver2 = __commonJS({
93686
93686
  module2.exports = {
93687
93687
  parse: parse2,
93688
93688
  valid,
93689
- clean,
93689
+ clean: clean2,
93690
93690
  inc,
93691
93691
  diff,
93692
93692
  major,
@@ -119787,14 +119787,19 @@ var require_node2 = __commonJS({
119787
119787
  // src/index.ts
119788
119788
  var index_exports = {};
119789
119789
  __export(index_exports, {
119790
+ StateUnreadableError: () => StateUnreadableError,
119791
+ assertBranchAssetIds: () => assertBranchAssetIds,
119790
119792
  assertPathWithinCwd: () => assertPathWithinCwd,
119793
+ buildBranchAssetMarker: () => buildBranchAssetMarker,
119791
119794
  createRepoSyncDependencies: () => createRepoSyncDependencies,
119795
+ decideBranchTier: () => decideBranchTier,
119792
119796
  getInput: () => getInput2,
119793
119797
  hasInput: () => hasInput,
119794
119798
  readActionInputs: () => readActionInputs,
119795
119799
  resolveInputs: () => resolveInputs,
119796
119800
  resolvePostmanApiKeyAndTeamId: () => resolvePostmanApiKeyAndTeamId,
119797
119801
  runAction: () => runAction,
119802
+ runGatedSkip: () => runGatedSkip,
119798
119803
  runRepoSync: () => runRepoSync
119799
119804
  });
119800
119805
  module.exports = __toCommonJS(index_exports);
@@ -122078,7 +122083,7 @@ function getIDToken(aud) {
122078
122083
  }
122079
122084
 
122080
122085
  // src/index.ts
122081
- var import_node_fs3 = require("node:fs");
122086
+ var import_node_fs4 = require("node:fs");
122082
122087
  var path8 = __toESM(require("node:path"), 1);
122083
122088
 
122084
122089
  // node_modules/js-yaml/dist/js-yaml.mjs
@@ -125129,12 +125134,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
125129
125134
  prod: {
125130
125135
  apiBaseUrl: "https://api.getpostman.com",
125131
125136
  bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
125137
+ fallbackBaseUrl: "https://go.postman.co/_api",
125132
125138
  cliInstallUrl: "https://dl-cli.pstmn.io/install/unix.sh",
125133
125139
  iapubBaseUrl: "https://iapub.postman.co"
125134
125140
  },
125135
125141
  beta: {
125136
125142
  apiBaseUrl: "https://api.getpostman-beta.com",
125137
125143
  bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
125144
+ fallbackBaseUrl: "https://go.postman-beta.co/_api",
125138
125145
  cliInstallUrl: "https://dl-cli.pstmn-beta.io/install/unix.sh",
125139
125146
  iapubBaseUrl: "https://iapub.postman.co"
125140
125147
  }
@@ -125201,6 +125208,9 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125201
125208
  " branches: [main]",
125202
125209
  " schedule:",
125203
125210
  ' - cron: "0 */6 * * *"',
125211
+ "concurrency:",
125212
+ " group: postman-onboard-${{ github.head_ref || github.ref_name }}",
125213
+ " cancel-in-progress: false",
125204
125214
  "jobs:",
125205
125215
  " test:",
125206
125216
  " runs-on: ubuntu-latest",
@@ -125217,7 +125227,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125217
125227
  " ruby <<'RUBY'",
125218
125228
  " require 'yaml'",
125219
125229
  " config = YAML.load_file('.postman/resources.yaml') || {}",
125220
- " cloud = config.fetch('cloudResources', {})",
125230
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
125221
125231
  " collections = cloud.fetch('collections', {})",
125222
125232
  " environments = cloud.fetch('environments', {})",
125223
125233
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
@@ -125415,6 +125425,58 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion) {
125415
125425
  ""
125416
125426
  ];
125417
125427
  }
125428
+ function renderGcWorkflowTemplate() {
125429
+ return [
125430
+ "name: Postman Preview GC",
125431
+ "on:",
125432
+ " delete:",
125433
+ ' branches: ["**"]',
125434
+ " pull_request:",
125435
+ " types: [closed]",
125436
+ " schedule:",
125437
+ ' - cron: "0 2 * * *"',
125438
+ " workflow_dispatch:",
125439
+ " inputs:",
125440
+ " branch:",
125441
+ " description: Branch name to GC (optional, otherwise sweep by TTL/branch-existence)",
125442
+ " required: false",
125443
+ "concurrency:",
125444
+ " group: postman-preview-gc-${{ github.ref_name }}",
125445
+ " cancel-in-progress: false",
125446
+ "jobs:",
125447
+ " gc:",
125448
+ " runs-on: ubuntu-latest",
125449
+ " if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
125450
+ " steps:",
125451
+ " - uses: actions/checkout@v5",
125452
+ " with:",
125453
+ " fetch-depth: 0",
125454
+ " - name: Run Postman preview GC (provider-neutral cli.cjs gc)",
125455
+ " env:",
125456
+ " POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}",
125457
+ " POSTMAN_ACCESS_TOKEN: ${{ secrets.POSTMAN_ACCESS_TOKEN }}",
125458
+ " POSTMAN_WORKSPACE_ID: ${{ vars.POSTMAN_WORKSPACE_ID }}",
125459
+ " REPO_URL: https://github.com/${{ github.repository }}",
125460
+ " run: |",
125461
+ " set -euo pipefail",
125462
+ " # The repo-sync action bundles a provider-neutral gc command (cli.cjs gc)",
125463
+ " # that uses the Postman access token for inventory/deletion and the",
125464
+ " # provider ambient credential (GITHUB_TOKEN via git ls-remote) for branch existence.",
125465
+ " # Daily scheduled run is the retention executor (TTL contract of last resort).",
125466
+ ' if [ -n "${{ inputs.branch }}" ]; then',
125467
+ ' npx @postman-cse/onboarding-repo-sync gc --branch "${{ inputs.branch }}" --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125468
+ " else",
125469
+ ' npx @postman-cse/onboarding-repo-sync gc --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125470
+ " fi",
125471
+ " - name: Orphan audit summary (job summary)",
125472
+ " if: always()",
125473
+ " run: |",
125474
+ ' echo "### Preview GC orphan audit" >> "$GITHUB_STEP_SUMMARY"',
125475
+ ' echo "Marker-guarded deletion only \u2014 strangers (no marker) are never deleted. See gc-summary-json for structured counts." >> "$GITHUB_STEP_SUMMARY"',
125476
+ ""
125477
+ ].join("\n");
125478
+ }
125479
+ var GC_WORKFLOW_TEMPLATE = renderGcWorkflowTemplate();
125418
125480
  function getCiWorkflowTemplate(provider, options = {}) {
125419
125481
  if (provider === "azure-devops") {
125420
125482
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
@@ -126985,6 +127047,53 @@ function createInternalIntegrationAdapter(options) {
126985
127047
  return new BifrostInternalIntegrationAdapter(options);
126986
127048
  }
126987
127049
 
127050
+ // src/lib/retry.ts
127051
+ function sleep(delayMs) {
127052
+ return new Promise((resolve3) => {
127053
+ setTimeout(resolve3, delayMs);
127054
+ });
127055
+ }
127056
+ function normalizeRetryOptions(options) {
127057
+ return {
127058
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
127059
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
127060
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
127061
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
127062
+ onRetry: options.onRetry ?? (async () => void 0),
127063
+ shouldRetry: options.shouldRetry ?? (() => true),
127064
+ sleep: options.sleep ?? sleep
127065
+ };
127066
+ }
127067
+ async function retry(operation, options = {}) {
127068
+ const normalized = normalizeRetryOptions(options);
127069
+ let nextDelayMs = normalized.delayMs;
127070
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
127071
+ try {
127072
+ return await operation();
127073
+ } catch (error2) {
127074
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
127075
+ attempt,
127076
+ maxAttempts: normalized.maxAttempts
127077
+ });
127078
+ if (!shouldRetry) {
127079
+ throw error2;
127080
+ }
127081
+ await normalized.onRetry({
127082
+ attempt,
127083
+ maxAttempts: normalized.maxAttempts,
127084
+ delayMs: nextDelayMs,
127085
+ error: error2
127086
+ });
127087
+ await normalized.sleep(nextDelayMs);
127088
+ nextDelayMs = Math.min(
127089
+ normalized.maxDelayMs,
127090
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
127091
+ );
127092
+ }
127093
+ }
127094
+ throw new Error("Retry exhausted without returning or throwing");
127095
+ }
127096
+
126988
127097
  // src/contracts.ts
126989
127098
  var postmanRepoSyncActionContract = {
126990
127099
  name: "postman-repo-sync-action",
@@ -127154,6 +127263,25 @@ var postmanRepoSyncActionContract = {
127154
127263
  default: "warn",
127155
127264
  allowedValues: ["enforce", "warn"]
127156
127265
  },
127266
+ "branch-strategy": {
127267
+ description: "Branch-aware sync strategy. legacy (default) keeps branch-blind behavior; publish-gate restricts canonical writes to the canonical branch and skips repo-sync on other branches; preview additionally maintains suffixed per-branch preview asset sets.",
127268
+ required: false,
127269
+ default: "legacy",
127270
+ allowedValues: ["legacy", "preview", "publish-gate"]
127271
+ },
127272
+ "canonical-branch": {
127273
+ description: "Explicit canonical branch (the sole writer of canonical assets and tracked state). Defaults to the provider-resolved default branch; required on providers without a default-branch variable (Bitbucket, Azure DevOps) when branch-strategy is not legacy.",
127274
+ required: false
127275
+ },
127276
+ "channels": {
127277
+ description: 'Comma-separated channel map for long-lived promotion branches, e.g. "develop=DEV, staging=STAGE, release/*=RC". Channel branches maintain prefix-named parallel asset sets and never mutate canonical assets.',
127278
+ required: false
127279
+ },
127280
+ "preview-ttl": {
127281
+ description: "Sliding TTL in days for preview asset sets (refreshed on every successful preview sync; the retention contract of last resort when no provider credential is available for branch-existence checks).",
127282
+ required: false,
127283
+ default: "30"
127284
+ },
127157
127285
  "github-token": {
127158
127286
  description: "GitHub token used for repo variable persistence and commits.",
127159
127287
  required: false
@@ -127191,6 +127319,11 @@ var postmanRepoSyncActionContract = {
127191
127319
  description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
127192
127320
  required: false
127193
127321
  },
127322
+ "spec-content-changed": {
127323
+ description: "Whether bootstrap changed canonical spec content; controls final native Spec Hub tag publication.",
127324
+ required: false,
127325
+ default: "true"
127326
+ },
127194
127327
  "spec-path": {
127195
127328
  description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
127196
127329
  required: false
@@ -127235,6 +127368,18 @@ var postmanRepoSyncActionContract = {
127235
127368
  },
127236
127369
  "commit-sha": {
127237
127370
  description: "Commit SHA produced by repo-write-mode, if any."
127371
+ },
127372
+ "sync-status": {
127373
+ description: "Branch-aware sync status: synced, skipped-branch-gate, or empty under branch-strategy legacy."
127374
+ },
127375
+ "branch-decision": {
127376
+ description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
127377
+ },
127378
+ "spec-version-tag": {
127379
+ description: "Native Spec Hub version tag created after successful canonical repo-sync finalization."
127380
+ },
127381
+ "spec-version-url": {
127382
+ description: "Read-only URL for the tagged Spec Hub snapshot."
127238
127383
  }
127239
127384
  },
127240
127385
  behavior: {
@@ -127302,53 +127447,6 @@ var PostmanAssetsClient = class {
127302
127447
  }
127303
127448
  };
127304
127449
 
127305
- // src/lib/retry.ts
127306
- function sleep(delayMs) {
127307
- return new Promise((resolve3) => {
127308
- setTimeout(resolve3, delayMs);
127309
- });
127310
- }
127311
- function normalizeRetryOptions(options) {
127312
- return {
127313
- maxAttempts: Math.max(1, options.maxAttempts ?? 3),
127314
- delayMs: Math.max(0, options.delayMs ?? 2e3),
127315
- backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
127316
- maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
127317
- onRetry: options.onRetry ?? (async () => void 0),
127318
- shouldRetry: options.shouldRetry ?? (() => true),
127319
- sleep: options.sleep ?? sleep
127320
- };
127321
- }
127322
- async function retry(operation, options = {}) {
127323
- const normalized = normalizeRetryOptions(options);
127324
- let nextDelayMs = normalized.delayMs;
127325
- for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
127326
- try {
127327
- return await operation();
127328
- } catch (error2) {
127329
- const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
127330
- attempt,
127331
- maxAttempts: normalized.maxAttempts
127332
- });
127333
- if (!shouldRetry) {
127334
- throw error2;
127335
- }
127336
- await normalized.onRetry({
127337
- attempt,
127338
- maxAttempts: normalized.maxAttempts,
127339
- delayMs: nextDelayMs,
127340
- error: error2
127341
- });
127342
- await normalized.sleep(nextDelayMs);
127343
- nextDelayMs = Math.min(
127344
- normalized.maxDelayMs,
127345
- Math.round(nextDelayMs * normalized.backoffMultiplier)
127346
- );
127347
- }
127348
- }
127349
- throw new Error("Retry exhausted without returning or throwing");
127350
- }
127351
-
127352
127450
  // src/lib/postman/postman-gateway-assets-client.ts
127353
127451
  var MAX_CREATE_FLIGHTS = 256;
127354
127452
  var createFlights = /* @__PURE__ */ new Map();
@@ -127374,6 +127472,84 @@ var PostmanGatewayAssetsClient = class {
127374
127472
  if (!envelope) return null;
127375
127473
  return this.asRecord(envelope.data) ?? envelope;
127376
127474
  }
127475
+ /** Native Spec Hub tags attach to the latest changelog group. */
127476
+ async tagSpecVersion(specId, name) {
127477
+ const trimmed = name.trim().slice(0, 255);
127478
+ const response = await this.gateway.requestJson({
127479
+ service: "specification",
127480
+ method: "post",
127481
+ path: `/specifications/${specId}/tags`,
127482
+ body: { name: trimmed }
127483
+ });
127484
+ const record = this.dataOf(response) ?? {};
127485
+ return { id: String(record.id ?? "").trim(), name: String(record.name ?? trimmed).trim() };
127486
+ }
127487
+ async listSpecVersionTags(specId) {
127488
+ const response = await this.gateway.requestJson({
127489
+ service: "specification",
127490
+ method: "get",
127491
+ path: `/specifications/${specId}/tags`,
127492
+ query: { limit: "50" }
127493
+ });
127494
+ const data = Array.isArray(response?.data) ? response.data : [];
127495
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({
127496
+ id: String(entry.id ?? "").trim(),
127497
+ // listTags returns `message`; createTag returns `name`. Accept both.
127498
+ name: String(entry.name ?? entry.message ?? "").trim()
127499
+ })).filter((entry) => entry.id || entry.name);
127500
+ }
127501
+ async deleteCollection(collectionUid) {
127502
+ const bareId = String(collectionUid).split("-").slice(-5).join("-") || collectionUid;
127503
+ await this.gateway.requestJson({
127504
+ service: "collection",
127505
+ method: "delete",
127506
+ path: `/v3/collections/${bareId}`
127507
+ });
127508
+ }
127509
+ async listSpecifications(workspaceId) {
127510
+ const response = await this.gateway.requestJson({
127511
+ service: "specification",
127512
+ method: "get",
127513
+ path: `/specifications?containerType=workspace&containerId=${workspaceId}`
127514
+ });
127515
+ const data = Array.isArray(response?.data) ? response.data : [];
127516
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({ uid: String(entry.id ?? entry.uid ?? "").trim(), name: String(entry.name ?? "").trim() })).filter((entry) => entry.uid && entry.name);
127517
+ }
127518
+ async getSpecContent(specId) {
127519
+ const files = await this.gateway.requestJson({
127520
+ service: "specification",
127521
+ method: "get",
127522
+ path: `/specifications/${specId}/files`
127523
+ });
127524
+ const entries = Array.isArray(files?.data) ? files.data : [];
127525
+ const root = entries.map((entry) => this.asRecord(entry)).find((entry) => entry?.type === "ROOT") ?? entries.map((entry) => this.asRecord(entry)).find((entry) => entry !== null);
127526
+ const fileId = String(root?.id ?? "").trim();
127527
+ if (!fileId) return void 0;
127528
+ const file = await this.gateway.requestJson({
127529
+ service: "specification",
127530
+ method: "get",
127531
+ path: `/specifications/${specId}/files/${fileId}`,
127532
+ query: { fields: "content" }
127533
+ });
127534
+ const record = this.dataOf(file);
127535
+ return typeof record?.content === "string" ? record.content : void 0;
127536
+ }
127537
+ async listSpecCollections(specId) {
127538
+ const response = await this.gateway.requestJson({
127539
+ service: "specification",
127540
+ method: "get",
127541
+ path: `/specifications/${specId}/collections`
127542
+ });
127543
+ const data = Array.isArray(response?.data) ? response.data : [];
127544
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({ uid: String(entry.collection ?? entry.collectionId ?? entry.id ?? "").trim(), name: String(entry.name ?? "").trim() })).filter((entry) => entry.uid);
127545
+ }
127546
+ async deleteSpec(specId) {
127547
+ await this.gateway.requestJson({
127548
+ service: "specification",
127549
+ method: "delete",
127550
+ path: `/specifications/${specId}`
127551
+ });
127552
+ }
127377
127553
  idOf(record) {
127378
127554
  if (!record) return "";
127379
127555
  const id = record.uid ?? record.id;
@@ -127433,6 +127609,14 @@ var PostmanGatewayAssetsClient = class {
127433
127609
  createFlights.set(key, { fingerprint, promise: pending });
127434
127610
  return pending;
127435
127611
  }
127612
+ /**
127613
+ * Reconcile an ambiguous create. Returns the adopted match, `undefined`
127614
+ * when every discovery read succeeded and found nothing (the create is
127615
+ * conclusively absent, so a fallback resend cannot duplicate), or `null`
127616
+ * when the outcome is inconclusive (non-ambiguous error, or a discovery
127617
+ * read itself failed — in that case a resend could duplicate a committed
127618
+ * twin and must not fire).
127619
+ */
127436
127620
  async discoverAfterAmbiguousCreate(discover, error2) {
127437
127621
  if (!this.isAmbiguousCreateOutcome(error2)) {
127438
127622
  return null;
@@ -127441,12 +127625,31 @@ var PostmanGatewayAssetsClient = class {
127441
127625
  if (attempt > 0) {
127442
127626
  await this.sleep(this.reconcileDelayMs);
127443
127627
  }
127444
- const found = await discover();
127628
+ let found;
127629
+ try {
127630
+ found = await discover();
127631
+ } catch {
127632
+ return null;
127633
+ }
127445
127634
  if (found) {
127446
127635
  return found;
127447
127636
  }
127448
127637
  }
127449
- return null;
127638
+ return void 0;
127639
+ }
127640
+ /**
127641
+ * After an ambiguous create failed AND discovery proved the asset absent,
127642
+ * re-attempt the create once with the cold `/_api` fallback enabled: the
127643
+ * reconcile above guarantees no committed twin, so the resend cannot
127644
+ * duplicate. Returns null when the resend also fails (caller rethrows the
127645
+ * original error).
127646
+ */
127647
+ async resendAbsentCreate(operation) {
127648
+ try {
127649
+ return await operation();
127650
+ } catch {
127651
+ return null;
127652
+ }
127450
127653
  }
127451
127654
  publicEnvironmentUid(data, bareId) {
127452
127655
  const owner = String(data?.owner ?? "").trim();
@@ -127494,17 +127697,18 @@ var PostmanGatewayAssetsClient = class {
127494
127697
  name: envName,
127495
127698
  values: normalizedValues
127496
127699
  };
127700
+ const send2 = (fallback) => this.gateway.requestJson(
127701
+ {
127702
+ service: "sync",
127703
+ method: "post",
127704
+ path: `/environment/import?workspace=${ws}`,
127705
+ body,
127706
+ ...fallback ? { fallback } : {}
127707
+ },
127708
+ { retryTransient: false }
127709
+ );
127497
127710
  try {
127498
- const response = await this.gateway.requestJson(
127499
- {
127500
- service: "sync",
127501
- method: "post",
127502
- path: `/environment/import?workspace=${ws}`,
127503
- body
127504
- },
127505
- { retryTransient: false }
127506
- );
127507
- const data = this.dataOf(response);
127711
+ const data = this.dataOf(await send2());
127508
127712
  const bareId = this.idOf(data);
127509
127713
  if (!bareId) {
127510
127714
  throw new Error("Environment import did not return a UID");
@@ -127519,6 +127723,15 @@ var PostmanGatewayAssetsClient = class {
127519
127723
  await this.updateEnvironment(adopted, envName, values);
127520
127724
  return adopted;
127521
127725
  }
127726
+ if (adopted === void 0) {
127727
+ const retried = await this.resendAbsentCreate(async () => {
127728
+ const data = this.dataOf(await send2("auto"));
127729
+ const bareId = this.idOf(data);
127730
+ if (!bareId) throw new Error("Environment import did not return a UID");
127731
+ return this.publicEnvironmentUid(data, bareId);
127732
+ });
127733
+ if (retried) return retried;
127734
+ }
127522
127735
  throw error2;
127523
127736
  }
127524
127737
  });
@@ -127650,16 +127863,20 @@ var PostmanGatewayAssetsClient = class {
127650
127863
  private: false,
127651
127864
  ...environment ? { environment } : {}
127652
127865
  };
127653
- try {
127654
- const response = await this.gateway.requestJson(
127655
- {
127656
- service: "mock",
127657
- method: "post",
127658
- path: `/mocks?workspace=${ws}`,
127659
- body
127660
- },
127661
- { retryTransient: false }
127662
- );
127866
+ const send2 = (fallback) => this.gateway.requestJson(
127867
+ {
127868
+ service: "mock",
127869
+ method: "post",
127870
+ path: `/mocks?workspace=${ws}`,
127871
+ body,
127872
+ ...fallback ? { fallback } : {}
127873
+ },
127874
+ // Unsafe create: never blind re-POST. An ESOCKETTIMEDOUT after accept
127875
+ // may still have created the mock; reconcile via discovery below and
127876
+ // let the orchestrator retry the whole create-or-adopt cycle.
127877
+ { retryTransient: false }
127878
+ );
127879
+ const parseMock = (response) => {
127663
127880
  const record = this.dataOf(response);
127664
127881
  const uid = this.idOf(record);
127665
127882
  if (!uid) {
@@ -127669,6 +127886,9 @@ var PostmanGatewayAssetsClient = class {
127669
127886
  uid,
127670
127887
  url: String(record?.url ?? record?.mockUrl ?? "").trim()
127671
127888
  };
127889
+ };
127890
+ try {
127891
+ return parseMock(await send2());
127672
127892
  } catch (error2) {
127673
127893
  const adopted = await this.discoverAfterAmbiguousCreate(
127674
127894
  () => this.findMockByCollection(collection, environment, mockName),
@@ -127677,6 +127897,10 @@ var PostmanGatewayAssetsClient = class {
127677
127897
  if (adopted) {
127678
127898
  return { uid: adopted.uid, url: adopted.mockUrl };
127679
127899
  }
127900
+ if (adopted === void 0) {
127901
+ const retried = await this.resendAbsentCreate(async () => parseMock(await send2("auto")));
127902
+ if (retried) return retried;
127903
+ }
127680
127904
  throw error2;
127681
127905
  }
127682
127906
  });
@@ -127696,6 +127920,33 @@ var PostmanGatewayAssetsClient = class {
127696
127920
  environment: String(m.environment ?? "")
127697
127921
  }));
127698
127922
  }
127923
+ /**
127924
+ * Delete an environment through the sync service (GC path). The path id is
127925
+ * the bare model id (public uid tail), mirroring updateEnvironment.
127926
+ */
127927
+ async deleteEnvironment(uid) {
127928
+ await this.gateway.requestJson({
127929
+ service: "sync",
127930
+ method: "delete",
127931
+ path: `/environment/${this.toModelId(uid)}`
127932
+ });
127933
+ }
127934
+ /** Delete a mock server (GC path). */
127935
+ async deleteMock(uid) {
127936
+ await this.gateway.requestJson({
127937
+ service: "mock",
127938
+ method: "delete",
127939
+ path: `/mocks/${this.toModelId(uid)}`
127940
+ });
127941
+ }
127942
+ /** Delete a collection-based monitor (jobTemplate) (GC path). */
127943
+ async deleteMonitor(uid) {
127944
+ await this.gateway.requestJson({
127945
+ service: "monitors",
127946
+ method: "delete",
127947
+ path: `/jobTemplates/${this.toModelId(uid)}`
127948
+ });
127949
+ }
127699
127950
  async mockExists(uid) {
127700
127951
  try {
127701
127952
  await this.gateway.requestJson({
@@ -127756,21 +128007,25 @@ var PostmanGatewayAssetsClient = class {
127756
128007
  distribution: null,
127757
128008
  ...environment ? { environment } : {}
127758
128009
  };
127759
- try {
127760
- const response = await this.gateway.requestJson(
127761
- {
127762
- service: "monitors",
127763
- method: "post",
127764
- path: `/jobTemplates?workspace=${ws}`,
127765
- body
127766
- },
127767
- { retryTransient: false }
127768
- );
128010
+ const send2 = (fallback) => this.gateway.requestJson(
128011
+ {
128012
+ service: "monitors",
128013
+ method: "post",
128014
+ path: `/jobTemplates?workspace=${ws}`,
128015
+ body,
128016
+ ...fallback ? { fallback } : {}
128017
+ },
128018
+ { retryTransient: false }
128019
+ );
128020
+ const parseMonitor = (response) => {
127769
128021
  const uid = this.idOf(this.dataOf(response));
127770
128022
  if (!uid) {
127771
128023
  throw new Error("Monitor create did not return a UID");
127772
128024
  }
127773
128025
  return uid;
128026
+ };
128027
+ try {
128028
+ return parseMonitor(await send2());
127774
128029
  } catch (error2) {
127775
128030
  const adopted = await this.discoverAfterAmbiguousCreate(
127776
128031
  () => this.findMonitorByCollection(collection, environment, monitorName),
@@ -127779,6 +128034,10 @@ var PostmanGatewayAssetsClient = class {
127779
128034
  if (adopted?.uid) {
127780
128035
  return adopted.uid;
127781
128036
  }
128037
+ if (adopted === void 0) {
128038
+ const retried = await this.resendAbsentCreate(async () => parseMonitor(await send2("auto")));
128039
+ if (retried) return retried;
128040
+ }
127782
128041
  throw error2;
127783
128042
  }
127784
128043
  });
@@ -128007,6 +128266,7 @@ var AccessTokenGatewayClient = class {
128007
128266
  fetchImpl;
128008
128267
  secretMasker;
128009
128268
  maxRetries;
128269
+ fallbackBaseUrl;
128010
128270
  retryBaseDelayMs;
128011
128271
  sleepImpl;
128012
128272
  constructor(options) {
@@ -128019,6 +128279,8 @@ var AccessTokenGatewayClient = class {
128019
128279
  this.fetchImpl = options.fetchImpl ?? fetch;
128020
128280
  this.secretMasker = options.secretMasker ?? createSecretMasker([this.tokenProvider.current()]);
128021
128281
  this.maxRetries = options.maxRetries ?? 3;
128282
+ const fallbackEnv = typeof process !== "undefined" ? process.env?.POSTMAN_ITEM_CREATE_FALLBACK : void 0;
128283
+ this.fallbackBaseUrl = fallbackEnv === "off" ? void 0 : options.fallbackBaseUrl?.replace(/\/+$/, "");
128022
128284
  this.retryBaseDelayMs = options.retryBaseDelayMs ?? 400;
128023
128285
  this.sleepImpl = options.sleepImpl ?? defaultSleep;
128024
128286
  }
@@ -128037,8 +128299,8 @@ var AccessTokenGatewayClient = class {
128037
128299
  }
128038
128300
  return headers;
128039
128301
  }
128040
- async send(request) {
128041
- const url = `${this.bifrostBaseUrl}/ws/proxy`;
128302
+ async send(request, baseUrl) {
128303
+ const url = `${baseUrl ?? this.bifrostBaseUrl}/ws/proxy`;
128042
128304
  return this.fetchImpl(url, {
128043
128305
  method: "POST",
128044
128306
  headers: this.buildHeaders(request.headers),
@@ -128051,6 +128313,43 @@ var AccessTokenGatewayClient = class {
128051
128313
  })
128052
128314
  });
128053
128315
  }
128316
+ /**
128317
+ * One cold, serial attempt against the fallback base URL after the primary
128318
+ * budget is exhausted on a transient failure. Never hedged in parallel with
128319
+ * the primary; only fires when the request would otherwise throw. Callers
128320
+ * with `retryTransient: false` still reconcile first — the fallback attempt
128321
+ * here is the resend, so it is only used for requests whose mutation is
128322
+ * known idempotent or already reconciled by the caller's adopt-on-ambiguous
128323
+ * loop.
128324
+ */
128325
+ async tryFallback(request) {
128326
+ if (!this.fallbackBaseUrl) return null;
128327
+ try {
128328
+ return await this.send(request, this.fallbackBaseUrl);
128329
+ } catch {
128330
+ return null;
128331
+ }
128332
+ }
128333
+ /**
128334
+ * Run the fallback attempt and classify its response the same way the
128335
+ * primary path would. Returns the success response, or null when the
128336
+ * fallback also failed transiently (caller then throws the original error).
128337
+ * Non-transient fallback failures (4xx) surface as their own HttpError since
128338
+ * they are the freshest authoritative answer.
128339
+ */
128340
+ fallbackEligible(request, retryTransient) {
128341
+ if (!this.fallbackBaseUrl) return false;
128342
+ return retryTransient || request.fallback === "auto";
128343
+ }
128344
+ async attemptFallback(request, retryTransient) {
128345
+ if (!this.fallbackEligible(request, retryTransient)) return null;
128346
+ const response = await this.tryFallback(request);
128347
+ if (!response) return null;
128348
+ if (response.ok) return response;
128349
+ const body = await response.text().catch(() => "");
128350
+ if (isRetryableSafeReadResponse(response.status)) return null;
128351
+ throw this.toHttpError(request, response, body);
128352
+ }
128054
128353
  /**
128055
128354
  * Send a gateway request, refreshing the token once on an auth failure and
128056
128355
  * optionally retrying transient downstream failures (5xx / Bifrost read
@@ -128073,6 +128372,8 @@ var AccessTokenGatewayClient = class {
128073
128372
  await this.sleepImpl(delay);
128074
128373
  continue;
128075
128374
  }
128375
+ const fallbackResponse2 = await this.attemptFallback(request, retryTransient);
128376
+ if (fallbackResponse2) return fallbackResponse2;
128076
128377
  throw error2;
128077
128378
  }
128078
128379
  if (response.ok) {
@@ -128094,6 +128395,8 @@ var AccessTokenGatewayClient = class {
128094
128395
  await this.sleepImpl(delay);
128095
128396
  continue;
128096
128397
  }
128398
+ const fallbackResponse = await this.attemptFallback(request, retryTransient);
128399
+ if (fallbackResponse) return fallbackResponse;
128097
128400
  throw this.toHttpError(request, response, body);
128098
128401
  }
128099
128402
  }
@@ -128202,6 +128505,331 @@ function validateCertMaterial(certBase64, keyBase64, passphrase) {
128202
128505
  }
128203
128506
  }
128204
128507
 
128508
+ // src/lib/repo/branch-decision.ts
128509
+ var import_node_fs3 = require("node:fs");
128510
+ var import_node_crypto3 = require("node:crypto");
128511
+ var ContractError = class extends Error {
128512
+ code;
128513
+ constructor(code, message) {
128514
+ super(`${code}: ${message}`);
128515
+ this.code = code;
128516
+ this.name = "ContractError";
128517
+ }
128518
+ };
128519
+ function clean(value) {
128520
+ const trimmed = (value ?? "").trim();
128521
+ return trimmed.length > 0 ? trimmed : void 0;
128522
+ }
128523
+ function stripRefPrefix(ref) {
128524
+ const raw = clean(ref);
128525
+ if (!raw) {
128526
+ return { kind: "unknown" };
128527
+ }
128528
+ if (raw.startsWith("refs/heads/")) {
128529
+ return { name: raw.slice("refs/heads/".length), kind: "branch" };
128530
+ }
128531
+ if (raw.startsWith("refs/tags/")) {
128532
+ return { name: raw.slice("refs/tags/".length), kind: "tag" };
128533
+ }
128534
+ if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
128535
+ return { kind: "unknown" };
128536
+ }
128537
+ return { name: raw, kind: "branch" };
128538
+ }
128539
+ function detectProvider(env) {
128540
+ if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
128541
+ if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
128542
+ if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
128543
+ if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
128544
+ return "unknown";
128545
+ }
128546
+ function readGithubEvent(env) {
128547
+ const path9 = clean(env.GITHUB_EVENT_PATH);
128548
+ if (!path9) return void 0;
128549
+ try {
128550
+ return JSON.parse((0, import_node_fs3.readFileSync)(path9, "utf8"));
128551
+ } catch {
128552
+ return void 0;
128553
+ }
128554
+ }
128555
+ function resolveBranchIdentity(env = process.env, overrides = {}) {
128556
+ const provider = detectProvider(env);
128557
+ const explicitDefault = clean(overrides.defaultBranch);
128558
+ let headBranch;
128559
+ let rawRef;
128560
+ let refKind;
128561
+ let isPrContext = false;
128562
+ let isForkPr = false;
128563
+ let defaultBranch = explicitDefault;
128564
+ let headSha;
128565
+ switch (provider) {
128566
+ case "github": {
128567
+ const event = readGithubEvent(env);
128568
+ headSha = clean(env.GITHUB_SHA);
128569
+ defaultBranch ??= clean(event?.repository?.default_branch);
128570
+ const headRef = clean(env.GITHUB_HEAD_REF);
128571
+ if (headRef) {
128572
+ isPrContext = true;
128573
+ headBranch = headRef;
128574
+ rawRef = clean(env.GITHUB_REF) ?? headRef;
128575
+ refKind = "branch";
128576
+ const headRepo = event?.pull_request?.head?.repo?.full_name;
128577
+ const baseRepo = event?.pull_request?.base?.repo?.full_name ?? event?.repository?.full_name;
128578
+ isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
128579
+ headSha = clean(event?.pull_request?.head?.sha) ?? headSha;
128580
+ } else {
128581
+ rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
128582
+ const parsed = stripRefPrefix(rawRef);
128583
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128584
+ refKind = parsed.kind;
128585
+ }
128586
+ break;
128587
+ }
128588
+ case "gitlab": {
128589
+ headSha = clean(env.CI_COMMIT_SHA);
128590
+ defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
128591
+ const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
128592
+ if (mrSource) {
128593
+ isPrContext = true;
128594
+ headBranch = mrSource;
128595
+ rawRef = mrSource;
128596
+ refKind = "branch";
128597
+ const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
128598
+ const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
128599
+ isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
128600
+ } else if (clean(env.CI_COMMIT_TAG)) {
128601
+ rawRef = clean(env.CI_COMMIT_TAG);
128602
+ refKind = "tag";
128603
+ } else {
128604
+ headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
128605
+ rawRef = headBranch;
128606
+ refKind = headBranch ? "branch" : "unknown";
128607
+ }
128608
+ break;
128609
+ }
128610
+ case "bitbucket": {
128611
+ headSha = clean(env.BITBUCKET_COMMIT);
128612
+ if (clean(env.BITBUCKET_TAG)) {
128613
+ rawRef = clean(env.BITBUCKET_TAG);
128614
+ refKind = "tag";
128615
+ } else {
128616
+ headBranch = clean(env.BITBUCKET_BRANCH);
128617
+ rawRef = headBranch;
128618
+ refKind = headBranch ? "branch" : "unknown";
128619
+ isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
128620
+ }
128621
+ break;
128622
+ }
128623
+ case "azure-devops": {
128624
+ headSha = clean(env.BUILD_SOURCEVERSION);
128625
+ const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
128626
+ if (prSource) {
128627
+ isPrContext = true;
128628
+ const parsed = stripRefPrefix(prSource);
128629
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128630
+ rawRef = prSource;
128631
+ refKind = parsed.kind;
128632
+ const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
128633
+ isForkPr = forkFlag?.toLowerCase() === "true";
128634
+ } else {
128635
+ rawRef = clean(env.BUILD_SOURCEBRANCH);
128636
+ const parsed = stripRefPrefix(rawRef);
128637
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128638
+ refKind = parsed.kind;
128639
+ }
128640
+ break;
128641
+ }
128642
+ default: {
128643
+ refKind = "unknown";
128644
+ }
128645
+ }
128646
+ if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
128647
+ refKind = "default-branch";
128648
+ }
128649
+ return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
128650
+ }
128651
+ function parseChannelRules(input) {
128652
+ const raw = clean(input);
128653
+ const rules = [];
128654
+ for (const part of raw ? raw.split(",") : []) {
128655
+ const entry = part.trim();
128656
+ if (!entry) continue;
128657
+ const eq = entry.indexOf("=");
128658
+ if (eq <= 0 || eq === entry.length - 1) {
128659
+ throw new ContractError(
128660
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128661
+ `channels entry "${entry}" must be <branch-or-glob>=<CODE>`
128662
+ );
128663
+ }
128664
+ const pattern = entry.slice(0, eq).trim();
128665
+ const code = entry.slice(eq + 1).trim().toUpperCase();
128666
+ if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
128667
+ throw new ContractError(
128668
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128669
+ `channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
128670
+ );
128671
+ }
128672
+ rules.push({ pattern, code });
128673
+ }
128674
+ if (!rules.some((rule) => rule.pattern === "release/*")) {
128675
+ rules.push({ pattern: "release/*", code: "RC" });
128676
+ }
128677
+ return rules;
128678
+ }
128679
+ function matchChannel(branch, rules) {
128680
+ for (const rule of rules) {
128681
+ if (rule.pattern.endsWith("*")) {
128682
+ const prefix = rule.pattern.slice(0, -1);
128683
+ if (branch.startsWith(prefix)) return rule;
128684
+ } else if (branch === rule.pattern) {
128685
+ return rule;
128686
+ }
128687
+ }
128688
+ return void 0;
128689
+ }
128690
+ function resolveBranchDecision(options) {
128691
+ const { strategy, identity } = options;
128692
+ const channels = options.channels ?? [];
128693
+ if (strategy === "legacy") {
128694
+ return {
128695
+ tier: "legacy",
128696
+ strategy,
128697
+ identity,
128698
+ canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
128699
+ reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
128700
+ };
128701
+ }
128702
+ const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
128703
+ if (!canonicalBranch) {
128704
+ throw new ContractError(
128705
+ "CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
128706
+ `cannot resolve the canonical branch on ${identity.provider} (no explicit canonical-branch input and the provider exposes no default-branch variable). Set the canonical-branch input.`
128707
+ );
128708
+ }
128709
+ if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
128710
+ return {
128711
+ tier: "gated",
128712
+ strategy,
128713
+ identity,
128714
+ canonicalBranch,
128715
+ reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
128716
+ };
128717
+ }
128718
+ if (identity.headBranch === canonicalBranch) {
128719
+ return {
128720
+ tier: "canonical",
128721
+ strategy,
128722
+ identity,
128723
+ canonicalBranch,
128724
+ reason: `head branch equals canonical branch ${canonicalBranch}`
128725
+ };
128726
+ }
128727
+ const channel = matchChannel(identity.headBranch, channels);
128728
+ if (channel) {
128729
+ return {
128730
+ tier: "channel",
128731
+ strategy,
128732
+ identity,
128733
+ canonicalBranch,
128734
+ channel,
128735
+ reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
128736
+ };
128737
+ }
128738
+ if (strategy === "preview") {
128739
+ if (identity.isForkPr) {
128740
+ return {
128741
+ tier: "gated",
128742
+ strategy,
128743
+ identity,
128744
+ canonicalBranch,
128745
+ reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
128746
+ };
128747
+ }
128748
+ return {
128749
+ tier: "preview",
128750
+ strategy,
128751
+ identity,
128752
+ canonicalBranch,
128753
+ reason: `branch ${identity.headBranch} under branch-strategy preview`
128754
+ };
128755
+ }
128756
+ return {
128757
+ tier: "gated",
128758
+ strategy,
128759
+ identity,
128760
+ canonicalBranch,
128761
+ reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
128762
+ };
128763
+ }
128764
+ var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
128765
+ function serializeBranchDecision(decision) {
128766
+ return JSON.stringify(decision);
128767
+ }
128768
+ function parseBranchDecision(raw) {
128769
+ const value = clean(raw);
128770
+ if (!value) return void 0;
128771
+ let parsed;
128772
+ try {
128773
+ parsed = JSON.parse(value);
128774
+ } catch {
128775
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
128776
+ }
128777
+ const candidate = parsed;
128778
+ const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
128779
+ if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
128780
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
128781
+ }
128782
+ return candidate;
128783
+ }
128784
+ function resolveEffectiveBranchDecision(options, env = process.env) {
128785
+ const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
128786
+ if (inherited) return inherited;
128787
+ return resolveBranchDecision(options);
128788
+ }
128789
+ var PREVIEW_SLUG_MAX = 30;
128790
+ function buildBranchSlug(rawBranch) {
128791
+ const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128792
+ const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
128793
+ const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
128794
+ if (!lossy) {
128795
+ return { suffix: truncated, slug: truncated, lossy };
128796
+ }
128797
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
128798
+ return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
128799
+ }
128800
+ function previewAssetName(baseName, rawBranch) {
128801
+ return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
128802
+ }
128803
+ function channelAssetName(baseName, code) {
128804
+ return `[${code}] ${baseName}`;
128805
+ }
128806
+ var MARKER_KEY = "x-pm-onboarding";
128807
+ function parseAssetMarker(description) {
128808
+ if (!description) return void 0;
128809
+ const index = description.indexOf(`${MARKER_KEY}:`);
128810
+ if (index === -1) return void 0;
128811
+ const jsonStart = description.indexOf("{", index);
128812
+ if (jsonStart === -1) return void 0;
128813
+ let depth = 0;
128814
+ for (let i = jsonStart; i < description.length; i += 1) {
128815
+ const ch = description[i];
128816
+ if (ch === "{") depth += 1;
128817
+ else if (ch === "}") {
128818
+ depth -= 1;
128819
+ if (depth === 0) {
128820
+ try {
128821
+ const parsed = JSON.parse(description.slice(jsonStart, i + 1));
128822
+ if (parsed && typeof parsed === "object" && parsed.repo && parsed.role) return parsed;
128823
+ } catch {
128824
+ return void 0;
128825
+ }
128826
+ return void 0;
128827
+ }
128828
+ }
128829
+ }
128830
+ return void 0;
128831
+ }
128832
+
128205
128833
  // src/index.ts
128206
128834
  function parseBooleanInput(value, defaultValue) {
128207
128835
  const normalized = String(value || "").trim().toLowerCase();
@@ -128220,16 +128848,16 @@ function getInput2(name, env = process.env) {
128220
128848
  const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
128221
128849
  const hasNormalized = normalizedRaw !== void 0;
128222
128850
  const hasRunner = runnerRaw !== void 0;
128851
+ const normalizedValue = normalizeInputValue(normalizedRaw);
128852
+ const runnerValue = normalizeInputValue(runnerRaw);
128223
128853
  if (hasNormalized && hasRunner) {
128224
- const normalizedValue = normalizeInputValue(normalizedRaw);
128225
- const runnerValue = normalizeInputValue(runnerRaw);
128226
- if (normalizedValue !== runnerValue) {
128854
+ if (normalizedValue && runnerValue && normalizedValue !== runnerValue) {
128227
128855
  throw new Error(
128228
128856
  `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
128229
128857
  );
128230
128858
  }
128231
128859
  }
128232
- return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
128860
+ return normalizedValue || runnerValue;
128233
128861
  }
128234
128862
  function hasInput(name, env = process.env) {
128235
128863
  const normalizedName = `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
@@ -128291,6 +128919,17 @@ function parseCredentialPreflight(value) {
128291
128919
  `Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
128292
128920
  );
128293
128921
  }
128922
+ function parseBranchStrategy(value) {
128923
+ const definition = postmanRepoSyncActionContract.inputs["branch-strategy"];
128924
+ const allowed = definition.allowedValues ?? [];
128925
+ const normalized = String(value || "").trim() || (definition.default ?? "legacy");
128926
+ if (allowed.includes(normalized)) {
128927
+ return normalized;
128928
+ }
128929
+ throw new Error(
128930
+ `Unsupported branch-strategy "${normalized}". Supported values: ${allowed.join(", ")}`
128931
+ );
128932
+ }
128294
128933
  function normalizeReleaseLabel(value) {
128295
128934
  const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128296
128935
  return cleaned;
@@ -128335,6 +128974,7 @@ function resolveInputs(env = process.env) {
128335
128974
  smokeCollectionId: getInput2("smoke-collection-id", env),
128336
128975
  contractCollectionId: getInput2("contract-collection-id", env),
128337
128976
  specId: getInput2("spec-id", env),
128977
+ specContentChanged: parseBooleanInput(getInput2("spec-content-changed", env), true),
128338
128978
  specPath: getInput2("spec-path", env),
128339
128979
  collectionSyncMode: normalizeCollectionSyncMode(getInput2("collection-sync-mode", env) || "refresh"),
128340
128980
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
@@ -128357,6 +128997,10 @@ function resolveInputs(env = process.env) {
128357
128997
  postmanApiKey: getInput2("postman-api-key", env),
128358
128998
  postmanAccessToken: getInput2("postman-access-token", env),
128359
128999
  credentialPreflight: parseCredentialPreflight(getInput2("credential-preflight", env)),
129000
+ branchStrategy: parseBranchStrategy(getInput2("branch-strategy", env)),
129001
+ canonicalBranch: getInput2("canonical-branch", env) || void 0,
129002
+ channels: getInput2("channels", env) || void 0,
129003
+ previewTtlDays: Math.max(1, Number.parseInt(getInput2("preview-ttl", env) || "30", 10) || 30),
128360
129004
  adoToken: getInput2("ado-token", env) || normalizeInputValue(env.SYSTEM_ACCESSTOKEN),
128361
129005
  githubToken: getInput2("github-token", env),
128362
129006
  ghFallbackToken: getInput2("gh-fallback-token", env),
@@ -128379,10 +129023,45 @@ function resolveInputs(env = process.env) {
128379
129023
  postmanStack,
128380
129024
  postmanApiBase: endpointProfile.apiBaseUrl,
128381
129025
  postmanBifrostBase: endpointProfile.bifrostBaseUrl,
129026
+ postmanFallbackBase: endpointProfile.fallbackBaseUrl,
128382
129027
  postmanCliInstallUrl: endpointProfile.cliInstallUrl,
128383
129028
  postmanIapubBase: endpointProfile.iapubBaseUrl
128384
129029
  };
128385
129030
  }
129031
+ function buildBranchAssetMarker(decision, inputs, now = /* @__PURE__ */ new Date()) {
129032
+ if (decision.tier !== "preview" && decision.tier !== "channel") {
129033
+ return void 0;
129034
+ }
129035
+ const rawBranch = decision.identity.headBranch;
129036
+ const repo = inputs.repoUrl || inputs.repository;
129037
+ if (!rawBranch || !repo) {
129038
+ return void 0;
129039
+ }
129040
+ const ttlMs = inputs.previewTtlDays * 24 * 60 * 60 * 1e3;
129041
+ return {
129042
+ repo,
129043
+ rawBranch,
129044
+ sanitizedBranch: buildBranchSlug(rawBranch).suffix,
129045
+ role: decision.tier,
129046
+ headSha: decision.identity.headSha,
129047
+ createdAt: now.toISOString(),
129048
+ lastSyncedAt: now.toISOString(),
129049
+ ...decision.tier === "preview" ? { expiresAt: new Date(now.getTime() + ttlMs).toISOString() } : {}
129050
+ };
129051
+ }
129052
+ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POSTMAN_BRANCH_ASSET_IDS === "owned") {
129053
+ if (decision.tier === "legacy" || decision.tier === "canonical" || branchOwnedIds) return;
129054
+ const provided = [
129055
+ ["baseline-collection-id", inputs.baselineCollectionId],
129056
+ ["smoke-collection-id", inputs.smokeCollectionId],
129057
+ ["contract-collection-id", inputs.contractCollectionId]
129058
+ ].filter(([, value]) => Boolean(value));
129059
+ if (provided.length > 0) {
129060
+ throw new Error(
129061
+ `CONTRACT_BRANCH_CANONICAL_WRITE: a ${decision.tier} repo-sync run cannot accept explicit collection IDs (${provided.map(([name]) => name).join(", ")}). Run bootstrap in the same branch-aware pipeline so it can produce branch-owned IDs.`
129062
+ );
129063
+ }
129064
+ }
128386
129065
  function buildEnvironmentValues(envName, baseUrl) {
128387
129066
  return [
128388
129067
  { key: "baseUrl", value: baseUrl, type: "default" },
@@ -128395,12 +129074,48 @@ function buildEnvironmentValues(envName, baseUrl) {
128395
129074
  ];
128396
129075
  }
128397
129076
  var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
129077
+ var RESOURCES_STATE_VERSION = 2;
129078
+ var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
129079
+ var StateUnreadableError = class extends Error {
129080
+ code = "CONTRACT_STATE_UNREADABLE";
129081
+ constructor(message) {
129082
+ super(`CONTRACT_STATE_UNREADABLE: ${message}`);
129083
+ this.name = "StateUnreadableError";
129084
+ }
129085
+ };
128398
129086
  function readResourcesState() {
129087
+ let raw;
128399
129088
  try {
128400
- return load((0, import_node_fs3.readFileSync)(".postman/resources.yaml", "utf8"));
129089
+ raw = (0, import_node_fs4.readFileSync)(".postman/resources.yaml", "utf8");
128401
129090
  } catch {
128402
129091
  return null;
128403
129092
  }
129093
+ let parsed;
129094
+ try {
129095
+ parsed = load(raw);
129096
+ } catch (error2) {
129097
+ throw new StateUnreadableError(
129098
+ `.postman/resources.yaml exists but is not parseable YAML (${error2 instanceof Error ? error2.message : String(error2)}). Fix or delete the file; refusing to treat tracked state as absent.`
129099
+ );
129100
+ }
129101
+ if (parsed === null || parsed === void 0) {
129102
+ return null;
129103
+ }
129104
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
129105
+ throw new StateUnreadableError(
129106
+ ".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
129107
+ );
129108
+ }
129109
+ const state = parsed;
129110
+ if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
129111
+ throw new StateUnreadableError(
129112
+ `.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
129113
+ );
129114
+ }
129115
+ if (state.canonical && !state.cloudResources) {
129116
+ state.cloudResources = { ...state.canonical };
129117
+ }
129118
+ return state;
128404
129119
  }
128405
129120
  function findCloudResourceId(map, matcher) {
128406
129121
  if (!map) {
@@ -128435,7 +129150,7 @@ function isOpenApiSpecFile(filePath) {
128435
129150
  return false;
128436
129151
  }
128437
129152
  try {
128438
- const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8");
129153
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
128439
129154
  const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
128440
129155
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
128441
129156
  return false;
@@ -128464,7 +129179,7 @@ function scanLocalSpecReferences(baseDir = ".") {
128464
129179
  const found = /* @__PURE__ */ new Set();
128465
129180
  const refs = [];
128466
129181
  const visit2 = (currentDir) => {
128467
- for (const entry of (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true })) {
129182
+ for (const entry of (0, import_node_fs4.readdirSync)(currentDir, { withFileTypes: true })) {
128468
129183
  if (ignoredDirs.has(entry.name)) {
128469
129184
  continue;
128470
129185
  }
@@ -128494,7 +129209,7 @@ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
128494
129209
  const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
128495
129210
  if (normalizedExplicitPath) {
128496
129211
  const explicitFullPath = path8.resolve(normalizedExplicitPath);
128497
- if ((0, import_node_fs3.existsSync)(explicitFullPath) && (0, import_node_fs3.statSync)(explicitFullPath).isFile()) {
129212
+ if ((0, import_node_fs4.existsSync)(explicitFullPath) && (0, import_node_fs4.statSync)(explicitFullPath).isFile()) {
128498
129213
  return {
128499
129214
  repoRelativePath: normalizedExplicitPath,
128500
129215
  configRelativePath: normalizeToPosix(path8.join("..", normalizedExplicitPath))
@@ -128521,7 +129236,11 @@ function createOutputs(inputs) {
128521
129236
  "mock-url": "",
128522
129237
  "monitor-id": "",
128523
129238
  "repo-sync-summary-json": "{}",
128524
- "commit-sha": ""
129239
+ "commit-sha": "",
129240
+ "sync-status": "",
129241
+ "branch-decision": "",
129242
+ "spec-version-tag": "",
129243
+ "spec-version-url": ""
128525
129244
  };
128526
129245
  }
128527
129246
  function readActionInputs(actionCore) {
@@ -128566,6 +129285,10 @@ function readActionInputs(actionCore) {
128566
129285
  INPUT_POSTMAN_API_KEY: postmanApiKey,
128567
129286
  INPUT_POSTMAN_ACCESS_TOKEN: postmanAccessToken,
128568
129287
  INPUT_CREDENTIAL_PREFLIGHT: readInput(actionCore, "credential-preflight") || "warn",
129288
+ INPUT_BRANCH_STRATEGY: readInput(actionCore, "branch-strategy"),
129289
+ INPUT_CANONICAL_BRANCH: readInput(actionCore, "canonical-branch"),
129290
+ INPUT_CHANNELS: readInput(actionCore, "channels"),
129291
+ INPUT_PREVIEW_TTL: readInput(actionCore, "preview-ttl"),
128569
129292
  INPUT_ADO_TOKEN: adoToken,
128570
129293
  INPUT_GITHUB_TOKEN: githubToken,
128571
129294
  INPUT_GH_FALLBACK_TOKEN: ghFallbackToken,
@@ -128680,7 +129403,7 @@ async function persistSslSecrets(inputs, actionCore, actionExec, repository, env
128680
129403
  );
128681
129404
  }
128682
129405
  }
128683
- async function upsertEnvironments(inputs, dependencies, resourcesState) {
129406
+ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
128684
129407
  const envUids = {
128685
129408
  ...getEnvironmentUidsFromResources(resourcesState),
128686
129409
  ...inputs.environmentUids
@@ -128690,7 +129413,6 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128690
129413
  }
128691
129414
  for (const envName of inputs.environments) {
128692
129415
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
128693
- const values = buildEnvironmentValues(envName, runtimeUrl);
128694
129416
  const displayName = `${inputs.projectName} - ${envName}`;
128695
129417
  let existingUid = String(envUids[envName] || "").trim();
128696
129418
  if (!existingUid) {
@@ -128706,10 +129428,27 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128706
129428
  }
128707
129429
  }
128708
129430
  if (existingUid) {
128709
- await dependencies.postman.updateEnvironment(existingUid, displayName, values);
129431
+ let marker = assetMarker;
129432
+ if (marker) {
129433
+ try {
129434
+ const existing = await dependencies.postman.getEnvironment(existingUid);
129435
+ const values3 = existing.data?.values ?? existing.values ?? [];
129436
+ const prior = values3.find((value) => value.key === "x-pm-onboarding")?.value;
129437
+ const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
129438
+ if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
129439
+ marker = { ...marker, createdAt: priorMarker.createdAt };
129440
+ }
129441
+ } catch {
129442
+ }
129443
+ }
129444
+ const values2 = buildEnvironmentValues(envName, runtimeUrl);
129445
+ if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
129446
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
128710
129447
  envUids[envName] = existingUid;
128711
129448
  continue;
128712
129449
  }
129450
+ const values = buildEnvironmentValues(envName, runtimeUrl);
129451
+ if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
128713
129452
  envUids[envName] = await dependencies.postman.createEnvironment(
128714
129453
  inputs.workspaceId,
128715
129454
  displayName,
@@ -128719,7 +129458,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128719
129458
  return envUids;
128720
129459
  }
128721
129460
  function ensureDir(path9) {
128722
- (0, import_node_fs3.mkdirSync)(path9, { recursive: true });
129461
+ (0, import_node_fs4.mkdirSync)(path9, { recursive: true });
128723
129462
  }
128724
129463
  function getCollectionDirectoryName(kind, projectName) {
128725
129464
  if (kind === "Baseline") {
@@ -128756,7 +129495,7 @@ function stripVolatileFields(obj) {
128756
129495
  }
128757
129496
  function writeJsonFile(path9, content, normalize3 = false) {
128758
129497
  const data = normalize3 ? stripVolatileFields(content) : content;
128759
- (0, import_node_fs3.writeFileSync)(path9, JSON.stringify(data, null, 2));
129498
+ (0, import_node_fs4.writeFileSync)(path9, JSON.stringify(data, null, 2));
128760
129499
  }
128761
129500
  function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
128762
129501
  if (specSyncMode !== "version") {
@@ -128780,31 +129519,30 @@ function resolveDurableWorkspaceId(options) {
128780
129519
  }
128781
129520
  return prior === candidate ? prior : void 0;
128782
129521
  }
128783
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
128784
- const manifest = {};
129522
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState) {
129523
+ const manifest = { ...priorState ?? {} };
129524
+ delete manifest.version;
129525
+ delete manifest.workspace;
129526
+ delete manifest.localResources;
129527
+ delete manifest.cloudResources;
129528
+ delete manifest.canonical;
129529
+ manifest.version = RESOURCES_STATE_VERSION;
128785
129530
  if (workspaceId) {
128786
129531
  manifest.workspace = { id: workspaceId };
128787
129532
  }
128788
- const localResources = {};
128789
129533
  const cloudResources = {};
128790
129534
  const collectionKeys = Object.keys(collectionMap);
128791
129535
  if (collectionKeys.length > 0) {
128792
- localResources.collections = collectionKeys;
128793
129536
  cloudResources.collections = collectionMap;
128794
129537
  }
128795
129538
  const envEntries = Object.entries(envMap);
128796
129539
  if (envEntries.length > 0) {
128797
- localResources.environments = envEntries.map(
128798
- ([envName]) => `../${artifactDir}/environments/${envName}.postman_environment.json`
128799
- );
128800
129540
  cloudResources.environments = {};
128801
129541
  for (const [envName, envUid] of envEntries) {
128802
129542
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
128803
129543
  }
128804
129544
  }
128805
- if (localSpecRefs.length > 0) {
128806
- localResources.specs = localSpecRefs;
128807
- }
129545
+ void localSpecRefs;
128808
129546
  const specs = { ...existingSpecs || {} };
128809
129547
  if (mappedSpecRef && specId) {
128810
129548
  specs[mappedSpecRef] = specId;
@@ -128812,11 +129550,8 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128812
129550
  if (Object.keys(specs).length > 0) {
128813
129551
  cloudResources.specs = specs;
128814
129552
  }
128815
- if (Object.keys(localResources).length > 0) {
128816
- manifest.localResources = localResources;
128817
- }
128818
129553
  if (Object.keys(cloudResources).length > 0) {
128819
- manifest.cloudResources = cloudResources;
129554
+ manifest.canonical = cloudResources;
128820
129555
  }
128821
129556
  return dump(manifest, {
128822
129557
  lineWidth: -1,
@@ -128857,21 +129592,21 @@ function assertPathWithinCwd(targetPath, fieldName) {
128857
129592
  if (!rawPath || hasControlCharacter2(originalPath) || path8.isAbsolute(rawPath) || path8.win32.isAbsolute(rawPath) || segments.includes("..") || rawPath.startsWith(":") || hasControlCharacter2(rawPath)) {
128858
129593
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128859
129594
  }
128860
- const base = (0, import_node_fs3.realpathSync)(process.cwd());
129595
+ const base = (0, import_node_fs4.realpathSync)(process.cwd());
128861
129596
  const resolved = path8.resolve(base, rawPath);
128862
129597
  const relative4 = path8.relative(base, resolved);
128863
129598
  if (relative4.startsWith("..") || path8.isAbsolute(relative4)) {
128864
129599
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128865
129600
  }
128866
129601
  let existingPath = resolved;
128867
- while (!(0, import_node_fs3.existsSync)(existingPath)) {
129602
+ while (!(0, import_node_fs4.existsSync)(existingPath)) {
128868
129603
  const parent = path8.dirname(existingPath);
128869
129604
  if (parent === existingPath) {
128870
129605
  break;
128871
129606
  }
128872
129607
  existingPath = parent;
128873
129608
  }
128874
- const realExistingPath = (0, import_node_fs3.realpathSync)(existingPath);
129609
+ const realExistingPath = (0, import_node_fs4.realpathSync)(existingPath);
128875
129610
  const realRelative = path8.relative(base, realExistingPath);
128876
129611
  if (realRelative.startsWith("..") || path8.isAbsolute(realRelative)) {
128877
129612
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
@@ -128899,8 +129634,8 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128899
129634
  ensureDir(specsDir);
128900
129635
  ensureDir(".postman");
128901
129636
  const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
128902
- if (!(0, import_node_fs3.existsSync)(globalsFilePath)) {
128903
- (0, import_node_fs3.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
129637
+ if (!(0, import_node_fs4.existsSync)(globalsFilePath)) {
129638
+ (0, import_node_fs4.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
128904
129639
  }
128905
129640
  if (inputs.generateCiWorkflow) {
128906
129641
  const ciDir = inputs.ciWorkflowPath.split("/").slice(0, -1).join("/");
@@ -128947,7 +129682,7 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128947
129682
  workspaceLinkEnabled: inputs.workspaceLinkEnabled,
128948
129683
  workspaceLinkStatus: options.workspaceLinkStatus
128949
129684
  });
128950
- (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
129685
+ (0, import_node_fs4.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
128951
129686
  durableWorkspaceId,
128952
129687
  manifestCollections,
128953
129688
  envUids,
@@ -128955,10 +129690,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128955
129690
  discoveredSpecs.map((spec) => spec.configRelativePath),
128956
129691
  mappedSpecCloudKey,
128957
129692
  inputs.specId || void 0,
128958
- options.existingSpecs
129693
+ options.existingSpecs,
129694
+ options.priorState
128959
129695
  ));
128960
129696
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
128961
- (0, import_node_fs3.writeFileSync)(
129697
+ (0, import_node_fs4.writeFileSync)(
128962
129698
  ".postman/workflows.yaml",
128963
129699
  buildSpecCollectionWorkflowManifest(
128964
129700
  mappedSpec.configRelativePath,
@@ -129003,19 +129739,29 @@ async function commitAndPushGeneratedFiles(inputs, dependencies) {
129003
129739
  const dir = parts.slice(0, -1).join("/");
129004
129740
  ensureDir(dir);
129005
129741
  }
129006
- (0, import_node_fs3.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129742
+ (0, import_node_fs4.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129743
+ if (inputs.provider === "github" || inputs.provider === "unknown") {
129744
+ const gcPath = ".github/workflows/postman-preview-gc.yml";
129745
+ if (inputs.ciWorkflowPath.endsWith(".github/workflows/ci.yml") || inputs.ciWorkflowPath === ".github/workflows/ci.yml") {
129746
+ ensureDir(".github/workflows");
129747
+ (0, import_node_fs4.writeFileSync)(gcPath, renderGcWorkflowTemplate());
129748
+ }
129749
+ }
129007
129750
  }
129008
129751
  if (!dependencies.repoMutation || inputs.repoWriteMode === "none") {
129009
129752
  return { commitSha: "", resolvedCurrentRef: "", pushed: false };
129010
129753
  }
129011
129754
  const provisionPath = ".github/workflows/provision.yml";
129012
- const provisionExists = inputs.provider === "github" && (0, import_node_fs3.existsSync)(provisionPath);
129755
+ const provisionExists = inputs.provider === "github" && (0, import_node_fs4.existsSync)(provisionPath);
129756
+ const gcWorkflowPath = ".github/workflows/postman-preview-gc.yml";
129757
+ const gcExists = inputs.generateCiWorkflow && (0, import_node_fs4.existsSync)(gcWorkflowPath);
129013
129758
  const stagePaths = [
129014
129759
  inputs.artifactDir,
129015
129760
  ".postman",
129016
129761
  inputs.generateCiWorkflow ? inputs.ciWorkflowPath : null,
129762
+ gcExists ? gcWorkflowPath : null,
129017
129763
  provisionExists ? provisionPath : null
129018
- ].filter((entry) => typeof entry === "string" && ((0, import_node_fs3.existsSync)(entry) || entry === provisionPath));
129764
+ ].filter((entry) => typeof entry === "string" && ((0, import_node_fs4.existsSync)(entry) || entry === provisionPath));
129019
129765
  if (stagePaths.length === 0) {
129020
129766
  dependencies.core.info("No generated repository paths were found; skipping repo mutation.");
129021
129767
  return {
@@ -129063,6 +129809,31 @@ async function runRepoSync(inputs, dependencies) {
129063
129809
  }
129064
129810
  }
129065
129811
  async function runRepoSyncInner(inputs, dependencies) {
129812
+ const branchDecision = decideBranchTier(inputs);
129813
+ assertBranchAssetIds(inputs, branchDecision);
129814
+ const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
129815
+ if (!isCanonicalWriter) {
129816
+ if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
129817
+ inputs = {
129818
+ ...inputs,
129819
+ projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch),
129820
+ workspaceLinkEnabled: false,
129821
+ environmentSyncEnabled: false,
129822
+ repoWriteMode: "none"
129823
+ };
129824
+ } else if (branchDecision.tier === "channel" && branchDecision.channel) {
129825
+ inputs = {
129826
+ ...inputs,
129827
+ projectName: channelAssetName(inputs.projectName, branchDecision.channel.code),
129828
+ workspaceLinkEnabled: false,
129829
+ environmentSyncEnabled: false,
129830
+ repoWriteMode: "none"
129831
+ };
129832
+ }
129833
+ dependencies.core.info(
129834
+ `branch-aware sync: ${branchDecision.tier} run \u2014 branch-scoped asset set "${inputs.projectName}", no workspace repo-link mutation, no state write-back`
129835
+ );
129836
+ }
129066
129837
  const outputs = createOutputs(inputs);
129067
129838
  const versionRequested = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
129068
129839
  const releaseLabel = deriveReleaseLabel(inputs);
@@ -129070,7 +129841,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129070
129841
  throw new Error("release-label is required when collection-sync-mode or spec-sync-mode is version");
129071
129842
  }
129072
129843
  const assetProjectName = createAssetProjectName(inputs, releaseLabel);
129073
- const resourcesState = readResourcesState();
129844
+ const trackedState = readResourcesState();
129845
+ const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
129074
129846
  if (resourcesState) {
129075
129847
  if (!inputs.workspaceId && resourcesState.workspace?.id) {
129076
129848
  inputs.workspaceId = resourcesState.workspace.id;
@@ -129096,7 +129868,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129096
129868
  }
129097
129869
  }
129098
129870
  }
129099
- const envUids = await upsertEnvironments(inputs, dependencies, resourcesState);
129871
+ const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
129872
+ const envUids = await upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker);
129100
129873
  outputs["environment-uids-json"] = JSON.stringify(envUids);
129101
129874
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
129102
129875
  const associations = Object.entries(envUids).map(([envName, envUid]) => ({
@@ -129152,11 +129925,23 @@ async function runRepoSyncInner(inputs, dependencies) {
129152
129925
  }
129153
129926
  }
129154
129927
  if (!resolvedMockUrl) {
129155
- const mock = await dependencies.postman.createMock(
129156
- inputs.workspaceId,
129157
- mockName,
129158
- inputs.baselineCollectionId,
129159
- mockEnvUid
129928
+ const mock = await retry(
129929
+ () => dependencies.postman.createMock(
129930
+ inputs.workspaceId,
129931
+ mockName,
129932
+ inputs.baselineCollectionId,
129933
+ mockEnvUid
129934
+ ),
129935
+ {
129936
+ maxAttempts: 3,
129937
+ delayMs: 2e3,
129938
+ backoffMultiplier: 2,
129939
+ onRetry: ({ attempt, error: error2 }) => {
129940
+ dependencies.core.warning(
129941
+ `Mock create attempt ${attempt} failed (${error2 instanceof Error ? error2.message : String(error2)}); retrying`
129942
+ );
129943
+ }
129944
+ }
129160
129945
  );
129161
129946
  resolvedMockUrl = mock.url;
129162
129947
  dependencies.core.info(`Created new mock: ${resolvedMockUrl}`);
@@ -129225,16 +130010,19 @@ async function runRepoSyncInner(inputs, dependencies) {
129225
130010
  );
129226
130011
  } catch (error2) {
129227
130012
  outputs["workspace-link-status"] = "failed";
129228
- dependencies.core.warning(
129229
- `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`
129230
- );
130013
+ const message = `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
130014
+ if (branchDecision.tier === "canonical") {
130015
+ throw new Error(message, { cause: error2 });
130016
+ }
130017
+ dependencies.core.warning(message);
129231
130018
  }
129232
130019
  }
129233
130020
  await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
129234
130021
  workspaceLinkStatus: outputs["workspace-link-status"],
129235
130022
  priorWorkspaceId: resourcesState?.workspace?.id,
129236
130023
  existingSpecs: resourcesState?.cloudResources?.specs,
129237
- releaseLabel
130024
+ releaseLabel,
130025
+ priorState: resourcesState
129238
130026
  });
129239
130027
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
129240
130028
  outputs["commit-sha"] = commit.commitSha;
@@ -129242,6 +130030,38 @@ async function runRepoSyncInner(inputs, dependencies) {
129242
130030
  outputs["resolved-current-ref"] = commit.resolvedCurrentRef;
129243
130031
  }
129244
130032
  outputs["repo-sync-summary-json"] = createRepoSummary(outputs, envUids, commit.pushed);
130033
+ if (branchDecision.tier === "canonical" && inputs.specId && inputs.specContentChanged !== false && dependencies.postman.tagSpecVersion) {
130034
+ const shortSha = (branchDecision.identity.headSha ?? "").slice(0, 7);
130035
+ const tagName = inputs.releaseLabel ? `${inputs.releaseLabel}${shortSha ? ` (${shortSha})` : ""}` : shortSha || `sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
130036
+ try {
130037
+ const tag = await dependencies.postman.tagSpecVersion(inputs.specId, tagName);
130038
+ outputs["spec-version-tag"] = tag.name || tagName;
130039
+ if (tag.id) {
130040
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(tag.id)}&versionLabel=${encodeURIComponent(tag.name || tagName)}`;
130041
+ }
130042
+ dependencies.core.info(`Tagged spec version at finalize: ${tag.name || tagName}`);
130043
+ } catch (error2) {
130044
+ const status = error2.status;
130045
+ if (status === 409) {
130046
+ const tags = await dependencies.postman.listSpecVersionTags?.(inputs.specId).catch(() => []) ?? [];
130047
+ const latest = tags[0];
130048
+ if (latest && (latest.name === tagName || /\([0-9a-f]{7}\)$/.test(latest.name) || /^[0-9a-f]{7}$/.test(latest.name))) {
130049
+ outputs["spec-version-tag"] = latest.name;
130050
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(latest.id)}&versionLabel=${encodeURIComponent(latest.name)}`;
130051
+ dependencies.core.info(`Latest changelog group already tagged as "${latest.name}"; adopting.`);
130052
+ } else {
130053
+ dependencies.core.warning("Latest changelog group already carries a hand-applied tag; leaving it in place.");
130054
+ }
130055
+ } else {
130056
+ dependencies.core.warning(`Spec version tagging failed (non-fatal): ${error2 instanceof Error ? error2.message : String(error2)}`);
130057
+ }
130058
+ }
130059
+ }
130060
+ if (inputs.branchStrategy !== "legacy" || process.env[BRANCH_DECISION_ENV]) {
130061
+ const decision = decideBranchTier(inputs);
130062
+ outputs["sync-status"] = "synced";
130063
+ outputs["branch-decision"] = serializeBranchDecision(decision);
130064
+ }
129245
130065
  for (const [name, value] of Object.entries(outputs)) {
129246
130066
  dependencies.core.setOutput(name, value);
129247
130067
  }
@@ -129345,6 +130165,8 @@ async function resolvePostmanApiKeyAndTeamId(inputs, actionCore, actionExec, mas
129345
130165
  const gateway = new AccessTokenGatewayClient({
129346
130166
  tokenProvider,
129347
130167
  bifrostBaseUrl: inputs.postmanBifrostBase,
130168
+ // No fallback on the org-mode probe: the expected non-org 400 must
130169
+ // surface verbatim, not be re-fired against the /_api alias.
129348
130170
  secretMasker: masker
129349
130171
  });
129350
130172
  const squads = await gateway.getSquads(teamId);
@@ -129400,6 +130222,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129400
130222
  const gateway = new AccessTokenGatewayClient({
129401
130223
  tokenProvider,
129402
130224
  bifrostBaseUrl: inputs.postmanBifrostBase,
130225
+ fallbackBaseUrl: inputs.postmanFallbackBase,
129403
130226
  teamId: resolved.teamId,
129404
130227
  orgMode: inputs.orgMode,
129405
130228
  secretMasker: masker
@@ -129431,7 +130254,19 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129431
130254
  listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
129432
130255
  monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
129433
130256
  findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
129434
- runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets)
130257
+ runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
130258
+ listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
130259
+ // GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
130260
+ deleteEnvironment: gatewayAssets.deleteEnvironment.bind(gatewayAssets),
130261
+ deleteMock: gatewayAssets.deleteMock.bind(gatewayAssets),
130262
+ deleteMonitor: gatewayAssets.deleteMonitor.bind(gatewayAssets),
130263
+ deleteCollection: gatewayAssets.deleteCollection.bind(gatewayAssets),
130264
+ listSpecifications: gatewayAssets.listSpecifications.bind(gatewayAssets),
130265
+ getSpecContent: gatewayAssets.getSpecContent.bind(gatewayAssets),
130266
+ listSpecCollections: gatewayAssets.listSpecCollections.bind(gatewayAssets),
130267
+ deleteSpec: gatewayAssets.deleteSpec.bind(gatewayAssets),
130268
+ tagSpecVersion: gatewayAssets.tagSpecVersion.bind(gatewayAssets),
130269
+ listSpecVersionTags: gatewayAssets.listSpecVersionTags.bind(gatewayAssets)
129435
130270
  };
129436
130271
  const repoMutation = repository && (inputs.repoWriteMode === "commit-only" || inputs.repoWriteMode === "commit-and-push") ? new RepoMutationService({
129437
130272
  provider: inputs.provider,
@@ -129468,8 +130303,43 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129468
130303
  repoMutation
129469
130304
  };
129470
130305
  }
130306
+ function decideBranchTier(inputs, env = process.env) {
130307
+ return resolveEffectiveBranchDecision(
130308
+ {
130309
+ strategy: inputs.branchStrategy,
130310
+ identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
130311
+ canonicalBranch: inputs.canonicalBranch,
130312
+ channels: parseChannelRules(inputs.channels)
130313
+ },
130314
+ env
130315
+ );
130316
+ }
130317
+ function runGatedSkip(inputs, decision, actionCore) {
130318
+ actionCore.info(
130319
+ `branch-aware sync: gated run (${decision.reason}) \u2014 repo-sync skipped, zero workspace writes`
130320
+ );
130321
+ const outputs = createOutputs(inputs);
130322
+ outputs["sync-status"] = "skipped-branch-gate";
130323
+ outputs["branch-decision"] = serializeBranchDecision(decision);
130324
+ outputs["repo-sync-summary-json"] = JSON.stringify({
130325
+ status: "skipped-branch-gate",
130326
+ reason: decision.reason
130327
+ });
130328
+ for (const [name, value] of Object.entries(outputs)) {
130329
+ actionCore.setOutput(name, value);
130330
+ }
130331
+ return outputs;
130332
+ }
129471
130333
  async function runAction(actionCore = core_exports, actionExec = exec_exports) {
129472
130334
  const inputs = readActionInputs(actionCore);
130335
+ const branchDecision = decideBranchTier(inputs);
130336
+ if (branchDecision.tier === "gated") {
130337
+ return runGatedSkip(inputs, branchDecision, actionCore);
130338
+ }
130339
+ if (branchDecision.tier !== "legacy") {
130340
+ actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
130341
+ process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
130342
+ }
129473
130343
  await mintAccessTokenIfNeeded(inputs, {
129474
130344
  info: (message) => actionCore.info(message),
129475
130345
  warning: (message) => actionCore.warning(message)
@@ -129526,14 +130396,19 @@ async function runAction(actionCore = core_exports, actionExec = exec_exports) {
129526
130396
  }
129527
130397
  // Annotate the CommonJS export names for ESM import in node:
129528
130398
  0 && (module.exports = {
130399
+ StateUnreadableError,
130400
+ assertBranchAssetIds,
129529
130401
  assertPathWithinCwd,
130402
+ buildBranchAssetMarker,
129530
130403
  createRepoSyncDependencies,
130404
+ decideBranchTier,
129531
130405
  getInput,
129532
130406
  hasInput,
129533
130407
  readActionInputs,
129534
130408
  resolveInputs,
129535
130409
  resolvePostmanApiKeyAndTeamId,
129536
130410
  runAction,
130411
+ runGatedSkip,
129537
130412
  runRepoSync
129538
130413
  });
129539
130414
  /*! Bundled license information: