@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/action.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,
@@ -122063,7 +122063,7 @@ function getIDToken(aud) {
122063
122063
  }
122064
122064
 
122065
122065
  // src/index.ts
122066
- var import_node_fs3 = require("node:fs");
122066
+ var import_node_fs4 = require("node:fs");
122067
122067
  var path8 = __toESM(require("node:path"), 1);
122068
122068
 
122069
122069
  // node_modules/js-yaml/dist/js-yaml.mjs
@@ -125186,6 +125186,9 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125186
125186
  " branches: [main]",
125187
125187
  " schedule:",
125188
125188
  ' - cron: "0 */6 * * *"',
125189
+ "concurrency:",
125190
+ " group: postman-onboard-${{ github.head_ref || github.ref_name }}",
125191
+ " cancel-in-progress: false",
125189
125192
  "jobs:",
125190
125193
  " test:",
125191
125194
  " runs-on: ubuntu-latest",
@@ -125202,7 +125205,7 @@ function buildCiWorkflowLines(installUrl, postmanRegion) {
125202
125205
  " ruby <<'RUBY'",
125203
125206
  " require 'yaml'",
125204
125207
  " config = YAML.load_file('.postman/resources.yaml') || {}",
125205
- " cloud = config.fetch('cloudResources', {})",
125208
+ " cloud = config.fetch('canonical', config.fetch('cloudResources', {}))",
125206
125209
  " collections = cloud.fetch('collections', {})",
125207
125210
  " environments = cloud.fetch('environments', {})",
125208
125211
  " smoke = collections.find { |path, _| path.include?('[Smoke]') }&.last",
@@ -125400,6 +125403,58 @@ function buildAdoCiWorkflowLines(installUrl, postmanRegion) {
125400
125403
  ""
125401
125404
  ];
125402
125405
  }
125406
+ function renderGcWorkflowTemplate() {
125407
+ return [
125408
+ "name: Postman Preview GC",
125409
+ "on:",
125410
+ " delete:",
125411
+ ' branches: ["**"]',
125412
+ " pull_request:",
125413
+ " types: [closed]",
125414
+ " schedule:",
125415
+ ' - cron: "0 2 * * *"',
125416
+ " workflow_dispatch:",
125417
+ " inputs:",
125418
+ " branch:",
125419
+ " description: Branch name to GC (optional, otherwise sweep by TTL/branch-existence)",
125420
+ " required: false",
125421
+ "concurrency:",
125422
+ " group: postman-preview-gc-${{ github.ref_name }}",
125423
+ " cancel-in-progress: false",
125424
+ "jobs:",
125425
+ " gc:",
125426
+ " runs-on: ubuntu-latest",
125427
+ " if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
125428
+ " steps:",
125429
+ " - uses: actions/checkout@v5",
125430
+ " with:",
125431
+ " fetch-depth: 0",
125432
+ " - name: Run Postman preview GC (provider-neutral cli.cjs gc)",
125433
+ " env:",
125434
+ " POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}",
125435
+ " POSTMAN_ACCESS_TOKEN: ${{ secrets.POSTMAN_ACCESS_TOKEN }}",
125436
+ " POSTMAN_WORKSPACE_ID: ${{ vars.POSTMAN_WORKSPACE_ID }}",
125437
+ " REPO_URL: https://github.com/${{ github.repository }}",
125438
+ " run: |",
125439
+ " set -euo pipefail",
125440
+ " # The repo-sync action bundles a provider-neutral gc command (cli.cjs gc)",
125441
+ " # that uses the Postman access token for inventory/deletion and the",
125442
+ " # provider ambient credential (GITHUB_TOKEN via git ls-remote) for branch existence.",
125443
+ " # Daily scheduled run is the retention executor (TTL contract of last resort).",
125444
+ ' if [ -n "${{ inputs.branch }}" ]; then',
125445
+ ' npx @postman-cse/onboarding-repo-sync gc --branch "${{ inputs.branch }}" --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125446
+ " else",
125447
+ ' npx @postman-cse/onboarding-repo-sync gc --workspace-id "$POSTMAN_WORKSPACE_ID" --repo-url "$REPO_URL"',
125448
+ " fi",
125449
+ " - name: Orphan audit summary (job summary)",
125450
+ " if: always()",
125451
+ " run: |",
125452
+ ' echo "### Preview GC orphan audit" >> "$GITHUB_STEP_SUMMARY"',
125453
+ ' echo "Marker-guarded deletion only \u2014 strangers (no marker) are never deleted. See gc-summary-json for structured counts." >> "$GITHUB_STEP_SUMMARY"',
125454
+ ""
125455
+ ].join("\n");
125456
+ }
125457
+ var GC_WORKFLOW_TEMPLATE = renderGcWorkflowTemplate();
125403
125458
  function getCiWorkflowTemplate(provider, options = {}) {
125404
125459
  if (provider === "azure-devops") {
125405
125460
  const rawUrl = String(options.postmanCliInstallUrl || "").trim() || DEFAULT_POSTMAN_CLI_INSTALL_URL;
@@ -126970,6 +127025,53 @@ function createInternalIntegrationAdapter(options) {
126970
127025
  return new BifrostInternalIntegrationAdapter(options);
126971
127026
  }
126972
127027
 
127028
+ // src/lib/retry.ts
127029
+ function sleep(delayMs) {
127030
+ return new Promise((resolve3) => {
127031
+ setTimeout(resolve3, delayMs);
127032
+ });
127033
+ }
127034
+ function normalizeRetryOptions(options) {
127035
+ return {
127036
+ maxAttempts: Math.max(1, options.maxAttempts ?? 3),
127037
+ delayMs: Math.max(0, options.delayMs ?? 2e3),
127038
+ backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
127039
+ maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
127040
+ onRetry: options.onRetry ?? (async () => void 0),
127041
+ shouldRetry: options.shouldRetry ?? (() => true),
127042
+ sleep: options.sleep ?? sleep
127043
+ };
127044
+ }
127045
+ async function retry(operation, options = {}) {
127046
+ const normalized = normalizeRetryOptions(options);
127047
+ let nextDelayMs = normalized.delayMs;
127048
+ for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
127049
+ try {
127050
+ return await operation();
127051
+ } catch (error2) {
127052
+ const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
127053
+ attempt,
127054
+ maxAttempts: normalized.maxAttempts
127055
+ });
127056
+ if (!shouldRetry) {
127057
+ throw error2;
127058
+ }
127059
+ await normalized.onRetry({
127060
+ attempt,
127061
+ maxAttempts: normalized.maxAttempts,
127062
+ delayMs: nextDelayMs,
127063
+ error: error2
127064
+ });
127065
+ await normalized.sleep(nextDelayMs);
127066
+ nextDelayMs = Math.min(
127067
+ normalized.maxDelayMs,
127068
+ Math.round(nextDelayMs * normalized.backoffMultiplier)
127069
+ );
127070
+ }
127071
+ }
127072
+ throw new Error("Retry exhausted without returning or throwing");
127073
+ }
127074
+
126973
127075
  // src/contracts.ts
