@wildo-ai/saas-technical-doc 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/esm/build/csp-emit.d.ts.map +1 -1
  2. package/dist/esm/build/load-materialized-frontend-providers.d.ts.map +1 -1
  3. package/dist/esm/companion/application-documentation/application-administration-documentation.d.ts.map +1 -1
  4. package/dist/esm/companion/application-documentation/application-administration-documentation.js.map +1 -1
  5. package/dist/esm/companion/application-documentation/application-authentication-documentation.d.ts.map +1 -1
  6. package/dist/esm/companion/application-documentation/application-connection-documentation.d.ts +37 -0
  7. package/dist/esm/companion/application-documentation/application-connection-documentation.d.ts.map +1 -1
  8. package/dist/esm/companion/application-documentation/application-connection-documentation.js +46 -5
  9. package/dist/esm/companion/application-documentation/application-connection-documentation.js.map +1 -1
  10. package/dist/esm/companion/application-documentation/application-documentation-chapters.d.ts +101 -0
  11. package/dist/esm/companion/application-documentation/application-documentation-chapters.d.ts.map +1 -0
  12. package/dist/esm/companion/application-documentation/application-documentation-chapters.js +205 -0
  13. package/dist/esm/companion/application-documentation/application-documentation-chapters.js.map +1 -0
  14. package/dist/esm/companion/application-documentation/application-domain-documentation.d.ts.map +1 -1
  15. package/dist/esm/companion/application-documentation/application-domain-documentation.js.map +1 -1
  16. package/dist/esm/companion/application-documentation/application-integration-documentation.d.ts.map +1 -1
  17. package/dist/esm/companion/application-documentation/application-integration-documentation.js.map +1 -1
  18. package/dist/esm/companion/application-documentation/application-organization-role-documentation.d.ts.map +1 -1
  19. package/dist/esm/companion/application-documentation/application-organization-role-documentation.js.map +1 -1
  20. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.d.ts.map +1 -1
  21. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js +3 -0
  22. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js.map +1 -1
  23. package/dist/esm/companion/application-documentation/docs-api-origin-substitution.d.ts +34 -0
  24. package/dist/esm/companion/application-documentation/docs-api-origin-substitution.d.ts.map +1 -0
  25. package/dist/esm/companion/application-documentation/docs-api-origin-substitution.js +45 -0
  26. package/dist/esm/companion/application-documentation/docs-api-origin-substitution.js.map +1 -0
  27. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts +24 -10
  28. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts.map +1 -1
  29. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js +25 -15
  30. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js.map +1 -1
  31. package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.d.ts.map +1 -1
  32. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts +11 -3
  33. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts.map +1 -1
  34. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js +9 -2
  35. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js.map +1 -1
  36. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts.map +1 -1
  37. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js +10 -4
  38. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js.map +1 -1
  39. package/dist/esm/companion/content/application-consumer-documentation-content-loader.d.ts.map +1 -1
  40. package/dist/esm/companion/index.d.ts +2 -0
  41. package/dist/esm/companion/index.d.ts.map +1 -1
  42. package/dist/esm/companion/index.js +2 -0
  43. package/dist/esm/companion/index.js.map +1 -1
  44. package/dist/esm/companion/manual-controller-route-projection.d.ts.map +1 -1
  45. package/dist/esm/companion/manual-controller-route-projection.js.map +1 -1
  46. package/dist/esm/companion/openapi-example-derivation.d.ts +53 -0
  47. package/dist/esm/companion/openapi-example-derivation.d.ts.map +1 -0
  48. package/dist/esm/companion/openapi-example-derivation.js +229 -0
  49. package/dist/esm/companion/openapi-example-derivation.js.map +1 -0
  50. package/dist/esm/companion/openapi-generator.d.ts +8 -4
  51. package/dist/esm/companion/openapi-generator.d.ts.map +1 -1
  52. package/dist/esm/companion/openapi-generator.js +323 -33
  53. package/dist/esm/companion/openapi-generator.js.map +1 -1
  54. package/dist/esm/companion/operation-projection.schemas.d.ts +57 -1
  55. package/dist/esm/companion/operation-projection.schemas.d.ts.map +1 -1
  56. package/dist/esm/companion/operation-projection.schemas.js +29 -0
  57. package/dist/esm/companion/operation-projection.schemas.js.map +1 -1
  58. package/dist/esm/companion/publish-result.types.d.ts.map +1 -1
  59. package/dist/esm/companion/publish-result.types.js.map +1 -1
  60. package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.d.ts.map +1 -1
  61. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts.map +1 -1
  62. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js +8 -0
  63. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js.map +1 -1
  64. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.d.ts.map +1 -1
  65. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js +20 -0
  66. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js.map +1 -1
  67. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts +2 -0
  68. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts.map +1 -1
  69. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js +48 -31
  70. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js.map +1 -1
  71. package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts.map +1 -1
  72. package/dist/esm/companion/rendering/technical-documentation-render-model.js +26 -10
  73. package/dist/esm/companion/rendering/technical-documentation-render-model.js.map +1 -1
  74. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.d.ts.map +1 -1
  75. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.js.map +1 -1
  76. package/dist/esm/companion/spec-to-operation-doc.d.ts +52 -10
  77. package/dist/esm/companion/spec-to-operation-doc.d.ts.map +1 -1
  78. package/dist/esm/companion/spec-to-operation-doc.js +125 -7
  79. package/dist/esm/companion/spec-to-operation-doc.js.map +1 -1
  80. package/dist/esm/companion/technical-documentation-asset-path.d.ts.map +1 -1
  81. package/dist/esm/companion/technical-documentation-capture-execution-port.d.ts.map +1 -1
  82. package/dist/esm/companion/technical-documentation-capture-execution-port.js.map +1 -1
  83. package/dist/esm/companion/technical-documentation-diagram-definitions.d.ts.map +1 -1
  84. package/dist/esm/companion/technical-documentation-diagram-definitions.js.map +1 -1
  85. package/dist/esm/companion/technical-documentation-diagram-materializer.d.ts.map +1 -1
  86. package/dist/esm/companion/technical-documentation-diagram-materializer.js.map +1 -1
  87. package/dist/esm/companion/technical-documentation-placeholder-materializer.d.ts.map +1 -1
  88. package/dist/esm/companion/zod-to-openapi.d.ts.map +1 -1
  89. package/dist/esm/companion-exports.d.ts.map +1 -1
  90. package/dist/esm/config/define-tech-doc-config.d.ts.map +1 -1
  91. package/dist/esm/config/index.d.ts.map +1 -1
  92. package/dist/esm/config/wildo-tech-doc-config.schemas.d.ts.map +1 -1
  93. package/dist/esm/config/wildo-tech-doc-config.schemas.js.map +1 -1
  94. package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.d.ts.map +1 -1
  95. package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts +27 -24
  96. package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts.map +1 -1
  97. package/dist/esm/content/application-consumer-documentation-content.techdoc.js +205 -82
  98. package/dist/esm/content/application-consumer-documentation-content.techdoc.js.map +1 -1
  99. package/dist/esm/content.exports.d.ts.map +1 -1
  100. package/dist/esm/index.d.ts.map +1 -1
  101. package/dist/esm/openapi/api-reference-link-index.d.ts +3 -0
  102. package/dist/esm/openapi/api-reference-link-index.d.ts.map +1 -1
  103. package/dist/esm/openapi/api-reference-link-index.js +26 -15
  104. package/dist/esm/openapi/api-reference-link-index.js.map +1 -1
  105. package/dist/esm/openapi/api-reference-pages.d.ts +55 -0
  106. package/dist/esm/openapi/api-reference-pages.d.ts.map +1 -0
  107. package/dist/esm/openapi/api-reference-pages.js +229 -0
  108. package/dist/esm/openapi/api-reference-pages.js.map +1 -0
  109. package/dist/esm/openapi/api-reference-search.d.ts +53 -0
  110. package/dist/esm/openapi/api-reference-search.d.ts.map +1 -0
  111. package/dist/esm/openapi/api-reference-search.js +100 -0
  112. package/dist/esm/openapi/api-reference-search.js.map +1 -0
  113. package/dist/esm/openapi/api-reference-targets.d.ts +11 -0
  114. package/dist/esm/openapi/api-reference-targets.d.ts.map +1 -1
  115. package/dist/esm/openapi/api-reference-targets.js +8 -0
  116. package/dist/esm/openapi/api-reference-targets.js.map +1 -1
  117. package/dist/esm/openapi/index.d.ts +2 -0
  118. package/dist/esm/openapi/index.d.ts.map +1 -1
  119. package/dist/esm/openapi/index.js +2 -0
  120. package/dist/esm/openapi/index.js.map +1 -1
  121. package/dist/esm/openapi/openapi-generation-output.schemas.d.ts.map +1 -1
  122. package/dist/esm/openapi-reference-model.exports.d.ts +2 -0
  123. package/dist/esm/openapi-reference-model.exports.d.ts.map +1 -1
  124. package/dist/esm/openapi-reference-model.exports.js +2 -0
  125. package/dist/esm/openapi-reference-model.exports.js.map +1 -1
  126. package/dist/esm/runtime/AuthExchangePage.d.ts +45 -4
  127. package/dist/esm/runtime/AuthExchangePage.d.ts.map +1 -1
  128. package/dist/esm/runtime/AuthExchangePage.js +45 -12
  129. package/dist/esm/runtime/AuthExchangePage.js.map +1 -1
  130. package/dist/esm/runtime/DocsAuthContext.d.ts +2 -2
  131. package/dist/esm/runtime/DocsAuthContext.d.ts.map +1 -1
  132. package/dist/esm/runtime/DocsAuthContext.js +8 -4
  133. package/dist/esm/runtime/DocsAuthContext.js.map +1 -1
  134. package/dist/esm/runtime/DocsFrontendProviders.d.ts +30 -0
  135. package/dist/esm/runtime/DocsFrontendProviders.d.ts.map +1 -0
  136. package/dist/esm/runtime/DocsFrontendProviders.js +39 -0
  137. package/dist/esm/runtime/DocsFrontendProviders.js.map +1 -0
  138. package/dist/esm/runtime/DocsProviderComponent.d.ts +41 -0
  139. package/dist/esm/runtime/DocsProviderComponent.d.ts.map +1 -0
  140. package/dist/esm/runtime/DocsProviderComponent.js +17 -0
  141. package/dist/esm/runtime/DocsProviderComponent.js.map +1 -0
  142. package/dist/esm/runtime/decode-jwt-claims.d.ts.map +1 -1
  143. package/dist/esm/runtime/docs-auth-client.d.ts.map +1 -1
  144. package/dist/esm/runtime/docs-auth-session.schemas.d.ts.map +1 -1
  145. package/dist/esm/runtime/documentation-site-translator.d.ts +53 -0
  146. package/dist/esm/runtime/documentation-site-translator.d.ts.map +1 -0
  147. package/dist/esm/runtime/documentation-site-translator.js +51 -0
  148. package/dist/esm/runtime/documentation-site-translator.js.map +1 -0
  149. package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts +1 -2
  150. package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts.map +1 -1
  151. package/dist/esm/runtime/frontend-provider-registry.techdoc.js.map +1 -1
  152. package/dist/esm/runtime/index.d.ts +8 -0
  153. package/dist/esm/runtime/index.d.ts.map +1 -1
  154. package/dist/esm/runtime/index.js +8 -0
  155. package/dist/esm/runtime/index.js.map +1 -1
  156. package/dist/esm/runtime/openapi-reference-conservation.d.ts.map +1 -1
  157. package/dist/esm/runtime/openapi-reference-model.d.ts +30 -0
  158. package/dist/esm/runtime/openapi-reference-model.d.ts.map +1 -1
  159. package/dist/esm/runtime/openapi-reference-model.js +85 -11
  160. package/dist/esm/runtime/openapi-reference-model.js.map +1 -1
  161. package/dist/esm/runtime/openapi-reference-navigation.d.ts +50 -0
  162. package/dist/esm/runtime/openapi-reference-navigation.d.ts.map +1 -0
  163. package/dist/esm/runtime/openapi-reference-navigation.js +46 -0
  164. package/dist/esm/runtime/openapi-reference-navigation.js.map +1 -0
  165. package/dist/esm/runtime/openapi-reference-samples.d.ts +40 -0
  166. package/dist/esm/runtime/openapi-reference-samples.d.ts.map +1 -0
  167. package/dist/esm/runtime/openapi-reference-samples.js +169 -0
  168. package/dist/esm/runtime/openapi-reference-samples.js.map +1 -0
  169. package/dist/esm/runtime/openapi-reference-styles.d.ts +28 -0
  170. package/dist/esm/runtime/openapi-reference-styles.d.ts.map +1 -0
  171. package/dist/esm/runtime/openapi-reference-styles.js +292 -0
  172. package/dist/esm/runtime/openapi-reference-styles.js.map +1 -0
  173. package/dist/esm/runtime/openapi-reference-view.d.ts +40 -5
  174. package/dist/esm/runtime/openapi-reference-view.d.ts.map +1 -1
  175. package/dist/esm/runtime/openapi-reference-view.js +828 -82
  176. package/dist/esm/runtime/openapi-reference-view.js.map +1 -1
  177. package/dist/esm/runtime/openapi-reference-words-context.d.ts +12 -0
  178. package/dist/esm/runtime/openapi-reference-words-context.d.ts.map +1 -0
  179. package/dist/esm/runtime/openapi-reference-words-context.js +30 -0
  180. package/dist/esm/runtime/openapi-reference-words-context.js.map +1 -0
  181. package/dist/esm/runtime/openapi-reference-words.d.ts +205 -0
  182. package/dist/esm/runtime/openapi-reference-words.d.ts.map +1 -0
  183. package/dist/esm/runtime/openapi-reference-words.js +162 -0
  184. package/dist/esm/runtime/openapi-reference-words.js.map +1 -0
  185. package/dist/esm/runtime/provider-component-registry.techdoc.d.ts +31 -0
  186. package/dist/esm/runtime/provider-component-registry.techdoc.d.ts.map +1 -0
  187. package/dist/esm/runtime/provider-component-registry.techdoc.js +35 -0
  188. package/dist/esm/runtime/provider-component-registry.techdoc.js.map +1 -0
  189. package/dist/esm/runtime/use-docs-auth-session.d.ts.map +1 -1
  190. package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts +1 -0
  191. package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts.map +1 -1
  192. package/dist/esm/runtime/use-docs-frontend-provider-registry.js +10 -2
  193. package/dist/esm/runtime/use-docs-frontend-provider-registry.js.map +1 -1
  194. package/dist/esm/runtime/use-docs-provider-component.d.ts +29 -0
  195. package/dist/esm/runtime/use-docs-provider-component.d.ts.map +1 -0
  196. package/dist/esm/runtime/use-docs-provider-component.js +42 -0
  197. package/dist/esm/runtime/use-docs-provider-component.js.map +1 -0
  198. package/dist/esm/runtime/use-docs-provider-scripts.d.ts +37 -0
  199. package/dist/esm/runtime/use-docs-provider-scripts.d.ts.map +1 -0
  200. package/dist/esm/runtime/use-docs-provider-scripts.js +47 -0
  201. package/dist/esm/runtime/use-docs-provider-scripts.js.map +1 -0
  202. package/dist/esm/runtime/use-docs-provider-sdks.d.ts.map +1 -1
  203. package/dist/esm/runtime/use-docs-provider-sdks.js.map +1 -1
  204. package/dist/tsconfig.build.tsbuildinfo +1 -1
  205. package/package.json +8 -24
