@postman-cse/onboarding-bootstrap 2.9.4 → 2.9.5

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
@@ -99989,11 +99989,11 @@ var require_valid = __commonJS({
99989
99989
  var require_clean = __commonJS({
99990
99990
  "node_modules/postman-collection/node_modules/semver/functions/clean.js"(exports2, module2) {
99991
99991
  var parse10 = require_parse6();
99992
- var clean = (version, options) => {
99992
+ var clean2 = (version, options) => {
99993
99993
  const s = parse10(version.trim().replace(/^[=v]+/, ""), options);
99994
99994
  return s ? s.version : null;
99995
99995
  };
99996
- module2.exports = clean;
99996
+ module2.exports = clean2;
99997
99997
  }
99998
99998
  });
99999
99999
 
@@ -101304,7 +101304,7 @@ var require_semver2 = __commonJS({
101304
101304
  var identifiers = require_identifiers();
101305
101305
  var parse10 = require_parse6();
101306
101306
  var valid = require_valid();
101307
- var clean = require_clean();
101307
+ var clean2 = require_clean();
101308
101308
  var inc = require_inc();
101309
101309
  var diff = require_diff();
101310
101310
  var major = require_major();
@@ -101342,7 +101342,7 @@ var require_semver2 = __commonJS({
101342
101342
  module2.exports = {
101343
101343
  parse: parse10,
101344
101344
  valid,
101345
- clean,
101345
+ clean: clean2,
101346
101346
  inc,
101347
101347
  diff,
101348
101348
  major,
@@ -253780,19 +253780,25 @@ var require_cjs4 = __commonJS({
253780
253780
  // src/index.ts
253781
253781
  var index_exports = {};
253782
253782
  __export(index_exports, {
253783
+ applyOas30TypeNullLintCompatibility: () => applyOas30TypeNullLintCompatibility,
253784
+ buildSpecVersionUrl: () => buildSpecVersionUrl,
253783
253785
  createBootstrapDependencies: () => createBootstrapDependencies,
253784
253786
  createExtensibleContractCollection: () => createExtensibleContractCollection,
253785
253787
  createPlannedOutputs: () => createPlannedOutputs,
253786
253788
  createRoutingPostmanClient: () => createRoutingPostmanClient,
253789
+ decideBranchTier: () => decideBranchTier,
253790
+ embedSpecBranchMarker: () => embedSpecBranchMarker,
253787
253791
  ensurePostmanCli: () => ensurePostmanCli,
253788
253792
  getInput: () => getInput2,
253789
253793
  lintSpecViaCli: () => lintSpecViaCli,
253790
253794
  mintAccessTokenIfNeeded: () => mintAccessTokenIfNeeded2,
253791
253795
  normalizeSpecDocument: () => normalizeSpecDocument,
253792
253796
  readActionInputs: () => readActionInputs,
253797
+ renderCollectionBranchMarker: () => renderCollectionBranchMarker,
253793
253798
  resolveInputs: () => resolveInputs,
253794
253799
  runAction: () => runAction,
253795
- runBootstrap: () => runBootstrap
253800
+ runBootstrap: () => runBootstrap,
253801
+ runGatedValidation: () => runGatedValidation
253796
253802
  });
253797
253803
  module.exports = __toCommonJS(index_exports);
253798
253804
 
@@ -256216,8 +256222,8 @@ function getIDToken(aud) {
256216
256222
  }
256217
256223
 
256218
256224
  // src/index.ts
256219
- var import_node_crypto5 = require("node:crypto");
256220
- var import_node_fs5 = require("node:fs");
256225
+ var import_node_crypto6 = require("node:crypto");
256226
+ var import_node_fs6 = require("node:fs");
256221
256227
  var path9 = __toESM(require("node:path"), 1);
256222
256228
  var import_yaml4 = __toESM(require_dist(), 1);
256223
256229
 
@@ -256325,6 +256331,12 @@ var bootstrapActionContract = {
256325
256331
  default: "",
256326
256332
  allowedValues: ["3.0", "3.1"]
256327
256333
  },
256334
+ "preserve-oas30-type-null": {
256335
+ description: "Opt-in compatibility mode for legacy OpenAPI 3.0 oneOf schemas that pair a normal schema with type: null. The original source bytes are uploaded unchanged while an internal nullable view is used for validation and generated artifacts.",
256336
+ required: false,
256337
+ default: "false",
256338
+ allowedValues: ["true", "false"]
256339
+ },
256328
256340
  "breaking-change-mode": {
256329
256341
  description: "OpenAPI breaking-change comparison mode.",
256330
256342
  required: false,
@@ -256381,6 +256393,20 @@ var bootstrapActionContract = {
256381
256393
  default: "warn",
256382
256394
  allowedValues: ["enforce", "warn"]
256383
256395
  },
256396
+ "branch-strategy": {
256397
+ description: "Branch-aware sync strategy. legacy (default) keeps branch-blind behavior; publish-gate restricts canonical writes to the canonical branch and runs credential-free static validation on other branches; preview additionally maintains suffixed per-branch preview asset sets.",
256398
+ required: false,
256399
+ default: "legacy",
256400
+ allowedValues: ["legacy", "preview", "publish-gate"]
256401
+ },
256402
+ "canonical-branch": {
256403
+ description: "Explicit canonical branch (the sole writer of canonical assets). Defaults to the provider-resolved default branch; required on providers without a default-branch variable (Bitbucket, Azure DevOps) when branch-strategy is not legacy.",
256404
+ required: false
256405
+ },
256406
+ "channels": {
256407
+ 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.',
256408
+ required: false
256409
+ },
256384
256410
  "integration-backend": {
256385
256411
  description: "Integration backend for downstream workspace connectivity.",
256386
256412
  required: false,
@@ -256450,6 +256476,21 @@ var bootstrapActionContract = {
256450
256476
  },
256451
256477
  "breaking-change-summary-json": {
256452
256478
  description: "JSON summary of the OpenAPI breaking-change check."
256479
+ },
256480
+ "sync-status": {
256481
+ description: "Branch-aware sync status: synced (canonical/channel/preview write ran), skipped-branch-gate (gated run, credential-free validation only), or empty under branch-strategy legacy."
256482
+ },
256483
+ "branch-decision": {
256484
+ description: "Serialized BranchDecision JSON for downstream actions (also exported as POSTMAN_BRANCH_DECISION)."
256485
+ },
256486
+ "spec-version-tag": {
256487
+ description: "Native Spec Hub version tag applied on this canonical publish (tag-per-publish), empty when tagging was skipped (no-op sync, non-canonical run, or legacy client)."
256488
+ },
256489
+ "spec-version-url": {
256490
+ description: "Reserved for the repo-sync finalizer; bootstrap does not tag before complete onboarding."
256491
+ },
256492
+ "spec-content-changed": {
256493
+ description: "Whether bootstrap changed canonical spec content; repo-sync uses this to skip native version tags on no-op syncs."
256453
256494
  }
256454
256495
  },
256455
256496
  retainedBehavior: [
@@ -257576,6 +257617,15 @@ function assertSupportedLocalViewContract(node, options) {
257576
257617
  }
257577
257618
 
257578
257619
  // src/lib/postman/additional-collections.ts
257620
+ var RESOURCES_STATE_VERSION = 2;
257621
+ var SUPPORTED_STATE_VERSIONS = /* @__PURE__ */ new Set([1, RESOURCES_STATE_VERSION]);
257622
+ var StateUnreadableError = class extends Error {
257623
+ code = "CONTRACT_STATE_UNREADABLE";
257624
+ constructor(message) {
257625
+ super(`CONTRACT_STATE_UNREADABLE: ${message}`);
257626
+ this.name = "StateUnreadableError";
257627
+ }
257628
+ };
257579
257629
  var ADDITIONAL_COLLECTION_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".yaml", ".yml"]);
257580
257630
  var POSTMAN_COLLECTION_V21_SCHEMA_FRAGMENT = "/collection/v2.1.0/collection.json";
257581
257631
  var V3_DEFINITION_PATH = import_node_path2.default.join(".resources", "definition.yaml");
@@ -257631,16 +257681,46 @@ function resourcesStatePath() {
257631
257681
  return import_node_path2.default.join(workspaceRootForLocalInputs(), RESOURCES_PATH);
257632
257682
  }
257633
257683
  function readResourcesState() {
257684
+ let raw;
257634
257685
  try {
257635
- return (0, import_yaml.parse)((0, import_node_fs2.readFileSync)(resourcesStatePath(), "utf8"));
257686
+ raw = (0, import_node_fs2.readFileSync)(resourcesStatePath(), "utf8");
257636
257687
  } catch {
257637
257688
  return null;
257638
257689
  }
257690
+ let parsed;
257691
+ try {
257692
+ parsed = (0, import_yaml.parse)(raw);
257693
+ } catch (error2) {
257694
+ throw new StateUnreadableError(
257695
+ `.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.`
257696
+ );
257697
+ }
257698
+ if (parsed === null || parsed === void 0) {
257699
+ return null;
257700
+ }
257701
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
257702
+ throw new StateUnreadableError(
257703
+ ".postman/resources.yaml exists but does not contain a YAML mapping. Fix or delete the file; refusing to treat tracked state as absent."
257704
+ );
257705
+ }
257706
+ const state = parsed;
257707
+ if (state.version !== void 0 && !SUPPORTED_STATE_VERSIONS.has(Number(state.version))) {
257708
+ throw new StateUnreadableError(
257709
+ `.postman/resources.yaml declares unsupported state version ${String(state.version)} (supported: 1, ${RESOURCES_STATE_VERSION}). Upgrade the action or fix the file.`
257710
+ );
257711
+ }
257712
+ if (state.canonical && !state.cloudResources) {
257713
+ state.cloudResources = { ...state.canonical };
257714
+ }
257715
+ return state;
257639
257716
  }
257640
257717
  function writeResourcesState(state) {
257641
257718
  const target = resourcesStatePath();
257642
257719
  (0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(target), { recursive: true });
257643
- (0, import_node_fs2.writeFileSync)(target, (0, import_yaml.stringify)(state), "utf8");
257720
+ const canonical = state.cloudResources ?? state.canonical ?? {};
257721
+ const persisted = { ...state, version: RESOURCES_STATE_VERSION, canonical };
257722
+ delete persisted.cloudResources;
257723
+ (0, import_node_fs2.writeFileSync)(target, (0, import_yaml.stringify)(persisted), "utf8");
257644
257724
  }
257645
257725
  function findCloudResourceId(map, matcher) {
257646
257726
  if (!map) {
@@ -257650,7 +257730,7 @@ function findCloudResourceId(map, matcher) {
257650
257730
  return match?.[1];
257651
257731
  }
257652
257732
  function findExistingAdditionalCollectionId(resourcesState, resourcePath) {
257653
- return resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
257733
+ return resourcesState?.canonical?.additionalCollections?.[resourcePath] ?? resourcesState?.canonical?.collections?.[resourcePath] ?? resourcesState?.cloudResources?.additionalCollections?.[resourcePath] ?? resourcesState?.cloudResources?.collections?.[resourcePath];
257654
257734
  }
257655
257735
  function resolveAdditionalCollectionsDir(directoryInput) {
257656
257736
  const workspaceRoot = workspaceRootForLocalInputs();
@@ -258652,6 +258732,8 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258652
258732
  * endpoint aborts rather than blocking the run forever. */
258653
258733
  static REQUEST_TIMEOUT_MS = 3e4;
258654
258734
  static POSTMAN_APP_VERSION_URL = `https://dl.pstmn.io/update/status?currentVersion=${_BifrostInternalIntegrationAdapter.MINIMUM_POSTMAN_APP_VERSION}&platform=osx_arm64`;
258735
+ /** Concurrent dual-trigger previews share one spec; peer sync holds a 423 lock. */
258736
+ static SYNC_LOCKED_MAX_RETRIES = 6;
258655
258737
  accessToken;
258656
258738
  tokenProvider;
258657
258739
  appVersionPromise;
@@ -258660,6 +258742,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258660
258742
  gatewayBaseUrl;
258661
258743
  orgMode;
258662
258744
  secretMasker;
258745
+ sleep;
258663
258746
  teamId;
258664
258747
  constructor(options) {
258665
258748
  this.accessToken = String(options.accessToken || "").trim();
@@ -258673,6 +258756,7 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258673
258756
  ).replace(/\/+$/, "");
258674
258757
  this.orgMode = options.orgMode ?? false;
258675
258758
  this.secretMasker = options.secretMasker ?? createSecretMasker([this.accessToken]);
258759
+ this.sleep = options.sleep ?? ((delayMs) => new Promise((resolve5) => setTimeout(resolve5, delayMs)));
258676
258760
  this.teamId = String(options.teamId || "").trim();
258677
258761
  }
258678
258762
  configureTeamContext(teamId, orgMode) {
@@ -258879,55 +258963,72 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258879
258963
  if (collections.length === 0) {
258880
258964
  return;
258881
258965
  }
258882
- const response = await this.proxyRequest(
258883
- "specification",
258884
- "put",
258885
- `/specifications/${specificationId}/collections`,
258886
- collections.map((collection) => ({
258887
- collectionId: collection.collectionId,
258888
- ...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
258889
- }))
258890
- );
258891
- if (response.ok) {
258892
- return;
258966
+ const body2 = collections.map((collection) => ({
258967
+ collectionId: collection.collectionId,
258968
+ ...collection.syncOptions ? { syncOptions: collection.syncOptions } : {}
258969
+ }));
258970
+ for (let lockedAttempt = 0; ; lockedAttempt += 1) {
258971
+ const response = await this.proxyRequest(
258972
+ "specification",
258973
+ "put",
258974
+ `/specifications/${specificationId}/collections`,
258975
+ body2
258976
+ );
258977
+ if (response.ok) {
258978
+ return;
258979
+ }
258980
+ const httpErr = await HttpError.fromResponse(response, {
258981
+ method: "POST",
258982
+ requestHeaders: {
258983
+ "Content-Type": "application/json",
258984
+ "x-access-token": this.currentToken(),
258985
+ ...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
258986
+ },
258987
+ secretValues: [this.currentToken()],
258988
+ url: `${this.bifrostBaseUrl}/ws/proxy`
258989
+ });
258990
+ if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
258991
+ await this.sleep(2e3 * Math.pow(2, lockedAttempt));
258992
+ continue;
258993
+ }
258994
+ const advised = adviseFromHttpError(
258995
+ httpErr,
258996
+ this.adviceContext("collection-to-specification linking")
258997
+ );
258998
+ throw advised ?? httpErr;
258893
258999
  }
258894
- const httpErr = await HttpError.fromResponse(response, {
258895
- method: "POST",
258896
- requestHeaders: {
258897
- "Content-Type": "application/json",
258898
- "x-access-token": this.currentToken(),
258899
- ...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
258900
- },
258901
- secretValues: [this.currentToken()],
258902
- url: `${this.bifrostBaseUrl}/ws/proxy`
258903
- });
258904
- const advised = adviseFromHttpError(
258905
- httpErr,
258906
- this.adviceContext("collection-to-specification linking")
258907
- );
258908
- throw advised ?? httpErr;
258909
259000
  }
258910
259001
  async syncCollection(specificationId, collectionId) {
258911
- const response = await this.proxyRequest(
258912
- "specification",
258913
- "post",
258914
- `/specifications/${specificationId}/collections/${collectionId}/sync`
258915
- );
258916
- if (response.ok) {
258917
- return;
259002
+ for (let lockedAttempt = 0; ; lockedAttempt += 1) {
259003
+ const response = await this.proxyRequest(
259004
+ "specification",
259005
+ "post",
259006
+ `/specifications/${specificationId}/collections/${collectionId}/sync`
259007
+ );
259008
+ if (response.ok) {
259009
+ return;
259010
+ }
259011
+ const bodyText = await response.clone().text().catch(() => "");
259012
+ if (response.status === 400 && /already in sync/i.test(bodyText)) {
259013
+ return;
259014
+ }
259015
+ const httpErr = await HttpError.fromResponse(response, {
259016
+ method: "POST",
259017
+ requestHeaders: {
259018
+ "Content-Type": "application/json",
259019
+ "x-access-token": this.currentToken(),
259020
+ ...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
259021
+ },
259022
+ secretValues: [this.currentToken()],
259023
+ url: `${this.bifrostBaseUrl}/ws/proxy`
259024
+ });
259025
+ if (httpErr.status === 423 && lockedAttempt < _BifrostInternalIntegrationAdapter.SYNC_LOCKED_MAX_RETRIES) {
259026
+ await this.sleep(2e3 * Math.pow(2, lockedAttempt));
259027
+ continue;
259028
+ }
259029
+ const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
259030
+ throw advised ?? httpErr;
258918
259031
  }
258919
- const httpErr = await HttpError.fromResponse(response, {
258920
- method: "POST",
258921
- requestHeaders: {
258922
- "Content-Type": "application/json",
258923
- "x-access-token": this.currentToken(),
258924
- ...this.teamId && this.orgMode ? { "x-entity-team-id": this.teamId } : {}
258925
- },
258926
- secretValues: [this.currentToken()],
258927
- url: `${this.bifrostBaseUrl}/ws/proxy`
258928
- });
258929
- const advised = adviseFromHttpError(httpErr, this.adviceContext("collection sync"));
258930
- throw advised ?? httpErr;
258931
259032
  }
258932
259033
  async getWorkspaceGitRepoUrl(workspaceId) {
258933
259034
  const response = await this.proxyRequest(
@@ -264072,6 +264173,16 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264072
264173
  if (openapiVersion !== "3.0" && openapiVersion !== "3.1") {
264073
264174
  throw new Error(`uploadSpec: unsupported openapiVersion "${openapiVersion}". Expected '3.0' or '3.1'.`);
264074
264175
  }
264176
+ const before = await this.findSpecificationsByExactName(workspaceId, projectName);
264177
+ const existing = adoptExactMatch(
264178
+ `specification:${workspaceId}:${projectName}`,
264179
+ before,
264180
+ (entry) => entry.id
264181
+ );
264182
+ if (existing) {
264183
+ await this.updateSpec(existing.id, specContent, workspaceId);
264184
+ return existing.id;
264185
+ }
264075
264186
  const specType = openapiVersion === "3.1" ? "OPENAPI:3.1" : "OPENAPI:3.0";
264076
264187
  let created;
264077
264188
  try {
@@ -264096,10 +264207,49 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264096
264207
  if (!match) throw error2;
264097
264208
  created = { data: { id: match.id } };
264098
264209
  }
264099
- const specId = String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim();
264210
+ let specId = String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim();
264100
264211
  if (!specId) {
264101
264212
  throw new Error("Spec upload did not return an ID");
264102
264213
  }
264214
+ let matches = await this.findSpecificationsByExactName(workspaceId, projectName);
264215
+ if (before.length === 0 && matches.length <= 1) {
264216
+ await this.sleep(1e3);
264217
+ const settled = await this.findSpecificationsByExactName(workspaceId, projectName);
264218
+ if (settled.length > 0) matches = settled;
264219
+ }
264220
+ if (matches.length > 1 && before.length === 0) {
264221
+ for (const duplicate of matches.slice(1)) {
264222
+ try {
264223
+ await this.deleteSpecification(duplicate.id);
264224
+ } catch (error2) {
264225
+ if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
264226
+ }
264227
+ }
264228
+ for (let attempt = 0; attempt < 5; attempt += 1) {
264229
+ matches = await this.findSpecificationsByExactName(workspaceId, projectName);
264230
+ if (matches.length <= 1) break;
264231
+ await this.sleep(250 * (attempt + 1));
264232
+ }
264233
+ const converged = adoptExactMatch(
264234
+ `specification:${workspaceId}:${projectName}`,
264235
+ matches,
264236
+ (entry) => entry.id
264237
+ );
264238
+ if (!converged) {
264239
+ throw new Error(`Concurrent specification create for ${projectName} did not converge`);
264240
+ }
264241
+ specId = converged.id;
264242
+ if (specId !== String(asRecord8(created?.data)?.id ?? created?.id ?? "").trim()) {
264243
+ await this.updateSpec(specId, specContent, workspaceId);
264244
+ }
264245
+ } else {
264246
+ const verified = adoptExactMatch(
264247
+ `specification:${workspaceId}:${projectName}`,
264248
+ matches,
264249
+ (entry) => entry.id
264250
+ );
264251
+ if (verified) specId = verified.id;
264252
+ }
264103
264253
  await this.gateway.requestJson({
264104
264254
  service: "specification",
264105
264255
  method: "get",
@@ -264107,6 +264257,14 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264107
264257
  });
264108
264258
  return specId;
264109
264259
  }
264260
+ async deleteSpecification(specId) {
264261
+ await this.gateway.requestJson({
264262
+ service: "specification",
264263
+ method: "delete",
264264
+ path: `/specifications/${specId}`,
264265
+ retry: "none"
264266
+ });
264267
+ }
264110
264268
  async findSpecificationsByExactName(workspaceId, name) {
264111
264269
  const response = await this.gateway.requestJson({
264112
264270
  service: "specification",
@@ -264170,6 +264328,41 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264170
264328
  return void 0;
264171
264329
  }
264172
264330
  }
264331
+ /**
264332
+ * Native Spec Hub version tags (branch-aware sync P3.5). Tags attach to the
264333
+ * LATEST changelog group; the backend 409s when that group is already tagged
264334
+ * (VersionControlService). Callers handle 409 as idempotent-by-group.
264335
+ */
264336
+ async tagSpecVersion(specId, name) {
264337
+ const trimmed = name.trim().slice(0, 255);
264338
+ const created = await this.gateway.requestJson({
264339
+ service: "specification",
264340
+ method: "post",
264341
+ path: `/specifications/${specId}/tags`,
264342
+ retry: "none",
264343
+ body: { name: trimmed }
264344
+ });
264345
+ const record = asRecord8(created?.data) ?? created ?? {};
264346
+ return {
264347
+ id: String(record.id ?? "").trim(),
264348
+ name: String(record.name ?? trimmed).trim()
264349
+ };
264350
+ }
264351
+ /** List a spec's native version tags (newest first per backend ordering). */
264352
+ async listSpecVersionTags(specId) {
264353
+ const response = await this.gateway.requestJson({
264354
+ service: "specification",
264355
+ method: "get",
264356
+ path: `/specifications/${specId}/tags`,
264357
+ query: { limit: "50" }
264358
+ });
264359
+ const entries = Array.isArray(response?.data) ? response.data : [];
264360
+ return entries.map((value) => asRecord8(value)).filter((value) => value !== null).map((value) => ({
264361
+ id: String(value.id ?? "").trim(),
264362
+ // listTags returns `message`; createTag returns `name`. Accept both.
264363
+ name: String(value.name ?? value.message ?? "").trim()
264364
+ })).filter((value) => value.id || value.name);
264365
+ }
264173
264366
  /** Resolve a specification's ROOT file uuid via the files list. */
264174
264367
  async resolveRootFileId(specId) {
264175
264368
  const files = await this.gateway.requestJson({
@@ -264189,79 +264382,182 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264189
264382
  */
264190
264383
  async generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) {
264191
264384
  const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
264192
- const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
264193
- const body2 = {
264194
- name: submittedName,
264195
- options: {
264196
- requestNameSource,
264197
- folderStrategy,
264198
- ...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
264199
- }
264200
- };
264201
264385
  const before = await this.listGeneratedCollectionRefs(specId);
264202
- let taskId;
264203
- try {
264204
- taskId = await this.postGenerationWithLockRetry(specId, body2);
264205
- } catch (error2) {
264206
- if (!isAmbiguousTransportError(error2)) throw error2;
264207
- const beforeIds2 = new Set(before.map((entry) => entry.id));
264208
- const appeared2 = (await this.listGeneratedCollectionRefs(specId)).filter(
264209
- (entry) => !beforeIds2.has(entry.id)
264210
- );
264211
- const exactAppeared = await this.filterGeneratedCollectionsByExactName(
264212
- appeared2,
264386
+ const existing = adoptExactMatch(
264387
+ `generated-collection:${specId}:${name}`,
264388
+ await this.filterGeneratedCollectionsByExactName(before, name),
264389
+ (entry) => entry.id
264390
+ );
264391
+ if (existing) return this.convergeGeneratedCollections(specId, name, existing.id);
264392
+ let lastError;
264393
+ for (let taskAttempt = 0; taskAttempt < 2; taskAttempt += 1) {
264394
+ const submittedName = `${name} [bootstrap:${this.createIdentity()}]`;
264395
+ const body2 = {
264396
+ name: submittedName,
264397
+ options: {
264398
+ requestNameSource,
264399
+ folderStrategy,
264400
+ ...folderStrategy === "Tags" ? { nestedFolderHierarchy } : {}
264401
+ }
264402
+ };
264403
+ let taskId;
264404
+ try {
264405
+ const generation = await this.postGenerationWithLockRetry(specId, body2, name);
264406
+ if (generation.adoptedId) {
264407
+ return this.convergeGeneratedCollections(specId, name, generation.adoptedId);
264408
+ }
264409
+ taskId = generation.taskId ?? "";
264410
+ } catch (error2) {
264411
+ if (!isAmbiguousTransportError(error2)) throw error2;
264412
+ const beforeIds2 = new Set(before.map((entry) => entry.id));
264413
+ const appeared2 = (await this.listGeneratedCollectionRefs(specId)).filter(
264414
+ (entry) => !beforeIds2.has(entry.id)
264415
+ );
264416
+ const exactAppeared = await this.filterGeneratedCollectionsByExactName(
264417
+ appeared2,
264418
+ submittedName
264419
+ );
264420
+ const match = adoptExactMatch(
264421
+ `generated-collection:${specId}:${submittedName}`,
264422
+ exactAppeared,
264423
+ (entry) => entry.id
264424
+ );
264425
+ if (!match) throw error2;
264426
+ await this.renameGeneratedCollection(match.id, name);
264427
+ return this.convergeGeneratedCollections(specId, name, match.id);
264428
+ }
264429
+ let taskFailed = false;
264430
+ if (taskId) {
264431
+ for (let attempt = 0; attempt < this.generationPollAttempts; attempt += 1) {
264432
+ await this.sleep(this.generationPollDelayMs);
264433
+ const task = await this.gateway.requestJson({
264434
+ service: "specification",
264435
+ method: "get",
264436
+ path: "/tasks",
264437
+ query: { entityId: specId, entityType: "specification", type: "collection-generation" }
264438
+ });
264439
+ const status = String(asRecord8(task?.data)?.[taskId] ?? "").toLowerCase();
264440
+ if (status === "failed" || status === "error") {
264441
+ taskFailed = true;
264442
+ lastError = new Error(`Collection generation task failed for ${prefix}`);
264443
+ break;
264444
+ }
264445
+ if (status && status !== "in-progress" && status !== "pending" && status !== "queued") {
264446
+ break;
264447
+ }
264448
+ if (attempt === this.generationPollAttempts - 1) {
264449
+ throw new Error(`Collection generation timed out for ${prefix}`);
264450
+ }
264451
+ }
264452
+ }
264453
+ if (taskFailed) {
264454
+ await this.sleep(1e3 * (taskAttempt + 1));
264455
+ continue;
264456
+ }
264457
+ const beforeIds = new Set(before.map((entry) => entry.id));
264458
+ const after = await this.listGeneratedCollectionRefs(specId);
264459
+ const appeared = after.filter((entry) => !beforeIds.has(entry.id));
264460
+ const candidates = await this.filterGeneratedCollectionsByExactName(
264461
+ appeared,
264213
264462
  submittedName
264214
264463
  );
264215
- const match = adoptExactMatch(
264464
+ const uid = adoptExactMatch(
264216
264465
  `generated-collection:${specId}:${submittedName}`,
264217
- exactAppeared,
264466
+ candidates,
264218
264467
  (entry) => entry.id
264219
- );
264220
- if (!match) throw error2;
264221
- await this.renameGeneratedCollection(match.id, name);
264222
- return match.id;
264468
+ )?.id;
264469
+ if (!uid) {
264470
+ throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
264471
+ }
264472
+ await this.renameGeneratedCollection(uid, name);
264473
+ return this.convergeGeneratedCollections(specId, name, uid);
264223
264474
  }
264224
- if (taskId) {
264225
- for (let attempt = 0; attempt < this.generationPollAttempts; attempt += 1) {
264226
- await this.sleep(this.generationPollDelayMs);
264227
- const task = await this.gateway.requestJson({
264228
- service: "specification",
264229
- method: "get",
264230
- path: "/tasks",
264231
- query: { entityId: specId, entityType: "specification", type: "collection-generation" }
264232
- });
264233
- const status = String(asRecord8(task?.data)?.[taskId] ?? "").toLowerCase();
264234
- if (status === "failed" || status === "error") {
264235
- throw new Error(`Collection generation task failed for ${prefix}`);
264236
- }
264237
- if (status && status !== "in-progress" && status !== "pending" && status !== "queued") {
264238
- break;
264239
- }
264240
- if (attempt === this.generationPollAttempts - 1) {
264241
- throw new Error(`Collection generation timed out for ${prefix}`);
264475
+ throw lastError ?? new Error(`Collection generation task failed for ${prefix}`);
264476
+ }
264477
+ /**
264478
+ * Re-elect the durable generated collection for a final name. Call after
264479
+ * concurrent dual-trigger generates and before description/inject/tag so a
264480
+ * peer orphan-sweep cannot leave this runner holding a deleted id.
264481
+ */
264482
+ async adoptGeneratedCollection(specId, projectName, prefix, preferredId = "") {
264483
+ const name = [prefix.trim(), projectName.trim()].filter(Boolean).join(" ");
264484
+ return this.convergeGeneratedCollections(specId, name, preferredId);
264485
+ }
264486
+ /**
264487
+ * Concurrent dual-trigger previews can each generate+rename the same final
264488
+ * collection identity. Elect the stable lowest-id winner.
264489
+ *
264490
+ * Losers only delete *their own* preferred collection (never a peer's still-
264491
+ * in-use id). Winners wait briefly for peers to self-delete, then clean any
264492
+ * leftover same-identity orphans (temps + extra finals).
264493
+ */
264494
+ async convergeGeneratedCollections(specId, finalName, preferredId) {
264495
+ const tempPrefix = `${finalName} [bootstrap:`;
264496
+ const hydrate = async () => {
264497
+ const linked = await this.listGeneratedCollectionRefs(specId);
264498
+ return Promise.all(
264499
+ linked.map(async (entry) => {
264500
+ if (entry.name) return { id: entry.id, name: entry.name };
264501
+ try {
264502
+ const collection = await this.gateway.requestJson({
264503
+ service: "collection",
264504
+ method: "get",
264505
+ path: `/v3/collections/${this.bareModelId(entry.id)}`
264506
+ });
264507
+ return {
264508
+ id: entry.id,
264509
+ name: String(asRecord8(collection?.data)?.name ?? "").trim()
264510
+ };
264511
+ } catch (error2) {
264512
+ if (error2 instanceof HttpError && error2.status === 404) {
264513
+ return { id: entry.id, name: "" };
264514
+ }
264515
+ throw error2;
264516
+ }
264517
+ })
264518
+ );
264519
+ };
264520
+ const selectSameIdentity = (entries) => entries.filter(
264521
+ (entry) => entry.name === finalName || entry.name.startsWith(tempPrefix)
264522
+ ).sort((a, b) => a.id.localeCompare(b.id));
264523
+ let sameIdentity = selectSameIdentity(await hydrate());
264524
+ if (sameIdentity.length === 0) return preferredId;
264525
+ if (sameIdentity.length === 1) {
264526
+ await this.sleep(1e3);
264527
+ sameIdentity = selectSameIdentity(await hydrate());
264528
+ if (sameIdentity.length === 0) return preferredId;
264529
+ }
264530
+ const winner = sameIdentity[0];
264531
+ if (winner.name !== finalName) {
264532
+ await this.renameGeneratedCollection(winner.id, finalName);
264533
+ }
264534
+ if (preferredId && preferredId !== winner.id) {
264535
+ const own = sameIdentity.find((entry) => entry.id === preferredId);
264536
+ if (own) {
264537
+ try {
264538
+ await this.deleteCollection(preferredId);
264539
+ } catch (error2) {
264540
+ if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
264242
264541
  }
264243
264542
  }
264543
+ return winner.id;
264244
264544
  }
264245
- const beforeIds = new Set(before.map((entry) => entry.id));
264246
- const after = await this.listGeneratedCollectionRefs(specId);
264247
- const appeared = after.filter((entry) => !beforeIds.has(entry.id));
264248
- const candidates = await this.filterGeneratedCollectionsByExactName(
264249
- appeared,
264250
- submittedName
264251
- );
264252
- const uid = adoptExactMatch(
264253
- `generated-collection:${specId}:${submittedName}`,
264254
- candidates,
264255
- (entry) => entry.id
264256
- )?.id;
264257
- if (!uid) {
264258
- throw new Error(`Collection generation did not yield a collection uid for ${prefix}`);
264545
+ if (sameIdentity.length > 1) {
264546
+ await this.sleep(1500);
264547
+ sameIdentity = selectSameIdentity(await hydrate());
264548
+ for (const duplicate of sameIdentity) {
264549
+ if (duplicate.id === winner.id) continue;
264550
+ try {
264551
+ await this.deleteCollection(duplicate.id);
264552
+ } catch (error2) {
264553
+ if (!(error2 instanceof HttpError && error2.status === 404)) throw error2;
264554
+ }
264555
+ }
264259
264556
  }
264260
- await this.renameGeneratedCollection(uid, name);
264261
- return uid;
264557
+ return winner.id;
264262
264558
  }
264263
264559
  /** POST the generation request, retrying a 423-locked spec; returns the task id. */
264264
- async postGenerationWithLockRetry(specId, body2) {
264560
+ async postGenerationWithLockRetry(specId, body2, finalName) {
264265
264561
  for (let lockedAttempt = 0; ; lockedAttempt += 1) {
264266
264562
  try {
264267
264563
  const created = await this.gateway.requestJson({
@@ -264271,13 +264567,22 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264271
264567
  retry: "none",
264272
264568
  body: body2
264273
264569
  });
264274
- return String(asRecord8(created?.data)?.taskId ?? "").trim();
264570
+ return { taskId: String(asRecord8(created?.data)?.taskId ?? "").trim() };
264275
264571
  } catch (error2) {
264276
264572
  const locked = error2 instanceof HttpError && error2.status === 423;
264277
264573
  if (!locked || lockedAttempt >= _PostmanGatewayAssetsClient.GENERATION_LOCKED_MAX_RETRIES) {
264278
264574
  throw error2;
264279
264575
  }
264280
264576
  await this.sleep(5e3 * Math.pow(2, lockedAttempt));
264577
+ const adopted = adoptExactMatch(
264578
+ `generated-collection:${specId}:${finalName}`,
264579
+ await this.filterGeneratedCollectionsByExactName(
264580
+ await this.listGeneratedCollectionRefs(specId),
264581
+ finalName
264582
+ ),
264583
+ (entry) => entry.id
264584
+ );
264585
+ if (adopted) return { adoptedId: adopted.id };
264281
264586
  }
264282
264587
  }
264283
264588
  }
@@ -264314,14 +264619,24 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264314
264619
  return hydrated.filter((entry) => entry.name === expectedName);
264315
264620
  }
264316
264621
  async renameGeneratedCollection(collectionId, name) {
264317
- await this.gateway.requestJson({
264318
- service: "collection",
264319
- method: "patch",
264320
- path: `/v3/collections/${this.bareModelId(collectionId)}`,
264321
- // Replacing a generated collection's name with the same value is idempotent.
264322
- retry: "safe",
264323
- body: [{ op: "replace", path: "/name", value: name }]
264324
- });
264622
+ try {
264623
+ await this.gateway.requestJson({
264624
+ service: "collection",
264625
+ method: "patch",
264626
+ path: `/v3/collections/${this.bareModelId(collectionId)}`,
264627
+ // Replacing a generated collection's name with the same value is idempotent.
264628
+ retry: "safe",
264629
+ body: [{ op: "replace", path: "/name", value: name }]
264630
+ });
264631
+ } catch (error2) {
264632
+ if (error2 instanceof HttpError && error2.status === 400 && /must update at least one|REJECTED_PATCH/i.test(
264633
+ `${error2.message}
264634
+ ${error2.responseBody ?? ""}`
264635
+ )) {
264636
+ return;
264637
+ }
264638
+ throw error2;
264639
+ }
264325
264640
  }
264326
264641
  /**
264327
264642
  * Create a team-visible workspace through the gateway workspaces service.
@@ -264596,6 +264911,7 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
264596
264911
  service: "tagging",
264597
264912
  method: "put",
264598
264913
  path: `/v1/tags/collections/${collectionUid}`,
264914
+ retry: "safe",
264599
264915
  body: { tags: normalized.map((slug) => ({ slug })) }
264600
264916
  });
264601
264917
  }
@@ -265222,6 +265538,15 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
265222
265538
  async createCollection(workspaceId, collection, options = {}) {
265223
265539
  const v3 = this.normalizeCollectionForWrite(collection);
265224
265540
  const desiredName = String(v3.name ?? "Untitled Collection");
265541
+ const existing = adoptExactMatch(
265542
+ `collection:${workspaceId}:${desiredName}`,
265543
+ await this.findCollectionsByExactName(workspaceId, desiredName),
265544
+ (entry) => entry.id
265545
+ );
265546
+ if (existing) {
265547
+ await this.updateCollection(existing.id, collection);
265548
+ return existing.id;
265549
+ }
265225
265550
  const submittedName = `${desiredName} [bootstrap:${this.createIdentity()}]`;
265226
265551
  const rootBody = { name: submittedName };
265227
265552
  let created;
@@ -265272,6 +265597,10 @@ var PostmanGatewayAssetsClient = class _PostmanGatewayAssetsClient {
265272
265597
  }
265273
265598
  return rawId;
265274
265599
  }
265600
+ /** Patch only the durable collection description without reconciling its item tree. */
265601
+ async updateCollectionDescription(collectionUid, description) {
265602
+ await this.applyCollectionLevelSettings(this.bareModelId(collectionUid), { description });
265603
+ }
265275
265604
  /**
265276
265605
  * Full-replace reconcile of a curated local v2.1.0 or collection v3 payload: delete every
265277
265606
  * root-level item (deleting a folder cascades its children server-side —
@@ -265496,7 +265825,8 @@ var AccessTokenGatewayClient = class {
265496
265825
  * `requestJson` can still parse it.
265497
265826
  */
265498
265827
  rebuildResponse(response, body2) {
265499
- return new Response(body2, {
265828
+ const nullBody = response.status === 204 || response.status === 205 || response.status === 304;
265829
+ return new Response(nullBody ? null : body2, {
265500
265830
  status: response.status,
265501
265831
  statusText: response.statusText,
265502
265832
  headers: response.headers
@@ -281948,6 +282278,61 @@ function serializeOpenApiDocument(document2) {
281948
282278
  return `${JSON.stringify(document2, null, 2)}
281949
282279
  `;
281950
282280
  }
282281
+ function typeNullPath(pathSegments) {
282282
+ return pathSegments.map(String).join(".");
282283
+ }
282284
+ function isNullOnlySchema(value) {
282285
+ const record = asRecord10(value);
282286
+ if (!record || record.type !== "null") return false;
282287
+ return Object.keys(record).every((key) => key === "type" || key.startsWith("x-"));
282288
+ }
282289
+ function createOas30TypeNullCompatibilityDocument(sourceDocument) {
282290
+ const document2 = structuredClone(sourceDocument);
282291
+ const sourceTypeNullPaths = [];
282292
+ const visit4 = (value, pathSegments) => {
282293
+ if (Array.isArray(value)) {
282294
+ value.forEach((entry, index) => visit4(entry, [...pathSegments, index]));
282295
+ return;
282296
+ }
282297
+ const record = asRecord10(value);
282298
+ if (!record) return;
282299
+ const oneOf = Array.isArray(record.oneOf) ? record.oneOf : void 0;
282300
+ const nullIndexes = oneOf ? oneOf.flatMap((entry, index) => isNullOnlySchema(entry) ? [index] : []) : [];
282301
+ if (nullIndexes.length > 0) {
282302
+ if (oneOf?.length !== 2 || nullIndexes.length !== 1) {
282303
+ throw new Error(
282304
+ `CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must use oneOf with exactly one null-only member and one non-null schema`
282305
+ );
282306
+ }
282307
+ const nullIndex = nullIndexes[0];
282308
+ const nonNullIndex = nullIndex === 0 ? 1 : 0;
282309
+ const nonNullSchema = asRecord10(oneOf[nonNullIndex]);
282310
+ if (!nonNullSchema || nonNullSchema.type === "null") {
282311
+ throw new Error(
282312
+ `CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath(pathSegments)} must pair the null-only member with one non-null schema`
282313
+ );
282314
+ }
282315
+ const siblings = Object.fromEntries(
282316
+ Object.entries(record).filter(([key]) => key !== "oneOf")
282317
+ );
282318
+ for (const key of Object.keys(record)) delete record[key];
282319
+ Object.assign(record, structuredClone(nonNullSchema), siblings, { nullable: true });
282320
+ sourceTypeNullPaths.push(typeNullPath([...pathSegments, "oneOf", nullIndex, "type"]));
282321
+ visit4(record, pathSegments);
282322
+ return;
282323
+ }
282324
+ if (record.type === "null") {
282325
+ throw new Error(
282326
+ `CONTRACT_OAS30_TYPE_NULL_UNSUPPORTED: ${typeNullPath([...pathSegments, "type"])} is not a supported nullable oneOf member`
282327
+ );
282328
+ }
282329
+ for (const [key, child4] of Object.entries(record)) {
282330
+ visit4(child4, [...pathSegments, key]);
282331
+ }
282332
+ };
282333
+ visit4(document2, []);
282334
+ return { document: document2, sourceTypeNullPaths };
282335
+ }
281951
282336
  async function bundleSpec(baseUrl, document2, options) {
281952
282337
  const budget = options.budget ?? { refs: 0, totalBytes: Buffer.byteLength(JSON.stringify(document2), "utf8") };
281953
282338
  const fetchText = options.fetchText ?? safeFetchText;
@@ -281989,7 +282374,14 @@ function createCachedFetchText(options) {
281989
282374
  async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
281990
282375
  const document2 = parseOpenApiDocument(content);
281991
282376
  const version = detectOpenApiVersion2(document2);
281992
- const bundledDocument = await bundleSpec(baseRef, document2, { ...options, budget, fetchText });
282377
+ let contractDocument = document2;
282378
+ let sourceTypeNullPaths = [];
282379
+ if (options.preserveOas30TypeNull && version === "3.0") {
282380
+ const compatibility = createOas30TypeNullCompatibilityDocument(document2);
282381
+ contractDocument = compatibility.document;
282382
+ sourceTypeNullPaths = compatibility.sourceTypeNullPaths;
282383
+ }
282384
+ const bundledDocument = await bundleSpec(baseRef, contractDocument, { ...options, budget, fetchText });
281993
282385
  const validation = await validate(bundledDocument, {
281994
282386
  resolve: { external: false, file: false },
281995
282387
  dereference: { circular: "ignore" },
@@ -282003,6 +282395,7 @@ async function buildLoadedSpec(content, baseRef, options, fetchText, budget) {
282003
282395
  bundledDocument,
282004
282396
  contractIndex: buildContractIndex(bundledDocument),
282005
282397
  content,
282398
+ sourceTypeNullPaths,
282006
282399
  version
282007
282400
  };
282008
282401
  }
@@ -282128,6 +282521,309 @@ function looksLikeIntrospection(record) {
282128
282521
  return Boolean(data && typeof data === "object" && data.__schema);
282129
282522
  }
282130
282523
 
282524
+ // src/lib/repo/branch-decision.ts
282525
+ var import_node_fs5 = require("node:fs");
282526
+ var import_node_crypto4 = require("node:crypto");
282527
+ var ContractError = class extends Error {
282528
+ code;
282529
+ constructor(code, message) {
282530
+ super(`${code}: ${message}`);
282531
+ this.code = code;
282532
+ this.name = "ContractError";
282533
+ }
282534
+ };
282535
+ function clean(value) {
282536
+ const trimmed = (value ?? "").trim();
282537
+ return trimmed.length > 0 ? trimmed : void 0;
282538
+ }
282539
+ function stripRefPrefix(ref) {
282540
+ const raw = clean(ref);
282541
+ if (!raw) {
282542
+ return { kind: "unknown" };
282543
+ }
282544
+ if (raw.startsWith("refs/heads/")) {
282545
+ return { name: raw.slice("refs/heads/".length), kind: "branch" };
282546
+ }
282547
+ if (raw.startsWith("refs/tags/")) {
282548
+ return { name: raw.slice("refs/tags/".length), kind: "tag" };
282549
+ }
282550
+ if (raw.startsWith("refs/pull/") || raw.startsWith("refs/merge")) {
282551
+ return { kind: "unknown" };
282552
+ }
282553
+ return { name: raw, kind: "branch" };
282554
+ }
282555
+ function detectProvider(env) {
282556
+ if (clean(env.GITHUB_ACTIONS) || clean(env.GITHUB_REPOSITORY)) return "github";
282557
+ if (clean(env.GITLAB_CI) || clean(env.CI_PROJECT_PATH)) return "gitlab";
282558
+ if (clean(env.BITBUCKET_REPO_SLUG) || clean(env.BITBUCKET_BRANCH)) return "bitbucket";
282559
+ if (clean(env.TF_BUILD) || clean(env.BUILD_REPOSITORY_URI)) return "azure-devops";
282560
+ return "unknown";
282561
+ }
282562
+ function readGithubEvent(env) {
282563
+ const path10 = clean(env.GITHUB_EVENT_PATH);
282564
+ if (!path10) return void 0;
282565
+ try {
282566
+ return JSON.parse((0, import_node_fs5.readFileSync)(path10, "utf8"));
282567
+ } catch {
282568
+ return void 0;
282569
+ }
282570
+ }
282571
+ function resolveBranchIdentity(env = process.env, overrides = {}) {
282572
+ const provider = detectProvider(env);
282573
+ const explicitDefault = clean(overrides.defaultBranch);
282574
+ let headBranch;
282575
+ let rawRef;
282576
+ let refKind;
282577
+ let isPrContext = false;
282578
+ let isForkPr = false;
282579
+ let defaultBranch = explicitDefault;
282580
+ let headSha;
282581
+ switch (provider) {
282582
+ case "github": {
282583
+ const event2 = readGithubEvent(env);
282584
+ headSha = clean(env.GITHUB_SHA);
282585
+ defaultBranch ??= clean(event2?.repository?.default_branch);
282586
+ const headRef = clean(env.GITHUB_HEAD_REF);
282587
+ if (headRef) {
282588
+ isPrContext = true;
282589
+ headBranch = headRef;
282590
+ rawRef = clean(env.GITHUB_REF) ?? headRef;
282591
+ refKind = "branch";
282592
+ const headRepo = event2?.pull_request?.head?.repo?.full_name;
282593
+ const baseRepo = event2?.pull_request?.base?.repo?.full_name ?? event2?.repository?.full_name;
282594
+ isForkPr = Boolean(headRepo && baseRepo && headRepo !== baseRepo);
282595
+ headSha = clean(event2?.pull_request?.head?.sha) ?? headSha;
282596
+ } else {
282597
+ rawRef = clean(env.GITHUB_REF) ?? clean(env.GITHUB_REF_NAME);
282598
+ const parsed = stripRefPrefix(rawRef);
282599
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
282600
+ refKind = parsed.kind;
282601
+ }
282602
+ break;
282603
+ }
282604
+ case "gitlab": {
282605
+ headSha = clean(env.CI_COMMIT_SHA);
282606
+ defaultBranch ??= clean(env.CI_DEFAULT_BRANCH);
282607
+ const mrSource = clean(env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME);
282608
+ if (mrSource) {
282609
+ isPrContext = true;
282610
+ headBranch = mrSource;
282611
+ rawRef = mrSource;
282612
+ refKind = "branch";
282613
+ const sourceProject = clean(env.CI_MERGE_REQUEST_SOURCE_PROJECT_ID);
282614
+ const targetProject = clean(env.CI_MERGE_REQUEST_PROJECT_ID);
282615
+ isForkPr = Boolean(sourceProject && targetProject && sourceProject !== targetProject);
282616
+ } else if (clean(env.CI_COMMIT_TAG)) {
282617
+ rawRef = clean(env.CI_COMMIT_TAG);
282618
+ refKind = "tag";
282619
+ } else {
282620
+ headBranch = clean(env.CI_COMMIT_BRANCH) ?? clean(env.CI_COMMIT_REF_NAME);
282621
+ rawRef = headBranch;
282622
+ refKind = headBranch ? "branch" : "unknown";
282623
+ }
282624
+ break;
282625
+ }
282626
+ case "bitbucket": {
282627
+ headSha = clean(env.BITBUCKET_COMMIT);
282628
+ if (clean(env.BITBUCKET_TAG)) {
282629
+ rawRef = clean(env.BITBUCKET_TAG);
282630
+ refKind = "tag";
282631
+ } else {
282632
+ headBranch = clean(env.BITBUCKET_BRANCH);
282633
+ rawRef = headBranch;
282634
+ refKind = headBranch ? "branch" : "unknown";
282635
+ isPrContext = Boolean(clean(env.BITBUCKET_PR_ID));
282636
+ }
282637
+ break;
282638
+ }
282639
+ case "azure-devops": {
282640
+ headSha = clean(env.BUILD_SOURCEVERSION);
282641
+ const prSource = clean(env.SYSTEM_PULLREQUEST_SOURCEBRANCH);
282642
+ if (prSource) {
282643
+ isPrContext = true;
282644
+ const parsed = stripRefPrefix(prSource);
282645
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
282646
+ rawRef = prSource;
282647
+ refKind = parsed.kind;
282648
+ const forkFlag = clean(env.SYSTEM_PULLREQUEST_ISFORK);
282649
+ isForkPr = forkFlag?.toLowerCase() === "true";
282650
+ } else {
282651
+ rawRef = clean(env.BUILD_SOURCEBRANCH);
282652
+ const parsed = stripRefPrefix(rawRef);
282653
+ headBranch = parsed.kind === "branch" ? parsed.name : void 0;
282654
+ refKind = parsed.kind;
282655
+ }
282656
+ break;
282657
+ }
282658
+ default: {
282659
+ refKind = "unknown";
282660
+ }
282661
+ }
282662
+ if (refKind === "branch" && headBranch && defaultBranch && headBranch === defaultBranch) {
282663
+ refKind = "default-branch";
282664
+ }
282665
+ return { provider, headBranch, rawRef, defaultBranch, refKind, isPrContext, isForkPr, headSha };
282666
+ }
282667
+ function parseChannelRules(input) {
282668
+ const raw = clean(input);
282669
+ const rules = [];
282670
+ for (const part of raw ? raw.split(",") : []) {
282671
+ const entry = part.trim();
282672
+ if (!entry) continue;
282673
+ const eq = entry.indexOf("=");
282674
+ if (eq <= 0 || eq === entry.length - 1) {
282675
+ throw new ContractError(
282676
+ "CONTRACT_CHANNELS_INPUT_INVALID",
282677
+ `channels entry "${entry}" must be <branch-or-glob>=<CODE>`
282678
+ );
282679
+ }
282680
+ const pattern = entry.slice(0, eq).trim();
282681
+ const code = entry.slice(eq + 1).trim().toUpperCase();
282682
+ if (!/^[A-Z][A-Z0-9_-]{0,15}$/.test(code)) {
282683
+ throw new ContractError(
282684
+ "CONTRACT_CHANNELS_INPUT_INVALID",
282685
+ `channel code "${code}" must be 1-16 chars, A-Z 0-9 _ -, starting with a letter`
282686
+ );
282687
+ }
282688
+ rules.push({ pattern, code });
282689
+ }
282690
+ if (!rules.some((rule) => rule.pattern === "release/*")) {
282691
+ rules.push({ pattern: "release/*", code: "RC" });
282692
+ }
282693
+ return rules;
282694
+ }
282695
+ function matchChannel(branch, rules) {
282696
+ for (const rule of rules) {
282697
+ if (rule.pattern.endsWith("*")) {
282698
+ const prefix = rule.pattern.slice(0, -1);
282699
+ if (branch.startsWith(prefix)) return rule;
282700
+ } else if (branch === rule.pattern) {
282701
+ return rule;
282702
+ }
282703
+ }
282704
+ return void 0;
282705
+ }
282706
+ function resolveBranchDecision(options) {
282707
+ const { strategy, identity } = options;
282708
+ const channels = options.channels ?? [];
282709
+ if (strategy === "legacy") {
282710
+ return {
282711
+ tier: "legacy",
282712
+ strategy,
282713
+ identity,
282714
+ canonicalBranch: clean(options.canonicalBranch) ?? identity.defaultBranch,
282715
+ reason: "branch-strategy legacy: branch-blind pre-v2 behavior"
282716
+ };
282717
+ }
282718
+ const canonicalBranch = clean(options.canonicalBranch) ?? identity.defaultBranch;
282719
+ if (!canonicalBranch) {
282720
+ throw new ContractError(
282721
+ "CONTRACT_DEFAULT_BRANCH_UNRESOLVED",
282722
+ `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.`
282723
+ );
282724
+ }
282725
+ if (identity.refKind === "tag" || identity.refKind === "unknown" || !identity.headBranch) {
282726
+ return {
282727
+ tier: "gated",
282728
+ strategy,
282729
+ identity,
282730
+ canonicalBranch,
282731
+ reason: `ref kind ${identity.refKind}: never canonical/preview-eligible; no-op with annotation`
282732
+ };
282733
+ }
282734
+ if (identity.headBranch === canonicalBranch) {
282735
+ return {
282736
+ tier: "canonical",
282737
+ strategy,
282738
+ identity,
282739
+ canonicalBranch,
282740
+ reason: `head branch equals canonical branch ${canonicalBranch}`
282741
+ };
282742
+ }
282743
+ const channel = matchChannel(identity.headBranch, channels);
282744
+ if (channel) {
282745
+ return {
282746
+ tier: "channel",
282747
+ strategy,
282748
+ identity,
282749
+ canonicalBranch,
282750
+ channel,
282751
+ reason: `branch ${identity.headBranch} matches channel ${channel.pattern}=${channel.code}`
282752
+ };
282753
+ }
282754
+ if (strategy === "preview") {
282755
+ if (identity.isForkPr) {
282756
+ return {
282757
+ tier: "gated",
282758
+ strategy,
282759
+ identity,
282760
+ canonicalBranch,
282761
+ reason: "fork PR: preview-ineligible (same-repo gate), gated instead"
282762
+ };
282763
+ }
282764
+ return {
282765
+ tier: "preview",
282766
+ strategy,
282767
+ identity,
282768
+ canonicalBranch,
282769
+ reason: `branch ${identity.headBranch} under branch-strategy preview`
282770
+ };
282771
+ }
282772
+ return {
282773
+ tier: "gated",
282774
+ strategy,
282775
+ identity,
282776
+ canonicalBranch,
282777
+ reason: `branch ${identity.headBranch} under branch-strategy publish-gate`
282778
+ };
282779
+ }
282780
+ var BRANCH_DECISION_ENV = "POSTMAN_BRANCH_DECISION";
282781
+ function serializeBranchDecision(decision) {
282782
+ return JSON.stringify(decision);
282783
+ }
282784
+ function parseBranchDecision(raw) {
282785
+ const value = clean(raw);
282786
+ if (!value) return void 0;
282787
+ let parsed;
282788
+ try {
282789
+ parsed = JSON.parse(value);
282790
+ } catch {
282791
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION is not valid JSON");
282792
+ }
282793
+ const candidate = parsed;
282794
+ const tiers = ["canonical", "channel", "preview", "gated", "legacy"];
282795
+ if (!candidate || typeof candidate !== "object" || !tiers.includes(candidate.tier) || !candidate.identity) {
282796
+ throw new ContractError("CONTRACT_BRANCH_DECISION_INVALID", "POSTMAN_BRANCH_DECISION does not carry a valid BranchDecision");
282797
+ }
282798
+ return candidate;
282799
+ }
282800
+ function resolveEffectiveBranchDecision(options, env = process.env) {
282801
+ const inherited = parseBranchDecision(env[BRANCH_DECISION_ENV]);
282802
+ if (inherited) return inherited;
282803
+ return resolveBranchDecision(options);
282804
+ }
282805
+ var PREVIEW_SLUG_MAX = 30;
282806
+ function buildBranchSlug(rawBranch) {
282807
+ const sanitized = rawBranch.replace(/^refs\/heads\//, "").replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
282808
+ const truncated = sanitized.slice(0, PREVIEW_SLUG_MAX);
282809
+ const lossy = truncated !== rawBranch.replace(/^refs\/heads\//, "");
282810
+ if (!lossy) {
282811
+ return { suffix: truncated, slug: truncated, lossy };
282812
+ }
282813
+ const hash = (0, import_node_crypto4.createHash)("sha256").update(rawBranch).digest("hex").slice(0, 6);
282814
+ return { suffix: `${truncated}-${hash}`, slug: truncated, lossy };
282815
+ }
282816
+ function previewAssetName(baseName, rawBranch) {
282817
+ return `${baseName} @${buildBranchSlug(rawBranch).suffix}`;
282818
+ }
282819
+ function channelAssetName(baseName, code) {
282820
+ return `[${code}] ${baseName}`;
282821
+ }
282822
+ var MARKER_KEY = "x-pm-onboarding";
282823
+ function renderAssetMarker(marker) {
282824
+ return `${MARKER_KEY}: ${JSON.stringify(marker)}`;
282825
+ }
282826
+
282131
282827
  // node_modules/graphql/jsutils/inspect.mjs
282132
282828
  var MAX_ARRAY_LENGTH = 10;
282133
282829
  var MAX_RECURSIVE_DEPTH = 2;
@@ -305459,7 +306155,7 @@ function parseWsdl(content, opts) {
305459
306155
  }
305460
306156
 
305461
306157
  // src/lib/protocols/soap/builder.ts
305462
- var import_node_crypto4 = require("node:crypto");
306158
+ var import_node_crypto5 = require("node:crypto");
305463
306159
  var COLLECTION_V210_SCHEMA2 = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
305464
306160
  var SOAP11_ENVELOPE_NS = "http://schemas.xmlsoap.org/soap/envelope/";
305465
306161
  var SOAP12_ENVELOPE_NS = "http://www.w3.org/2003/05/soap-envelope";
@@ -305467,7 +306163,7 @@ var SOAP11_CONTENT_TYPE = "text/xml; charset=UTF-8";
305467
306163
  var SOAP12_CONTENT_TYPE = "application/soap+xml; charset=UTF-8";
305468
306164
  var WSA_NS = "http://www.w3.org/2005/08/addressing";
305469
306165
  function stableId2(seed) {
305470
- return (0, import_node_crypto4.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
306166
+ return (0, import_node_crypto5.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
305471
306167
  }
305472
306168
  function envelopeNamespace(version) {
305473
306169
  return version === "1.2" ? SOAP12_ENVELOPE_NS : SOAP11_ENVELOPE_NS;
@@ -312230,6 +312926,11 @@ function resolveInputs(env = process.env) {
312230
312926
  ),
312231
312927
  protocolEndpointUrl: getInput2("protocol-endpoint-url", env),
312232
312928
  openapiVersion: resolveOpenapiVersion(getInput2("openapi-version", env)),
312929
+ preserveOas30TypeNull: parseBooleanInput(
312930
+ "preserve-oas30-type-null",
312931
+ getInput2("preserve-oas30-type-null", env),
312932
+ false
312933
+ ),
312233
312934
  breakingChangeMode: parseBreakingChangeMode(getInput2("breaking-change-mode", env)),
312234
312935
  breakingBaselineSpecPath: getInput2("breaking-baseline-spec-path", env),
312235
312936
  breakingRulesPath: getInput2("breaking-rules-path", env) ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
@@ -312260,8 +312961,74 @@ function resolveInputs(env = process.env) {
312260
312961
  githubRef: env.GITHUB_REF,
312261
312962
  githubSha: env.GITHUB_SHA,
312262
312963
  githubToken: getInput2("github-token", env) || env.GITHUB_TOKEN || "",
312263
- ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || ""
312964
+ ghFallbackToken: getInput2("gh-fallback-token", env) || env.GH_FALLBACK_TOKEN || "",
312965
+ branchStrategy: parseEnumInput(
312966
+ "branch-strategy",
312967
+ getInput2("branch-strategy", env),
312968
+ "legacy"
312969
+ ),
312970
+ canonicalBranch: getInput2("canonical-branch", env),
312971
+ channels: getInput2("channels", env)
312972
+ };
312973
+ }
312974
+ function decideBranchTier(inputs, env = process.env) {
312975
+ return resolveEffectiveBranchDecision(
312976
+ {
312977
+ // Tolerate hand-built inputs objects (tests, embedders) that omit the
312978
+ // field: absent means legacy, exactly like the action default.
312979
+ strategy: inputs.branchStrategy ?? "legacy",
312980
+ identity: resolveBranchIdentity(env, { defaultBranch: inputs.canonicalBranch }),
312981
+ canonicalBranch: inputs.canonicalBranch,
312982
+ channels: parseChannelRules(inputs.channels)
312983
+ },
312984
+ env
312985
+ );
312986
+ }
312987
+ function buildSpecVersionUrl(workspaceId, specId, tagId, versionLabel) {
312988
+ return "https://go.postman.co/workspace/" + workspaceId + "/specification/" + specId + "?tagId=" + encodeURIComponent(tagId) + "&versionLabel=" + encodeURIComponent(versionLabel);
312989
+ }
312990
+ function embedSpecBranchMarker(content, decision, repo) {
312991
+ if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
312992
+ return content;
312993
+ }
312994
+ const rawBranch = decision.identity.headBranch;
312995
+ const now = (/* @__PURE__ */ new Date()).toISOString();
312996
+ const marker = {
312997
+ repo,
312998
+ rawBranch,
312999
+ sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
313000
+ role: decision.tier,
313001
+ headSha: decision.identity.headSha,
313002
+ createdAt: now,
313003
+ lastSyncedAt: now
312264
313004
  };
313005
+ try {
313006
+ if (content.trim().startsWith("{")) {
313007
+ return `${JSON.stringify({ ...JSON.parse(content), "x-postman-onboarding": marker }, null, 2)}
313008
+ `;
313009
+ }
313010
+ const document2 = (0, import_yaml4.parse)(content);
313011
+ if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) return content;
313012
+ return (0, import_yaml4.stringify)({ ...document2, "x-postman-onboarding": marker });
313013
+ } catch {
313014
+ return content;
313015
+ }
313016
+ }
313017
+ function renderCollectionBranchMarker(decision, repo, now = /* @__PURE__ */ new Date()) {
313018
+ if (decision.tier !== "preview" && decision.tier !== "channel" || !decision.identity.headBranch || !repo) {
313019
+ return void 0;
313020
+ }
313021
+ const rawBranch = decision.identity.headBranch;
313022
+ const timestamp = now.toISOString();
313023
+ return renderAssetMarker({
313024
+ repo,
313025
+ rawBranch,
313026
+ sanitizedBranch: rawBranch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 30),
313027
+ role: decision.tier,
313028
+ headSha: decision.identity.headSha,
313029
+ createdAt: timestamp,
313030
+ lastSyncedAt: timestamp
313031
+ });
312265
313032
  }
312266
313033
  function createPlannedOutputs(inputs) {
312267
313034
  const workspaceName = inputs.domainCode ? `[${inputs.domainCode}] ${inputs.projectName}` : inputs.projectName;
@@ -312294,7 +313061,12 @@ function createPlannedOutputs(inputs) {
312294
313061
  mode: inputs.breakingChangeMode,
312295
313062
  status: "skipped",
312296
313063
  summaryPath: ""
312297
- })
313064
+ }),
313065
+ "sync-status": "",
313066
+ "branch-decision": "",
313067
+ "spec-version-tag": "",
313068
+ "spec-version-url": "",
313069
+ "spec-content-changed": ""
312298
313070
  };
312299
313071
  }
312300
313072
  function warnIfDeprecatedAccessToken(actionCore, inputs) {
@@ -312331,9 +313103,6 @@ function readActionInputs(actionCore) {
312331
313103
  }
312332
313104
  const postmanApiKey = optionalInput(actionCore, "postman-api-key") ?? "";
312333
313105
  const postmanAccessToken = optionalInput(actionCore, "postman-access-token");
312334
- if (!postmanApiKey && !postmanAccessToken) {
312335
- throw new Error("One of postman-api-key or postman-access-token is required.");
312336
- }
312337
313106
  const githubToken = optionalInput(actionCore, "github-token") || process.env.GITHUB_TOKEN;
312338
313107
  const ghFallbackToken = optionalInput(actionCore, "gh-fallback-token") || process.env.GH_FALLBACK_TOKEN;
312339
313108
  if (postmanApiKey) actionCore.setSecret(postmanApiKey);
@@ -312377,6 +313146,7 @@ function readActionInputs(actionCore) {
312377
313146
  INPUT_NESTED_FOLDER_HIERARCHY: optionalInput(actionCore, "nested-folder-hierarchy") ?? bootstrapActionContract.inputs["nested-folder-hierarchy"].default,
312378
313147
  INPUT_REQUEST_NAME_SOURCE: optionalInput(actionCore, "request-name-source") ?? bootstrapActionContract.inputs["request-name-source"].default,
312379
313148
  INPUT_OPENAPI_VERSION: optionalInput(actionCore, "openapi-version") ?? "",
313149
+ INPUT_PRESERVE_OAS30_TYPE_NULL: optionalInput(actionCore, "preserve-oas30-type-null") ?? bootstrapActionContract.inputs["preserve-oas30-type-null"].default,
312380
313150
  INPUT_BREAKING_CHANGE_MODE: optionalInput(actionCore, "breaking-change-mode") ?? bootstrapActionContract.inputs["breaking-change-mode"].default,
312381
313151
  INPUT_BREAKING_BASELINE_SPEC_PATH: optionalInput(actionCore, "breaking-baseline-spec-path"),
312382
313152
  INPUT_BREAKING_RULES_PATH: optionalInput(actionCore, "breaking-rules-path") ?? bootstrapActionContract.inputs["breaking-rules-path"].default,
@@ -312487,6 +313257,27 @@ async function lintSpecViaCli(dependencies, workspaceId, specId) {
312487
313257
  warnings
312488
313258
  };
312489
313259
  }
313260
+ function normalizeLintPath(value) {
313261
+ return value.trim().replace(/^\$\.?/, "").replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "");
313262
+ }
313263
+ function applyOas30TypeNullLintCompatibility(summary2, sourceTypeNullPaths) {
313264
+ const acceptedPaths = new Set(sourceTypeNullPaths.map(normalizeLintPath));
313265
+ if (acceptedPaths.size === 0) return summary2;
313266
+ const violations = summary2.violations.map((violation) => {
313267
+ const path10 = normalizeLintPath(violation.path ?? "");
313268
+ const issue2 = violation.issue ?? "";
313269
+ const isTypeEnumFinding = /["']?type["']? property/i.test(issue2) && /allowed values|must be equal to one of/i.test(issue2);
313270
+ if (violation.severity === "ERROR" && acceptedPaths.has(path10) && isTypeEnumFinding) {
313271
+ return { ...violation, severity: "WARNING" };
313272
+ }
313273
+ return violation;
313274
+ });
313275
+ return {
313276
+ errors: violations.filter((entry) => entry.severity === "ERROR").length,
313277
+ violations,
313278
+ warnings: violations.filter((entry) => entry.severity === "WARNING").length
313279
+ };
313280
+ }
312490
313281
  function shouldRetrySpecFetch(error2) {
312491
313282
  const retryability = classifySafeFetchRetryability(error2);
312492
313283
  return retryability === "retryable" || retryability === "unknown";
@@ -312967,7 +313758,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
312967
313758
  const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
312968
313759
  const serviceConfigPath = path9.join(path9.dirname(path9.resolve(workspaceRoot, inputs.specPath)), "grpc_service_config.json");
312969
313760
  try {
312970
- grpcServiceConfigJson = (0, import_node_fs5.readFileSync)(serviceConfigPath, "utf8");
313761
+ grpcServiceConfigJson = (0, import_node_fs6.readFileSync)(serviceConfigPath, "utf8");
312971
313762
  dependencies.core.info(`Found gRPC service config at ${serviceConfigPath}; validating it against the proto contract`);
312972
313763
  } catch {
312973
313764
  }
@@ -312981,7 +313772,7 @@ async function runProtocolBootstrap(specType, rawSpecContent, inputs, dependenci
312981
313772
  const resolved = path9.resolve(specDir, location2);
312982
313773
  if (!resolved.startsWith(specDir + path9.sep)) return void 0;
312983
313774
  try {
312984
- return (0, import_node_fs5.readFileSync)(resolved, "utf8");
313775
+ return (0, import_node_fs6.readFileSync)(resolved, "utf8");
312985
313776
  } catch {
312986
313777
  return void 0;
312987
313778
  }
@@ -313122,6 +313913,42 @@ async function createExtensibleContractCollection(workspaceId, built, inputs, de
313122
313913
  }
313123
313914
  async function runBootstrapInner(inputs, dependencies, telemetry) {
313124
313915
  const outputs = createPlannedOutputs(inputs);
313916
+ const branchDecision = decideBranchTier(inputs);
313917
+ const isCanonicalWriter = branchDecision.tier === "legacy" || branchDecision.tier === "canonical";
313918
+ const canonicalProjectName = inputs.projectName;
313919
+ const workspaceName = createWorkspaceName(inputs);
313920
+ const aboutText = `Auto-provisioned by Postman for ${inputs.projectName}`;
313921
+ if (branchDecision.tier === "preview" && branchDecision.identity.headBranch) {
313922
+ inputs = {
313923
+ ...inputs,
313924
+ projectName: previewAssetName(inputs.projectName, branchDecision.identity.headBranch)
313925
+ };
313926
+ dependencies.core.info(`branch-aware sync: preview asset set "${inputs.projectName}"`);
313927
+ } else if (branchDecision.tier === "channel" && branchDecision.channel) {
313928
+ inputs = {
313929
+ ...inputs,
313930
+ projectName: channelAssetName(inputs.projectName, branchDecision.channel.code)
313931
+ };
313932
+ dependencies.core.info(`branch-aware sync: channel asset set "${inputs.projectName}"`);
313933
+ }
313934
+ const collectionBranchMarker = renderCollectionBranchMarker(branchDecision, inputs.repoUrl);
313935
+ if (branchDecision.tier !== "legacy") {
313936
+ outputs["sync-status"] = "synced";
313937
+ outputs["branch-decision"] = serializeBranchDecision(branchDecision);
313938
+ }
313939
+ if (!isCanonicalWriter) {
313940
+ const explicitCanonicalIds = [
313941
+ ["spec-id", inputs.specId],
313942
+ ["baseline-collection-id", inputs.baselineCollectionId],
313943
+ ["smoke-collection-id", inputs.smokeCollectionId],
313944
+ ["contract-collection-id", inputs.contractCollectionId]
313945
+ ].filter(([, value]) => Boolean(value));
313946
+ if (explicitCanonicalIds.length > 0) {
313947
+ throw new Error(
313948
+ `CONTRACT_BRANCH_CANONICAL_WRITE: a ${branchDecision.tier} run must not mutate canonical assets, but explicit asset id input(s) were provided: ${explicitCanonicalIds.map(([name]) => name).join(", ")}. Remove them (preview/channel runs discover or create their own suffixed asset sets).`
313949
+ );
313950
+ }
313951
+ }
313125
313952
  const requiresReleaseLabel = inputs.collectionSyncMode === "version" || inputs.specSyncMode === "version";
313126
313953
  const releaseLabel = requiresReleaseLabel ? deriveReleaseLabel(inputs) : void 0;
313127
313954
  if (requiresReleaseLabel && !releaseLabel) {
@@ -313129,11 +313956,23 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313129
313956
  "Versioned spec or collection sync requires a release-label or derivable GitHub ref metadata"
313130
313957
  );
313131
313958
  }
313132
- const collectionAssetProjectName = inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
313133
- const workspaceName = createWorkspaceName(inputs);
313134
- const aboutText = `Auto-provisioned by Postman for ${inputs.projectName}`;
313135
- const stateStore = resolveResourcesStateStore(dependencies);
313136
- const resourcesState = stateStore.read();
313959
+ const collectionAssetProjectName = branchDecision.tier === "channel" ? canonicalProjectName : inputs.collectionSyncMode === "version" ? createAssetProjectName(inputs, releaseLabel) : inputs.projectName;
313960
+ const rawStateStore = resolveResourcesStateStore(dependencies);
313961
+ const trackedState = rawStateStore.read();
313962
+ const stateStore = isCanonicalWriter ? rawStateStore : {
313963
+ read: rawStateStore.read,
313964
+ write: () => {
313965
+ dependencies.core.info(
313966
+ `branch-aware sync: skipping .postman/resources.yaml write on ${branchDecision.tier} run (canonical-only tracked state)`
313967
+ );
313968
+ }
313969
+ };
313970
+ const resourcesState = isCanonicalWriter ? trackedState : trackedState?.workspace ? { workspace: trackedState.workspace } : null;
313971
+ if (!isCanonicalWriter && trackedState?.cloudResources) {
313972
+ dependencies.core.info(
313973
+ "branch-aware sync: canonical asset ids in .postman/resources.yaml are not resolved on a non-canonical run"
313974
+ );
313975
+ }
313137
313976
  const writableResourcesState = resourcesState ?? {};
313138
313977
  const additionalCollections = loadAdditionalCollectionFiles(
313139
313978
  inputs.additionalCollectionsDir,
@@ -313153,16 +313992,19 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313153
313992
  }
313154
313993
  let previousSpecContent;
313155
313994
  let previousSpecRollbackHash;
313995
+ let specContentUnchanged = false;
313156
313996
  let detectedOpenapiVersion = "3.0";
313157
313997
  let contractIndex;
313158
313998
  let sourceSpecContent = "";
313999
+ let sourceTypeNullPaths = [];
314000
+ let preserveSourceSpecBytes = false;
313159
314001
  const rawSpecContent = await runGroup(
313160
314002
  dependencies.core,
313161
314003
  "Read API Spec",
313162
314004
  async () => {
313163
314005
  if (inputs.specPath) {
313164
314006
  const workspaceRoot = path9.resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
313165
- return (0, import_node_fs5.readFileSync)(path9.resolve(workspaceRoot, inputs.specPath), "utf8");
314007
+ return (0, import_node_fs6.readFileSync)(path9.resolve(workspaceRoot, inputs.specPath), "utf8");
313166
314008
  }
313167
314009
  if (dependencies.specFetcher === fetch) {
313168
314010
  return safeFetchText(inputs.specUrl, { depth: 0 });
@@ -313198,6 +314040,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313198
314040
  };
313199
314041
  const rootKey = inputs.specPath ? void 0 : normalizeRef(inputs.specUrl);
313200
314042
  const loaderOptions = {
314043
+ preserveOas30TypeNull: Boolean(inputs.preserveOas30TypeNull),
313201
314044
  fetchText: async (url, fetchOptions) => {
313202
314045
  if (rootKey && normalizeRef(url) === rootKey) return rawSpecContent;
313203
314046
  if (dependencies.specFetcher === fetch) return safeFetchText(url, fetchOptions);
@@ -313206,6 +314049,8 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313206
314049
  };
313207
314050
  const loaded = inputs.specPath ? await loadOpenApiContractSpecFromPath(inputs.specPath, loaderOptions) : await loadOpenApiContractSpec(inputs.specUrl, loaderOptions);
313208
314051
  sourceSpecContent = loaded.content;
314052
+ sourceTypeNullPaths = loaded.sourceTypeNullPaths;
314053
+ preserveSourceSpecBytes = sourceTypeNullPaths.length > 0;
313209
314054
  const document2 = normalizeSpecDocument(
313210
314055
  loaded.bundledContent,
313211
314056
  (msg) => dependencies.core.warning(msg)
@@ -313228,11 +314073,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313228
314073
  `Unable to verify existing Spec Hub OpenAPI version for spec-id ${specId}; clear spec-id to create a fresh spec`
313229
314074
  );
313230
314075
  }
313231
- previousSpecContent = normalizeSpecDocument(
314076
+ previousSpecContent = preserveSourceSpecBytes ? previousRaw : normalizeSpecDocument(
313232
314077
  previousRaw,
313233
314078
  (msg) => dependencies.core.warning(`Previous spec normalization: ${msg}`)
313234
314079
  );
313235
- previousSpecRollbackHash = (0, import_node_crypto5.createHash)("sha256").update(previousSpecContent).digest("hex");
314080
+ previousSpecRollbackHash = (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex");
313236
314081
  const existingSpecType = normalizeSpecTypeFromContent(previousSpecContent);
313237
314082
  if (existingSpecType !== incomingSpecType) {
313238
314083
  throw new Error(
@@ -313243,6 +314088,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313243
314088
  dependencies.core.info(
313244
314089
  `Auto-detected OpenAPI version from spec content: ${detectedOpenapiVersion}`
313245
314090
  );
314091
+ if (preserveSourceSpecBytes) {
314092
+ dependencies.core.info(
314093
+ `Preserving original OpenAPI source bytes; accepted ${sourceTypeNullPaths.length} legacy type: null member(s) for internal validation`
314094
+ );
314095
+ return loaded.content;
314096
+ }
313246
314097
  return document2;
313247
314098
  }
313248
314099
  );
@@ -313278,6 +314129,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313278
314129
  `OpenAPI breaking-change check failed: ${breakingChangeResult.message || "breaking changes detected"}`
313279
314130
  );
313280
314131
  }
314132
+ const uploadSpecContent = embedSpecBranchMarker(
314133
+ specContent,
314134
+ branchDecision,
314135
+ inputs.repoUrl
314136
+ );
313281
314137
  const provisioned = await provisionWorkspace(
313282
314138
  inputs,
313283
314139
  dependencies,
@@ -313402,10 +314258,17 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313402
314258
  specId ? "Update Spec in Spec Hub" : "Upload Spec to Spec Hub",
313403
314259
  async () => {
313404
314260
  if (specId) {
313405
- dependencies.core.info(
313406
- `Updating existing spec ${specId} (detected version: ${detectedOpenapiVersion}). Note: the spec type (OPENAPI:3.0 / OPENAPI:3.1) is set at creation and cannot be changed on update. If you changed OpenAPI versions, clear the spec-id input to create a fresh spec.`
313407
- );
313408
- await dependencies.postman.updateSpec(specId, specContent, workspaceId);
314261
+ if (previousSpecContent !== void 0 && (0, import_node_crypto6.createHash)("sha256").update(uploadSpecContent).digest("hex") === (0, import_node_crypto6.createHash)("sha256").update(previousSpecContent).digest("hex")) {
314262
+ specContentUnchanged = true;
314263
+ dependencies.core.info(
314264
+ `Spec content unchanged (sha256 match); skipping Spec Hub update and version tag for ${specId}.`
314265
+ );
314266
+ } else {
314267
+ dependencies.core.info(
314268
+ `Updating existing spec ${specId} (detected version: ${detectedOpenapiVersion}). Note: the spec type (OPENAPI:3.0 / OPENAPI:3.1) is set at creation and cannot be changed on update. If you changed OpenAPI versions, clear the spec-id input to create a fresh spec.`
314269
+ );
314270
+ await dependencies.postman.updateSpec(specId, uploadSpecContent, workspaceId);
314271
+ }
313409
314272
  } else {
313410
314273
  specId = await dependencies.postman.uploadSpec(
313411
314274
  workspaceId || "",
@@ -313413,7 +314276,7 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313413
314276
  inputs,
313414
314277
  inputs.specSyncMode === "version" ? releaseLabel : void 0
313415
314278
  ),
313416
- specContent,
314279
+ uploadSpecContent,
313417
314280
  detectedOpenapiVersion
313418
314281
  );
313419
314282
  }
@@ -313430,8 +314293,9 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313430
314293
  }
313431
314294
  )
313432
314295
  );
314296
+ outputs["spec-content-changed"] = isCanonicalWriter && !specContentUnchanged ? "true" : "false";
313433
314297
  if (lintEnabled) {
313434
- const lintSummary = await runRollbackStage(
314298
+ let lintSummary = await runRollbackStage(
313435
314299
  "Lint Spec via Postman CLI",
313436
314300
  async () => runGroup(
313437
314301
  dependencies.core,
@@ -313439,6 +314303,16 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313439
314303
  async () => lintSpecViaCli(dependencies, workspaceId || "", outputs["spec-id"])
313440
314304
  )
313441
314305
  );
314306
+ if (preserveSourceSpecBytes) {
314307
+ const originalErrorCount = lintSummary.errors;
314308
+ lintSummary = applyOas30TypeNullLintCompatibility(lintSummary, sourceTypeNullPaths);
314309
+ const acceptedCount = originalErrorCount - lintSummary.errors;
314310
+ if (acceptedCount > 0) {
314311
+ dependencies.core.info(
314312
+ `Accepted ${acceptedCount} Postman CLI type: null finding(s) covered by preserve-oas30-type-null`
314313
+ );
314314
+ }
314315
+ }
313442
314316
  outputs["lint-summary-json"] = JSON.stringify({
313443
314317
  errors: lintSummary.errors,
313444
314318
  total: lintSummary.violations.length,
@@ -313463,7 +314337,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313463
314337
  });
313464
314338
  dependencies.core.warning("lint skipped: governance errors not enforced (no postman-api-key)");
313465
314339
  }
313466
- await runRollbackStage(
314340
+ if (specContentUnchanged) {
314341
+ outputs["baseline-collection-id"] = baselineCollectionId || "";
314342
+ outputs["smoke-collection-id"] = smokeCollectionId || "";
314343
+ outputs["contract-collection-id"] = contractCollectionId || "";
314344
+ dependencies.core.info("Spec content unchanged; skipping collection regeneration and version finalization.");
314345
+ } else await runRollbackStage(
313467
314346
  "Generate Collections from Spec",
313468
314347
  async () => runGroup(
313469
314348
  dependencies.core,
@@ -313502,10 +314381,11 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313502
314381
  return;
313503
314382
  }
313504
314383
  }
314384
+ const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
313505
314385
  outputs[outputKey] = await dependencies.postman.generateCollection(
313506
314386
  specId2,
313507
314387
  assetProjectName,
313508
- prefix,
314388
+ effectivePrefix,
313509
314389
  inputs.folderStrategy,
313510
314390
  inputs.nestedFolderHierarchy,
313511
314391
  inputs.requestNameSource
@@ -313526,6 +314406,32 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313526
314406
  await ensureCollection(BASELINE_COLLECTION_PREFIX, baselineCollectionId, "baseline-collection-id");
313527
314407
  await ensureCollection(SMOKE_COLLECTION_PREFIX, smokeCollectionId, "smoke-collection-id");
313528
314408
  await ensureCollection(CONTRACT_COLLECTION_PREFIX, contractCollectionId, "contract-collection-id");
314409
+ if (dependencies.postman.adoptGeneratedCollection) {
314410
+ const reelect = async (prefix, outputKey) => {
314411
+ const effectivePrefix = branchDecision.tier === "channel" && branchDecision.channel ? channelAssetName(prefix, branchDecision.channel.code).trim() : prefix;
314412
+ const preferred = outputs[outputKey];
314413
+ if (!preferred) return;
314414
+ outputs[outputKey] = await dependencies.postman.adoptGeneratedCollection(
314415
+ specId2,
314416
+ assetProjectName,
314417
+ effectivePrefix,
314418
+ preferred
314419
+ );
314420
+ };
314421
+ await reelect(BASELINE_COLLECTION_PREFIX, "baseline-collection-id");
314422
+ await reelect(SMOKE_COLLECTION_PREFIX, "smoke-collection-id");
314423
+ await reelect(CONTRACT_COLLECTION_PREFIX, "contract-collection-id");
314424
+ }
314425
+ if (collectionBranchMarker) {
314426
+ if (!dependencies.postman.updateCollectionDescription) {
314427
+ throw new Error("Branch-scoped collections require updateCollectionDescription support");
314428
+ }
314429
+ await Promise.all([
314430
+ outputs["baseline-collection-id"],
314431
+ outputs["smoke-collection-id"],
314432
+ outputs["contract-collection-id"]
314433
+ ].filter(Boolean).map((id) => dependencies.postman.updateCollectionDescription(id, collectionBranchMarker)));
314434
+ }
313529
314435
  if (!dependencies.postman.injectContractTests) {
313530
314436
  throw new Error(
313531
314437
  "Dynamic contract tests require injectContractTests support from the access-token gateway client"
@@ -313628,6 +314534,12 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313628
314534
  workspaceId: workspaceId || ""
313629
314535
  });
313630
314536
  for (const result of additionalResults) {
314537
+ if (collectionBranchMarker) {
314538
+ if (!dependencies.postman.updateCollectionDescription) {
314539
+ throw new Error("Branch-scoped collections require updateCollectionDescription support");
314540
+ }
314541
+ await dependencies.postman.updateCollectionDescription(result.collectionId, collectionBranchMarker);
314542
+ }
313631
314543
  completedExternalSideEffects.push(
313632
314544
  `${result.operation}AdditionalCollection(${result.collectionId} from ${result.displayPath})`
313633
314545
  );
@@ -313703,8 +314615,66 @@ async function runBootstrapInner(inputs, dependencies, telemetry) {
313703
314615
  }
313704
314616
  return outputs;
313705
314617
  }
314618
+ async function runGatedValidation(inputs, decision, actionCore) {
314619
+ actionCore.info(`branch-aware sync: gated run (${decision.reason}) \u2014 credential-free static validation, zero workspace writes`);
314620
+ const outputs = createPlannedOutputs(inputs);
314621
+ outputs["sync-status"] = "skipped-branch-gate";
314622
+ outputs["branch-decision"] = serializeBranchDecision(decision);
314623
+ let violations = [];
314624
+ let validated = false;
314625
+ try {
314626
+ let content;
314627
+ if (inputs.specPath) {
314628
+ content = (0, import_node_fs6.readFileSync)(inputs.specPath, "utf8");
314629
+ } else if (inputs.specUrl) {
314630
+ content = await safeFetchText(inputs.specUrl, { depth: 0 });
314631
+ }
314632
+ if (content) {
314633
+ const specType = detectSpecType(content, inputs.specPath);
314634
+ if (specType === "openapi") {
314635
+ const document2 = parseOpenApiDocument(content);
314636
+ const index = buildContractIndex(document2);
314637
+ violations = index.warnings;
314638
+ validated = true;
314639
+ } else {
314640
+ actionCore.info(`branch gate: static lint for spec type ${specType} runs through its protocol builder on publish; parse-only gate applied`);
314641
+ validated = true;
314642
+ }
314643
+ } else {
314644
+ actionCore.info("branch gate: no spec-url/spec-path provided; nothing to validate");
314645
+ }
314646
+ } catch (error2) {
314647
+ throw new Error(
314648
+ `branch gate: static validation failed: ${error2 instanceof Error ? error2.message : String(error2)}`,
314649
+ { cause: error2 }
314650
+ );
314651
+ }
314652
+ outputs["lint-summary-json"] = JSON.stringify({
314653
+ status: validated ? "static-only" : "skipped",
314654
+ reason: "branch-gated run: governance lint requires upload and runs on publish/preview syncs",
314655
+ errors: 0,
314656
+ warnings: violations.length,
314657
+ total: violations.length,
314658
+ violations: violations.map((issue2) => ({ issue: issue2, severity: "WARNING" }))
314659
+ });
314660
+ for (const [name, value] of Object.entries(outputs)) {
314661
+ actionCore.setOutput(name, value);
314662
+ }
314663
+ return outputs;
314664
+ }
313706
314665
  async function runAction(actionCore = core_exports, actionExec = exec_exports, actionIo = io_exports) {
313707
314666
  const inputs = readActionInputs(actionCore);
314667
+ const branchDecision = decideBranchTier(inputs);
314668
+ if (branchDecision.tier === "gated") {
314669
+ return runGatedValidation(inputs, branchDecision, actionCore);
314670
+ }
314671
+ if (!inputs.postmanApiKey && !inputs.postmanAccessToken) {
314672
+ throw new Error("One of postman-api-key or postman-access-token is required for a writing sync.");
314673
+ }
314674
+ if (branchDecision.tier !== "legacy") {
314675
+ actionCore.info(`branch-aware sync: tier=${branchDecision.tier} (${branchDecision.reason})`);
314676
+ process.env[BRANCH_DECISION_ENV] = serializeBranchDecision(branchDecision);
314677
+ }
313708
314678
  await mintAccessTokenIfNeeded2(inputs, {
313709
314679
  info: (message) => actionCore.info(message),
313710
314680
  warning: (message) => actionCore.warning(message)
@@ -313781,6 +314751,7 @@ function createRoutingPostmanClient(options) {
313781
314751
  updateSpec: requireAccessToken("updateSpec"),
313782
314752
  getSpecContent: requireAccessToken("getSpecContent"),
313783
314753
  generateCollection: requireAccessToken("generateCollection"),
314754
+ adoptGeneratedCollection: requireAccessToken("adoptGeneratedCollection"),
313784
314755
  createWorkspace: requireAccessToken("createWorkspace"),
313785
314756
  getWorkspaceVisibility: requireAccessToken("getWorkspaceVisibility"),
313786
314757
  getWorkspaceGitRepoUrl: requireAccessToken("getWorkspaceGitRepoUrl"),
@@ -313790,10 +314761,13 @@ function createRoutingPostmanClient(options) {
313790
314761
  inviteRequesterToWorkspace: requireAccessToken("inviteRequesterToWorkspace"),
313791
314762
  injectTests: requireAccessToken("injectTests"),
313792
314763
  tagCollection: requireAccessToken("tagCollection"),
314764
+ tagSpecVersion: requireAccessToken("tagSpecVersion"),
314765
+ listSpecVersionTags: requireAccessToken("listSpecVersionTags"),
313793
314766
  deleteCollection: requireAccessToken("deleteCollection"),
313794
314767
  injectContractTests: requireAccessToken("injectContractTests"),
313795
314768
  createCollection: requireAccessToken("createCollection"),
313796
- updateCollection: requireAccessToken("updateCollection")
314769
+ updateCollection: requireAccessToken("updateCollection"),
314770
+ updateCollectionDescription: requireAccessToken("updateCollectionDescription")
313797
314771
  };
313798
314772
  }
313799
314773
  return {
@@ -313805,6 +314779,7 @@ function createRoutingPostmanClient(options) {
313805
314779
  // not a reason to reach for the API key.
313806
314780
  uploadSpec: (workspaceId, projectName, specContent, openapiVersion) => gateway.uploadSpec(workspaceId, projectName, specContent, openapiVersion ?? "3.0"),
313807
314781
  generateCollection: (specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource) => gateway.generateCollection(specId, projectName, prefix, folderStrategy, nestedFolderHierarchy, requestNameSource),
314782
+ adoptGeneratedCollection: (specId, projectName, prefix, preferredId) => gateway.adoptGeneratedCollection(specId, projectName, prefix, preferredId),
313808
314783
  updateSpec: (specId, specContent, workspaceId) => gateway.updateSpec(specId, specContent, workspaceId),
313809
314784
  getSpecContent: (specId) => gateway.getSpecContent(specId),
313810
314785
  createWorkspace: (name, about, targetTeamId) => gateway.createWorkspace(name, about, targetTeamId),
@@ -313816,6 +314791,8 @@ function createRoutingPostmanClient(options) {
313816
314791
  // + tagging surfaces (live-proven). PMAK is reserved for token minting, so
313817
314792
  // these never fall back to the API key even when one is present.
313818
314793
  injectTests: (collectionId, type) => gateway.injectTests(collectionId, type),
314794
+ tagSpecVersion: (specId, name) => gateway.tagSpecVersion(specId, name),
314795
+ listSpecVersionTags: (specId) => gateway.listSpecVersionTags(specId),
313819
314796
  tagCollection: (collectionId, tags) => gateway.tagCollection(collectionId, tags),
313820
314797
  // Sub-team (squad) enumeration over the gateway `ums` service. Access-token
313821
314798
  // only — never PMAK — so org-mode detection no longer needs a PMAK GET /teams.
@@ -313833,7 +314810,8 @@ function createRoutingPostmanClient(options) {
313833
314810
  // so no asset op ever reaches for the API key.
313834
314811
  injectContractTests: (collectionUid, index) => gateway.injectContractTests(collectionUid, index),
313835
314812
  createCollection: (workspaceId, collection, options2) => gateway.createCollection(workspaceId, collection, options2),
313836
- updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection)
314813
+ updateCollection: (collectionUid, collection) => gateway.updateCollection(collectionUid, collection),
314814
+ updateCollectionDescription: (collectionUid, description) => gateway.updateCollectionDescription(collectionUid, description)
313837
314815
  };
313838
314816
  }
313839
314817
  function createBootstrapDependencies(inputs, factories, orgMode = false) {
@@ -313907,19 +314885,25 @@ function createBootstrapDependencies(inputs, factories, orgMode = false) {
313907
314885
  }
313908
314886
  // Annotate the CommonJS export names for ESM import in node:
313909
314887
  0 && (module.exports = {
314888
+ applyOas30TypeNullLintCompatibility,
314889
+ buildSpecVersionUrl,
313910
314890
  createBootstrapDependencies,
313911
314891
  createExtensibleContractCollection,
313912
314892
  createPlannedOutputs,
313913
314893
  createRoutingPostmanClient,
314894
+ decideBranchTier,
314895
+ embedSpecBranchMarker,
313914
314896
  ensurePostmanCli,
313915
314897
  getInput,
313916
314898
  lintSpecViaCli,
313917
314899
  mintAccessTokenIfNeeded,
313918
314900
  normalizeSpecDocument,
313919
314901
  readActionInputs,
314902
+ renderCollectionBranchMarker,
313920
314903
  resolveInputs,
313921
314904
  runAction,
313922
- runBootstrap
314905
+ runBootstrap,
314906
+ runGatedValidation
313923
314907
  });
313924
314908
  /*! Bundled license information:
313925
314909