126974
127076
  var postmanRepoSyncActionContract = {
126975
127077
  name: "postman-repo-sync-action",
@@ -127139,6 +127241,25 @@ var postmanRepoSyncActionContract = {
127139
127241
  default: "warn",
127140
127242
  allowedValues: ["enforce", "warn"]
127141
127243
  },
127244
+ "branch-strategy": {
127245
+ 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.",
127246
+ required: false,
127247
+ default: "legacy",
127248
+ allowedValues: ["legacy", "preview", "publish-gate"]
127249
+ },
127250
+ "canonical-branch": {
127251
+ 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.",
127252
+ required: false
127253
+ },
127254
+ "channels": {
127255
+ 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.',
127256
+ required: false
127257
+ },
127258
+ "preview-ttl": {
127259
+ 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).",
127260
+ required: false,
127261
+ default: "30"
127262
+ },
127142
127263
  "github-token": {
127143
127264
  description: "GitHub token used for repo variable persistence and commits.",
127144
127265
  required: false
@@ -127176,6 +127297,11 @@ var postmanRepoSyncActionContract = {
127176
127297
  description: "Spec UID from bootstrap, persisted into .postman/resources.yaml cloudResources.",
127177
127298
  required: false
127178
127299
  },
127300
+ "spec-content-changed": {
127301
+ description: "Whether bootstrap changed canonical spec content; controls final native Spec Hub tag publication.",
127302
+ required: false,
127303
+ default: "true"
127304
+ },
127179
127305
  "spec-path": {
127180
127306
  description: "Optional repo-root-relative path to the local spec file for resources/workflows metadata.",
127181
127307
  required: false
@@ -127220,6 +127346,18 @@ var postmanRepoSyncActionContract = {
127220
127346
  },
127221
127347
  "commit-sha": {
127222
127348
  description: "Commit SHA produced by repo-write-mode, if any."
127349
+ },
127350
+ "sync-status": {
127351
+ description: "Branch-aware sync status: synced, skipped-branch-gate, or empty under branch-strategy legacy."
127352
+ },
127353
+ "branch-decision": {
127354
+ description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
127355
+ },
127356
+ "spec-version-tag": {
127357
+ description: "Native Spec Hub version tag created after successful canonical repo-sync finalization."
127358
+ },
127359
+ "spec-version-url": {
127360
+ description: "Read-only URL for the tagged Spec Hub snapshot."
127223
127361
  }
127224
127362
  },
127225
127363
  behavior: {
@@ -127287,53 +127425,6 @@ var PostmanAssetsClient = class {
127287
127425
  }
127288
127426
  };
127289
127427
 
127290
- // src/lib/retry.ts
127291
- function sleep(delayMs) {
127292
- return new Promise((resolve3) => {
127293
- setTimeout(resolve3, delayMs);
127294
- });
127295
- }
127296
- function normalizeRetryOptions(options) {
127297
- return {
127298
- maxAttempts: Math.max(1, options.maxAttempts ?? 3),
127299
- delayMs: Math.max(0, options.delayMs ?? 2e3),
127300
- backoffMultiplier: Math.max(1, options.backoffMultiplier ?? 1),
127301
- maxDelayMs: options.maxDelayMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, options.maxDelayMs),
127302
- onRetry: options.onRetry ?? (async () => void 0),
127303
- shouldRetry: options.shouldRetry ?? (() => true),
127304
- sleep: options.sleep ?? sleep
127305
- };
127306
- }
127307
- async function retry(operation, options = {}) {
127308
- const normalized = normalizeRetryOptions(options);
127309
- let nextDelayMs = normalized.delayMs;
127310
- for (let attempt = 1; attempt <= normalized.maxAttempts; attempt += 1) {
127311
- try {
127312
- return await operation();
127313
- } catch (error2) {
127314
- const shouldRetry = attempt < normalized.maxAttempts && normalized.shouldRetry(error2, {
127315
- attempt,
127316
- maxAttempts: normalized.maxAttempts
127317
- });
127318
- if (!shouldRetry) {
127319
- throw error2;
127320
- }
127321
- await normalized.onRetry({
127322
- attempt,
127323
- maxAttempts: normalized.maxAttempts,
127324
- delayMs: nextDelayMs,
127325
- error: error2
127326
- });
127327
- await normalized.sleep(nextDelayMs);
127328
- nextDelayMs = Math.min(
127329
- normalized.maxDelayMs,
127330
- Math.round(nextDelayMs * normalized.backoffMultiplier)
127331
- );
127332
- }
127333
- }
127334
- throw new Error("Retry exhausted without returning or throwing");
127335
- }
127336
-
127337
127428
  // src/lib/postman/postman-gateway-assets-client.ts
127338
127429
  var MAX_CREATE_FLIGHTS = 256;
127339
127430
  var createFlights = /* @__PURE__ */ new Map();
@@ -127359,6 +127450,84 @@ var PostmanGatewayAssetsClient = class {
127359
127450
  if (!envelope) return null;
127360
127451
  return this.asRecord(envelope.data) ?? envelope;
127361
127452
  }
127453
+ /** Native Spec Hub tags attach to the latest changelog group. */
127454
+ async tagSpecVersion(specId, name) {
127455
+ const trimmed = name.trim().slice(0, 255);
127456
+ const response = await this.gateway.requestJson({
127457
+ service: "specification",
127458
+ method: "post",
127459
+ path: `/specifications/${specId}/tags`,
127460
+ body: { name: trimmed }
127461
+ });
127462
+ const record = this.dataOf(response) ?? {};
127463
+ return { id: String(record.id ?? "").trim(), name: String(record.name ?? trimmed).trim() };
127464
+ }
127465
+ async listSpecVersionTags(specId) {
127466
+ const response = await this.gateway.requestJson({
127467
+ service: "specification",
127468
+ method: "get",
127469
+ path: `/specifications/${specId}/tags`,
127470
+ query: { limit: "50" }
127471
+ });
127472
+ const data = Array.isArray(response?.data) ? response.data : [];
127473
+ return data.map((entry) => this.asRecord(entry)).filter((entry) => entry !== null).map((entry) => ({
127474
+ id: String(entry.id ?? "").trim(),
127475
+ // listTags returns `message`; createTag returns `name`. Accept both.
127476
+ name: String(entry.name ?? entry.message ?? "").trim()
127477
+ })).filter((entry) => entry.id || entry.name);
127478
+ }
127479
+ async deleteCollection(collectionUid) {
127480
+ const bareId = String(collectionUid).split("-").slice(-5).join("-") || collectionUid;
127481
+ await this.gateway.requestJson({
127482
+ service: "collection",
127483
+ method: "delete",
127484
+ path: `/v3/collections/${bareId}`
127485
+ });
127486
+ }
127487
+ async listSpecifications(workspaceId) {
127488
+ const response = await this.gateway.requestJson({
127489
+ service: "specification",
127490
+ method: "get",
127491
+ path: `/specifications?containerType=workspace&containerId=${workspaceId}`
127492
+ });
127493
+ const data = Array.isArray(response?.data) ? response.data : [];
127494
+ 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);
127495
+ }
127496
+ async getSpecContent(specId) {
127497
+ const files = await this.gateway.requestJson({
127498
+ service: "specification",
127499
+ method: "get",
127500
+ path: `/specifications/${specId}/files`
127501
+ });
127502
+ const entries = Array.isArray(files?.data) ? files.data : [];
127503
+ const root = entries.map((entry) => this.asRecord(entry)).find((entry) => entry?.type === "ROOT") ?? entries.map((entry) => this.asRecord(entry)).find((entry) => entry !== null);
127504
+ const fileId = String(root?.id ?? "").trim();
127505
+ if (!fileId) return void 0;
127506
+ const file = await this.gateway.requestJson({
127507
+ service: "specification",
127508
+ method: "get",
127509
+ path: `/specifications/${specId}/files/${fileId}`,
127510
+ query: { fields: "content" }
127511
+ });
127512
+ const record = this.dataOf(file);
127513
+ return typeof record?.content === "string" ? record.content : void 0;
127514
+ }
127515
+ async listSpecCollections(specId) {
127516
+ const response = await this.gateway.requestJson({
127517
+ service: "specification",
127518
+ method: "get",
127519
+ path: `/specifications/${specId}/collections`
127520
+ });
127521
+ const data = Array.isArray(response?.data) ? response.data : [];
127522
+ 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);
127523
+ }
127524
+ async deleteSpec(specId) {
127525
+ await this.gateway.requestJson({
127526
+ service: "specification",
127527
+ method: "delete",
127528
+ path: `/specifications/${specId}`
127529
+ });
127530
+ }
127362
127531
  idOf(record) {
127363
127532
  if (!record) return "";
127364
127533
  const id = record.uid ?? record.id;
@@ -127643,6 +127812,9 @@ var PostmanGatewayAssetsClient = class {
127643
127812
  path: `/mocks?workspace=${ws}`,
127644
127813
  body
127645
127814
  },
127815
+ // Unsafe create: never blind re-POST. An ESOCKETTIMEDOUT after accept
127816
+ // may still have created the mock; reconcile via discovery below and
127817
+ // let the orchestrator retry the whole create-or-adopt cycle.
127646
127818
  { retryTransient: false }
127647
127819
  );