@@ -35,15 +35,20 @@
35
35
  * cross this contract.
36
36
  *
37
37
  * @wildo-boundary
38
- * Imports only the portable OpenAPI output contract, the local operation
39
- * projection contract, the lightweight public runtime constants and
40
- * specification-prose cleanup. No serializer, React or saas-models root
41
- * barrel enters this semantic generator.
38
+ * Imports the portable OpenAPI output contract, the local operation
39
+ * projection contract, the lightweight public runtime constants (header
40
+ * keys) and specification-prose cleanup. No serializer or React enters this
41
+ * semantic generator. The saas-models ROOT barrel does, for the atoms not
42
+ * carved into `public-runtime`: the error definitions, collection query
43
+ * parameters, pagination request schema, the M2M webhook delivery contract
44
+ * and the Idempotency-Key vocabulary. A module has ONE public door, so the
45
+ * vocabulary is not re-exported from `public-runtime` as well.
42
46
  */
43
- import { WildoHeaderKeys } from '@wildo-ai/saas-models/public-runtime';
44
- import { COLLECTION_QUERY_PARAMETERS, ERROR_DEFINITIONS, ErrorHandling_Strategy, ErrorSeverity, ErrorType, M2M_WEBHOOK_DELIVERY_CONTRACT, Resources_PaginationRequestSchema, } from '@wildo-ai/saas-models';
47
+ import { SUBMISSION_CHALLENGE_RESPONSE_MAX_LENGTH, SubmissionChallengeRefusalCode, SubmissionChallengeResponseKind, WildoHeaderKeys, } from '@wildo-ai/saas-models/public-runtime';
48
+ import { COLLECTION_QUERY_PARAMETERS, ERROR_DEFINITIONS, IDEMPOTENCY_KEY_MAX_LENGTH, IdempotencyKeyRefusalCode, ErrorHandling_Strategy, ErrorSeverity, ErrorType, M2M_WEBHOOK_DELIVERY_CONTRACT, Resources_PaginationRequestSchema, } from '@wildo-ai/saas-models';
45
49
  import { OpenApiSection, } from '../openapi/openapi-generation-output.schemas.js';
