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

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
@@ -125201,6 +125206,9 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125201
125206
  " branches: [main]",
125202
125207
  " schedule:",
125203
125208
  ' - cron: "0 */6 * * *"',
125209
+ "concurrency:",
125210
+ " group: postman-onboard-${{ github.head_ref || github.ref_name }}",
125211
+ " cancel-in-progress: false",
125204
125212
  "jobs:",
125205
125213
  " test:",
125206
125214
  " runs-on: ubuntu-latest",
@@ -125217,7 +125225,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125217
125225
  " ruby <<'RUBY'",
125218
125226
  " require 'yaml'",
125219
125227
  " config = YAML.load_file('.postman/resources.yaml') || {}",
125220
- " cloud = config.fetch('cloudResources', {})",
125228
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
125221
125229
  " collections = cloud.fetch('collections', {})",
125222
125230
  " environments = cloud.fetch('environments', {})",
125223
125231
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
@@ -125415,6 +125423,58 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion) {
125415
125423
  ""
125416
125424
  ];
125417
125425
  }
125426
+ function renderGcWorkflowTemplate() {
125427
+ return [
125428
+ "name: Postman Preview GC",
125429
+ "on:",
125430
+ " delete:",
125431
+ ' branches: ["**"]',
125432
+ " pull_request:",
125433
+ " types: [closed]",
125434
+ " schedule:",
125435
+ ' - cron: "0 2 * * *"',
125436
+ " workflow_dispatch:",
125437
+ " inputs:",
125438
+ " branch:",
125439
+ " description: Branch name to GC (optional, otherwise sweep by TTL/branch-existence)",
125440
+ " required: false",
125441
+ "concurrency:",
125442
+ " group: postman-preview-gc-${{ github.ref_name }}",
125443
+ " cancel-in-progress: false",
125444
+ "jobs:",
125445
+ " gc:",
125446
+ " runs-on: ubuntu-latest",
125447
+ " if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
125448
+ " steps:",
125449
+ " - uses: actions/checkout@v5",
125450
+ " with:",
125451
+ " fetch-depth: 0",
125452
+ " - name: Run Postman preview GC (provider-neutral cli.cjs gc)",
125453
+ " env:",
125454
+ " POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}",
125455
+ " POSTMAN_ACCESS_TOKEN: ${{ secrets.POSTMAN_ACCESS_TOKEN }}",
125456
+ " POSTMAN_WORKSPACE_ID: ${{ vars.POSTMAN_WORKSPACE_ID }}",
125457
+ " REPO_URL: https://github.com/${{ github.repository }}",
125458
+ " run: |",
125459
+ " set -euo pipefail",
125460
+ " # The repo-sync action bundles a provider-neutral gc command (cli.cjs gc)",
125461
+ " # that uses the Postman access token for inventory/deletion and the",
125462
+ " # provider ambient credential (GITHUB_TOKEN via git ls-remote) for branch existence.",
125463
+ " # Daily scheduled run is the retention executor (TTL contract of last resort).",
125464
+ ' if [ -n "${{ inputs.branch }}" ]; then',
125465
+ ' npx @postman-cse/onboarding-repo-sync gc --branch "${{ inputs.branch }}" --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125466
+ " else",
125467
+ ' npx @postman-cse/onboarding-repo-sync gc --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125468
+ " fi",
125469
+ " - name: Orphan audit summary (job summary)",
125470
+ " if: always()",
125471
+ " run: |",
125472
+ ' echo "### Preview GC orphan audit" >> "$GITHUB_STEP_SUMMARY"',
125473
+ ' echo "Marker-guarded deletion only \u2014 strangers (no marker) are never deleted. See gc-summary-json for structured counts." >> "$GITHUB_STEP_SUMMARY"',
125474
+ ""
125475
+ ].join("\n");
125476
+ }
125477
+ var GC_WORKFLOW_TEMPLATE = renderGcWorkflowTemplate();
125418
125478
  function getCiWorkflowTemplate(provider, options = {}) {
125419
125479
  if (provider === "azure-devops") {
125420
125480
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
@@ -126985,6 +127045,53 @@ function createInternalIntegrationAdapter(options) {
126985
127045
  return new BifrostInternalIntegrationAdapter(options);
126986
127046
  }
126987
127047
 
127048
+ // src/lib/retry.ts
127049
+ function sleep(delayMs) {
127050
+ return new Promise((resolve3) => {
127051
+ setTimeout(resolve3, delayMs);
127052
+ });
127053
+ }
127054
+ function normalizeRetryOptions(options) {
127055
+ return {
127056
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
127057
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
127058
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
127059
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
127060
+ onRetry: options.onRetry ?? (async () => void 0),
127061
+ shouldRetry: options.shouldRetry ?? (() => true),
127062
+ sleep: options.sleep ?? sleep
127063
+ };
127064
+ }
127065
+ async function retry(operation, options = {}) {
127066
+ const normalized = normalizeRetryOptions(options);
127067
+ let nextDelayMs = normalized.delayMs;
127068
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
127069
+ try {
127070
+ return await operation();
127071
+ } catch (error2) {
127072
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
127073
+ attempt,
127074
+ maxAttempts: normalized.maxAttempts
127075
+ });
127076
+ if (!shouldRetry) {
127077
+ throw error2;
127078
+ }
127079
+ await normalized.onRetry({
127080
+ attempt,
127081
+ maxAttempts: normalized.maxAttempts,
127082
+ delayMs: nextDelayMs,
127083
+ error: error2
127084
+ });
127085
+ await normalized.sleep(nextDelayMs);
127086
+ nextDelayMs = Math.min(
127087
+ normalized.maxDelayMs,
127088
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
127089
+ );
127090
+ }
127091
+ }
127092
+ throw new Error("Retry exhausted without returning or throwing");
127093
+ }
127094
+
126988
127095
  // src/contracts.ts