127648
127820
  const record = this.dataOf(response);
@@ -127681,6 +127853,33 @@ var PostmanGatewayAssetsClient = class {
127681
127853
  environment: String(m.environment ?? "")
127682
127854
  }));
127683
127855
  }
127856
+ /**
127857
+ * Delete an environment through the sync service (GC path). The path id is
127858
+ * the bare model id (public uid tail), mirroring updateEnvironment.
127859
+ */
127860
+ async deleteEnvironment(uid) {
127861
+ await this.gateway.requestJson({
127862
+ service: "sync",
127863
+ method: "delete",
127864
+ path: `/environment/${this.toModelId(uid)}`
127865
+ });
127866
+ }
127867
+ /** Delete a mock server (GC path). */
127868
+ async deleteMock(uid) {
127869
+ await this.gateway.requestJson({
127870
+ service: "mock",
127871
+ method: "delete",
127872
+ path: `/mocks/${this.toModelId(uid)}`
127873
+ });
127874
+ }
127875
+ /** Delete a collection-based monitor (jobTemplate) (GC path). */
127876
+ async deleteMonitor(uid) {
127877
+ await this.gateway.requestJson({
127878
+ service: "monitors",
127879
+ method: "delete",
127880
+ path: `/jobTemplates/${this.toModelId(uid)}`
127881
+ });
127882
+ }
127684
127883
  async mockExists(uid) {
127685
127884
  try {
127686
127885
  await this.gateway.requestJson({
@@ -128187,6 +128386,331 @@ function validateCertMaterial(certBase64, keyBase64, passphrase) {
128187
128386
  }
128188
128387
  }
128189
128388
 
128389
+ // src/lib/repo/branch-decision.ts
128390
+ var import_node_fs3 = require("node:fs");
128391
+ var import_node_crypto3 = require("node:crypto");
128392
+ var ContractError = class extends Error {
128393
+ code;
128394
+ constructor(code, message) {
128395
+ super(`${code}: ${message}`);
128396
+ this.code = code;
128397
+ this.name = "ContractError";
128398
+ }
128399
+ };
128400
+ function clean(value) {
128401
+ const trimmed = (value ?? "").trim();
128402
+ return trimmed.length > 0 ? trimmed : void 0;
128403
+ }
128404
+ function stripRefPrefix(ref) {
128405
+ const raw = clean(ref);
128406
+ if (!raw) {
128407
+ return { kind: "unknown" };
128408
+ }
128409
+ if (raw.startsWith("refs/heads/")) {
128410
+ return { name: raw.slice("refs/heads/".length), kind: "branch" };
128411
+ }
128412
+ if (raw.startsWith("refs/tags/")) {
128413
+ return { name: raw.slice("refs/tags/".length), kind: "tag" };
128414
+ }
128415
+ if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
128416
+ return { kind: "unknown" };
128417
+ }
128418
+ return { name: raw, kind: "branch" };
128419
+ }
128420
+ function detectProvider(env) {
128421
+ if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
128422
+ if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
128423
+ if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
128424
+ if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
128425
+ return "unknown";
128426
+ }
128427
+ function readGithubEvent(env) {
128428
+ const path9 = clean(env.GITHUB_EVENT_PATH);
128429
+ if (!path9) return void 0;
128430
+ try {
128431
+ return JSON.parse((0, import_node_fs3.readFileSync)(path9, "utf8"));
128432
+ } catch {
128433
+ return void 0;
128434
+ }
128435
+ }
128436
+ function resolveBranchIdentity(env = process.env, overrides = {}) {
128437
+ const provider = detectProvider(env);
128438
+ const explicitDefault = clean(overrides.defaultBranch);
128439
+ let headBranch;
128440
+ let rawRef;
128441
+ let refKind;
128442
+ let isPrContext = false;
128443
+ let isForkPr = false;
128444
+ let defaultBranch = explicitDefault;
128445
+ let headSha;
128446
+ switch (provider) {
128447
+ case "github": {
128448
+ const event = readGithubEvent(env);
128449
+ headSha = clean(env.GITHUB_SHA);
128450
+ defaultBranch ??= clean(event?.repository?.default_branch);
128451
+ const headRef = clean(env.GITHUB_HEAD_REF);
128452
+ if (headRef) {
128453
+ isPrContext = true;
128454
+ headBranch = headRef;
128455
+ rawRef = clean(env.GITHUB_REF) ?? headRef;
128456
+ refKind = "branch";
128457
+ const headRepo = event?.pull_request?.head?.repo?.full_name;
128458
+ const baseRepo = event?.pull_request?.base?.repo?.full_name ?? event?.repository?.full_name;
128459
+ isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
128460
+ headSha = clean(event?.pull_request?.head?.sha) ?? headSha;
128461
+ } else {
128462
+ rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
128463
+ const parsed = stripRefPrefix(rawRef);
128464
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128465
+ refKind = parsed.kind;
128466
+ }
128467
+ break;
128468
+ }
128469
+ case "gitlab": {
128470
+ headSha = clean(env.CI_COMMIT_SHA);
128471
+ defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
128472
+ const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
128473
+ if (mrSource) {
128474
+ isPrContext = true;
128475
+ headBranch = mrSource;
128476
+ rawRef = mrSource;
128477
+ refKind = "branch";
128478
+ const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
128479
+ const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
128480
+ isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
128481
+ } else if (clean(env.CI_COMMIT_TAG)) {
128482
+ rawRef = clean(env.CI_COMMIT_TAG);
128483
+ refKind = "tag";
128484
+ } else {
128485
+ headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
128486
+ rawRef = headBranch;
128487
+ refKind = headBranch ? "branch" : "unknown";
128488
+ }
128489
+ break;
128490
+ }
128491
+ case "bitbucket": {
128492
+ headSha = clean(env.BITBUCKET_COMMIT);
128493
+ if (clean(env.BITBUCKET_TAG)) {
128494
+ rawRef = clean(env.BITBUCKET_TAG);
128495
+ refKind = "tag";
128496
+ } else {
128497
+ headBranch = clean(env.BITBUCKET_BRANCH);
128498
+ rawRef = headBranch;
128499
+ refKind = headBranch ? "branch" : "unknown";
128500
+ isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
128501
+ }
128502
+ break;
128503
+ }
128504
+ case "azure-devops": {
128505
+ headSha = clean(env.BUILD_SOURCEVERSION);
128506
+ const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
128507
+ if (prSource) {
128508
+ isPrContext = true;
128509
+ const parsed = stripRefPrefix(prSource);
128510
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128511
+ rawRef = prSource;
128512
+ refKind = parsed.kind;
128513
+ const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
128514
+ isForkPr = forkFlag?.toLowerCase() === "true";
128515
+ } else {
128516
+ rawRef = clean(env.BUILD_SOURCEBRANCH);
128517
+ const parsed = stripRefPrefix(rawRef);
128518
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
128519
+ refKind = parsed.kind;
128520
+ }
128521
+ break;
128522
+ }
128523
+ default: {
128524
+ refKind = "unknown";
128525
+ }
128526
+ }
128527
+ if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
128528
+ refKind = "default-branch";
128529
+ }
128530
+ return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
128531
+ }
128532
+ function parseChannelRules(input) {
128533
+ const raw = clean(input);
128534
+ const rules = [];
128535
+ for (const part of raw ? raw.split(",") : []) {
128536
+ const entry = part.trim();
128537
+ if (!entry) continue;
128538
+ const eq = entry.indexOf("=");
128539
+ if (eq <= 0 || eq === entry.length - 1) {
128540
+ throw new ContractError(
128541
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128542
+ `channels entry "${entry}" must be <branch-or-glob>=<CODE>`
128543
+ );
128544
+ }
128545
+ const pattern = entry.slice(0, eq).trim();
128546
+ const code = entry.slice(eq + 1).trim().toUpperCase();
128547
+ if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
128548
+ throw new ContractError(
128549
+ "CONTRACT_CHANNELS_INPUT_INVALID",
128550
+ `channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
128551
+ );
128552
+ }
128553
+ rules.push({ pattern, code });
128554
+ }
128555
+ if (!rules.some((rule) => rule.pattern === "release/*")) {
128556
+ rules.push({ pattern: "release/*", code: "RC" });
128557
+ }
128558
+ return rules;
128559
+ }
128560
+ function matchChannel(branch, rules) {
128561
+ for (const rule of rules) {
128562
+ if (rule.pattern.endsWith("*")) {
128563
+ const prefix = rule.pattern.slice(0, -1);
128564
+ if (branch.startsWith(prefix)) return rule;
128565
+ } else if (branch === rule.pattern) {
128566
+ return rule;
128567
+ }
128568
+ }
128569
+ return void 0;
128570
+ }
128571
+ function resolveBranchDecision(options) {
128572
+ const { strategy, identity } = options;
128573
+ const channels = options.channels ?? [];
128574
+ if (strategy === "legacy") {
128575
+ return {
128576
+ tier: "legacy",
128577
+ strategy,
128578
+ identity,
128579
+ canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
128580
+ reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
128581
+ };
128582
+ }
128583
+ const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
128584
+ if (!canonicalBranch) {
128585
+ throw new ContractError(
128586
+ "CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
128587
+ `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.`
128588
+ );
128589
+ }
128590
+ if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
128591
+ return {
128592
+ tier: "gated",
128593
+ strategy,
128594
+ identity,
128595
+ canonicalBranch,
128596
+ reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
128597
+ };
128598
+ }
128599
+ if (identity.headBranch === canonicalBranch) {
128600
+ return {
128601
+ tier: "canonical",
128602
+ strategy,
128603
+ identity,
128604
+ canonicalBranch,
128605
+ reason: `head branch equals canonical branch ${canonicalBranch}`
128606
+ };
128607
+ }
128608
+ const channel = matchChannel(identity.headBranch, channels);
128609
+ if (channel) {
128610
+ return {
128611
+ tier: "channel",
128612
+ strategy,
128613
+ identity,
128614
+ canonicalBranch,
128615
+ channel,
128616
+ reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
128617
+ };
128618
+ }
128619
+ if (strategy === "preview") {
128620
+ if (identity.isForkPr) {
128621
+ return {
128622
+ tier: "gated",
128623
+ strategy,
128624
+ identity,
128625
+ canonicalBranch,
128626
+ reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
128627
+ };
128628
+ }
128629
+ return {
128630
+ tier: "preview",
128631
+ strategy,
128632
+ identity,
128633
+ canonicalBranch,
128634
+ reason: `branch ${identity.headBranch} under branch-strategy preview`
128635
+ };
128636
+ }
128637
+ return {
128638
+ tier: "gated",
128639
+ strategy,
128640
+ identity,
128641
+ canonicalBranch,
128642
+ reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
128643
+ };
128644
+ }
128645
+ var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
128646
+ function serializeBranchDecision(decision) {
128647
+ return JSON.stringify(decision);
128648
+ }
128649
+ function parseBranchDecision(raw) {
128650
+ const value = clean(raw);
128651
+ if (!value) return void 0;
128652
+ let parsed;
128653
+ try {
128654
+ parsed = JSON.parse(value);
128655
+ } catch {
128656
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
128657
+ }
128658
+ const candidate = parsed;
128659
+ const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
128660
+ if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
128661
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
128662
+ }
128663
+ return candidate;
128664
+ }
128665
+ function resolveEffectiveBranchDecision(options, env = process.env) {
128666
+ const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
128667
+ if (inherited) return inherited;
128668
+ return resolveBranchDecision(options);
128669
+ }
128670
+ var PREVIEW_SLUG_MAX = 30;
128671
+ function buildBranchSlug(rawBranch) {
128672
+ const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128673
+ const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
128674
+ const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
128675
+ if (!lossy) {
128676
+ return { suffix: truncated, slug: truncated, lossy };
128677
+ }
128678
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
128679
+ return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
128680
+ }
128681
+ function previewAssetName(baseName, rawBranch) {
128682
+ return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
128683
+ }
128684
+ function channelAssetName(baseName, code) {
128685
+ return `[${code}] ${baseName}`;
128686
+ }
128687
+ var MARKER_KEY = "x-pm-onboarding";
128688
+ function parseAssetMarker(description) {
128689
+ if (!description) return void 0;
128690
+ const index = description.indexOf(`${MARKER_KEY}:`);
128691
+ if (index === -1) return void 0;
128692
+ const jsonStart = description.indexOf("{", index);
128693
+ if (jsonStart === -1) return void 0;
128694
+ let depth = 0;
128695
+ for (let i = jsonStart; i < description.length; i += 1) {
128696
+ const ch = description[i];
128697
+ if (ch === "{") depth += 1;
128698
+ else if (ch === "}") {
128699
+ depth -= 1;
128700
+ if (depth === 0) {
128701
+ try {
128702
+ const parsed = JSON.parse(description.slice(jsonStart, i + 1));
128703
+ if (parsed && typeof parsed === "object" && parsed.repo && parsed.role) return parsed;
128704
+ } catch {
128705
+ return void 0;
128706
+ }
128707
+ return void 0;
128708
+ }
128709
+ }
128710
+ }
128711
+ return void 0;
128712
+ }
128713
+
128190
128714
  // src/index.ts
128191
128715
  function parseBooleanInput(value, defaultValue) {
128192
128716
  const normalized = String(value || "").trim().toLowerCase();
@@ -128205,16 +128729,16 @@ function getInput2(name, env = process.env) {
128205
128729
  const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
128206
128730
  const hasNormalized = normalizedRaw !== void 0;
128207
128731
  const hasRunner = runnerRaw !== void 0;
128732
+ const normalizedValue = normalizeInputValue(normalizedRaw);
128733
+ const runnerValue = normalizeInputValue(runnerRaw);
128208
128734
  if (hasNormalized && hasRunner) {
128209
- const normalizedValue = normalizeInputValue(normalizedRaw);
128210
- const runnerValue = normalizeInputValue(runnerRaw);
128211
- if (normalizedValue !== runnerValue) {
128735
+ if (normalizedValue && runnerValue && normalizedValue !== runnerValue) {
128212
128736
  throw new Error(
128213
128737
  `Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
128214
128738
  );
128215
128739
  }
128216
128740
  }
128217
- return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
128741
+ return normalizedValue || runnerValue;
128218
128742
  }