46
50
  import { OpenApiGenerationInputSchema, OPENAPI_RESOURCE_IDENTITY_CONTRACT_VERSION, OperationProjectionAuthenticationMode, OperationProjectionVariantType, } from './operation-projection.schemas.js';
51
+ import { attachDerivedExamples } from './openapi-example-derivation.js';
47
52
  import { stripImplementationNoise } from './spec-to-operation-doc.js';
48
53
  /**
49
54
  * Retains every projected URL-bearing operation.
@@ -599,25 +604,47 @@ function buildCollectionFilterQueryParameters(op) {
599
604
  parameter['explode'] = true;
600
605
  }
601
606
  parameter['schema'] = isPlainObject(propertySchema) ? propertySchema : {};
602
- if (isPlainObject(propertySchema) && typeof propertySchema.description === 'string') {
603
- parameter['description'] = propertySchema.description;
604
- }
605
- else if (isObjectValued) {
606
- // No authored description on the range object — synthesise a truthful hint
607
- // that names the deepObject bracket form for each sub-field it carries.
607
+ const fieldDescription = isPlainObject(propertySchema) && typeof propertySchema.description === 'string'
608
+ ? propertySchema.description
609
+ : undefined;
610
+ if (isObjectValued) {
611
+ // The wire form of a range filter is not guessable, so its deepObject bracket
612
+ // hint is stated whether or not the field also carries a meaning — the field's
613
+ // meaning (injected from its specification) says WHAT it filters, the hint says
614
+ // HOW to send it, and neither replaces the other.
608
615
  const subKeys = isPlainObject(propertySchema) && isPlainObject(propertySchema.properties)
609
616
  ? Object.keys(propertySchema.properties)
610
617
  : [];
611
618
  const bracketHint = subKeys.length > 0
612
619
  ? subKeys.map((subKey) => `\`${name}[${subKey}]=…\``).join(' & ')
613
620
  : `\`${name}[<field>]=…\``;
614
- parameter['description'] =
615
- `Object-valued filter — serialise each sub-field with deepObject bracket notation (${bracketHint}).`;
621
+ const wireHint = `Object-valued filter — serialise each sub-field with deepObject bracket notation (${bracketHint}).`;
622
+ parameter['description'] = fieldDescription === undefined ? wireHint : `${fieldDescription}\n\n${wireHint}`;
623
+ }
624
+ else if (fieldDescription !== undefined) {
625
+ parameter['description'] = fieldDescription;
616
626
  }
617
627
  parameters.push(attachParameterExamples(op, parameter));
618
628
  }
619
629
  return parameters;
620
630
  }
631
+ /**
632
+ * The error-body identifiers one documented failure mode names, each written as the field a
633
+ * client reads it from. `error.type`, `error.customMessageReference` and `error.code` are three
634
+ * different fields; the prose used to call every one of them "error code", which sent a client to
635
+ * `error.code` for values that arrive in the other two.
636
+ */
637
+ function errorScenarioIdentifiers(scenario) {
638
+ const identifiers = [];
639
+ if (scenario.errorType !== undefined)
640
+ identifiers.push(`\`error.type\`: \`${scenario.errorType}\``);
641
+ if (scenario.customMessageReference !== undefined) {
642
+ identifiers.push(`\`error.customMessageReference\`: \`${scenario.customMessageReference}\``);
643
+ }
644
+ if (scenario.errorCode !== undefined)
645
+ identifiers.push(`\`error.code\`: \`${scenario.errorCode}\``);
646
+ return identifiers;
647
+ }
621
648
  /**
622
649
  * Framework-universal pagination defaults, mirrored from the backend
623
650
  * repository adapters (`_doList` in both the MongoDB and PostgreSQL resource
@@ -810,6 +837,24 @@ function buildOperationObject(op, operationId, errorResponseSchema) {
810
837
  if (isPlainObject(wildo))
811
838
  wildo['m2mNotifications'] = notifications.map((notification) => ({ ...notification }));
812
839
  }
840
+ /*
841
+ * The documented failure modes as DATA, beside the prose the error responses carry. A client, an
842
+ * SDK generator or the reference's errors table branches on these values; prose cannot be read
843
+ * that way, which is how a code used to exist only inside a sentence (#1620).
844
+ */
845
+ const errorScenarios = op.errorScenarios ?? [];
846
+ if (errorScenarios.length > 0) {
847
+ const wildo = operationObject['x-wildo'];
848
+ if (isPlainObject(wildo)) {
849
+ wildo['errorScenarios'] = errorScenarios.map((scenario) => ({
850
+ status: scenario.code,
851
+ when: scenario.when,
852
+ ...(scenario.errorType !== undefined && { errorType: scenario.errorType }),
853
+ ...(scenario.customMessageReference !== undefined && { customMessageReference: scenario.customMessageReference }),
854
+ ...(scenario.errorCode !== undefined && { errorCode: scenario.errorCode }),
855
+ }));
856
+ }
857
+ }
813
858
  const security = buildOperationSecurity(op);