126989
127096
  var postmanRepoSyncActionContract = {
126990
127097
  name: "postman-repo-sync-action",
@@ -127154,6 +127261,25 @@ var postmanRepoSyncActionContract = {
127154
127261
  default: "warn",
127155
127262
  allowedValues: ["enforce", "warn"]
127156
127263
  },
127264
+ "branch-strategy": {
127265
+ 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.",
127266
+ required: false,
127267
+ default: "legacy",
127268
+ allowedValues: ["legacy", "preview", "publish-gate"]
127269
+ },
127270
+ "canonical-branch": {
127271
+ 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.",
127272
+ required: false
127273
+ },
127274
+ "channels": {
127275
+ 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.',
127276
+ required: false
127277
+ },
127278
+ "preview-ttl": {
127279
+ 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).",
127280
+ required: false,
127281
+ default: "30"
127282
+ },
127157
127283
  "github-token": {
127158
127284
  description: "GitHub token used for repo variable persistence and commits.",
127159
127285
  required: false
@@ -127191,6 +127317,11 @@ var postmanRepoSyncActionContract = {
127191
127317
  description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
127192
127318
  required: false
127193
127319
  },
127320
+ "spec-content-changed": {
127321
+ description: "Whether bootstrap changed canonical spec content; controls final native Spec Hub tag publication.",
127322
+ required: false,
127323
+ default: "true"
127324
+ },
127194
127325
  "spec-path": {
127195
127326
  description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
127196
127327
  required: false
@@ -127235,6 +127366,18 @@ var postmanRepoSyncActionContract = {
127235
127366
  },
127236
127367
  "commit-sha": {
127237
127368
  description: "Commit SHA produced by repo-write-mode, if any."
127369
+ },
127370
+ "sync-status": {
127371
+ description: "Branch-aware sync status: synced, skipped-branch-gate, or empty under branch-strategy legacy."
127372
+ },
127373
+ "branch-decision": {
127374
+ description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
127375
+ },
127376
+ "spec-version-tag": {
127377
+ description: "Native Spec Hub version tag created after successful canonical repo-sync finalization."
127378
+ },
127379
+ "spec-version-url": {
127380
+ description: "Read-only URL for the tagged Spec Hub snapshot."
127238
127381
  }
127239
127382
  },
127240
127383
  behavior: {
@@ -127302,53 +127445,6 @@ var PostmanAssetsClient = class {
127302
127445
  }
127303
127446
  };
127304
127447
 
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
127448
  // src/lib/postman/postman-gateway-assets-client.ts
127353
127449
  var MAX_CREATE_FLIGHTS = 256;
127354
127450
  var createFlights = /* @__PURE__ */ new Map();
@@ -127374,6 +127470,84 @@ var PostmanGatewayAssetsClient = class {
127374
127470
  if (!envelope) return null;
127375
127471
  return this.asRecord(envelope.data) ?? envelope;
127376
127472
  }
127473
+ /** Native Spec Hub tags attach to the latest changelog group. */
127474
+ async tagSpecVersion(specId, name) {
127475
+ const trimmed = name.trim().slice(0, 255);
127476
+ const response = await this.gateway.requestJson({
127477
+ service: "specification",
127478
+ method: "post",
127479
+ path: `/specifications/${specId}/tags`,
127480
+ body: { name: trimmed }
127481
+ });
127482
+ const record = this.dataOf(response) ?? {};
127483
+ return { id: String(record.id ?? "").trim(), name: String(record.name ?? trimmed).trim() };
127484
+ }
127485
+ async listSpecVersionTags(specId) {
127486
+ const response = await this.gateway.requestJson({
127487
+ service: "specification",
127488
+ method: "get",
127489
+ path: `/specifications/${specId}/tags`,
127490
+ query: { limit: "50" }
127491
+ });
127492
+ const data = Array.isArray(response?.data) ? response.data : [];
127493
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({
127494
+ id: String(entry.id ?? "").trim(),
127495
+ // listTags returns `message`; createTag returns `name`. Accept both.
127496
+ name: String(entry.name ?? entry.message ?? "").trim()
127497
+ })).filter((entry) => entry.id || entry.name);
127498
+ }
127499
+ async deleteCollection(collectionUid) {
127500
+ const bareId = String(collectionUid).split("-").slice(-5).join("-") || collectionUid;
127501
+ await this.gateway.requestJson({
127502
+ service: "collection",
127503
+ method: "delete",
127504
+ path: `/v3/collections/${bareId}`
127505
+ });
127506
+ }
127507
+ async listSpecifications(workspaceId) {
127508
+ const response = await this.gateway.requestJson({
127509
+ service: "specification",
127510
+ method: "get",
127511
+ path: `/specifications?containerType=workspace&containerId=${workspaceId}`
127512
+ });
127513
+ const data = Array.isArray(response?.data) ? response.data : [];
127514
+ 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);
127515
+ }
127516
+ async getSpecContent(specId) {
127517
+ const files = await this.gateway.requestJson({
127518
+ service: "specification",
127519
+ method: "get",
127520
+ path: `/specifications/${specId}/files`
127521
+ });
127522
+ const entries = Array.isArray(files?.data) ? files.data : [];
127523
+ const root = entries.map((entry) => this.asRecord(entry)).find((entry) => entry?.type === "ROOT") ?? entries.map((entry) => this.asRecord(entry)).find((entry) => entry !== null);
127524
+ const fileId = String(root?.id ?? "").trim();
127525
+ if (!fileId) return void 0;
127526
+ const file = await this.gateway.requestJson({
127527
+ service: "specification",
127528
+ method: "get",
127529
+ path: `/specifications/${specId}/files/${fileId}`,
127530
+ query: { fields: "content" }
127531
+ });
127532
+ const record = this.dataOf(file);
127533
+ return typeof record?.content === "string" ? record.content : void 0;
127534
+ }
127535
+ async listSpecCollections(specId) {
127536
+ const response = await this.gateway.requestJson({
127537
+ service: "specification",
127538
+ method: "get",
127539
+ path: `/specifications/${specId}/collections`
127540
+ });
127541
+ const data = Array.isArray(response?.data) ? response.data : [];
127542
+ 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);
127543
+ }
127544
+ async deleteSpec(specId) {
127545
+ await this.gateway.requestJson({
127546
+ service: "specification",
127547
+ method: "delete",
127548
+ path: `/specifications/${specId}`
127549
+ });
127550
+ }
127377
127551
  idOf(record) {
127378
127552
  if (!record) return "";
127379
127553
  const id = record.uid ?? record.id;
@@ -127658,6 +127832,9 @@ var PostmanGatewayAssetsClient = class {
127658
127832
  path: `/mocks?workspace=${ws}`,
127659
127833
  body
127660
127834
  },
127835
+ // Unsafe create: never blind re-POST. An ESOCKETTIMEDOUT after accept
127836
+ // may still have created the mock; reconcile via discovery below and
127837
+ // let the orchestrator retry the whole create-or-adopt cycle.
127661
127838
  { retryTransient: false }
127662
127839
  );
127663
127840
  const record = this.dataOf(response);
@@ -127696,6 +127873,33 @@ var PostmanGatewayAssetsClient = class {
127696
127873
  environment: String(m.environment ?? "")
127697
127874
  }));
127698
127875
  }