128219
128743
  function hasInput(name, env = process.env) {
128220
128744
  const normalizedName = `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
@@ -128276,6 +128800,17 @@ function parseCredentialPreflight(value) {
128276
128800
  `Unsupported credential-preflight "${normalized}". Supported values: ${allowed.join(", ")}`
128277
128801
  );
128278
128802
  }
128803
+ function parseBranchStrategy(value) {
128804
+ const definition = postmanRepoSyncActionContract.inputs["branch-strategy"];
128805
+ const allowed = definition.allowedValues ?? [];
128806
+ const normalized = String(value || "").trim() || (definition.default ?? "legacy");
128807
+ if (allowed.includes(normalized)) {
128808
+ return normalized;
128809
+ }
128810
+ throw new Error(
128811
+ `Unsupported branch-strategy "${normalized}". Supported values: ${allowed.join(", ")}`
128812
+ );
128813
+ }
128279
128814
  function normalizeReleaseLabel(value) {
128280
128815
  const cleaned = normalizeInputValue(value).replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
128281
128816
  return cleaned;
@@ -128320,6 +128855,7 @@ function resolveInputs(env = process.env) {
128320
128855
  smokeCollectionId: getInput2("smoke-collection-id", env),
128321
128856
  contractCollectionId: getInput2("contract-collection-id", env),
128322
128857
  specId: getInput2("spec-id", env),
128858
+ specContentChanged: parseBooleanInput(getInput2("spec-content-changed", env), true),
128323
128859
  specPath: getInput2("spec-path", env),
128324
128860
  collectionSyncMode: normalizeCollectionSyncMode(getInput2("collection-sync-mode", env) || "refresh"),
128325
128861
  specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
@@ -128342,6 +128878,10 @@ function resolveInputs(env = process.env) {
128342
128878
  postmanApiKey: getInput2("postman-api-key", env),
128343
128879
  postmanAccessToken: getInput2("postman-access-token", env),
128344
128880
  credentialPreflight: parseCredentialPreflight(getInput2("credential-preflight", env)),
128881
+ branchStrategy: parseBranchStrategy(getInput2("branch-strategy", env)),
128882
+ canonicalBranch: getInput2("canonical-branch", env) || void 0,
128883
+ channels: getInput2("channels", env) || void 0,
128884
+ previewTtlDays: Math.max(1, Number.parseInt(getInput2("preview-ttl", env) || "30", 10) || 30),
128345
128885
  adoToken: getInput2("ado-token", env) || normalizeInputValue(env.SYSTEM_ACCESSTOKEN),
128346
128886
  githubToken: getInput2("github-token", env),
128347
128887
  ghFallbackToken: getInput2("gh-fallback-token", env),
@@ -128368,6 +128908,40 @@ function resolveInputs(env = process.env) {
128368
128908
  postmanIapubBase: endpointProfile.iapubBaseUrl
128369
128909
  };
128370
128910
  }
128911
+ function buildBranchAssetMarker(decision, inputs, now = /* @__PURE__ */ new Date()) {
128912
+ if (decision.tier !== "preview" && decision.tier !== "channel") {
128913
+ return void 0;
128914
+ }
128915
+ const rawBranch = decision.identity.headBranch;
128916
+ const repo = inputs.repoUrl || inputs.repository;
128917
+ if (!rawBranch || !repo) {
128918
+ return void 0;
128919
+ }
128920
+ const ttlMs = inputs.previewTtlDays * 24 * 60 * 60 * 1e3;
128921
+ return {
128922
+ repo,
128923
+ rawBranch,
128924
+ sanitizedBranch: buildBranchSlug(rawBranch).suffix,
128925
+ role: decision.tier,
128926
+ headSha: decision.identity.headSha,
128927
+ createdAt: now.toISOString(),
128928
+ lastSyncedAt: now.toISOString(),
128929
+ ...decision.tier === "preview" ? { expiresAt: new Date(now.getTime() + ttlMs).toISOString() } : {}
128930
+ };
128931
+ }
128932
+ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POSTMAN_BRANCH_ASSET_IDS === "owned") {
128933
+ if (decision.tier === "legacy" || decision.tier === "canonical" || branchOwnedIds) return;
128934
+ const provided = [
128935
+ ["baseline-collection-id", inputs.baselineCollectionId],
128936
+ ["smoke-collection-id", inputs.smokeCollectionId],
128937
+ ["contract-collection-id", inputs.contractCollectionId]
128938
+ ].filter(([, value]) => Boolean(value));
128939
+ if (provided.length > 0) {
128940
+ throw new Error(
128941
+ `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.`
128942
+ );
128943
+ }
128944
+ }
128371
128945
  function buildEnvironmentValues(envName, baseUrl) {
128372
128946
  return [
128373
128947
  { key: "baseUrl", value: baseUrl, type: "default" },
@@ -128380,12 +128954,48 @@ function buildEnvironmentValues(envName, baseUrl) {
128380
128954
  ];
128381
128955
  }
128382
128956
  var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
128957
+ var RESOURCES_STATE_VERSION = 2;
128958
+ var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
128959
+ var StateUnreadableError = class extends Error {
128960
+ code = "CONTRACT_STATE_UNREADABLE";
128961
+ constructor(message) {
128962
+ super(`CONTRACT_STATE_UNREADABLE: ${message}`);
128963
+ this.name = "StateUnreadableError";
128964
+ }
128965
+ };
128383
128966
  function readResourcesState() {
128967
+ let raw;
128384
128968
  try {
128385
- return load((0, import_node_fs3.readFileSync)(".postman/resources.yaml", "utf8"));
128969
+ raw = (0, import_node_fs4.readFileSync)(".postman/resources.yaml", "utf8");
128386
128970
  } catch {
128387
128971
  return null;
128388
128972
  }
128973
+ let parsed;
128974
+ try {
128975
+ parsed = load(raw);
128976
+ } catch (error2) {
128977
+ throw new StateUnreadableError(
128978
+ `.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.`
128979
+ );
128980
+ }
128981
+ if (parsed === null || parsed === void 0) {
128982
+ return null;
128983
+ }
128984
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
128985
+ throw new StateUnreadableError(
128986
+ ".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
128987
+ );
128988
+ }
128989
+ const state = parsed;
128990
+ if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
128991
+ throw new StateUnreadableError(
128992
+ `.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
128993
+ );
128994
+ }
128995
+ if (state.canonical && !state.cloudResources) {
128996
+ state.cloudResources = { ...state.canonical };
128997
+ }
128998
+ return state;
128389
128999
  }