814
859
  operationObject['security'] = security;
815
860
  if (op.access.authenticationMode !== OperationProjectionAuthenticationMode.PUBLIC && op.access.roleRequirements.length > 0) {
@@ -836,6 +881,35 @@ function buildOperationObject(op, operationId, errorResponseSchema) {
836
881
  + 'Required for this sensitive operation and consumed when the request is authorized.',
837
882
  });
838
883
  }
884
+ if (op.idempotencyKey !== undefined) {
885
+ parameters.push({
886
+ name: op.idempotencyKey.headerName,
887
+ in: 'header',
888
+ required: op.idempotencyKey.required,
889
+ schema: { type: 'string', minLength: 1 },
890
+ examples: { 'One key per intended change': { value: '"8e03978e-40d5-43e8-bc93-6894a57f9324"' } },
891
+ description: 'Your identity for this one intended change: a quoted string (or a bare token) of 1 to '
892
+ + `${IDEMPOTENCY_KEY_MAX_LENGTH} printable ASCII characters (a UUID is typical). Send a new key for each new change and `
893
+ + 'the SAME key when you retry it, so a retry after a lost response receives the first outcome instead of running again.'
894
+ + (op.idempotencyKey.required ? ' Required: a request without it is refused.' : ' Optional: a request without it runs, but its retries are not deduplicated.'),
895
+ });
896
+ }
897
+ if (op.submissionChallenge !== undefined) {
898
+ parameters.push({
899
+ name: op.submissionChallenge.headerName,
900
+ in: 'header',
901
+ // Optional in the reference because it is conditional: asked only when the application declares a captcha provider,
902
+ // and only of a caller that is not signed in. The description states both conditions.
903
+ required: false,
904
+ schema: { type: 'string', minLength: 1, maxLength: SUBMISSION_CHALLENGE_RESPONSE_MAX_LENGTH },
905
+ description: 'Your answer to this application\'s submission challenge, as JSON. Asked only when the application protects its '
906
+ + 'public forms with a captcha provider, and only of a caller that is not signed in; a signed-in or machine caller '
907
+ + `omits it. Send either \`{"kind":"${SubmissionChallengeResponseKind.PROVIDER_TOKEN}","providerRef":"<captcha provider>",`
908
+ + `"token":"<vendor token>"}\` or a solved proof of work, \`{"kind":"${SubmissionChallengeResponseKind.PROOF_OF_WORK}",`
909
+ + `"challenge":{…},"number":<n>}\`, for a challenge fetched from `
910
+ + `GET ${op.submissionChallenge.proofOfWorkChallengePath}. Each proof of work is accepted once.`,
911
+ });
912
+ }
839
913
  if (op.acceptsIfMatch) {
840
914
  parameters.push({
841
915
  name: WildoHeaderKeys.IF_MATCH,
@@ -986,6 +1060,10 @@ const FRAMEWORK_OWNED_PROPERTY_DESCRIPTIONS = Object.freeze({
986
1060
  createdAt: 'When the record was created, set by the application and never accepted from a client.',
987
1061
  updatedAt: 'When the record last changed, set by the application on every write.',
988
1062
  organizationId: 'The organization this record belongs to. It scopes every read and write: a caller only ever addresses records in an organization they are a member of.',
1063
+ applicationId: 'The application this record belongs to, for a record scoped to the application rather than to one organization.',
1064
+ // The USER scope foreign key `declarePolymorphicSchemas` injects on a user-scoped variant, as `organizationId` is for
1065
+ // an organization-scoped one (#1625).
1066
+ userId: 'The user this record belongs to, for a record scoped to one user rather than to an organization.',
989
1067
  // retention-authority: N/A — an API consumer's ENGLISH description of what the two values mean,
990
1068
  // rendered into the OpenAPI document. There is no emitter to consume here: the audience is a
991
1069
  // person reading a schema, and the sentence has to say `active` and `retained` in words for the
@@ -995,6 +1073,29 @@ const FRAMEWORK_OWNED_PROPERTY_DESCRIPTIONS = Object.freeze({
995
1073
  data: 'The page of records this response carries.',
996
1074
  pagination: 'Where this page sits in the full result set — the page number, the page size, and the totals needed to ask for the next one.',
997
1075
  });
1076
+ /**
1077
+ * The four fields of the `pagination` block every collection response carries (#1625). One shape,
1078
+ * built by `createPaginationResponseMetadataSchema` and owned by no resource, so it was 272
1079
+ * undescribed properties in Wonder Todos' API document alone — the same four, once per list.
1080
+ *
1081
+ * Applied only INSIDE a property named `pagination`, where these names mean exactly this; a
1082
+ * resource's own `page` or `total` field is never touched. `totalPages` is `0` for an empty result on
1083
+ * every adapter, computed by one function since #1714.
1084
+ */
1085
+ const PAGINATION_PROPERTY_DESCRIPTIONS = Object.freeze({
1086
+ page: 'The page this response carries, counted from 1.',
1087
+ limit: 'How many records a page holds: the `limit` the request asked for, or the default when it asked for none.',
1088
+ total: 'How many records match the request, across every page.',
1089
+ totalPages: 'How many pages of `limit` records the matching set spans: `0` when nothing matches. Ask for pages 1 to `totalPages`.',
1090
+ });
1091
+ /** An authored description always wins; these fill gaps. */
1092
+ function fillMissingDescriptions(properties, descriptions) {
1093
+ for (const [name, description] of Object.entries(descriptions)) {
1094
+ const property = properties[name];
1095
+ if (isPlainObject(property) && property['description'] === undefined)
1096
+ property['description'] = description;
1097
+ }
1098
+ }
998
1099
  /**
999
1100
  * The structural signature of a MATERIALIZED resource document in a response
1000
1101
  * schema is the `_id` property. The serializer attaches `_version` /
@@ -1085,6 +1186,9 @@ function fillFrameworkOwnedPropertyDescriptions(node) {
1085
1186
  if (framework !== undefined && property['description'] === undefined) {
1086
1187
  property['description'] = framework;
1087
1188
  }
1189
+ if (name === 'pagination' && isPlainObject(property['properties'])) {
1190
+ fillMissingDescriptions(property['properties'], PAGINATION_PROPERTY_DESCRIPTIONS);
1191
+ }
1088
1192
  fillFrameworkOwnedPropertyDescriptions(property);
1089
1193
  }
1090
1194
  }
@@ -1176,9 +1280,8 @@ function buildResponsesObject(op, responseBodySchema, errorResponseSchema) {
1176
1280
  responses['204'] = { description: 'No Content' };
1177
1281
  }
1178
1282
  for (const error of op.errorScenarios ?? []) {
1179
- const description = error.errorCode !== undefined
1180
- ? `${error.when} (error code: \`${error.errorCode}\`)`
1181
- : error.when;
1283
+ const identifiers = errorScenarioIdentifiers(error);
1284
+ const description = identifiers.length > 0 ? `${error.when} (${identifiers.join(', ')})` : error.when;
1182
1285
  const errorJsonMedia = { schema: errorResponseSchema };
1183
1286
  const examples = responseExamplesByStatus.get(error.code);
1184
1287
  if (examples !== undefined) {
@@ -1197,19 +1300,30 @@ function buildResponsesObject(op, responseBodySchema, errorResponseSchema) {
1197
1300
  };
1198
1301
  }
1199
1302
  }
1200
- if (op.acceptsIfMatch) {
1201
- const mergeFrameworkError = (code, description) => {
1202
- const existing = responses[code];
1203
- if (isPlainObject(existing) && typeof existing['description'] === 'string') {
1204
- existing['description'] = `${existing['description']}\n\n${description}`;
1205
- existing['content'] = { 'application/json': { schema: errorResponseSchema } };
1206
- return;
1207
- }
1208
- responses[code] = {
1209
- description,
1210
- content: { 'application/json': { schema: errorResponseSchema } },
1211
- };
1303
+ // A framework-owned outcome merged into an operation's response map, beside whatever its specification authored.
1304
+ const mergeFrameworkError = (code, description) => {
1305
+ const existing = responses[code];
1306
+ if (isPlainObject(existing) && typeof existing['description'] === 'string') {
1307
+ existing['description'] = `${existing['description']}\n\n${description}`;
1308
+ existing['content'] = { 'application/json': { schema: errorResponseSchema } };
1309
+ return;
1310
+ }
1311
+ responses[code] = {
1312
+ description,
1313
+ content: { 'application/json': { schema: errorResponseSchema } },
1212
1314
  };
1315
+ };
1316
+ if (op.idempotencyKey !== undefined) {
1317
+ mergeFrameworkError('400', `The \`${op.idempotencyKey.headerName}\` header is malformed (\`error.code\` \`${IdempotencyKeyRefusalCode.MALFORMED}\`)`
1318
+ + (op.idempotencyKey.required ? `, or missing (\`${IdempotencyKeyRefusalCode.REQUIRED}\`).` : '.'));
1319
+ }
1320
+ if (op.submissionChallenge !== undefined) {
1321
+ mergeFrameworkError('400', `The application protects this operation with a submission challenge and a caller who is not signed in sent no \``
1322
+ + `${op.submissionChallenge.headerName}\` header (\`error.code\` \`${SubmissionChallengeRefusalCode.REQUIRED}\`), or an answer `
1323
+ + `it did not accept: a refused vendor token, or a proof of work that is wrong, expired or already used `
1324
+ + `(\`${SubmissionChallengeRefusalCode.REFUSED}\`). Fetch a fresh proof-of-work challenge and send the request again.`);
1325
+ }
1326
+ if (op.acceptsIfMatch) {
1213
1327
  mergeFrameworkError('400', 'The optional `If-Match` header is present but does not contain a non-negative integer resource version.');
1214
1328
  mergeFrameworkError('409', 'The `If-Match` version no longer matches the current resource version, so the mutation was not applied.');
1215
1329
  }
@@ -1267,6 +1381,17 @@ const GENERATOR_OWNED_ERROR_RESPONSE_NAMES = Object.freeze({
1267
1381
  [OperationProjectionAuthenticationMode.PUBLIC]: 'Unauthorized',
1268
1382
  });
1269
1383
  const INTERNAL_ERROR_RESPONSE_NAME = 'InternalServerError';
1384
+ /** The status each shared `components.responses` entry answers with, read by the example pass. */
1385
+ function errorStatusByComponentResponseName() {
1386
+ return new Map([
1387
+ [INTERNAL_ERROR_RESPONSE_NAME, '500'],
1388
+ ...[
1389
+ OperationProjectionAuthenticationMode.ANONYMOUS_SESSION,
1390
+ OperationProjectionAuthenticationMode.ANONYMOUS_OR_AUTHENTICATED,
1391
+ OperationProjectionAuthenticationMode.AUTHENTICATED,
1392
+ ].map((mode) => [unauthorizedResponseName(mode), '401']),
1393
+ ]);
1394
+ }
1270
1395
  function unauthorizedResponseName(mode) {
1271
1396
  return GENERATOR_OWNED_ERROR_RESPONSE_NAMES[mode];
1272
1397
  }
@@ -1294,14 +1419,16 @@ function unauthorizedDescription(mode) {
1294
1419
  * entry of its own: a public operation never gets a synthesised 401.
1295
1420
  */
1296
1421
  function buildErrorResponseComponents(usedNames, errorResponseSchema) {
1297
- const content = { 'application/json': { schema: errorResponseSchema } };
1298
- const all = { [INTERNAL_ERROR_RESPONSE_NAME]: { description: internalServerErrorDescription(), content } };
1422
+ // One content object PER entry: the example pass writes each one's own example into it, and a
1423
+ // shared object would make the last write every entry's example.
1424
+ const content = () => ({ 'application/json': { schema: errorResponseSchema } });
1425
+ const all = { [INTERNAL_ERROR_RESPONSE_NAME]: { description: internalServerErrorDescription(), content: content() } };
1299
1426
  for (const mode of [
1300
1427
  OperationProjectionAuthenticationMode.ANONYMOUS_SESSION,
1301
1428
  OperationProjectionAuthenticationMode.ANONYMOUS_OR_AUTHENTICATED,
1302
1429
  OperationProjectionAuthenticationMode.AUTHENTICATED,
1303
1430
  ]) {
1304
- all[unauthorizedResponseName(mode)] = { description: unauthorizedDescription(mode), content };
1431
+ all[unauthorizedResponseName(mode)] = { description: unauthorizedDescription(mode), content: content() };
1305
1432
  }
1306
1433
  return Object.fromEntries(Object.entries(all).filter(([name]) => usedNames.has(name)));
1307
1434
  }
@@ -1499,6 +1626,44 @@ function componentSchemaName(op, role) {
1499
1626
  * Returns a described COPY. The caller's schema belongs to the projection input and a pin asserts
1500
1627
  * the generator leaves its input untouched.
1501
1628
  */
1629
+ /**
1630
+ * The error envelope's remaining fields, by their path under the envelope root (#1625). `type` and
1631
+ * `severity` are described from `ERROR_DEFINITIONS` below; these are the rest of what
1632
+ * `ErrorHandlerService.sendErrorResponse` writes, and each sentence states what that method puts
1633
+ * there. `message` and `customMessage` both carry the error's message reference, not prose: the
1634
+ * words are the client's to render in its reader's language.
1635
+ */
1636
+ const ERROR_ENVELOPE_PROPERTY_DESCRIPTIONS = Object.freeze({
1637
+ 'error': 'What went wrong. The application\'s error responses carry this one object.',
1638
+ 'error.message': 'The message reference for this failure, the same value as `customMessageReference`. It is a key for a message catalog, not a sentence to show a reader.',
1639
+ 'error.customMessage': 'The message reference for this failure, the same value as `message` and `customMessageReference`.',
1640
+ 'error.customMessageReference': 'A stable key naming this failure more precisely than `type` (for example `controller_resource_not_found`). Use it to choose the message you show.',
1641
+ 'error.code': 'A stable, operation-specific refusal code such as `LAST_ORGANIZATION_OWNER`, present when the operation declares one. Branch on this for the refusals an operation documents.',
1642
+ 'error.operationPath': 'Which operation refused the request, as the application names it internally. Useful in a support request; do not branch on it.',
1643
+ 'error.correlationId': 'An identifier for this request. Quote it in a support request: it joins this response to the application\'s own logs and audit records.',
1644
+ 'error.details': 'Extra facts about the failure that are safe to return. Present only for some failures; the keys depend on the failure.',
1645
+ 'error.details.versionConflict': 'Present when an update was refused because the record changed since it was read. Re-read the record, reapply the change, and retry.',
1646
+ 'error.details.versionConflict.currentVersion': 'The version the record is at now.',
1647
+ 'error.details.versionConflict.expectedVersion': 'The version the request was based on.',
1648
+ 'error.details.versionConflict.conflictingFields': 'The field paths the request submitted whose value differs from the record as it is now.',
1649
+ 'error.details.versionConflict.currentValues': 'The record as it is now, in the shape a read returns.',
1650
+ 'error.details.versionConflict.submittedValues': 'The values the request submitted.',
1651
+ 'error.timestamp': 'When the failure happened (ISO 8601, UTC).',
1652
+ 'error.initiatorIds': 'Who the application understood the request to come from: the organization, user or application identifiers it resolved. Absent or `null` when it resolved none.',
1653
+ 'error.validationErrors': 'Present on a validation failure: each key is a field path and each value is either a message or a `{ code, params }` entry naming the problem, so a form can show it beside the field.',
1654
+ 'error.userGuidance': 'Present when the application can say how to resolve the failure: an explanation, a resolution and, where it applies, a next action.',
1655
+ 'error.userGuidance.guidanceKey': 'A stable key for this guidance, to look up localized wording. When absent, use `customMessageReference`.',
1656
+ 'error.userGuidance.explanation': 'What went wrong, in the application\'s own words.',
1657
+ 'error.userGuidance.resolution': 'What to do about it.',
1658
+ 'error.userGuidance.params': 'Values the localized guidance wording interpolates.',
1659
+ 'error.userGuidance.completedSteps': 'Steps that had already completed before the failure.',
1660
+ 'error.userGuidance.nextAction': 'The suggested next step.',
1661
+ 'error.userGuidance.nextAction.label': 'A short label for the next step.',
1662
+ 'error.userGuidance.nextAction.ref': 'The operation path or URL the next step goes to.',
1663
+ 'error.userGuidance.nextAction.type': 'Whether `ref` names an operation or a URL.',
1664
+ 'error.userGuidance.retryAfterSeconds': 'On a rate limit: how many seconds to wait before retrying.',
1665
+ 'error.userGuidance.missingConfiguration': 'On a configuration failure: the configuration the application is missing.',
1666
+ });
1502
1667
  function describeFrameworkErrorEnvelopeEnums(schema) {
1503
1668
  if (!isPlainObject(schema))
1504
1669
  return schema;
@@ -1537,6 +1702,27 @@ function describeFrameworkErrorEnvelopeEnums(schema) {
1537
1702
  visit(entry);
1538
1703
  };
1539
1704
  visit(described);
1705
+ const describeProperties = (node, prefix) => {
1706
+ if (!isPlainObject(node))
1707
+ return;
1708
+ for (const key of ['anyOf', 'oneOf', 'allOf']) {
1709
+ const branches = node[key];
1710
+ if (Array.isArray(branches))
1711
+ for (const branch of branches)
1712
+ describeProperties(branch, prefix);
1713
+ }
1714
+ const properties = node['properties'];
1715
+ if (!isPlainObject(properties))
1716
+ return;
1717
+ for (const [name, property] of Object.entries(properties)) {
1718
+ const path = prefix === '' ? name : `${prefix}.${name}`;
1719
+ const description = ERROR_ENVELOPE_PROPERTY_DESCRIPTIONS[path];
1720
+ if (isPlainObject(property) && description !== undefined && property['description'] === undefined)
1721
+ property['description'] = description;
1722
+ describeProperties(property, path);
1723
+ }
1724
+ };
1725
+ describeProperties(described, '');
1540
1726
  return described;
1541
1727
  }
1542
1728
  function createSchemaRegistry() {
@@ -1587,6 +1773,91 @@ function createSchemaRegistry() {
1587
1773
  },
1588
1774
  };
1589
1775
  }
1776
+ const COMPONENT_SCHEMA_REF_PREFIX = '#/components/schemas/';
1777
+ /**
1778
+ * One object definition per resource: "the Tasks object" (#1621).
1779
+ *
1780
+ * Every operation hoists its own named body component, so a resource's representation used to be
1781
+ * published once per operation that returns it. Measured on the Wonder Todos document of 2026-09-24:
1782
+ * 135 response components and 80 list-row schemas were byte-identical to their resource's read
1783
+ * representation, each carrying the same field documentation — 215 copies, and no single place a
1784
+ * reader could learn what a task IS.
1785
+ *
1786
+ * This pass names each resource's canonical read representation after the resource (`Tasks`) and
1787
+ * folds into it every response component OF THAT RESOURCE whose content is identical (ignoring the
1788
+ * per-operation description), plus every list's `data[]` rows that are. A context or summary shape
1789
+ * that differs keeps its own component: identity is decided by CONTENT, never assumed from the verb.
1790
+ *
1791
+ * Folding removes per-operation component names, which are public identities. That is safe because
1792
+ * no authored guide links to a component schema (guides link resources and operation families), and
1793
+ * the link index fails publication closed if one ever does.
1794
+ *
1795
+ * Mutates `paths`, `schemas` and `canonicalReadResponseRefByResource` (so the webhooks block, which
1796
+ * sends the resource document itself, references the object). Returns the object name per resource.
1797
+ */
1798
+ function consolidateResourceObjects(paths, schemas, canonicalReadResponseRefByResource, responseComponentOwner, purposeByResourceIdentifier) {
1799
+ const contentOf = (schema) => {
1800
+ if (!isPlainObject(schema))
1801
+ return stableStringify(schema);
1802
+ const { description: _description, $schema: _dialect, ...content } = schema;
1803
+ return stableStringify(content);
1804
+ };
1805
+ const objectNameByResource = new Map();
1806
+ const replacement = new Map();
1807
+ const objectContentByName = new Map();
1808
+ for (const [resourceIdentifier, readRef] of [...canonicalReadResponseRefByResource.entries()].sort(([left], [right]) => left.localeCompare(right))) {
1809
+ const readSchema = schemas[readRef.slice(COMPONENT_SCHEMA_REF_PREFIX.length)];
1810
+ if (!isPlainObject(readSchema))
1811
+ continue;
1812
+ let objectName = pascal(resourceIdentifier);
1813
+ for (let suffix = 2; schemas[objectName] !== undefined; suffix += 1)
1814
+ objectName = `${pascal(resourceIdentifier)}Object${suffix === 2 ? '' : suffix}`;
1815
+ const purpose = purposeByResourceIdentifier.get(resourceIdentifier);
1816
+ const { description: _description, ...content } = readSchema;
1817
+ schemas[objectName] = {
1818
+ ...content,
1819
+ description: `The \`${resourceIdentifier}\` resource as the API returns it.${purpose === undefined ? '' : ` ${stripImplementationNoise(purpose)}`}`,
1820
+ };
1821
+ const objectContent = contentOf(readSchema);
1822
+ objectNameByResource.set(resourceIdentifier, objectName);
1823
+ objectContentByName.set(objectName, objectContent);
1824
+ for (const [ref, owner] of responseComponentOwner) {
1825
+ if (owner !== resourceIdentifier)
1826
+ continue;
1827
+ const name = ref.slice(COMPONENT_SCHEMA_REF_PREFIX.length);
1828
+ const candidate = schemas[name];
1829
+ if (!isPlainObject(candidate))
1830
+ continue;
1831
+ if (contentOf(candidate) === objectContent) {
1832
+ replacement.set(ref, `${COMPONENT_SCHEMA_REF_PREFIX}${objectName}`);
1833
+ continue;
1834
+ }
1835
+ const data = isPlainObject(candidate['properties']) ? candidate['properties']['data'] : undefined;
1836
+ if (isPlainObject(data) && data['type'] === 'array' && contentOf(data['items']) === objectContent) {
1837
+ data['items'] = { $ref: `${COMPONENT_SCHEMA_REF_PREFIX}${objectName}` };
1838
+ }
1839
+ }
1840
+ canonicalReadResponseRefByResource.set(resourceIdentifier, `${COMPONENT_SCHEMA_REF_PREFIX}${objectName}`);
1841
+ }
1842
+ for (const ref of replacement.keys())
1843
+ delete schemas[ref.slice(COMPONENT_SCHEMA_REF_PREFIX.length)];
1844
+ const rewrite = (node) => {
1845
+ if (Array.isArray(node)) {
1846
+ for (const entry of node)
1847
+ rewrite(entry);
1848
+ return;
1849
+ }
1850
+ if (!isPlainObject(node))
1851
+ return;
1852
+ const ref = node['$ref'];
1853
+ if (typeof ref === 'string' && replacement.has(ref))
1854
+ node['$ref'] = replacement.get(ref);
1855
+ for (const entry of Object.values(node))
1856
+ rewrite(entry);
1857
+ };
1858
+ rewrite(paths);
1859
+ return objectNameByResource;
1860
+ }
1590
1861
  /**
1591
1862
  * Replace an operation object's inline request/response BODY schemas with
1592
1863
  * `$ref`s into `components/schemas` (via `registry`). Walks only the
@@ -1760,6 +2031,9 @@ function buildOpenApiDocument(section, operations, input) {
1760
2031
  * reference rather than a guess — and a resource with no read operation simply gets prose.
1761
2032
  */
1762
2033
  const canonicalReadResponseRefByResource = new Map();
2034
+ // The resource each response component was hoisted for, so the resource-object pass below only
2035
+ // ever folds a resource's OWN representations into its object.
2036
+ const responseComponentOwner = new Map();
1763
2037
  const paths = {};
1764
2038
  const ownershipByPathAndVerb = new Map();
1765
2039
  const tagSet = new Set();
@@ -1772,6 +2046,9 @@ function buildOpenApiDocument(section, operations, input) {
1772
2046
  // ones get `2`, `3`, … — applied here rather than in the projector because
1773
2047
  // uniqueness is a per-document (per-section) property.
1774
2048
  const operationIdUseCount = new Map();
2049
+ // Each published operation id's projection, so the example pass can compose an error body from
2050
+ // that operation's own documented scenarios.
2051
+ const projectionByOperationId = new Map();
1775
2052
  const schemaRegistry = createSchemaRegistry();
1776
2053
  // The schema comes from the same model the backend error handler serializes.
1777
2054
  // Register before per-operation hoisting so every non-success response shares
@@ -1801,6 +2078,7 @@ function buildOpenApiDocument(section, operations, input) {
1801
2078
  operationIdUseCount.set(baseOperationId, priorUses + 1);
1802
2079
  const operationId = priorUses === 0 ? baseOperationId : `${baseOperationId}${priorUses + 1}`;
1803
2080
  const operationObject = buildOperationObject(op, operationId, errorResponseSchema);
2081
+ projectionByOperationId.set(operationId, op);
1804
2082
  for (const response of Object.values((operationObject['responses'] ?? {}))) {
1805
2083
  const ref = isPlainObject(response) ? response['$ref'] : undefined;
1806
2084
  if (typeof ref === 'string' && ref.startsWith('#/components/responses/')) {
@@ -1830,6 +2108,13 @@ function buildOpenApiDocument(section, operations, input) {
1830
2108
  operationObject['description'] = existing.length > 0 ? `${existing}\n\n${note}` : note;
1831
2109
  }
1832
2110
  hoistOperationSchemas(operationObject, op, schemaRegistry, purposeByResourceIdentifier.get(op.resourceIdentifier));
2111
+ for (const response of Object.values((operationObject['responses'] ?? {}))) {
2112
+ const media = isPlainObject(response) && isPlainObject(response['content']) ? response['content']['application/json'] : undefined;
2113
+ const ref = isPlainObject(media) && isPlainObject(media['schema']) ? media['schema']['$ref'] : undefined;
2114
+ if (typeof ref === 'string' && ref.startsWith(COMPONENT_SCHEMA_REF_PREFIX) && !responseComponentOwner.has(ref)) {
2115
+ responseComponentOwner.set(ref, op.resourceIdentifier);
2116
+ }
2117
+ }
1833
2118
  if (op.baseOperationIdentifier === 'read' && splitOperationQualifiers(op).via.length === 0 && !canonicalReadResponseRefByResource.has(op.resourceIdentifier)) {
1834
2119
  const okSchema = (operationObject['responses']?.['200']?.content?.['application/json']?.schema);
1835
2120
  if (typeof okSchema?.$ref === 'string')
@@ -1853,8 +2138,9 @@ function buildOpenApiDocument(section, operations, input) {
1853
2138
  }
1854
2139
  resourceTagsByName.set(tag.name, tag);
1855
2140
  }
2141
+ const objectSchemaNameByResource = consolidateResourceObjects(paths, schemaRegistry.schemas(), canonicalReadResponseRefByResource, responseComponentOwner, purposeByResourceIdentifier);
1856
2142
  const webhooks = buildWebhooksObject(sortedOps, canonicalReadResponseRefByResource);
1857
- return {
2143
+ const document = {
1858
2144
  openapi: '3.1.0',
1859
2145
  info: {
1860
2146
  title: `${titleBase} ${sectionLabel}`,
@@ -1878,6 +2164,7 @@ function buildOpenApiDocument(section, operations, input) {
1878
2164
  ...(descriptor?.lifecycleRole === undefined ? {} : { lifecycleRole: stripImplementationNoise(descriptor.lifecycleRole) }),
1879
2165
  ...(descriptor?.relationships === undefined ? {} : { relationships: descriptor.relationships }),
1880
2166
  ...(descriptor?.category === undefined ? {} : { category: descriptor.category }),
2167
+ ...(objectSchemaNameByResource.has(tagName) ? { objectSchema: objectSchemaNameByResource.get(tagName) } : {}),
1881
2168
  };
1882
2169
  const tag = {
1883
2170
  name: tagName,
@@ -1906,6 +2193,9 @@ function buildOpenApiDocument(section, operations, input) {
1906
2193
  securitySchemes: SECURITY_SCHEMES,
1907
2194
  },
1908
2195
  };
2196
+ // Every JSON body with no authored example gets a derived one, marked as generated (#1619).
2197
+ attachDerivedExamples(document, projectionByOperationId, errorStatusByComponentResponseName());
2198
+ return document;
1909
2199
  }
1910
2200
  /**
1911
2201
  * Counts the unique `resourceIdentifier` values among a set of