127876
+ /**
127877
+ * Delete an environment through the sync service (GC path). The path id is
127878
+ * the bare model id (public uid tail), mirroring updateEnvironment.
127879
+ */
127880
+ async deleteEnvironment(uid) {
127881
+ await this.gateway.requestJson({
127882
+ service: "sync",
127883
+ method: "delete",
127884
+ path: `/environment/${this.toModelId(uid)}`
127885
+ });
127886
+ }
127887
+ /** Delete a mock server (GC path). */
127888
+ async deleteMock(uid) {
127889
+ await this.gateway.requestJson({
127890
+ service: "mock",
127891
+ method: "delete",
127892
+ path: `/mocks/${this.toModelId(uid)}`
127893
+ });
127894
+ }
127895
+ /** Delete a collection-based monitor (jobTemplate) (GC path). */
127896
+ async deleteMonitor(uid) {
127897
+ await this.gateway.requestJson({
127898
+ service: "monitors",
127899
+ method: "delete",
127900
+ path: `/jobTemplates/${this.toModelId(uid)}`
127901
+ });
127902
+ }
127699
127903
  async mockExists(uid) {
127700
127904
  try {
127701
127905
  await this.gateway.requestJson({
@@ -128202,6 +128406,331 @@ function validateCertMaterial(certBase64, keyBase64, passphrase) {
128202
128406
  }
128203
128407
  }
128204
128408
 
128409
+ // src/lib/repo/branch-decision.ts
128410
+ var import_node_fs3 = require("node:fs");
128411
+ var import_node_crypto3 = require("node:crypto");
128412
+ var ContractError = class extends Error {
128413
+ code;
128414
+ constructor(code, message) {
128415
+ super(`${code}: ${message}`);
128416
+ this.code = code;
128417
+ this.name = "ContractError";
128418
+ }
128419
+ };
128420
+ function clean(value) {
128421
+ const trimmed = (value ?? "").trim();
128422
+ return trimmed.length > 0 ? trimmed : void 0;
128423
+ }
128424
+ function stripRefPrefix(ref) {
128425
+ const raw = clean(ref);
128426
+ if (!raw) {
128427
+ return { kind: "unknown" };
128428
+ }
128429
+ if (raw.startsWith("refs/heads/")) {
128430
+ return { name: raw.slice("refs/heads/".length), kind: "branch" };
128431
+ }
128432
+ if (raw.startsWith("refs/tags/")) {
128433
+ return { name: raw.slice("refs/tags/".length), kind: "tag" };
128434
+ }
128435
+ if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
128436
+ return { kind: "unknown" };
128437
+ }
128438
+ return { name: raw, kind: "branch" };
128439
+ }
128440
+ function detectProvider(env) {
128441
+ if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
128442
+ if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
128443
+ if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
128444
+ if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
128445
+ return "unknown";
128446
+ }
128447
+ function readGithubEvent(env) {
128448
+ const path9 = clean(env.GITHUB_EVENT_PATH);
128449
+ if (!path9) return void 0;
128450
+ try {
128451
+ return JSON.parse((0, import_node_fs3.readFileSync)(path9, "utf8"));
128452
+ } catch {
128453
+ return void 0;
128454
+ }
128455
+ }
128456
+ function resolveBranchIdentity(env = process.env, overrides = {}) {
128457
+ const provider = detectProvider(env);
128458
+ const explicitDefault = clean(overrides.defaultBranch);
128459
+ let headBranch;
128460
+ let rawRef;
128461
+ let refKind;
128462
+ let isPrContext = false;
128463
+ let isForkPr = false;
128464
+ let defaultBranch = explicitDefault;
128465
+ let headSha;
128466
+ switch (provider) {
128467
+ case "github": {
128468
+ const event = readGithubEvent(env);
128469
+ headSha = clean(env.GITHUB_SHA);
128470
+ defaultBranch ??= clean(event?.repository?.default_branch);
128471
+ const headRef = clean(env.GITHUB_HEAD_REF);
128472
+ if (headRef) {
128473
+ isPrContext = true;
128474
+ headBranch = headRef;
128475
+ rawRef = clean(env.GITHUB_REF) ?? headRef;
128476
+ refKind = "branch";
128477
+ const headRepo = event?.pull_request?.head?.repo?.full_name;
128478
+ const baseRepo = event?.pull_request?.base?.repo?.full_name ?? event?.repository?.full_name;
128479
+ isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
128480
+ headSha = clean(event?.pull_request?.head?.sha) ?? headSha;
128481
+ } else {
128482
+ rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
128483
+ const parsed = stripRefPrefix(rawRef);
128484
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128485
+ refKind = parsed.kind;
128486
+ }
128487
+ break;
128488
+ }
128489
+ case "gitlab": {
128490
+ headSha = clean(env.CI_COMMIT_SHA);
128491
+ defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
128492
+ const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
128493
+ if (mrSource) {
128494
+ isPrContext = true;
128495
+ headBranch = mrSource;
128496
+ rawRef = mrSource;
128497
+ refKind = "branch";
128498
+ const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
128499
+ const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
128500
+ isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
128501
+ } else if (clean(env.CI_COMMIT_TAG)) {
128502
+ rawRef = clean(env.CI_COMMIT_TAG);
128503
+ refKind = "tag";
128504
+ } else {
128505
+ headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
128506
+ rawRef = headBranch;
128507
+ refKind = headBranch ? "branch" : "unknown";
128508
+ }
128509
+ break;
128510
+ }
128511
+ case "bitbucket": {
128512
+ headSha = clean(env.BITBUCKET_COMMIT);
128513
+ if (clean(env.BITBUCKET_TAG)) {
128514
+ rawRef = clean(env.BITBUCKET_TAG);
128515
+ refKind = "tag";
128516
+ } else {
128517
+ headBranch = clean(env.BITBUCKET_BRANCH);
128518
+ rawRef = headBranch;
128519
+ refKind = headBranch ? "branch" : "unknown";
128520
+ isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
128521
+ }
128522
+ break;
128523
+ }
128524
+ case "azure-devops": {
128525
+ headSha = clean(env.BUILD_SOURCEVERSION);
128526
+ const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
128527
+ if (prSource) {
128528
+ isPrContext = true;
128529
+ const parsed = stripRefPrefix(prSource);
128530
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128531
+ rawRef = prSource;
128532
+ refKind = parsed.kind;
128533
+ const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
128534
+ isForkPr = forkFlag?.toLowerCase() === "true";
128535
+ } else {
128536
+ rawRef = clean(env.BUILD_SOURCEBRANCH);
128537
+ const parsed = stripRefPrefix(rawRef);
128538
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128539
+ refKind = parsed.kind;
128540
+ }
128541
+ break;
128542
+ }
128543
+ default: {
128544
+ refKind = "unknown";
128545
+ }
128546
+ }
128547
+ if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
128548
+ refKind = "default-branch";
128549
+ }
128550
+ return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
128551
+ }
128552
+ function parseChannelRules(input) {
128553
+ const raw = clean(input);
128554
+ const rules = [];
128555
+ for (const part of raw ? raw.split(",") : []) {
128556
+ const entry = part.trim();
128557
+ if (!entry) continue;
128558
+ const eq = entry.indexOf("=");
128559
+ if (eq <= 0 || eq === entry.length - 1) {
128560
+ throw new ContractError(
128561
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128562
+ `channels entry "${entry}" must be <branch-or-glob>=<CODE>`
128563
+ );
128564
+ }
128565
+ const pattern = entry.slice(0, eq).trim();
128566
+ const code = entry.slice(eq + 1).trim().toUpperCase();
128567
+ if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
128568
+ throw new ContractError(
128569
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128570
+ `channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
128571
+ );
128572
+ }
128573
+ rules.push({ pattern, code });
128574
+ }
128575
+ if (!rules.some((rule) => rule.pattern === "release/*")) {
128576
+ rules.push({ pattern: "release/*", code: "RC" });
128577
+ }
128578
+ return rules;
128579
+ }
128580
+ function matchChannel(branch, rules) {
128581
+ for (const rule of rules) {
128582
+ if (rule.pattern.endsWith("*")) {
128583
+ const prefix = rule.pattern.slice(0, -1);
128584
+ if (branch.startsWith(prefix)) return rule;
128585
+ } else if (branch === rule.pattern) {
128586
+ return rule;
128587
+ }
128588
+ }
128589
+ return void 0;
128590
+ }
128591
+ function resolveBranchDecision(options) {
128592
+ const { strategy, identity } = options;
128593
+ const channels = options.channels ?? [];
128594
+ if (strategy === "legacy") {
128595
+ return {
128596
+ tier: "legacy",
128597
+ strategy,
128598
+ identity,
128599
+ canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
128600
+ reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
128601
+ };
128602
+ }
128603
+ const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
128604
+ if (!canonicalBranch) {
128605
+ throw new ContractError(
128606
+ "CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
128607
+ `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.`
128608
+ );
128609
+ }
128610
+ if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
128611
+ return {
128612
+ tier: "gated",
128613
+ strategy,
128614
+ identity,
128615
+ canonicalBranch,
128616
+ reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
128617
+ };
128618
+ }
128619
+ if (identity.headBranch === canonicalBranch) {
128620
+ return {
128621
+ tier: "canonical",
128622
+ strategy,
128623
+ identity,
128624
+ canonicalBranch,
128625
+ reason: `head branch equals canonical branch ${canonicalBranch}`
128626
+ };
128627
+ }
128628
+ const channel = matchChannel(identity.headBranch, channels);
128629
+ if (channel) {
128630
+ return {
128631
+ tier: "channel",
128632
+ strategy,
128633
+ identity,
128634
+ canonicalBranch,
128635
+ channel,
128636
+ reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
128637
+ };
128638
+ }
128639
+ if (strategy === "preview") {
128640
+ if (identity.isForkPr) {
128641
+ return {
128642
+ tier: "gated",
128643
+ strategy,
128644
+ identity,
128645
+ canonicalBranch,
128646
+ reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
128647
+ };
128648
+ }
128649
+ return {
128650
+ tier: "preview",
128651
+ strategy,
128652
+ identity,
128653
+ canonicalBranch,
128654
+ reason: `branch ${identity.headBranch} under branch-strategy preview`
128655
+ };
128656
+ }
128657
+ return {
128658
+ tier: "gated",
128659
+ strategy,
128660
+ identity,
128661
+ canonicalBranch,
128662
+ reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
128663
+ };
128664
+ }
128665
+ var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
128666
+ function serializeBranchDecision(decision) {
128667
+ return JSON.stringify(decision);
128668
+ }
128669
+ function parseBranchDecision(raw) {
128670
+ const value = clean(raw);
128671
+ if (!value) return void 0;
128672
+ let parsed;
128673
+ try {
128674
+ parsed = JSON.parse(value);
128675
+ } catch {
128676
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
128677
+ }
128678
+ const candidate = parsed;
128679
+ const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
128680
+ if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
128681
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
128682
+ }
128683
+ return candidate;
128684
+ }
128685
+ function resolveEffectiveBranchDecision(options, env = process.env) {
128686
+ const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
128687
+ if (inherited) return inherited;
128688
+ return resolveBranchDecision(options);
128689
+ }
128690
+ var PREVIEW_SLUG_MAX = 30;
128691
+ function buildBranchSlug(rawBranch) {
128692
+ const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128693
+ const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
128694
+ const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
128695
+ if (!lossy) {
128696
+ return { suffix: truncated, slug: truncated, lossy };
128697
+ }
128698
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
128699
+ return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
128700
+ }
128701
+ function previewAssetName(baseName, rawBranch) {
128702
+ return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
128703
+ }
128704
+ function channelAssetName(baseName, code) {
128705
+ return `[${code}] ${baseName}`;
128706
+ }
128707
+ var MARKER_KEY = "x-pm-onboarding";
128708
+ function parseAssetMarker(description) {
128709
+ if (!description) return void 0;
128710
+ const index = description.indexOf(`${MARKER_KEY}:`);
128711
+ if (index === -1) return void 0;
128712
+ const jsonStart = description.indexOf("{", index);
128713
+ if (jsonStart === -1) return void 0;
128714
+ let depth = 0;
128715
+ for (let i = jsonStart; i < description.length; i += 1) {
128716
+ const ch = description[i];
128717
+ if (ch === "{") depth += 1;
128718
+ else if (ch === "}") {
128719
+ depth -= 1;
128720
+ if (depth === 0) {
128721
+ try {
128722
+ const parsed = JSON.parse(description.slice(jsonStart, i + 1));
128723
+ if (parsed && typeof parsed === "object" && parsed.repo && parsed.role) return parsed;
128724
+ } catch {
128725
+ return void 0;
128726
+ }
128727
+ return void 0;
128728
+ }
128729
+ }
128730
+ }
128731
+ return void 0;
128732
+ }
128733
+
128205
128734
  // src/index.ts
128206
128735
  function parseBooleanInput(value, defaultValue) {
128207
128736
  const normalized = String(value || "").trim().toLowerCase();
@@ -128220,16 +128749,16 @@ function getInput2(name, env = process.env) {
128220
128749
  const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
128221
128750
  const hasNormalized = normalizedRaw !== void 0;
128222
128751
  const hasRunner = runnerRaw !== void 0;
128752
+ const normalizedValue = normalizeInputValue(normalizedRaw);
128753
+ const runnerValue = normalizeInputValue(runnerRaw);
128223
128754
  if (hasNormalized && hasRunner) {
128224
- const normalizedValue = normalizeInputValue(normalizedRaw);
128225
- const runnerValue = normalizeInputValue(runnerRaw);
128226
- if (normalizedValue !== runnerValue) {
128755
+ if (normalizedValue && runnerValue && normalizedValue !== runnerValue) {
128227
128756
  throw new Error(
128228
128757
  `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
128229
128758
  );
128230
128759
  }
128231
128760
  }
128232
- return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
128761
+ return normalizedValue || runnerValue;
128233
128762
  }