128390
129000
  function findCloudResourceId(map, matcher) {
128391
129001
  if (!map) {
@@ -128420,7 +129030,7 @@ function isOpenApiSpecFile(filePath) {
128420
129030
  return false;
128421
129031
  }
128422
129032
  try {
128423
- const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8");
129033
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
128424
129034
  const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : load(raw);
128425
129035
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
128426
129036
  return false;
@@ -128449,7 +129059,7 @@ function scanLocalSpecReferences(baseDir = ".") {
128449
129059
  const found = /* @__PURE__ */ new Set();
128450
129060
  const refs = [];
128451
129061
  const visit2 = (currentDir) => {
128452
- for (const entry of (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true })) {
129062
+ for (const entry of (0, import_node_fs4.readdirSync)(currentDir, { withFileTypes: true })) {
128453
129063
  if (ignoredDirs.has(entry.name)) {
128454
129064
  continue;
128455
129065
  }
@@ -128479,7 +129089,7 @@ function resolveMappedSpecReference(explicitSpecPath, discoveredSpecs) {
128479
129089
  const normalizedExplicitPath = normalizeToPosix(explicitSpecPath.trim());
128480
129090
  if (normalizedExplicitPath) {
128481
129091
  const explicitFullPath = path8.resolve(normalizedExplicitPath);
128482
- if ((0, import_node_fs3.existsSync)(explicitFullPath) && (0, import_node_fs3.statSync)(explicitFullPath).isFile()) {
129092
+ if ((0, import_node_fs4.existsSync)(explicitFullPath) && (0, import_node_fs4.statSync)(explicitFullPath).isFile()) {
128483
129093
  return {
128484
129094
  repoRelativePath: normalizedExplicitPath,
128485
129095
  configRelativePath: normalizeToPosix(path8.join("..", normalizedExplicitPath))
@@ -128506,7 +129116,11 @@ function createOutputs(inputs) {
128506
129116
  "mock-url": "",
128507
129117
  "monitor-id": "",
128508
129118
  "repo-sync-summary-json": "{}",
128509
- "commit-sha": ""
129119
+ "commit-sha": "",
129120
+ "sync-status": "",
129121
+ "branch-decision": "",
129122
+ "spec-version-tag": "",
129123
+ "spec-version-url": ""
128510
129124
  };
128511
129125
  }
128512
129126
  function readActionInputs(actionCore) {
@@ -128551,6 +129165,10 @@ function readActionInputs(actionCore) {
128551
129165
  INPUT_POSTMAN_API_KEY: postmanApiKey,
128552
129166
  INPUT_POSTMAN_ACCESS_TOKEN: postmanAccessToken,
128553
129167
  INPUT_CREDENTIAL_PREFLIGHT: readInput(actionCore, "credential-preflight") || "warn",
129168
+ INPUT_BRANCH_STRATEGY: readInput(actionCore, "branch-strategy"),
129169
+ INPUT_CANONICAL_BRANCH: readInput(actionCore, "canonical-branch"),
129170
+ INPUT_CHANNELS: readInput(actionCore, "channels"),
129171
+ INPUT_PREVIEW_TTL: readInput(actionCore, "preview-ttl"),
128554
129172
  INPUT_ADO_TOKEN: adoToken,
128555
129173
  INPUT_GITHUB_TOKEN: githubToken,
128556
129174
  INPUT_GH_FALLBACK_TOKEN: ghFallbackToken,
@@ -128665,7 +129283,7 @@ async function persistSslSecrets(inputs, actionCore, actionExec, repository, env
128665
129283
  );
128666
129284
  }
128667
129285
  }
128668
- async function upsertEnvironments(inputs, dependencies, resourcesState) {
129286
+ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMarker) {
128669
129287
  const envUids = {
128670
129288
  ...getEnvironmentUidsFromResources(resourcesState),
128671
129289
  ...inputs.environmentUids
@@ -128675,7 +129293,6 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128675
129293
  }
128676
129294
  for (const envName of inputs.environments) {
128677
129295
  const runtimeUrl = String(inputs.envRuntimeUrls[envName] || "").trim();
128678
- const values = buildEnvironmentValues(envName, runtimeUrl);
128679
129296
  const displayName = `${inputs.projectName} - ${envName}`;
128680
129297
  let existingUid = String(envUids[envName] || "").trim();
128681
129298
  if (!existingUid) {
@@ -128691,10 +129308,27 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128691
129308
  }
128692
129309
  }
128693
129310
  if (existingUid) {
128694
- await dependencies.postman.updateEnvironment(existingUid, displayName, values);
129311
+ let marker = assetMarker;
129312
+ if (marker) {
129313
+ try {
129314
+ const existing = await dependencies.postman.getEnvironment(existingUid);
129315
+ const values3 = existing.data?.values ?? existing.values ?? [];
129316
+ const prior = values3.find((value) => value.key === "x-pm-onboarding")?.value;
129317
+ const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
129318
+ if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
129319
+ marker = { ...marker, createdAt: priorMarker.createdAt };
129320
+ }
129321
+ } catch {
129322
+ }
129323
+ }
129324
+ const values2 = buildEnvironmentValues(envName, runtimeUrl);
129325
+ if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
129326
+ await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
128695
129327
  envUids[envName] = existingUid;
128696
129328
  continue;
128697
129329
  }
129330
+ const values = buildEnvironmentValues(envName, runtimeUrl);
129331
+ if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
128698
129332
  envUids[envName] = await dependencies.postman.createEnvironment(
128699
129333
  inputs.workspaceId,
128700
129334
  displayName,
@@ -128704,7 +129338,7 @@ async function upsertEnvironments(inputs, dependencies, resourcesState) {
128704
129338
  return envUids;
128705
129339
  }
128706
129340
  function ensureDir(path9) {
128707
- (0, import_node_fs3.mkdirSync)(path9, { recursive: true });
129341
+ (0, import_node_fs4.mkdirSync)(path9, { recursive: true });
128708
129342
  }
128709
129343
  function getCollectionDirectoryName(kind, projectName) {
128710
129344
  if (kind === "Baseline") {
@@ -128741,7 +129375,7 @@ function stripVolatileFields(obj) {
128741
129375
  }
128742
129376
  function writeJsonFile(path9, content, normalize3 = false) {
128743
129377
  const data = normalize3 ? stripVolatileFields(content) : content;
128744
- (0, import_node_fs3.writeFileSync)(path9, JSON.stringify(data, null, 2));
129378
+ (0, import_node_fs4.writeFileSync)(path9, JSON.stringify(data, null, 2));
128745
129379
  }
128746
129380
  function buildMappedSpecCloudKey(mappedSource, specSyncMode, releaseLabel) {
128747
129381
  if (specSyncMode !== "version") {
@@ -128765,31 +129399,30 @@ function resolveDurableWorkspaceId(options) {
128765
129399
  }
128766
129400
  return prior === candidate ? prior : void 0;
128767
129401
  }
128768
- function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs) {
128769
- const manifest = {};
129402
+ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir, localSpecRefs, mappedSpecRef, specId, existingSpecs, priorState) {
129403
+ const manifest = { ...priorState ?? {} };
129404
+ delete manifest.version;
129405
+ delete manifest.workspace;
129406
+ delete manifest.localResources;
129407
+ delete manifest.cloudResources;
129408
+ delete manifest.canonical;
129409
+ manifest.version = RESOURCES_STATE_VERSION;
128770
129410
  if (workspaceId) {
128771
129411
  manifest.workspace = { id: workspaceId };
128772
129412
  }
128773
- const localResources = {};
128774
129413
  const cloudResources = {};
128775
129414
  const collectionKeys = Object.keys(collectionMap);
128776
129415
  if (collectionKeys.length > 0) {
128777
- localResources.collections = collectionKeys;
128778
129416
  cloudResources.collections = collectionMap;
128779
129417
  }
128780
129418
  const envEntries = Object.entries(envMap);
128781
129419
  if (envEntries.length > 0) {
128782
- localResources.environments = envEntries.map(
128783
- ([envName]) => `../${artifactDir}/environments/${envName}.postman_environment.json`
128784
- );
128785
129420
  cloudResources.environments = {};
128786
129421
  for (const [envName, envUid] of envEntries) {
128787
129422
  cloudResources.environments[`../${artifactDir}/environments/${envName}.postman_environment.json`] = envUid;
128788
129423
  }
128789
129424
  }
128790
- if (localSpecRefs.length > 0) {
128791
- localResources.specs = localSpecRefs;
128792
- }
129425
+ void localSpecRefs;
128793
129426
  const specs = { ...existingSpecs || {} };
128794
129427
  if (mappedSpecRef && specId) {
128795
129428
  specs[mappedSpecRef] = specId;
@@ -128797,11 +129430,8 @@ function buildResourcesManifest(workspaceId, collectionMap, envMap, artifactDir,
128797
129430
  if (Object.keys(specs).length > 0) {
128798
129431
  cloudResources.specs = specs;
128799
129432
  }
128800
- if (Object.keys(localResources).length > 0) {
128801
- manifest.localResources = localResources;
128802
- }
128803
129433
  if (Object.keys(cloudResources).length > 0) {
128804
- manifest.cloudResources = cloudResources;
129434
+ manifest.canonical = cloudResources;
128805
129435
  }
128806
129436
  return dump(manifest, {
128807
129437
  lineWidth: -1,
@@ -128842,21 +129472,21 @@ function assertPathWithinCwd(targetPath, fieldName) {
128842
129472
  if (!rawPath || hasControlCharacter2(originalPath) || path8.isAbsolute(rawPath) || path8.win32.isAbsolute(rawPath) || segments.includes("..") || rawPath.startsWith(":") || hasControlCharacter2(rawPath)) {
128843
129473
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128844
129474
  }
128845
- const base = (0, import_node_fs3.realpathSync)(process.cwd());
129475
+ const base = (0, import_node_fs4.realpathSync)(process.cwd());
128846
129476
  const resolved = path8.resolve(base, rawPath);
128847
129477
  const relative4 = path8.relative(base, resolved);
128848
129478
  if (relative4.startsWith("..") || path8.isAbsolute(relative4)) {
128849
129479
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
128850
129480
  }
128851
129481
  let existingPath = resolved;
128852
- while (!(0, import_node_fs3.existsSync)(existingPath)) {
129482
+ while (!(0, import_node_fs4.existsSync)(existingPath)) {
128853
129483
  const parent = path8.dirname(existingPath);
128854
129484
  if (parent === existingPath) {
128855
129485
  break;
128856
129486
  }
128857
129487
  existingPath = parent;
128858
129488
  }
128859
- const realExistingPath = (0, import_node_fs3.realpathSync)(existingPath);
129489
+ const realExistingPath = (0, import_node_fs4.realpathSync)(existingPath);
128860
129490
  const realRelative = path8.relative(base, realExistingPath);
128861
129491
  if (realRelative.startsWith("..") || path8.isAbsolute(realRelative)) {
128862
129492
  throw new Error(`${fieldName} must stay within the repository root; received ${targetPath}`);
@@ -128884,8 +129514,8 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128884
129514
  ensureDir(specsDir);
128885
129515
  ensureDir(".postman");
128886
129516
  const globalsFilePath = `${globalsDir}/workspace.globals.yaml`;
128887
- if (!(0, import_node_fs3.existsSync)(globalsFilePath)) {
128888
- (0, import_node_fs3.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
129517
+ if (!(0, import_node_fs4.existsSync)(globalsFilePath)) {
129518
+ (0, import_node_fs4.writeFileSync)(globalsFilePath, "name: Globals\nvalues: []\n");
128889
129519
  }
128890
129520
  if (inputs.generateCiWorkflow) {
128891
129521
  const ciDir = inputs.ciWorkflowPath.split("/").slice(0, -1).join("/");
@@ -128932,7 +129562,7 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128932
129562
  workspaceLinkEnabled: inputs.workspaceLinkEnabled,
128933
129563
  workspaceLinkStatus: options.workspaceLinkStatus
128934
129564
  });
128935
- (0, import_node_fs3.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
129565
+ (0, import_node_fs4.writeFileSync)(".postman/resources.yaml", buildResourcesManifest(
128936
129566
  durableWorkspaceId,
128937
129567
  manifestCollections,
128938
129568
  envUids,
@@ -128940,10 +129570,11 @@ async function exportArtifacts(inputs, dependencies, envUids, assetProjectName,
128940
129570
  discoveredSpecs.map((spec) => spec.configRelativePath),
128941
129571
  mappedSpecCloudKey,
128942
129572
  inputs.specId || void 0,
128943
- options.existingSpecs
129573
+ options.existingSpecs,
129574
+ options.priorState
128944
129575
  ));
128945
129576
  if (mappedSpec && Object.keys(manifestCollections).length > 0) {
128946
- (0, import_node_fs3.writeFileSync)(
129577
+ (0, import_node_fs4.writeFileSync)(
128947
129578
  ".postman/workflows.yaml",
128948
129579
  buildSpecCollectionWorkflowManifest(
128949
129580
  mappedSpec.configRelativePath,
@@ -128988,19 +129619,29 @@ async function commitAndPushGeneratedFiles(inputs, dependencies) {
128988
129619
  const dir = parts.slice(0, -1).join("/");
128989
129620
  ensureDir(dir);
128990
129621
  }
128991
- (0, import_node_fs3.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129622
+ (0, import_node_fs4.writeFileSync)(inputs.ciWorkflowPath, ciWorkflow);
129623
+ if (inputs.provider === "github" || inputs.provider === "unknown") {
129624
+ const gcPath = ".github/workflows/postman-preview-gc.yml";
129625
+ if (inputs.ciWorkflowPath.endsWith(".github/workflows/ci.yml") || inputs.ciWorkflowPath === ".github/workflows/ci.yml") {
129626
+ ensureDir(".github/workflows");
129627
+ (0, import_node_fs4.writeFileSync)(gcPath, renderGcWorkflowTemplate());
129628
+ }
129629
+ }
128992
129630
  }
128993
129631
  if (!dependencies.repoMutation || inputs.repoWriteMode === "none") {
128994
129632
  return { commitSha: "", resolvedCurrentRef: "", pushed: false };
128995
129633
  }
128996
129634
  const provisionPath = ".github/workflows/provision.yml";
128997
- const provisionExists = inputs.provider === "github" && (0, import_node_fs3.existsSync)(provisionPath);
129635
+ const provisionExists = inputs.provider === "github" && (0, import_node_fs4.existsSync)(provisionPath);
129636
+ const gcWorkflowPath = ".github/workflows/postman-preview-gc.yml";
129637
+ const gcExists = inputs.generateCiWorkflow && (0, import_node_fs4.existsSync)(gcWorkflowPath);
128998
129638
  const stagePaths = [
128999
129639
  inputs.artifactDir,
129000
129640
  ".postman",
129001
129641
  inputs.generateCiWorkflow ? inputs.ciWorkflowPath : null,
129642
+ gcExists ? gcWorkflowPath : null,
129002
129643
  provisionExists ? provisionPath : null
129003
- ].filter((entry) => typeof entry === "string" && ((0, import_node_fs3.existsSync)(entry) || entry === provisionPath));
129644
+ ].filter((entry) => typeof entry === "string" && ((0, import_node_fs4.existsSync)(entry) || entry === provisionPath));
129004
129645
  if (stagePaths.length === 0) {
129005
129646
  dependencies.core.info("No generated repository paths were found; skipping repo mutation.");
129006
129647
  return {
@@ -129048,6 +129689,31 @@ async function runRepoSync(inputs, dependencies) {
129048
129689
  }
129049
129690
  }
129050
129691
  async function runRepoSyncInner(inputs, dependencies) {
129692
+ const branchDecision = decideBranchTier(inputs);
129693
+ assertBranchAssetIds(inputs, branchDecision);
129694
+ const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
129695
+ if (!isCanonicalWriter) {
129696
+ if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
129697
+ inputs = {
129698
+ ...inputs,
129699
+ projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch),
129700
+ workspaceLinkEnabled: false,
129701
+ environmentSyncEnabled: false,
129702
+ repoWriteMode: "none"
129703
+ };
129704
+ } else if (branchDecision.tier === "channel" && branchDecision.channel) {
129705
+ inputs = {
129706
+ ...inputs,
129707
+ projectName: channelAssetName(inputs.projectName, branchDecision.channel.code),
129708
+ workspaceLinkEnabled: false,
129709
+ environmentSyncEnabled: false,
129710
+ repoWriteMode: "none"
129711
+ };
129712
+ }
129713
+ dependencies.core.info(
129714
+ `branch-aware sync: ${branchDecision.tier} run \u2014 branch-scoped asset set "${inputs.projectName}", no workspace repo-link mutation, no state write-back`
129715
+ );
129716
+ }
129051
129717
  const outputs = createOutputs(inputs);
129052
129718
  const versionRequested = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
129053
129719
  const releaseLabel = deriveReleaseLabel(inputs);
@@ -129055,7 +129721,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129055
129721
  throw new Error("release-label is required when collection-sync-mode or spec-sync-mode is version");
129056
129722
  }
129057
129723
  const assetProjectName = createAssetProjectName(inputs, releaseLabel);
129058
- const resourcesState = readResourcesState();
129724
+ const trackedState = readResourcesState();
129725
+ const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
129059
129726
  if (resourcesState) {
129060
129727
  if (!inputs.workspaceId && resourcesState.workspace?.id) {
129061
129728
  inputs.workspaceId = resourcesState.workspace.id;
@@ -129081,7 +129748,8 @@ async function runRepoSyncInner(inputs, dependencies) {
129081
129748
  }
129082
129749
  }
129083
129750
  }
129084
- const envUids = await upsertEnvironments(inputs, dependencies, resourcesState);
129751
+ const branchAssetMarker = buildBranchAssetMarker(branchDecision, inputs);
129752
+ const envUids = await upsertEnvironments(inputs, dependencies, resourcesState, branchAssetMarker);
129085
129753
  outputs["environment-uids-json"] = JSON.stringify(envUids);
129086
129754
  if (inputs.environmentSyncEnabled && inputs.workspaceId && dependencies.internalIntegration) {
129087
129755
  const associations = Object.entries(envUids).map(([envName, envUid]) => ({
@@ -129137,11 +129805,23 @@ async function runRepoSyncInner(inputs, dependencies) {
129137
129805
  }
129138
129806
  }
129139
129807
  if (!resolvedMockUrl) {
129140
- const mock = await dependencies.postman.createMock(
129141
- inputs.workspaceId,
129142
- mockName,
129143
- inputs.baselineCollectionId,
129144
- mockEnvUid
129808
+ const mock = await retry(
129809
+ () => dependencies.postman.createMock(
129810
+ inputs.workspaceId,
129811
+ mockName,
129812
+ inputs.baselineCollectionId,
129813
+ mockEnvUid
129814
+ ),
129815
+ {
129816
+ maxAttempts: 3,
129817
+ delayMs: 2e3,
129818
+ backoffMultiplier: 2,
129819
+ onRetry: ({ attempt, error: error2 }) => {
129820
+ dependencies.core.warning(
129821
+ `Mock create attempt ${attempt} failed (${error2 instanceof Error ? error2.message : String(error2)}); retrying`
129822
+ );
129823
+ }
129824
+ }
129145
129825
  );
129146
129826
  resolvedMockUrl = mock.url;
129147
129827
  dependencies.core.info(`Created new mock: ${resolvedMockUrl}`);
@@ -129210,16 +129890,19 @@ async function runRepoSyncInner(inputs, dependencies) {
129210
129890
  );
129211
129891
  } catch (error2) {
129212
129892
  outputs["workspace-link-status"] = "failed";
129213
- dependencies.core.warning(
129214
- `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`
129215
- );
129893
+ const message = `Workspace link failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
129894
+ if (branchDecision.tier === "canonical") {
129895
+ throw new Error(message, { cause: error2 });
129896
+ }
129897
+ dependencies.core.warning(message);
129216
129898
  }
129217
129899
  }
129218
129900
  await exportArtifacts(inputs, dependencies, envUids, assetProjectName, {
129219
129901
  workspaceLinkStatus: outputs["workspace-link-status"],
129220
129902
  priorWorkspaceId: resourcesState?.workspace?.id,
129221
129903
  existingSpecs: resourcesState?.cloudResources?.specs,
129222
- releaseLabel
129904
+ releaseLabel,
129905
+ priorState: resourcesState
129223
129906
  });
129224
129907
  const commit = await commitAndPushGeneratedFiles(inputs, dependencies);
129225
129908
  outputs["commit-sha"] = commit.commitSha;
@@ -129227,6 +129910,38 @@ async function runRepoSyncInner(inputs, dependencies) {
129227
129910
  outputs["resolved-current-ref"] = commit.resolvedCurrentRef;
129228
129911
  }
129229
129912
  outputs["repo-sync-summary-json"] = createRepoSummary(outputs, envUids, commit.pushed);
129913
+ if (branchDecision.tier === "canonical" && inputs.specId && inputs.specContentChanged !== false && dependencies.postman.tagSpecVersion) {
129914
+ const shortSha = (branchDecision.identity.headSha ?? "").slice(0, 7);
129915
+ const tagName = inputs.releaseLabel ? `${inputs.releaseLabel}${shortSha ? ` (${shortSha})` : ""}` : shortSha || `sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
129916
+ try {
129917
+ const tag = await dependencies.postman.tagSpecVersion(inputs.specId, tagName);
129918
+ outputs["spec-version-tag"] = tag.name || tagName;
129919
+ if (tag.id) {
129920
+ 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)}`;
129921
+ }
129922
+ dependencies.core.info(`Tagged spec version at finalize: ${tag.name || tagName}`);
129923
+ } catch (error2) {
129924
+ const status = error2.status;
129925
+ if (status === 409) {
129926
+ const tags = await dependencies.postman.listSpecVersionTags?.(inputs.specId).catch(() => []) ?? [];
129927
+ const latest = tags[0];
129928
+ if (latest && (latest.name === tagName || /\([0-9a-f]{7}\)$/.test(latest.name) || /^[0-9a-f]{7}$/.test(latest.name))) {
129929
+ outputs["spec-version-tag"] = latest.name;
129930
+ outputs["spec-version-url"] = `https://web.postman.co/workspace/${encodeURIComponent(inputs.workspaceId)}/specification/${encodeURIComponent(inputs.specId)}?tagId=${encodeURIComponent(latest.id)}&versionLabel=${encodeURIComponent(latest.name)}`;
129931
+ dependencies.core.info(`Latest changelog group already tagged as "${latest.name}"; adopting.`);
129932
+ } else {
129933
+ dependencies.core.warning("Latest changelog group already carries a hand-applied tag; leaving it in place.");
129934
+ }
129935
+ } else {
129936
+ dependencies.core.warning(`Spec version tagging failed (non-fatal): ${error2 instanceof Error ? error2.message : String(error2)}`);
129937
+ }
129938
+ }
129939
+ }
129940
+ if (inputs.branchStrategy !== "legacy" || process.env[BRANCH_DECISION_ENV]) {
129941
+ const decision = decideBranchTier(inputs);
129942
+ outputs["sync-status"] = "synced";
129943
+ outputs["branch-decision"] = serializeBranchDecision(decision);
129944
+ }
129230
129945
  for (const [name, value] of Object.entries(outputs)) {
129231
129946
  dependencies.core.setOutput(name, value);
129232
129947
  }
@@ -129416,7 +130131,19 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129416
130131
  listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
129417
130132
  monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
129418
130133
  findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
129419
- runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets)
130134
+ runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
130135
+ listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
130136
+ // GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
130137
+ deleteEnvironment: gatewayAssets.deleteEnvironment.bind(gatewayAssets),
130138
+ deleteMock: gatewayAssets.deleteMock.bind(gatewayAssets),
130139
+ deleteMonitor: gatewayAssets.deleteMonitor.bind(gatewayAssets),
130140
+ deleteCollection: gatewayAssets.deleteCollection.bind(gatewayAssets),
130141
+ listSpecifications: gatewayAssets.listSpecifications.bind(gatewayAssets),
130142
+ getSpecContent: gatewayAssets.getSpecContent.bind(gatewayAssets),
130143
+ listSpecCollections: gatewayAssets.listSpecCollections.bind(gatewayAssets),
130144
+ deleteSpec: gatewayAssets.deleteSpec.bind(gatewayAssets),
130145
+ tagSpecVersion: gatewayAssets.tagSpecVersion.bind(gatewayAssets),
130146
+ listSpecVersionTags: gatewayAssets.listSpecVersionTags.bind(gatewayAssets)
129420
130147
  };
129421
130148
  const repoMutation = repository && (inputs.repoWriteMode === "commit-only" || inputs.repoWriteMode === "commit-and-push") ? new RepoMutationService({
129422
130149
  provider: inputs.provider,
@@ -129453,8 +130180,43 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
129453
130180
  repoMutation
129454
130181
  };
129455
130182
  }
130183
+ function decideBranchTier(inputs, env = process.env) {
130184
+ return resolveEffectiveBranchDecision(
130185
+ {
130186
+ strategy: inputs.branchStrategy,
130187
+ identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
130188
+ canonicalBranch: inputs.canonicalBranch,
130189
+ channels: parseChannelRules(inputs.channels)
130190
+ },
130191
+ env
130192
+ );
130193
+ }
130194
+ function runGatedSkip(inputs, decision, actionCore) {
130195
+ actionCore.info(
130196
+ `branch-aware sync: gated run (${decision.reason}) \u2014 repo-sync skipped, zero workspace writes`
130197
+ );
130198
+ const outputs = createOutputs(inputs);
130199
+ outputs["sync-status"] = "skipped-branch-gate";
130200
+ outputs["branch-decision"] = serializeBranchDecision(decision);
130201
+ outputs["repo-sync-summary-json"] = JSON.stringify({
130202
+ status: "skipped-branch-gate",
130203
+ reason: decision.reason
130204
+ });
130205
+ for (const [name, value] of Object.entries(outputs)) {
130206
+ actionCore.setOutput(name, value);
130207
+ }
130208
+ return outputs;
130209
+ }
129456
130210
  async function runAction(actionCore = core_exports, actionExec = exec_exports) {
129457
130211
  const inputs = readActionInputs(actionCore);
130212
+ const branchDecision = decideBranchTier(inputs);
130213
+ if (branchDecision.tier === "gated") {
130214
+ return runGatedSkip(inputs, branchDecision, actionCore);
130215
+ }
130216
+ if (branchDecision.tier !== "legacy") {
130217
+ actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
130218
+ process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
130219
+ }
129458
130220
  await mintAccessTokenIfNeeded(inputs, {
129459
130221
  info: (message) => actionCore.info(message),
129460
130222
  warning: (message) => actionCore.warning(message)