@postman-cse/onboarding-bootstrap 2.9.8 → 2.9.10

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
@@ -258936,6 +258936,87 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258936
258936
  const advised = adviseFromHttpError(httpErr, this.adviceContext("workspace repository linking"));
258937
258937
  throw advised ?? httpErr;
258938
258938
  }
258939
+ async findWorkspaceForRepo(repoUrl, path10 = "/") {
258940
+ const encodedRepo = encodeURIComponent(repoUrl);
258941
+ const encodedPath = encodeURIComponent(path10);
258942
+ const requestPath2 = `/workspaces/filesystem?repo=${encodedRepo}&path=${encodedPath}`;
258943
+ let response;
258944
+ try {
258945
+ response = await this.proxyRequest("workspaces", "GET", requestPath2);
258946
+ } catch (error2) {
258947
+ return {
258948
+ state: "unknown",
258949
+ reason: error2 instanceof Error ? error2.message : String(error2)
258950
+ };
258951
+ }
258952
+ let bodyText;
258953
+ try {
258954
+ bodyText = await response.text();
258955
+ } catch (error2) {
258956
+ return {
258957
+ state: "unknown",
258958
+ reason: error2 instanceof Error ? error2.message : String(error2)
258959
+ };
258960
+ }
258961
+ let parsed = null;
258962
+ if (bodyText.trim()) {
258963
+ try {
258964
+ parsed = JSON.parse(bodyText);
258965
+ } catch (error2) {
258966
+ return {
258967
+ state: "unknown",
258968
+ reason: error2 instanceof Error ? error2.message : String(error2)
258969
+ };
258970
+ }
258971
+ }
258972
+ const wrappedInvisibleWorkspaceIdRaw = parsed?.error?.meta?.workspaceId;
258973
+ const wrappedInvisibleWorkspaceId = typeof wrappedInvisibleWorkspaceIdRaw === "string" ? wrappedInvisibleWorkspaceIdRaw.trim() : "";
258974
+ if (wrappedInvisibleWorkspaceId) {
258975
+ return { state: "linked-invisible", workspaceId: wrappedInvisibleWorkspaceId };
258976
+ }
258977
+ if (response.status === 200) {
258978
+ if (!parsed || !Object.prototype.hasOwnProperty.call(parsed, "data")) {
258979
+ return {
258980
+ state: "unknown",
258981
+ reason: "Filesystem probe returned 200 without a data payload"
258982
+ };
258983
+ }
258984
+ const data = parsed?.data ?? null;
258985
+ if (data == null) {
258986
+ return { state: "free" };
258987
+ }
258988
+ if (typeof data === "object" && !Array.isArray(data)) {
258989
+ const workspace = data;
258990
+ const id = typeof workspace.id === "string" ? workspace.id.trim() : "";
258991
+ if (id) {
258992
+ const name = typeof workspace.name === "string" ? workspace.name : "";
258993
+ return {
258994
+ state: "linked-visible",
258995
+ workspace: { ...workspace, id, name }
258996
+ };
258997
+ }
258998
+ }
258999
+ return {
259000
+ state: "unknown",
259001
+ reason: "Filesystem probe returned 200 with unrecognized workspace payload"
259002
+ };
259003
+ }
259004
+ if (response.status === 403) {
259005
+ const workspaceIdRaw = parsed?.error?.meta?.workspaceId ?? parsed?.meta?.workspaceId;
259006
+ const workspaceId = typeof workspaceIdRaw === "string" ? workspaceIdRaw.trim() : "";
259007
+ if (workspaceId) {
259008
+ return { state: "linked-invisible", workspaceId };
259009
+ }
259010
+ return {
259011
+ state: "unknown",
259012
+ reason: "Filesystem probe returned 403 without error.meta.workspaceId"
259013
+ };
259014
+ }
259015
+ return {
259016
+ state: "unknown",
259017
+ reason: `Filesystem probe returned HTTP ${response.status}`
259018
+ };
259019
+ }
258939
259020
  async linkCollectionsToSpecification(specificationId, collections) {
258940
259021
  if (collections.length === 0) {
258941
259022
  return;
@@ -261017,6 +261098,13 @@ function normalizeResponseKey(status) {
261017
261098
  const raw = String(status);
261018
261099
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
261019
261100
  }
261101
+ function responseBodyExpectation(method, status, content) {
261102
+ const normalizedStatus = normalizeResponseKey(status);
261103
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
261104
+ return "forbidden";
261105
+ }
261106
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
261107
+ }
261020
261108
  function collectSecurityApiKeys(root, operation) {
261021
261109
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
261022
261110
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -261447,6 +261535,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
261447
261535
  const body2 = resolveInternalRef(root, operation.requestBody);
261448
261536
  if (!body2) return void 0;
261449
261537
  const content = asRecord5(body2.content);
261538
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
261539
+ if (asRecord5(mediaObject)?.schema === void 0) {
261540
+ warnings.push(
261541
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
261542
+ );
261543
+ }
261544
+ }
261450
261545
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
261451
261546
  if (fieldRules) {
261452
261547
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -262293,9 +262388,22 @@ function buildContractIndex(root) {
262293
262388
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
262294
262389
  const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
262295
262390
  const content = responseContent(root, version, response, responseContext, responseWarnings);
262296
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
262391
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
262392
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
262297
262393
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
262298
262394
  }
262395
+ if (bodyExpectation === "unknown") {
262396
+ responseWarnings.add(
262397
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
262398
+ );
262399
+ }
262400
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
262401
+ if (asRecord5(mediaObject)?.schema === void 0) {
262402
+ responseWarnings.add(
262403
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
262404
+ );
262405
+ }
262406
+ }
262299
262407
  for (const [contentType2, media] of Object.entries(content)) {
262300
262408
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
262301
262409
  const schemaType = asRecord5(media.schema)?.type;
@@ -262307,7 +262415,7 @@ function buildContractIndex(root) {
262307
262415
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
262308
262416
  contractResponses[normalizeResponseKey(status)] = {
262309
262417
  content,
262310
- hasBody: Object.keys(content).length > 0,
262418
+ bodyExpectation,
262311
262419
  headers,
262312
262420
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
262313
262421
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -262583,7 +262691,12 @@ function createContractScript(operation, warnings = []) {
262583
262691
  " return null;",
262584
262692
  "}",
262585
262693
  'function responseText() { return pm.response.text() || ""; }',
262586
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262694
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262695
+ "function selectedBodyExpectation() {",
262696
+ ' if (isBodyless()) return "forbidden";',
262697
+ ' if (!selected) return "unknown";',
262698
+ ' return selected.value.bodyExpectation || "unknown";',
262699
+ "}",
262587
262700
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
262588
262701
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
262589
262702
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -262623,6 +262736,7 @@ function createContractScript(operation, warnings = []) {
262623
262736
  " return matches[0];",
262624
262737
  "}",
262625
262738
  "var selected = selectedResponseContract();",
262739
+ "var bodyExpectation = selectedBodyExpectation();",
262626
262740
  ...skipped.length > 0 ? [
262627
262741
  // A schema schemasafe could not compile is NOT silently ignored: it is
262628
262742
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -262654,10 +262768,8 @@ function createContractScript(operation, warnings = []) {
262654
262768
  "});",
262655
262769
  "pm.test('Response body matches OpenAPI body contract', function () {",
262656
262770
  " if (!selected) return;",
262657
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
262658
- " var content = selected.value.content || {};",
262659
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
262660
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
262771
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
262772
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
262661
262773
  "});",
262662
262774
  "pm.test('Content-Type matches OpenAPI response content', function () {",
262663
262775
  " if (!selected || isBodyless()) return;",
@@ -263700,8 +263812,7 @@ function createContractScript(operation, warnings = []) {
263700
263812
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
263701
263813
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
263702
263814
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
263703
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
263704
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263815
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263705
263816
  "});",
263706
263817
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
263707
263818
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -312623,7 +312734,28 @@ async function provisionWorkspace(inputs, dependencies, telemetry, outputs, reso
312623
312734
  }
312624
312735
  telemetry.setTeamId(teamId);
312625
312736
  const repoUrl = inputs.repoUrl || "";
312626
- if (!explicitWorkspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
312737
+ if (repoUrl && dependencies.internalIntegration?.findWorkspaceForRepo) {
312738
+ const probe = await dependencies.internalIntegration.findWorkspaceForRepo(repoUrl);
312739
+ if (probe.state === "linked-invisible") {
312740
+ const orgTail = inputs.workspaceTeamId ? " Verify workspace-team-id; if the owner is in another sub-team, ask that sub-team's admin to disconnect it." : "";
312741
+ throw new Error(
312742
+ `REPOSITORY_LINK_CONFLICT_INVISIBLE: Repository ${repoUrl} at path / is linked to workspace ${probe.workspaceId}, but these credentials cannot view it. No Postman assets were changed. Ask its owner or a team admin to disconnect it.${orgTail}`
312743
+ );
312744
+ }
312745
+ if (probe.state === "linked-visible") {
312746
+ workspaceId = probe.workspace.id;
312747
+ workspaceMutationOwned = true;
312748
+ const nameSuffix = probe.workspace.name ? ` ("${probe.workspace.name}")` : "";
312749
+ dependencies.core.info(
312750
+ `Repository ${repoUrl} at path / is already linked to workspace ${probe.workspace.id}${nameSuffix}; adopting it as the canonical workspace.`
312751
+ );
312752
+ } else if (probe.state === "unknown") {
312753
+ dependencies.core.warning(
312754
+ `Repository link preflight could not determine ownership for ${repoUrl}: ${probe.reason}. Continuing with normal workspace selection.`
312755
+ );
312756
+ }
312757
+ }
312758
+ if (!workspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
312627
312759
  const selection = await runGroup(
312628
312760
  dependencies.core,
312629
312761
  "Resolve Canonical Workspace",
package/dist/cli.cjs CHANGED
@@ -257254,6 +257254,87 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
257254
257254
  const advised = adviseFromHttpError(httpErr, this.adviceContext("workspace repository linking"));
257255
257255
  throw advised ?? httpErr;
257256
257256
  }
257257
+ async findWorkspaceForRepo(repoUrl, path8 = "/") {
257258
+ const encodedRepo = encodeURIComponent(repoUrl);
257259
+ const encodedPath = encodeURIComponent(path8);
257260
+ const requestPath2 = `/workspaces/filesystem?repo=${encodedRepo}&path=${encodedPath}`;
257261
+ let response;
257262
+ try {
257263
+ response = await this.proxyRequest("workspaces", "GET", requestPath2);
257264
+ } catch (error) {
257265
+ return {
257266
+ state: "unknown",
257267
+ reason: error instanceof Error ? error.message : String(error)
257268
+ };
257269
+ }
257270
+ let bodyText;
257271
+ try {
257272
+ bodyText = await response.text();
257273
+ } catch (error) {
257274
+ return {
257275
+ state: "unknown",
257276
+ reason: error instanceof Error ? error.message : String(error)
257277
+ };
257278
+ }
257279
+ let parsed = null;
257280
+ if (bodyText.trim()) {
257281
+ try {
257282
+ parsed = JSON.parse(bodyText);
257283
+ } catch (error) {
257284
+ return {
257285
+ state: "unknown",
257286
+ reason: error instanceof Error ? error.message : String(error)
257287
+ };
257288
+ }
257289
+ }
257290
+ const wrappedInvisibleWorkspaceIdRaw = parsed?.error?.meta?.workspaceId;
257291
+ const wrappedInvisibleWorkspaceId = typeof wrappedInvisibleWorkspaceIdRaw === "string" ? wrappedInvisibleWorkspaceIdRaw.trim() : "";
257292
+ if (wrappedInvisibleWorkspaceId) {
257293
+ return { state: "linked-invisible", workspaceId: wrappedInvisibleWorkspaceId };
257294
+ }
257295
+ if (response.status === 200) {
257296
+ if (!parsed || !Object.prototype.hasOwnProperty.call(parsed, "data")) {
257297
+ return {
257298
+ state: "unknown",
257299
+ reason: "Filesystem probe returned 200 without a data payload"
257300
+ };
257301
+ }
257302
+ const data = parsed?.data ?? null;
257303
+ if (data == null) {
257304
+ return { state: "free" };
257305
+ }
257306
+ if (typeof data === "object" && !Array.isArray(data)) {
257307
+ const workspace = data;
257308
+ const id = typeof workspace.id === "string" ? workspace.id.trim() : "";
257309
+ if (id) {
257310
+ const name = typeof workspace.name === "string" ? workspace.name : "";
257311
+ return {
257312
+ state: "linked-visible",
257313
+ workspace: { ...workspace, id, name }
257314
+ };
257315
+ }
257316
+ }
257317
+ return {
257318
+ state: "unknown",
257319
+ reason: "Filesystem probe returned 200 with unrecognized workspace payload"
257320
+ };
257321
+ }
257322
+ if (response.status === 403) {
257323
+ const workspaceIdRaw = parsed?.error?.meta?.workspaceId ?? parsed?.meta?.workspaceId;
257324
+ const workspaceId = typeof workspaceIdRaw === "string" ? workspaceIdRaw.trim() : "";
257325
+ if (workspaceId) {
257326
+ return { state: "linked-invisible", workspaceId };
257327
+ }
257328
+ return {
257329
+ state: "unknown",
257330
+ reason: "Filesystem probe returned 403 without error.meta.workspaceId"
257331
+ };
257332
+ }
257333
+ return {
257334
+ state: "unknown",
257335
+ reason: `Filesystem probe returned HTTP ${response.status}`
257336
+ };
257337
+ }
257257
257338
  async linkCollectionsToSpecification(specificationId, collections) {
257258
257339
  if (collections.length === 0) {
257259
257340
  return;
@@ -259335,6 +259416,13 @@ function normalizeResponseKey(status) {
259335
259416
  const raw = String(status);
259336
259417
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
259337
259418
  }
259419
+ function responseBodyExpectation(method, status, content) {
259420
+ const normalizedStatus = normalizeResponseKey(status);
259421
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
259422
+ return "forbidden";
259423
+ }
259424
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
259425
+ }
259338
259426
  function collectSecurityApiKeys(root, operation) {
259339
259427
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
259340
259428
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -259765,6 +259853,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
259765
259853
  const body2 = resolveInternalRef(root, operation.requestBody);
259766
259854
  if (!body2) return void 0;
259767
259855
  const content = asRecord5(body2.content);
259856
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
259857
+ if (asRecord5(mediaObject)?.schema === void 0) {
259858
+ warnings.push(
259859
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
259860
+ );
259861
+ }
259862
+ }
259768
259863
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
259769
259864
  if (fieldRules) {
259770
259865
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -260611,9 +260706,22 @@ function buildContractIndex(root) {
260611
260706
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path8}`, responseWarnings, version, linkTargetSchemas);
260612
260707
  const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
260613
260708
  const content = responseContent(root, version, response, responseContext, responseWarnings);
260614
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
260709
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
260710
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
260615
260711
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path8} declares content for status ${status}, which RFC 9110 forbids on the wire`);
260616
260712
  }
260713
+ if (bodyExpectation === "unknown") {
260714
+ responseWarnings.add(
260715
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
260716
+ );
260717
+ }
260718
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
260719
+ if (asRecord5(mediaObject)?.schema === void 0) {
260720
+ responseWarnings.add(
260721
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
260722
+ );
260723
+ }
260724
+ }
260617
260725
  for (const [contentType2, media] of Object.entries(content)) {
260618
260726
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
260619
260727
  const schemaType = asRecord5(media.schema)?.type;
@@ -260625,7 +260733,7 @@ function buildContractIndex(root) {
260625
260733
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
260626
260734
  contractResponses[normalizeResponseKey(status)] = {
260627
260735
  content,
260628
- hasBody: Object.keys(content).length > 0,
260736
+ bodyExpectation,
260629
260737
  headers,
260630
260738
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
260631
260739
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -260901,7 +261009,12 @@ function createContractScript(operation, warnings = []) {
260901
261009
  " return null;",
260902
261010
  "}",
260903
261011
  'function responseText() { return pm.response.text() || ""; }',
260904
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
261012
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
261013
+ "function selectedBodyExpectation() {",
261014
+ ' if (isBodyless()) return "forbidden";',
261015
+ ' if (!selected) return "unknown";',
261016
+ ' return selected.value.bodyExpectation || "unknown";',
261017
+ "}",
260905
261018
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
260906
261019
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
260907
261020
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -260941,6 +261054,7 @@ function createContractScript(operation, warnings = []) {
260941
261054
  " return matches[0];",
260942
261055
  "}",
260943
261056
  "var selected = selectedResponseContract();",
261057
+ "var bodyExpectation = selectedBodyExpectation();",
260944
261058
  ...skipped.length > 0 ? [
260945
261059
  // A schema schemasafe could not compile is NOT silently ignored: it is
260946
261060
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -260972,10 +261086,8 @@ function createContractScript(operation, warnings = []) {
260972
261086
  "});",
260973
261087
  "pm.test('Response body matches OpenAPI body contract', function () {",
260974
261088
  " if (!selected) return;",
260975
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
260976
- " var content = selected.value.content || {};",
260977
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
260978
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
261089
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
261090
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
260979
261091
  "});",
260980
261092
  "pm.test('Content-Type matches OpenAPI response content', function () {",
260981
261093
  " if (!selected || isBodyless()) return;",
@@ -262018,8 +262130,7 @@ function createContractScript(operation, warnings = []) {
262018
262130
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
262019
262131
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
262020
262132
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
262021
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
262022
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
262133
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
262023
262134
  "});",
262024
262135
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
262025
262136
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -310849,7 +310960,28 @@ async function provisionWorkspace(inputs, dependencies, telemetry, outputs, reso
310849
310960
  }
310850
310961
  telemetry.setTeamId(teamId);
310851
310962
  const repoUrl = inputs.repoUrl || "";
310852
- if (!explicitWorkspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
310963
+ if (repoUrl && dependencies.internalIntegration?.findWorkspaceForRepo) {
310964
+ const probe = await dependencies.internalIntegration.findWorkspaceForRepo(repoUrl);
310965
+ if (probe.state === "linked-invisible") {
310966
+ const orgTail = inputs.workspaceTeamId ? " Verify workspace-team-id; if the owner is in another sub-team, ask that sub-team's admin to disconnect it." : "";
310967
+ throw new Error(
310968
+ `REPOSITORY_LINK_CONFLICT_INVISIBLE: Repository ${repoUrl} at path / is linked to workspace ${probe.workspaceId}, but these credentials cannot view it. No Postman assets were changed. Ask its owner or a team admin to disconnect it.${orgTail}`
310969
+ );
310970
+ }
310971
+ if (probe.state === "linked-visible") {
310972
+ workspaceId = probe.workspace.id;
310973
+ workspaceMutationOwned = true;
310974
+ const nameSuffix = probe.workspace.name ? ` ("${probe.workspace.name}")` : "";
310975
+ dependencies.core.info(
310976
+ `Repository ${repoUrl} at path / is already linked to workspace ${probe.workspace.id}${nameSuffix}; adopting it as the canonical workspace.`
310977
+ );
310978
+ } else if (probe.state === "unknown") {
310979
+ dependencies.core.warning(
310980
+ `Repository link preflight could not determine ownership for ${repoUrl}: ${probe.reason}. Continuing with normal workspace selection.`
310981
+ );
310982
+ }
310983
+ }
310984
+ if (!workspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
310853
310985
  const selection = await runGroup(
310854
310986
  dependencies.core,
310855
310987
  "Resolve Canonical Workspace",
package/dist/index.cjs CHANGED
@@ -258961,6 +258961,87 @@ var BifrostInternalIntegrationAdapter = class _BifrostInternalIntegrationAdapter
258961
258961
  const advised = adviseFromHttpError(httpErr, this.adviceContext("workspace repository linking"));
258962
258962
  throw advised ?? httpErr;
258963
258963
  }
258964
+ async findWorkspaceForRepo(repoUrl, path10 = "/") {
258965
+ const encodedRepo = encodeURIComponent(repoUrl);
258966
+ const encodedPath = encodeURIComponent(path10);
258967
+ const requestPath2 = `/workspaces/filesystem?repo=${encodedRepo}&path=${encodedPath}`;
258968
+ let response;
258969
+ try {
258970
+ response = await this.proxyRequest("workspaces", "GET", requestPath2);
258971
+ } catch (error2) {
258972
+ return {
258973
+ state: "unknown",
258974
+ reason: error2 instanceof Error ? error2.message : String(error2)
258975
+ };
258976
+ }
258977
+ let bodyText;
258978
+ try {
258979
+ bodyText = await response.text();
258980
+ } catch (error2) {
258981
+ return {
258982
+ state: "unknown",
258983
+ reason: error2 instanceof Error ? error2.message : String(error2)
258984
+ };
258985
+ }
258986
+ let parsed = null;
258987
+ if (bodyText.trim()) {
258988
+ try {
258989
+ parsed = JSON.parse(bodyText);
258990
+ } catch (error2) {
258991
+ return {
258992
+ state: "unknown",
258993
+ reason: error2 instanceof Error ? error2.message : String(error2)
258994
+ };
258995
+ }
258996
+ }
258997
+ const wrappedInvisibleWorkspaceIdRaw = parsed?.error?.meta?.workspaceId;
258998
+ const wrappedInvisibleWorkspaceId = typeof wrappedInvisibleWorkspaceIdRaw === "string" ? wrappedInvisibleWorkspaceIdRaw.trim() : "";
258999
+ if (wrappedInvisibleWorkspaceId) {
259000
+ return { state: "linked-invisible", workspaceId: wrappedInvisibleWorkspaceId };
259001
+ }
259002
+ if (response.status === 200) {
259003
+ if (!parsed || !Object.prototype.hasOwnProperty.call(parsed, "data")) {
259004
+ return {
259005
+ state: "unknown",
259006
+ reason: "Filesystem probe returned 200 without a data payload"
259007
+ };
259008
+ }
259009
+ const data = parsed?.data ?? null;
259010
+ if (data == null) {
259011
+ return { state: "free" };
259012
+ }
259013
+ if (typeof data === "object" && !Array.isArray(data)) {
259014
+ const workspace = data;
259015
+ const id = typeof workspace.id === "string" ? workspace.id.trim() : "";
259016
+ if (id) {
259017
+ const name = typeof workspace.name === "string" ? workspace.name : "";
259018
+ return {
259019
+ state: "linked-visible",
259020
+ workspace: { ...workspace, id, name }
259021
+ };
259022
+ }
259023
+ }
259024
+ return {
259025
+ state: "unknown",
259026
+ reason: "Filesystem probe returned 200 with unrecognized workspace payload"
259027
+ };
259028
+ }
259029
+ if (response.status === 403) {
259030
+ const workspaceIdRaw = parsed?.error?.meta?.workspaceId ?? parsed?.meta?.workspaceId;
259031
+ const workspaceId = typeof workspaceIdRaw === "string" ? workspaceIdRaw.trim() : "";
259032
+ if (workspaceId) {
259033
+ return { state: "linked-invisible", workspaceId };
259034
+ }
259035
+ return {
259036
+ state: "unknown",
259037
+ reason: "Filesystem probe returned 403 without error.meta.workspaceId"
259038
+ };
259039
+ }
259040
+ return {
259041
+ state: "unknown",
259042
+ reason: `Filesystem probe returned HTTP ${response.status}`
259043
+ };
259044
+ }
258964
259045
  async linkCollectionsToSpecification(specificationId, collections) {
258965
259046
  if (collections.length === 0) {
258966
259047
  return;
@@ -261042,6 +261123,13 @@ function normalizeResponseKey(status) {
261042
261123
  const raw = String(status);
261043
261124
  return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
261044
261125
  }
261126
+ function responseBodyExpectation(method, status, content) {
261127
+ const normalizedStatus = normalizeResponseKey(status);
261128
+ if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
261129
+ return "forbidden";
261130
+ }
261131
+ return Object.keys(content).length > 0 ? "declared" : "unknown";
261132
+ }
261045
261133
  function collectSecurityApiKeys(root, operation) {
261046
261134
  const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
261047
261135
  const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
@@ -261472,6 +261560,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
261472
261560
  const body2 = resolveInternalRef(root, operation.requestBody);
261473
261561
  if (!body2) return void 0;
261474
261562
  const content = asRecord5(body2.content);
261563
+ for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
261564
+ if (asRecord5(mediaObject)?.schema === void 0) {
261565
+ warnings.push(
261566
+ `CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
261567
+ );
261568
+ }
261569
+ }
261475
261570
  const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
261476
261571
  if (fieldRules) {
261477
261572
  for (const [base, rule] of Object.entries(fieldRules)) {
@@ -262318,9 +262413,22 @@ function buildContractIndex(root) {
262318
262413
  const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
262319
262414
  const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
262320
262415
  const content = responseContent(root, version, response, responseContext, responseWarnings);
262321
- if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
262416
+ const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
262417
+ if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
262322
262418
  responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
262323
262419
  }
262420
+ if (bodyExpectation === "unknown") {
262421
+ responseWarnings.add(
262422
+ `CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
262423
+ );
262424
+ }
262425
+ for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
262426
+ if (asRecord5(mediaObject)?.schema === void 0) {
262427
+ responseWarnings.add(
262428
+ `CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
262429
+ );
262430
+ }
262431
+ }
262324
262432
  for (const [contentType2, media] of Object.entries(content)) {
262325
262433
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
262326
262434
  const schemaType = asRecord5(media.schema)?.type;
@@ -262332,7 +262440,7 @@ function buildContractIndex(root) {
262332
262440
  const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
262333
262441
  contractResponses[normalizeResponseKey(status)] = {
262334
262442
  content,
262335
- hasBody: Object.keys(content).length > 0,
262443
+ bodyExpectation,
262336
262444
  headers,
262337
262445
  ...linkExpressions.length > 0 ? { links: linkExpressions } : {},
262338
262446
  ...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
@@ -262608,7 +262716,12 @@ function createContractScript(operation, warnings = []) {
262608
262716
  " return null;",
262609
262717
  "}",
262610
262718
  'function responseText() { return pm.response.text() || ""; }',
262611
- 'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262719
+ 'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
262720
+ "function selectedBodyExpectation() {",
262721
+ ' if (isBodyless()) return "forbidden";',
262722
+ ' if (!selected) return "unknown";',
262723
+ ' return selected.value.bodyExpectation || "unknown";',
262724
+ "}",
262612
262725
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
262613
262726
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
262614
262727
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
@@ -262648,6 +262761,7 @@ function createContractScript(operation, warnings = []) {
262648
262761
  " return matches[0];",
262649
262762
  "}",
262650
262763
  "var selected = selectedResponseContract();",
262764
+ "var bodyExpectation = selectedBodyExpectation();",
262651
262765
  ...skipped.length > 0 ? [
262652
262766
  // A schema schemasafe could not compile is NOT silently ignored: it is
262653
262767
  // surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
@@ -262679,10 +262793,8 @@ function createContractScript(operation, warnings = []) {
262679
262793
  "});",
262680
262794
  "pm.test('Response body matches OpenAPI body contract', function () {",
262681
262795
  " if (!selected) return;",
262682
- " if (isBodyless()) { pm.expect(responseText().trim().length).to.equal(0); return; }",
262683
- " var content = selected.value.content || {};",
262684
- ' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
262685
- ' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
262796
+ ' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
262797
+ ' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
262686
262798
  "});",
262687
262799
  "pm.test('Content-Type matches OpenAPI response content', function () {",
262688
262800
  " if (!selected || isBodyless()) return;",
@@ -263725,8 +263837,7 @@ function createContractScript(operation, warnings = []) {
263725
263837
  ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
263726
263838
  " var actualBytes = unescape(encodeURIComponent(responseText())).length;",
263727
263839
  ' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
263728
- " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
263729
- ' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263840
+ ' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
263730
263841
  "});",
263731
263842
  "pm.test('RFC SHOULD-level advisories are documented', function () {",
263732
263843
  ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
@@ -312651,7 +312762,28 @@ async function provisionWorkspace(inputs, dependencies, telemetry, outputs, reso
312651
312762
  }
312652
312763
  telemetry.setTeamId(teamId);
312653
312764
  const repoUrl = inputs.repoUrl || "";
312654
- if (!explicitWorkspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
312765
+ if (repoUrl && dependencies.internalIntegration?.findWorkspaceForRepo) {
312766
+ const probe = await dependencies.internalIntegration.findWorkspaceForRepo(repoUrl);
312767
+ if (probe.state === "linked-invisible") {
312768
+ const orgTail = inputs.workspaceTeamId ? " Verify workspace-team-id; if the owner is in another sub-team, ask that sub-team's admin to disconnect it." : "";
312769
+ throw new Error(
312770
+ `REPOSITORY_LINK_CONFLICT_INVISIBLE: Repository ${repoUrl} at path / is linked to workspace ${probe.workspaceId}, but these credentials cannot view it. No Postman assets were changed. Ask its owner or a team admin to disconnect it.${orgTail}`
312771
+ );
312772
+ }
312773
+ if (probe.state === "linked-visible") {
312774
+ workspaceId = probe.workspace.id;
312775
+ workspaceMutationOwned = true;
312776
+ const nameSuffix = probe.workspace.name ? ` ("${probe.workspace.name}")` : "";
312777
+ dependencies.core.info(
312778
+ `Repository ${repoUrl} at path / is already linked to workspace ${probe.workspace.id}${nameSuffix}; adopting it as the canonical workspace.`
312779
+ );
312780
+ } else if (probe.state === "unknown") {
312781
+ dependencies.core.warning(
312782
+ `Repository link preflight could not determine ownership for ${repoUrl}: ${probe.reason}. Continuing with normal workspace selection.`
312783
+ );
312784
+ }
312785
+ }
312786
+ if (!workspaceId && repoUrl && inputs.postmanAccessToken && teamId) {
312655
312787
  const selection = await runGroup(
312656
312788
  dependencies.core,
312657
312789
  "Resolve Canonical Workspace",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-bootstrap",
3
- "version": "2.9.8",
3
+ "version": "2.9.10",
4
4
  "description": "Bootstrap Postman workspaces, specs, and collections from OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",