128234
128763
  function hasInput(name, env = process.env) {
128235
128764
  const normalizedName = `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
@@ -128291,6 +128820,17 @@ function parseCredentialPreflight(value) {
128291
128820
  `Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
128292
128821
  );
128293
128822
  }
128823
+ function parseBranchStrategy(value) {
128824
+ const definition = postmanRepoSyncActionContract.inputs["branch-strategy"];
128825
+ const allowed = definition.allowedValues ?? [];
128826
+ const normalized = String(value || "").trim() || (definition.default ?? "legacy");
128827
+ if (allowed.includes(normalized)) {
128828
+ return normalized;
128829
+ }
128830
+ throw new Error(
128831
+ `Unsupported branch-strategy "${normalized}". Supported values: ${allowed.join(", ")}`
128832
+ );
128833
+ }
128294
128834
  function normalizeReleaseLabel(value) {
128295
128835
  const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128296
128836
  return cleaned;
@@ -128335,6 +128875,7 @@ function resolveInputs(env = process.env) {
128335
128875
  smokeCollectionId: getInput2("smoke-collection-id", env),
128336
128876
  contractCollectionId: getInput2("contract-collection-id", env),
128337
128877
  specId: getInput2("spec-id", env),
128878
+ specContentChanged: parseBooleanInput(getInput2("spec-content-changed", env), true),
128338
128879
  specPath: getInput2("spec-path", env),
128339
128880
  collectionSyncMode: normalizeCollectionSyncMode(getInput2("collection-sync-mode", env) || "refresh"),
128340
128881
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
@@ -128357,6 +128898,10 @@ function resolveInputs(env = process.env) {
128357
128898
  postmanApiKey: getInput2("postman-api-key", env),
128358
128899
  postmanAccessToken: getInput2("postman-access-token", env),
128359
128900
  credentialPreflight: parseCredentialPreflight(getInput2("credential-preflight", env)),
128901
+ branchStrategy: parseBranchStrategy(getInput2("branch-strategy", env)),
128902
+ canonicalBranch: getInput2("canonical-branch", env) || void 0,
128903
+ channels: getInput2("channels", env) || void 0,
128904
+ previewTtlDays: Math.max(1, Number.parseInt(getInput2("preview-ttl", env) || "30", 10) || 30),
128360
128905
  adoToken: getInput2("ado-token", env) || normalizeInputValue(env.SYSTEM_ACCESSTOKEN),
128361
128906
  githubToken: getInput2("github-token", env),
128362
128907
  ghFallbackToken: getInput2("gh-fallback-token", env),
@@ -128383,6 +128928,40 @@ function resolveInputs(env = process.env) {
128383
128928
  postmanIapubBase: endpointProfile.iapubBaseUrl
128384
128929
  };
128385
128930
  }
128931
+ function buildBranchAssetMarker(decision, inputs, now = /* @__PURE__ */ new Date()) {
128932
+ if (decision.tier !== "preview" && decision.tier !== "channel") {
128933
+ return void 0;
128934
+ }
128935
+ const rawBranch = decision.identity.headBranch;
128936
+ const repo = inputs.repoUrl || inputs.repository;
128937
+ if (!rawBranch || !repo) {
128938
+ return void 0;
128939
+ }
128940
+ const ttlMs = inputs.previewTtlDays * 24 * 60 * 60 * 1e3;
128941
+ return {
128942
+ repo,
128943
+ rawBranch,
128944
+ sanitizedBranch: buildBranchSlug(rawBranch).suffix,
128945
+ role: decision.tier,
128946
+ headSha: decision.identity.headSha,
128947
+ createdAt: now.toISOString(),
128948
+ lastSyncedAt: now.toISOString(),
128949
+ ...decision.tier === "preview" ? { expiresAt: new Date(now.getTime() + ttlMs).toISOString() } : {}
128950
+ };
128951
+ }
128952
+ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POSTMAN_BRANCH_ASSET_IDS === "owned") {
128953
+ if (decision.tier === "legacy" || decision.tier === "canonical" || branchOwnedIds) return;
128954
+ const provided = [
128955
+ ["baseline-collection-id", inputs.baselineCollectionId],
128956
+ ["smoke-collection-id", inputs.smokeCollectionId],
128957
+ ["contract-collection-id", inputs.contractCollectionId]
128958
+ ].filter(([, value]) => Boolean(value));
128959
+ if (provided.length > 0) {
128960
+ throw new Error(
128961
+ `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.`
128962
+ );
128963
+ }
128964
+ }
128386
128965
  function buildEnvironmentValues(envName, baseUrl) {
128387
128966
  return [
128388
128967
  { key: "baseUrl", value: baseUrl, type: "default" },
@@ -128395,12 +128974,48 @@ function buildEnvironmentValues(envName, baseUrl) {
128395
128974
  ];
128396
128975
  }
128397
128976
  var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
128977
+ var RESOURCES_STATE_VERSION = 2;
128978
+ var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
128979
+ var StateUnreadableError = class extends Error {
128980
+ code = "CONTRACT_STATE_UNREADABLE";
128981
+ constructor(message) {
128982
+ super(`CONTRACT_STATE_UNREADABLE: ${message}`);
128983
+ this.name = "StateUnreadableError";
128984
+ }
128985
+ };
128398
128986
  function readResourcesState() {
128987
+ let raw;
128399
128988
  try {
128400
- return load((0, import_node_fs3.readFileSync)(".postman/resources.yaml", "utf8"));
128989
+ raw = (0, import_node_fs4.readFileSync)(".postman/resources.yaml", "utf8");
128401
128990
  } catch {
128402
128991
  return null;
128403
128992
  }
128993
+ let parsed;
128994
+ try {
128995
+ parsed = load(raw);
128996
+ } catch (error2) {
128997
+ throw new StateUnreadableError(
128998
+ `.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.`
128999
+ );
129000
+ }
129001
+ if (parsed === null || parsed === void 0) {
129002
+ return null;
129003
+ }
129004
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
129005
+ throw new StateUnreadableError(
129006
+ ".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
129007
+ );
129008
+ }
129009
+ const state = parsed;
129010
+ if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
129011
+ throw new StateUnreadableError(
129012
+ `.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
129013
+ );
129014
+ }
129015
+ if (state.canonical && !state.cloudResources) {
129016
+ state.cloudResources = { ...state.canonical };
129017
+ }
129018
+ return state;
128404
129019
  }
128405
129020
  function findCloudResourceId(map, matcher) {
128406
129021
  if (!map) {
@@ -128435,7 +129050,7 @@ function isOpenApiSpecFile(filePath) {
128435
129050
  return false;
128436
129051
  }
128437
129052
  try {
128438
- const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8");
129053
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
128439
129054
  const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
128440
129055
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
128441
129056
  return false;
@@ -128464,7 +129079,7 @@ function scanLocalSpecReferences(baseDir = ".") {
128464
129079
  const found = /* @__PURE__ */ new Set();
128465
129080
  const refs = [];
128466
129081
  const visit2 = (currentDir) => {
128467
- for (const entry of (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true })) {
129082
+ for (const entry of (0, import_node_fs4.readdirSync)(currentDir, { withFileTypes: true })) {
128468
129083
  if (ignoredDirs.has(entry.name)) {
128469
129084
  continue;
128470
129085
  }
@@ -128494,7 +129109,7 @@ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
128494
129109
  const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
128495
129110
  if (normalizedExplicitPath) {
128496
129111
  const explicitFullPath = path8.resolve(normalizedExplicitPath);
128497
- if ((0, import_node_fs3.existsSync)(explicitFullPath) && (0, import_node_fs3.statSync)(explicitFullPath).isFile()) {
129112
+ if ((0, import_node_fs4.existsSync)(explicitFullPath) && (0, import_node_fs4.statSync)(explicitFullPath).isFile()) {
128498
129113
  return {
128499
129114
  repoRelativePath: normalizedExplicitPath,
128500
129115
  configRelativePath: normalizeToPosix(path8.join("..", normalizedExplicitPath))
@@ -128521,7 +129136,11 @@ function createOutputs(inputs) {
128521
129136
  "mock-url": "",
128522
129137
  "monitor-id": "",
128523
129138
  "repo-sync-summary-json": "{}",
128524
- "commit-sha": ""
129139
+ "commit-sha": "",
129140
+ "sync-status": "",
129141
+ "branch-decision": "",
129142
+ "spec-version-tag": "",
129143
+ "spec-version-url": ""
128525
129144
  };
128526
129145
  }
128527
129146
  function readActionInputs(actionCore) {
@@ -128566,6 +129185,10 @@ function readActionInputs(actionCore) {
128566
129185
  INPUT_POSTMAN_API_KEY: postmanApiKey,
128567
129186
  INPUT_POSTMAN_ACCESS_TOKEN: postmanAccessToken,
128568
129187
  INPUT_CREDENTIAL_PREFLIGHT: readInput(actionCore, "credential-preflight") || "warn",
129188
+ INPUT_BRANCH_STRATEGY: readInput(actionCore, "branch-strategy"),
129189
+ INPUT_CANONICAL_BRANCH: readInput(actionCore, "canonical-branch"),
129190
+ INPUT_CHANNELS: readInput(actionCore, "channels"),
129191
+ INPUT_PREVIEW_TTL: readInput(actionCore, "preview-ttl"),
128569
129192
  INPUT_ADO_TOKEN: adoToken,
128570
129193
  INPUT_GITHUB_TOKEN: githubToken,
128571
129194
  INPUT_GH_FALLBACK_TOKEN: ghFallbackToken,
@@ -128680,7 +129303,7 @@ async function persistSslSecrets(inputs, actionCore, actionExec, repository, env
128680
129303
  );
128681
129304
  }
128682
129305
  }
128683
- async function upsertEnvironments(inputs, dependencies, resourcesState) {
129306
+ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
128684
129307
  const envUids = {
128685
129308
  ...getEnvironmentUidsFromResources(resourcesState),
128686
129309
  ...inputs.environmentUids
@@ -128690,7 +129313,6 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128690
129313
  }
128691
129314
  for (const envName of inputs.environments) {
128692
129315
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
128693
- const values = buildEnvironmentValues(envName, runtimeUrl);
128694
129316
  const displayName = `${inputs.projectName} - ${envName}`;
128695
129317
  let existingUid = String(envUids[envName] || "").trim();
128696
129318
  if (!existingUid) {
@@ -128706,10 +129328,27 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128706
129328
  }
128707
129329
  }
128708
129330
  if (existingUid) {
128709
- await dependencies.postman.updateEnvironment(existingUid, displayName, values);
129331
+ let marker = assetMarker;
129332
+ if (marker) {
129333
+ try {
129334
+ const existing = await dependencies.postman.getEnvironment(existingUid);
129335
+ const values3 = existing.data?.values ?? existing.values ?? [];
129336
+ const prior = values3.find((value) => value.key === "x-pm-onboarding")?.value;
129337
+ const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
129338
+ if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
129339
+ marker = { ...marker, createdAt: priorMarker.createdAt };
129340
+ }
129341
+ } catch {
129342
+ }
129343
+ }
129344
+ const values2 = buildEnvironmentValues(envName, runtimeUrl);
129345
+ if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
129346
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
128710
129347
  envUids[envName] = existingUid;
128711
129348
  continue;
128712
129349
  }
129350
+ const values = buildEnvironmentValues(envName, runtimeUrl);
129351
+ if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
128713
129352
  envUids[envName] = await dependencies.postman.createEnvironment(
128714
129353
  inputs.workspaceId,
128715
129354
  displayName,
@@ -128719,7 +129358,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128719
129358
  return envUids;
128720
129359
  }
128721
129360
  function ensureDir(path9) {
128722
- (0, import_node_fs3.mkdirSync)(path9, { recursive: true });
129361
+ (0, import_node_fs4.mkdirSync)(path9, { recursive: true });
128723
129362
  }
128724
129363
  function getCollectionDirectoryName(kind, projectName) {
128725
129364
  if (kind === "Baseline") {
@@ -128756,7 +129395,7 @@ function stripVolatileFields(obj) {
128756
129395
  }
128757
129396
  function writeJsonFile(path9, content, normalize3 = false) {
128758
129397
  const data = normalize3 ? stripVolatileFields(content) : content;
128759
- (0, import_node_fs3.writeFileSync)(path9, JSON.stringify(data, null, 2));
129398
+ (0, import_node_fs4.writeFileSync)(path9, JSON.stringify(data, null, 2));
128760
129399
  }
128761
129400
  function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
128762
129401
  if (specSyncMode !== "version") {
@@ -128780,31 +129419,30 @@ function resolveDurableWorkspaceId(options) {
128780
129419
  }
128781
129420
  return prior === candidate ? prior : void 0;
128782
129421
  }
128783
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
128784
- const manifest = {};
129422
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState) {
129423
+ const manifest = { ...priorState ?? {} };
129424
+ delete manifest.version;
129425
+ delete manifest.workspace;
129426
+ delete manifest.localResources;
129427
+ delete manifest.cloudResources;
129428
+ delete manifest.canonical;
129429
+ manifest.version = RESOURCES_STATE_VERSION;
128785
129430
  if (workspaceId) {
128786
129431
  manifest.workspace = { id: workspaceId };
128787
129432
  }
128788
- const localResources = {};
128789
129433
  const cloudResources = {};
128790
129434
  const collectionKeys = Object.keys(collectionMap);
128791
129435
  if (collectionKeys.length > 0) {
128792
- localResources.collections = collectionKeys;
128793
129436
  cloudResources.collections = collectionMap;
128794
129437
  }
128795
129438
  const envEntries = Object.entries(envMap);
128796
129439
  if (envEntries.length > 0) {
128797
- localResources.environments = envEntries.map(
128798
- ([envName]) => `../${artifactDir}/environments/${envName}.postman_environment.json`
128799
- );
128800
129440
  cloudResources.environments = {};
128801
129441
  for (const [envName, envUid] of envEntries) {
128802
129442
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
128803
129443
  }
128804
129444
  }
128805
- if (localSpecRefs.length > 0) {
128806
- localResources.specs = localSpecRefs;
128807
- }
129445
+ void localSpecRefs;
128808
129446
  const specs = { ...existingSpecs || {} };
128809
129447
  if (mappedSpecRef && specId) {
128810
129448
  specs[mappedSpecRef] = specId;
@@ -128812,11 +129450,8 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128812
129450
  if (Object.keys(specs).length > 0) {
128813
129451
  cloudResources.specs = specs;
128814
129452
  }
128815
- if (Object.keys(localResources).length > 0) {
128816
- manifest.localResources = localResources;
128817
- }
128818
129453
  if (Object.keys(cloudResources).length > 0) {
128819
- manifest.cloudResources = cloudResources;
129454
+ manifest.canonical = cloudResources;
128820
129455
  }
128821
129456
  return dump(manifest, {
128822
129457
  lineWidth: -1,
@@ -128857,21 +129492,21 @@ function assertPathWithinCwd(targetPath, fieldName) {
128857
129492
  if (!rawPath || hasControlCharacter2(originalPath) || path8.isAbsolute(rawPath) || path8.win32.isAbsolute(rawPath) || segments.includes("..") || rawPath.startsWith(":") || hasControlCharacter2(rawPath)) {
128858
129493
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128859
129494
  }
128860
- const base = (0, import_node_fs3.realpathSync)(process.cwd());
129495
+ const base = (0, import_node_fs4.realpathSync)(process.cwd());
128861
129496
  const resolved = path8.resolve(base, rawPath);
128862
129497
  const relative4 = path8.relative(base, resolved);
128863
129498
  if (relative4.startsWith("..") || path8.isAbsolute(relative4)) {
128864
129499
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128865
129500
  }
128866
129501
  let existingPath = resolved;
128867
- while (!(0, import_node_fs3.existsSync)(existingPath)) {
129502
+ while (!(0, import_node_fs4.existsSync)(existingPath)) {
128868
129503
  const parent = path8.dirname(existingPath);
128869
129504
  if (parent === existingPath) {
128870
129505
  break;
128871
129506
  }
128872
129507
  existingPath = parent;
128873
129508
  }
128874
- const realExistingPath = (0, import_node_fs3.realpathSync)(existingPath);
129509
+ const realExistingPath = (0, import_node_fs4.realpathSync)(existingPath);
128875
129510
  const realRelative = path8.relative(base, realExistingPath);
128876
129511
  if (realRelative.startsWith("..") || path8.isAbsolute(realRelative)) {
128877
129512
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
@@ -128899,8 +129534,8 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128899
129534
  ensureDir(specsDir);
128900
129535
  ensureDir(".postman");
128901
129536
  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");
129537
+ if (!(0, import_node_fs4.existsSync)(globalsFilePath)) {
129538
+ (0, import_node_fs4.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
128904
129539
  }
128905
129540
  if (inputs.generateCiWorkflow) {
128906
129541
  const ciDir = inputs.ciWorkflowPath.split("/").slice(0, -1).join("/");
@@ -128947,7 +129582,7 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128947
129582
  workspaceLinkEnabled: inputs.workspaceLinkEnabled,
128948
129583
  workspaceLinkStatus: options.workspaceLinkStatus
128949
129584
  });
128950
- (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
129585
+ (0, import_node_fs4.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
128951
129586
  durableWorkspaceId,
128952
129587
  manifestCollections,
128953
129588
  envUids,
@@ -128955,10 +129590,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128955
129590
  discoveredSpecs.map((spec) => spec.configRelativePath),
128956
129591
  mappedSpecCloudKey,
128957
129592
  inputs.specId || void 0,
128958
- options.existingSpecs
129593
+ options.existingSpecs,
129594
+ options.priorState
128959
129595
  ));
128960
129596
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
128961
- (0, import_node_fs3.writeFileSync)(
129597
+ (0, import_node_fs4.writeFileSync)(
128962
129598
  ".postman/workflows.yaml",
128963
129599
  buildSpecCollectionWorkflowManifest(
128964
129600
  mappedSpec.configRelativePath,
@@ -129003,19 +129639,29 @@ async function commitAndPushGeneratedFiles(inputs, dependencies) {
129003
129639
  const dir = parts.slice(0, -1).join("/");
129004
129640
  ensureDir(dir);
129005
129641
  }
129006
- (0, import_node_fs3.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129642
+ (0, import_node_fs4.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129643
+ if (inputs.provider === "github" || inputs.provider === "unknown") {
129644
+ const gcPath = ".github/workflows/postman-preview-gc.yml";
129645
+ if (inputs.ciWorkflowPath.endsWith(".github/workflows/ci.yml") || inputs.ciWorkflowPath === ".github/workflows/ci.yml") {
129646
+ ensureDir(".github/workflows");
129647
+ (0, import_node_fs4.writeFileSync)(gcPath, renderGcWorkflowTemplate());
129648
+ }
129649
+ }
129007
129650
  }
129008
129651
  if (!dependencies.repoMutation || inputs.repoWriteMode === "none") {
129009
129652
  return { commitSha: "", resolvedCurrentRef: "", pushed: false };
129010
129653
  }
129011
129654
  const provisionPath = ".github/workflows/provision.yml";
129012
- const provisionExists = inputs.provider === "github" && (0, import_node_fs3.existsSync)(provisionPath);
129655
+ const provisionExists = inputs.provider === "github" && (0, import_node_fs4.existsSync)(provisionPath);
129656
+ const gcWorkflowPath = ".github/workflows/postman-preview-gc.yml";
129657
+ const gcExists = inputs.generateCiWorkflow && (0, import_node_fs4.existsSync)(gcWorkflowPath);
129013
129658
  const stagePaths = [
129014
129659
  inputs.artifactDir,
129015
129660
  ".postman",
129016
129661
  inputs.generateCiWorkflow ? inputs.ciWorkflowPath : null,
129662
+ gcExists ? gcWorkflowPath : null,
129017
129663
  provisionExists ? provisionPath : null
129018
- ].filter((entry) => typeof entry === "string" && ((0, import_node_fs3.existsSync)(entry) || entry === provisionPath));
129664
+ ].filter((entry) => typeof entry === "string" && ((0, import_node_fs4.existsSync)(entry) || entry === provisionPath));
129019
129665
  if (stagePaths.length === 0) {
129020
129666
  dependencies.core.info("No generated repository paths were found; skipping repo mutation.");
129021
129667
  return {
@@ -129063,6 +129709,31 @@ async function runRepoSync(inputs, dependencies) {
129063
129709
  }
129064
129710
  }
129065
129711
  async function runRepoSyncInner(inputs, dependencies) {
129712
+ const branchDecision = decideBranchTier(inputs);
129713
+ assertBranchAssetIds(inputs, branchDecision);
129714
+ const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
129715
+ if (!isCanonicalWriter) {
129716
+ if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
129717
+ inputs = {
129718
+ ...inputs,
129719
+ projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch),
129720
+ workspaceLinkEnabled: false,
129721
+ environmentSyncEnabled: false,
129722
+ repoWriteMode: "none"
129723
+ };
129724
+ } else if (branchDecision.tier === "channel" && branchDecision.channel) {
129725
+ inputs = {
129726
+ ...inputs,
129727
+ projectName: channelAssetName(inputs.projectName, branchDecision.channel.code),
129728
+ workspaceLinkEnabled: false,
129729
+ environmentSyncEnabled: false,
129730
+ repoWriteMode: "none"
129731
+ };
129732
+ }
129733
+ dependencies.core.info(
129734
+ `branch-aware sync: ${branchDecision.tier} run \u2014 branch-scoped asset set "${inputs.projectName}", no workspace repo-link mutation, no state write-back`
129735
+ );
129736
+ }
129066
129737
  const outputs = createOutputs(inputs);
129067
129738
  const versionRequested = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
129068
129739
  const releaseLabel = deriveReleaseLabel(inputs);
@@ -129070,7 +129741,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129070
129741
  throw new Error("release-label is required when collection-sync-mode or spec-sync-mode is version");
129071
129742
  }
129072
129743
  const assetProjectName = createAssetProjectName(inputs, releaseLabel);
129073
- const resourcesState = readResourcesState();
129744
+ const trackedState = readResourcesState();
129745
+ const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
129074
129746
  if (resourcesState) {
129075
129747
  if (!inputs.workspaceId && resourcesState.workspace?.id) {
129076
129748
  inputs.workspaceId = resourcesState.workspace.id;
@@ -129096,7 +129768,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129096
129768
  }
129097
129769
  }
129098
129770
  }
129099
- const envUids = await upsertEnvironments(inputs, dependencies, resourcesState);
129771
+ const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
129772
+ const envUids = await upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker);
129100
129773
  outputs["environment-uids-json"] = JSON.stringify(envUids);
129101
129774
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
129102
129775
  const associations = Object.entries(envUids).map(([envName, envUid]) => ({
@@ -129152,11 +129825,23 @@ async function runRepoSyncInner(inputs, dependencies) {
129152
129825
  }
129153
129826
  }
129154
129827
  if (!resolvedMockUrl) {
129155
- const mock = await dependencies.postman.createMock(
129156
- inputs.workspaceId,
129157
- mockName,
129158
- inputs.baselineCollectionId,
129159
- mockEnvUid
129828
+ const mock = await retry(
129829
+ () => dependencies.postman.createMock(
129830
+ inputs.workspaceId,
129831
+ mockName,
129832
+ inputs.baselineCollectionId,
129833
+ mockEnvUid
129834
+ ),
129835
+ {
129836
+ maxAttempts: 3,
129837
+ delayMs: 2e3,
129838
+ backoffMultiplier: 2,
129839
+ onRetry: ({ attempt, error: error2 }) => {
129840
+ dependencies.core.warning(
129841
+ `Mock create attempt ${attempt} failed (${error2 instanceof Error ? error2.message : String(error2)}); retrying`
129842
+ );
129843
+ }
129844
+ }
129160
129845
  );
129161
129846
  resolvedMockUrl = mock.url;
129162
129847
  dependencies.core.info(`Created new mock: ${resolvedMockUrl}`);
@@ -129225,16 +129910,19 @@ async function runRepoSyncInner(inputs, dependencies) {
129225
129910
  );
129226
129911
  } catch (error2) {
129227
129912
  outputs["workspace-link-status"] = "failed";
129228
- dependencies.core.warning(
129229
- `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`
129230
- );
129913
+ const message = `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
129914
+ if (branchDecision.tier === "canonical") {
129915
+ throw new Error(message, { cause: error2 });
129916
+ }
129917
+ dependencies.core.warning(message);
129231
129918
  }
129232
129919
  }
129233
129920
  await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
129234
129921
  workspaceLinkStatus: outputs["workspace-link-status"],
129235
129922
  priorWorkspaceId: resourcesState?.workspace?.id,
129236
129923
  existingSpecs: resourcesState?.cloudResources?.specs,
129237
- releaseLabel
129924
+ releaseLabel,
129925
+ priorState: resourcesState
129238
129926
  });
129239
129927
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
129240
129928
  outputs["commit-sha"] = commit.commitSha;
@@ -129242,6 +129930,38 @@ async function runRepoSyncInner(inputs, dependencies) {
129242
129930
  outputs["resolved-current-ref"] = commit.resolvedCurrentRef;
129243
129931
  }
129244
129932
  outputs["repo-sync-summary-json"] = createRepoSummary(outputs, envUids, commit.pushed);
129933
+ if (branchDecision.tier === "canonical" && inputs.specId && inputs.specContentChanged !== false && dependencies.postman.tagSpecVersion) {
129934
+ const shortSha = (branchDecision.identity.headSha ?? "").slice(0, 7);
129935
+ const tagName = inputs.releaseLabel ? `${inputs.releaseLabel}${shortSha ? ` (${shortSha})` : ""}` : shortSha || `sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
129936
+ try {
129937
+ const tag = await dependencies.postman.tagSpecVersion(inputs.specId, tagName);
129938
+ outputs["spec-version-tag"] = tag.name || tagName;
129939
+ if (tag.id) {
129940
+ 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)}`;
129941
+ }
129942
+ dependencies.core.info(`Tagged spec version at finalize: ${tag.name || tagName}`);
129943
+ } catch (error2) {
129944
+ const status = error2.status;
129945
+ if (status === 409) {
129946
+ const tags = await dependencies.postman.listSpecVersionTags?.(inputs.specId).catch(() => []) ?? [];
129947
+ const latest = tags[0];
129948
+ if (latest && (latest.name === tagName || /\([0-9a-f]{7}\)$/.test(latest.name) || /^[0-9a-f]{7}$/.test(latest.name))) {
129949
+ outputs["spec-version-tag"] = latest.name;
129950
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(latest.id)}&versionLabel=${encodeURIComponent(latest.name)}`;
129951
+ dependencies.core.info(`Latest changelog group already tagged as "${latest.name}"; adopting.`);
129952
+ } else {
129953
+ dependencies.core.warning("Latest changelog group already carries a hand-applied tag; leaving it in place.");
129954
+ }
129955
+ } else {
129956
+ dependencies.core.warning(`Spec version tagging failed (non-fatal): ${error2 instanceof Error ? error2.message : String(error2)}`);
129957
+ }
129958
+ }
129959
+ }
129960
+ if (inputs.branchStrategy !== "legacy" || process.env[BRANCH_DECISION_ENV]) {
129961
+ const decision = decideBranchTier(inputs);
129962
+ outputs["sync-status"] = "synced";
129963
+ outputs["branch-decision"] = serializeBranchDecision(decision);
129964
+ }
129245
129965
  for (const [name, value] of Object.entries(outputs)) {
129246
129966
  dependencies.core.setOutput(name, value);
129247
129967
  }
@@ -129431,7 +130151,19 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129431
130151
  listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
129432
130152
  monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
129433
130153
  findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
129434
- runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets)
130154
+ runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
130155
+ listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
130156
+ // GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
130157
+ deleteEnvironment: gatewayAssets.deleteEnvironment.bind(gatewayAssets),
130158
+ deleteMock: gatewayAssets.deleteMock.bind(gatewayAssets),
130159
+ deleteMonitor: gatewayAssets.deleteMonitor.bind(gatewayAssets),
130160
+ deleteCollection: gatewayAssets.deleteCollection.bind(gatewayAssets),
130161
+ listSpecifications: gatewayAssets.listSpecifications.bind(gatewayAssets),
130162
+ getSpecContent: gatewayAssets.getSpecContent.bind(gatewayAssets),
130163
+ listSpecCollections: gatewayAssets.listSpecCollections.bind(gatewayAssets),
130164
+ deleteSpec: gatewayAssets.deleteSpec.bind(gatewayAssets),
130165
+ tagSpecVersion: gatewayAssets.tagSpecVersion.bind(gatewayAssets),
130166
+ listSpecVersionTags: gatewayAssets.listSpecVersionTags.bind(gatewayAssets)
129435
130167
  };
129436
130168
  const repoMutation = repository && (inputs.repoWriteMode === "commit-only" || inputs.repoWriteMode === "commit-and-push") ? new RepoMutationService({
129437
130169
  provider: inputs.provider,
@@ -129468,8 +130200,43 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129468
130200
  repoMutation
129469
130201
  };
129470
130202
  }
130203
+ function decideBranchTier(inputs, env = process.env) {
130204
+ return resolveEffectiveBranchDecision(
130205
+ {
130206
+ strategy: inputs.branchStrategy,
130207
+ identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
130208
+ canonicalBranch: inputs.canonicalBranch,
130209
+ channels: parseChannelRules(inputs.channels)
130210
+ },
130211
+ env
130212
+ );
130213
+ }
130214
+ function runGatedSkip(inputs, decision, actionCore) {
130215
+ actionCore.info(
130216
+ `branch-aware sync: gated run (${decision.reason}) \u2014 repo-sync skipped, zero workspace writes`
130217
+ );
130218
+ const outputs = createOutputs(inputs);
130219
+ outputs["sync-status"] = "skipped-branch-gate";
130220
+ outputs["branch-decision"] = serializeBranchDecision(decision);
130221
+ outputs["repo-sync-summary-json"] = JSON.stringify({
130222
+ status: "skipped-branch-gate",
130223
+ reason: decision.reason
130224
+ });
130225
+ for (const [name, value] of Object.entries(outputs)) {
130226
+ actionCore.setOutput(name, value);
130227
+ }
130228
+ return outputs;
130229
+ }
129471
130230
  async function runAction(actionCore = core_exports, actionExec = exec_exports) {
129472
130231
  const inputs = readActionInputs(actionCore);
130232
+ const branchDecision = decideBranchTier(inputs);
130233
+ if (branchDecision.tier === "gated") {
130234
+ return runGatedSkip(inputs, branchDecision, actionCore);
130235
+ }
130236
+ if (branchDecision.tier !== "legacy") {
130237
+ actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
130238
+ process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
130239
+ }
129473
130240
  await mintAccessTokenIfNeeded(inputs, {
129474
130241
  info: (message) => actionCore.info(message),
129475
130242
  warning: (message) => actionCore.warning(message)
@@ -129526,14 +130293,19 @@ async function runAction(actionCore = core_exports, actionExec = exec_exports) {
129526
130293
  }
129527
130294
  // Annotate the CommonJS export names for ESM import in node:
129528
130295
  0 && (module.exports = {
130296
+ StateUnreadableError,
130297
+ assertBranchAssetIds,
129529
130298
  assertPathWithinCwd,
130299
+ buildBranchAssetMarker,
129530
130300
  createRepoSyncDependencies,
130301
+ decideBranchTier,
129531
130302
  getInput,
129532
130303
  hasInput,
129533
130304
  readActionInputs,
129534
130305
  resolveInputs,
129535
130306
  resolvePostmanApiKeyAndTeamId,
129536
130307
  runAction,
130308
+ runGatedSkip,
129537
130309
  runRepoSync
129538
130310
  });
129539
130311
  /*! Bundled license information: