@wildo-ai/saas-technical-doc 1.1.1

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 (216) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/.builder.pid +9 -0
  3. package/dist/esm/build/csp-emit.d.ts +9 -0
  4. package/dist/esm/build/csp-emit.d.ts.map +1 -0
  5. package/dist/esm/build/csp-emit.js +8 -0
  6. package/dist/esm/build/csp-emit.js.map +1 -0
  7. package/dist/esm/build/load-materialized-frontend-providers.d.ts +9 -0
  8. package/dist/esm/build/load-materialized-frontend-providers.d.ts.map +1 -0
  9. package/dist/esm/build/load-materialized-frontend-providers.js +9 -0
  10. package/dist/esm/build/load-materialized-frontend-providers.js.map +1 -0
  11. package/dist/esm/companion/application-documentation/application-organization-role-documentation.d.ts +66 -0
  12. package/dist/esm/companion/application-documentation/application-organization-role-documentation.d.ts.map +1 -0
  13. package/dist/esm/companion/application-documentation/application-organization-role-documentation.js +195 -0
  14. package/dist/esm/companion/application-documentation/application-organization-role-documentation.js.map +1 -0
  15. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.d.ts +36 -0
  16. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.d.ts.map +1 -0
  17. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js +71 -0
  18. package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js.map +1 -0
  19. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts +37 -0
  20. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts.map +1 -0
  21. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js +1865 -0
  22. package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js.map +1 -0
  23. package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.d.ts +22 -0
  24. package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.d.ts.map +1 -0
  25. package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.js +31 -0
  26. package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.js.map +1 -0
  27. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts +31 -0
  28. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts.map +1 -0
  29. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js +33 -0
  30. package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js.map +1 -0
  31. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts +13 -0
  32. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts.map +1 -0
  33. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js +93 -0
  34. package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js.map +1 -0
  35. package/dist/esm/companion/content/application-consumer-documentation-content-loader.d.ts +52 -0
  36. package/dist/esm/companion/content/application-consumer-documentation-content-loader.d.ts.map +1 -0
  37. package/dist/esm/companion/content/application-consumer-documentation-content-loader.js +191 -0
  38. package/dist/esm/companion/content/application-consumer-documentation-content-loader.js.map +1 -0
  39. package/dist/esm/companion/index.d.ts +39 -0
  40. package/dist/esm/companion/index.d.ts.map +1 -0
  41. package/dist/esm/companion/index.js +39 -0
  42. package/dist/esm/companion/index.js.map +1 -0
  43. package/dist/esm/companion/openapi-generator.d.ts +94 -0
  44. package/dist/esm/companion/openapi-generator.d.ts.map +1 -0
  45. package/dist/esm/companion/openapi-generator.js +1562 -0
  46. package/dist/esm/companion/openapi-generator.js.map +1 -0
  47. package/dist/esm/companion/operation-projection.schemas.d.ts +797 -0
  48. package/dist/esm/companion/operation-projection.schemas.d.ts.map +1 -0
  49. package/dist/esm/companion/operation-projection.schemas.js +610 -0
  50. package/dist/esm/companion/operation-projection.schemas.js.map +1 -0
  51. package/dist/esm/companion/publish-result.types.d.ts +124 -0
  52. package/dist/esm/companion/publish-result.types.d.ts.map +1 -0
  53. package/dist/esm/companion/publish-result.types.js +28 -0
  54. package/dist/esm/companion/publish-result.types.js.map +1 -0
  55. package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.d.ts +26 -0
  56. package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.d.ts.map +1 -0
  57. package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.js +63 -0
  58. package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.js.map +1 -0
  59. package/dist/esm/companion/rendering/technical-documentation-build-measurement.d.ts +23 -0
  60. package/dist/esm/companion/rendering/technical-documentation-build-measurement.d.ts.map +1 -0
  61. package/dist/esm/companion/rendering/technical-documentation-build-measurement.js +104 -0
  62. package/dist/esm/companion/rendering/technical-documentation-build-measurement.js.map +1 -0
  63. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts +9 -0
  64. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts.map +1 -0
  65. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js +421 -0
  66. package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js.map +1 -0
  67. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.d.ts +72 -0
  68. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.d.ts.map +1 -0
  69. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js +204 -0
  70. package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js.map +1 -0
  71. package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts +9 -0
  72. package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts.map +1 -0
  73. package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js +106 -0
  74. package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js.map +1 -0
  75. package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.d.ts +43 -0
  76. package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.d.ts.map +1 -0
  77. package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.js +88 -0
  78. package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.js.map +1 -0
  79. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts +7 -0
  80. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts.map +1 -0
  81. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js +51 -0
  82. package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js.map +1 -0
  83. package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts +45 -0
  84. package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts.map +1 -0
  85. package/dist/esm/companion/rendering/technical-documentation-render-model.js +178 -0
  86. package/dist/esm/companion/rendering/technical-documentation-render-model.js.map +1 -0
  87. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.d.ts +3 -0
  88. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.d.ts.map +1 -0
  89. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.js +49 -0
  90. package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.js.map +1 -0
  91. package/dist/esm/companion/spec-to-operation-doc.d.ts +176 -0
  92. package/dist/esm/companion/spec-to-operation-doc.d.ts.map +1 -0
  93. package/dist/esm/companion/spec-to-operation-doc.js +326 -0
  94. package/dist/esm/companion/spec-to-operation-doc.js.map +1 -0
  95. package/dist/esm/companion/technical-documentation-asset-path.d.ts +16 -0
  96. package/dist/esm/companion/technical-documentation-asset-path.d.ts.map +1 -0
  97. package/dist/esm/companion/technical-documentation-asset-path.js +19 -0
  98. package/dist/esm/companion/technical-documentation-asset-path.js.map +1 -0
  99. package/dist/esm/companion/technical-documentation-capture-execution-port.d.ts +14 -0
  100. package/dist/esm/companion/technical-documentation-capture-execution-port.d.ts.map +1 -0
  101. package/dist/esm/companion/technical-documentation-capture-execution-port.js +1 -0
  102. package/dist/esm/companion/technical-documentation-capture-execution-port.js.map +1 -0
  103. package/dist/esm/companion/technical-documentation-diagram-materializer.d.ts +25 -0
  104. package/dist/esm/companion/technical-documentation-diagram-materializer.d.ts.map +1 -0
  105. package/dist/esm/companion/technical-documentation-diagram-materializer.js +86 -0
  106. package/dist/esm/companion/technical-documentation-diagram-materializer.js.map +1 -0
  107. package/dist/esm/companion/technical-documentation-placeholder-materializer.d.ts +17 -0
  108. package/dist/esm/companion/technical-documentation-placeholder-materializer.d.ts.map +1 -0
  109. package/dist/esm/companion/technical-documentation-placeholder-materializer.js +63 -0
  110. package/dist/esm/companion/technical-documentation-placeholder-materializer.js.map +1 -0
  111. package/dist/esm/companion/zod-to-openapi.d.ts +67 -0
  112. package/dist/esm/companion/zod-to-openapi.d.ts.map +1 -0
  113. package/dist/esm/companion/zod-to-openapi.js +211 -0
  114. package/dist/esm/companion/zod-to-openapi.js.map +1 -0
  115. package/dist/esm/companion-exports.d.ts +32 -0
  116. package/dist/esm/companion-exports.d.ts.map +1 -0
  117. package/dist/esm/companion-exports.js +32 -0
  118. package/dist/esm/companion-exports.js.map +1 -0
  119. package/dist/esm/config/define-tech-doc-config.d.ts +38 -0
  120. package/dist/esm/config/define-tech-doc-config.d.ts.map +1 -0
  121. package/dist/esm/config/define-tech-doc-config.js +40 -0
  122. package/dist/esm/config/define-tech-doc-config.js.map +1 -0
  123. package/dist/esm/config/index.d.ts +22 -0
  124. package/dist/esm/config/index.d.ts.map +1 -0
  125. package/dist/esm/config/index.js +22 -0
  126. package/dist/esm/config/index.js.map +1 -0
  127. package/dist/esm/config/wildo-tech-doc-config.schemas.d.ts +204 -0
  128. package/dist/esm/config/wildo-tech-doc-config.schemas.d.ts.map +1 -0
  129. package/dist/esm/config/wildo-tech-doc-config.schemas.js +192 -0
  130. package/dist/esm/config/wildo-tech-doc-config.schemas.js.map +1 -0
  131. package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.d.ts +82 -0
  132. package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.d.ts.map +1 -0
  133. package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.js +114 -0
  134. package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.js.map +1 -0
  135. package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts +420 -0
  136. package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts.map +1 -0
  137. package/dist/esm/content/application-consumer-documentation-content.techdoc.js +9619 -0
  138. package/dist/esm/content/application-consumer-documentation-content.techdoc.js.map +1 -0
  139. package/dist/esm/content.exports.d.ts +9 -0
  140. package/dist/esm/content.exports.d.ts.map +1 -0
  141. package/dist/esm/content.exports.js +9 -0
  142. package/dist/esm/content.exports.js.map +1 -0
  143. package/dist/esm/index.d.ts +26 -0
  144. package/dist/esm/index.d.ts.map +1 -0
  145. package/dist/esm/index.js +26 -0
  146. package/dist/esm/index.js.map +1 -0
  147. package/dist/esm/openapi/api-reference-link-index.d.ts +98 -0
  148. package/dist/esm/openapi/api-reference-link-index.d.ts.map +1 -0
  149. package/dist/esm/openapi/api-reference-link-index.js +301 -0
  150. package/dist/esm/openapi/api-reference-link-index.js.map +1 -0
  151. package/dist/esm/openapi/api-reference-targets.d.ts +71 -0
  152. package/dist/esm/openapi/api-reference-targets.d.ts.map +1 -0
  153. package/dist/esm/openapi/api-reference-targets.js +114 -0
  154. package/dist/esm/openapi/api-reference-targets.js.map +1 -0
  155. package/dist/esm/openapi/index.d.ts +20 -0
  156. package/dist/esm/openapi/index.d.ts.map +1 -0
  157. package/dist/esm/openapi/index.js +20 -0
  158. package/dist/esm/openapi/index.js.map +1 -0
  159. package/dist/esm/openapi/openapi-generation-output.schemas.d.ts +80 -0
  160. package/dist/esm/openapi/openapi-generation-output.schemas.d.ts.map +1 -0
  161. package/dist/esm/openapi/openapi-generation-output.schemas.js +76 -0
  162. package/dist/esm/openapi/openapi-generation-output.schemas.js.map +1 -0
  163. package/dist/esm/openapi-reference-model.exports.d.ts +10 -0
  164. package/dist/esm/openapi-reference-model.exports.d.ts.map +1 -0
  165. package/dist/esm/openapi-reference-model.exports.js +10 -0
  166. package/dist/esm/openapi-reference-model.exports.js.map +1 -0
  167. package/dist/esm/runtime/AuthExchangePage.d.ts +84 -0
  168. package/dist/esm/runtime/AuthExchangePage.d.ts.map +1 -0
  169. package/dist/esm/runtime/AuthExchangePage.js +188 -0
  170. package/dist/esm/runtime/AuthExchangePage.js.map +1 -0
  171. package/dist/esm/runtime/DocsAuthContext.d.ts +119 -0
  172. package/dist/esm/runtime/DocsAuthContext.d.ts.map +1 -0
  173. package/dist/esm/runtime/DocsAuthContext.js +171 -0
  174. package/dist/esm/runtime/DocsAuthContext.js.map +1 -0
  175. package/dist/esm/runtime/decode-jwt-claims.d.ts +39 -0
  176. package/dist/esm/runtime/decode-jwt-claims.d.ts.map +1 -0
  177. package/dist/esm/runtime/decode-jwt-claims.js +86 -0
  178. package/dist/esm/runtime/decode-jwt-claims.js.map +1 -0
  179. package/dist/esm/runtime/docs-auth-client.d.ts +193 -0
  180. package/dist/esm/runtime/docs-auth-client.d.ts.map +1 -0
  181. package/dist/esm/runtime/docs-auth-client.js +211 -0
  182. package/dist/esm/runtime/docs-auth-client.js.map +1 -0
  183. package/dist/esm/runtime/docs-auth-session.schemas.d.ts +77 -0
  184. package/dist/esm/runtime/docs-auth-session.schemas.d.ts.map +1 -0
  185. package/dist/esm/runtime/docs-auth-session.schemas.js +50 -0
  186. package/dist/esm/runtime/docs-auth-session.schemas.js.map +1 -0
  187. package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts +17 -0
  188. package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts.map +1 -0
  189. package/dist/esm/runtime/frontend-provider-registry.techdoc.js +23 -0
  190. package/dist/esm/runtime/frontend-provider-registry.techdoc.js.map +1 -0
  191. package/dist/esm/runtime/index.d.ts +57 -0
  192. package/dist/esm/runtime/index.d.ts.map +1 -0
  193. package/dist/esm/runtime/index.js +76 -0
  194. package/dist/esm/runtime/index.js.map +1 -0
  195. package/dist/esm/runtime/openapi-reference-conservation.d.ts +20 -0
  196. package/dist/esm/runtime/openapi-reference-conservation.d.ts.map +1 -0
  197. package/dist/esm/runtime/openapi-reference-conservation.js +102 -0
  198. package/dist/esm/runtime/openapi-reference-conservation.js.map +1 -0
  199. package/dist/esm/runtime/openapi-reference-model.d.ts +224 -0
  200. package/dist/esm/runtime/openapi-reference-model.d.ts.map +1 -0
  201. package/dist/esm/runtime/openapi-reference-model.js +579 -0
  202. package/dist/esm/runtime/openapi-reference-model.js.map +1 -0
  203. package/dist/esm/runtime/openapi-reference-view.d.ts +13 -0
  204. package/dist/esm/runtime/openapi-reference-view.d.ts.map +1 -0
  205. package/dist/esm/runtime/openapi-reference-view.js +284 -0
  206. package/dist/esm/runtime/openapi-reference-view.js.map +1 -0
  207. package/dist/esm/runtime/use-docs-auth-session.d.ts +26 -0
  208. package/dist/esm/runtime/use-docs-auth-session.d.ts.map +1 -0
  209. package/dist/esm/runtime/use-docs-auth-session.js +34 -0
  210. package/dist/esm/runtime/use-docs-auth-session.js.map +1 -0
  211. package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts +3 -0
  212. package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts.map +1 -0
  213. package/dist/esm/runtime/use-docs-frontend-provider-registry.js +5 -0
  214. package/dist/esm/runtime/use-docs-frontend-provider-registry.js.map +1 -0
  215. package/dist/tsconfig.build.tsbuildinfo +1 -0
  216. package/package.json +117 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * @wildo-package @wildo-ai/saas-technical-doc/companion (spec → OpenAPI enrichment)
3
+ *
4
+ * Pure mappers from a resource SPECIFICATION's authored prose into OpenAPI:
5
+ * - **Tier 1** `buildOperationDocFromSpec` — operation `purpose` / `outcome` /
6
+ * `whenToUse` (with per-variant overrides) → operation `description` on an
7
+ * `OperationProjection`. The `summary` (operation TITLE) is intentionally NOT
8
+ * produced here: it is a short verb-first label the generator derives
9
+ * (`buildDefaultSummary`), kept separate from the authored prose.
10
+ * - **Tier 2** `injectFieldDocsIntoJsonSchema` — per-field `meaning` /
11
+ * `whyItMatters` / `relationshipContext` / enum-value meanings → property
12
+ * `description` + `x-enum-descriptions` on a request/response JSON-Schema
13
+ * fragment.
14
+ *
15
+ * See `.claude/plans/technical-doc-openapi-spec-integration.md`. Without authored
16
+ * spec prose the description is empty and the operation falls back to the
17
+ * generator's humanized verb-first title alone (e.g. `"List users"`) with no
18
+ * field documentation — the "quality will be very poor" failure mode this
19
+ * integration removes.
20
+ *
21
+ * @wildo-boundary
22
+ * Imports NOTHING — pure string composition. It deliberately declares its
23
+ * own NARROW structural input types instead of importing
24
+ * `ResourceOperationSpecification` / `ResourceOperationVariantSpecification`
25
+ * from `@wildo-ai/saas-specifications`. Same K-3 boundary reasoning the rest
26
+ * of `companion/` follows (see `operation-projection.schemas.ts`): pulling
27
+ * the spec package would drag a heavy type graph into the decorator-free
28
+ * technical-doc companion bundle. The types below MUST remain a structural
29
+ * SUBSET of the authored spec types — only the prose fields this mapper
30
+ * reads. If the spec layer renames a prose field, update these in lockstep.
31
+ */
32
+ /** OperationProjection description cap: ≤2000 (Zod-enforced downstream). The
33
+ * summary (title) is derived by the generator, not built here. */
34
+ const DESCRIPTION_MAX_LENGTH = 2000;
35
+ /** Collapse all whitespace runs to single spaces and trim (single-line summary). */
36
+ const toSingleLine = (value) => value.replace(/\s+/gu, ' ').trim();
37
+ /** Clamp to `max` chars, appending an ellipsis when truncated. */
38
+ const clampLength = (value, max) => value.length <= max ? value : `${value.slice(0, max - 1).trimEnd()}…`;
39
+ /**
40
+ * Strip implementation-only noise that authored spec prose carries for the
41
+ * FRAMEWORK-KNOWLEDGE audience but that means nothing to an API consumer
42
+ * reading the generated docs:
43
+ * - Parentheticals naming a runtime variant — `(API_CALL in factory)`,
44
+ * `(REPOSITORY_ONLY create in factory — …)`.
45
+ * - Dangling `in factory` / `in the factory` phrases.
46
+ * Then normalise the leftover spacing/punctuation. Conservative by design — it
47
+ * only removes parentheticals that actually contain a variant marker, so
48
+ * ordinary parenthetical prose survives.
49
+ *
50
+ * This is the single canonical "spec prose → consumer prose" cleaner, applied
51
+ * to EVERY prose surface that reaches the OpenAPI output: the Tier-1 operation
52
+ * `description` (here), Tier-2 field descriptions + `x-enum-descriptions`
53
+ * (`composeFieldDescription` / `cleanEnumMeanings`), Tier-3 response/error/
54
+ * example/rate-limit prose (`asCleanProse`), and the resource `tags[].description`
55
+ * (applied by the generator — `openapi-generator.ts` imports this). Exported so
56
+ * those out-of-module surfaces clean prose identically rather than diverging.
57
+ */
58
+ export const stripImplementationNoise = (value) => value
59
+ .replace(/\s*\([^()]*\b(?:API_CALL_WITH_CALLBACK|API_CALL|REPOSITORY_ONLY|INTERNAL_CALL|CRON_JOB|BATCH_JOB)\b[^()]*\)/gu, '')
60
+ .replace(/\s+in\s+(?:the\s+)?factory\b/giu, '')
61
+ .replace(/\(\s*\)/gu, '')
62
+ .replace(/\s+([.,;:])/gu, '$1')
63
+ .replace(/\s{2,}/gu, ' ')
64
+ .trim();
65
+ /**
66
+ * Resolve the EFFECTIVE prose for an operation, applying variant overlay
67
+ * semantics: a present variant field REPLACES the base field; an absent
68
+ * variant field falls back to the base. `variantKey === undefined` resolves
69
+ * the reserved `'default'` variant.
70
+ *
71
+ * Returns `{}` when the spec is absent or carries no prose at all — callers
72
+ * leave `OperationProjection.summary` / `.description` unset so the
73
+ * generator's humanized fallback applies (graceful degradation for
74
+ * operations / resources with no authored spec).
75
+ */
76
+ export const buildOperationDocFromSpec = (operationSpec, variantKey) => {
77
+ if (operationSpec === undefined) {
78
+ return {};
79
+ }
80
+ const variant = operationSpec.variants?.[variantKey ?? 'default'];
81
+ const effectivePurpose = variant?.purpose ?? operationSpec.purpose;
82
+ const effectiveOutcome = variant?.outcome ?? operationSpec.outcome;
83
+ const effectiveWhenToUse = variant?.whenToUse ?? operationSpec.whenToUse;
84
+ const doc = {};
85
+ // The OpenAPI `summary` (operation TITLE / nav label / breadcrumb) is
86
+ // intentionally NOT taken from `purpose`. `purpose` is impl-aware intent prose
87
+ // authored for the framework-knowledge layer — a full sentence, often with
88
+ // runtime detail like "(API_CALL in factory)" — which is far too long and too
89
+ // internal to be a title. The generator derives a short verb-first title
90
+ // (`buildDefaultSummary`); here we only build the DESCRIPTION from the authored
91
+ // prose. The description LEADS with `purpose` (the intent), then the observable
92
+ // `outcome`, then a "When to use" clause — each cleaned of implementation noise.
93
+ const descriptionParts = [];
94
+ if (effectivePurpose !== undefined) {
95
+ const purpose = stripImplementationNoise(toSingleLine(effectivePurpose));
96
+ if (purpose.length > 0)
97
+ descriptionParts.push(purpose);
98
+ }
99
+ if (effectiveOutcome !== undefined) {
100
+ const outcome = stripImplementationNoise(effectiveOutcome.trim());
101
+ if (outcome.length > 0)
102
+ descriptionParts.push(outcome);
103
+ }
104
+ if (effectiveWhenToUse !== undefined) {
105
+ const whenToUse = stripImplementationNoise(effectiveWhenToUse.trim());
106
+ if (whenToUse.length > 0)
107
+ descriptionParts.push(`**When to use:** ${whenToUse}`);
108
+ }
109
+ if (descriptionParts.length > 0) {
110
+ doc.description = clampLength(descriptionParts.join('\n\n'), DESCRIPTION_MAX_LENGTH);
111
+ }
112
+ return doc;
113
+ };
114
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
115
+ /**
116
+ * Clean an enum value→meaning record for consumer-facing output: strip
117
+ * implementation-only noise from each meaning (see `stripImplementationNoise`)
118
+ * and drop any entry that is non-string or empty after cleaning. Used for both
119
+ * the human-readable "Values:" legend and the machine `x-enum-descriptions`
120
+ * extension so the two never diverge.
121
+ */
122
+ const cleanEnumMeanings = (enumValueMeanings) => {
123
+ const cleaned = {};
124
+ for (const [value, meaning] of Object.entries(enumValueMeanings)) {
125
+ if (typeof meaning !== 'string') {
126
+ continue;
127
+ }
128
+ const cleanedMeaning = stripImplementationNoise(meaning.trim());
129
+ if (cleanedMeaning.length > 0) {
130
+ cleaned[value] = cleanedMeaning;
131
+ }
132
+ }
133
+ return cleaned;
134
+ };
135
+ /**
136
+ * Compose a single property `description` from a field's authored docs:
137
+ * meaning + whyItMatters + relationshipContext as space-joined sentences,
138
+ * then an optional markdown "Values:" legend from enum meanings. Each authored
139
+ * sentence is run through `stripImplementationNoise` — the field-spec prose is
140
+ * authored for the framework-knowledge audience and may carry runtime markers
141
+ * (`(API_CALL in factory)`) that mean nothing to an API consumer. Returns
142
+ * `undefined` when the field carries no usable prose.
143
+ */
144
+ const composeFieldDescription = (doc) => {
145
+ const sentences = [];
146
+ for (const candidate of [doc.meaning, doc.whyItMatters, doc.relationshipContext]) {
147
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
148
+ const cleaned = stripImplementationNoise(candidate.trim());
149
+ if (cleaned.length > 0) {
150
+ sentences.push(cleaned);
151
+ }
152
+ }
153
+ }
154
+ let description = sentences.join(' ');
155
+ if (doc.enumValueMeanings !== undefined) {
156
+ const legend = Object.entries(cleanEnumMeanings(doc.enumValueMeanings)).map(([value, meaning]) => `- \`${value}\`: ${meaning}`);
157
+ if (legend.length > 0) {
158
+ const block = `Values:\n${legend.join('\n')}`;
159
+ description = description.length > 0 ? `${description}\n\n${block}` : block;
160
+ }
161
+ }
162
+ return description.length > 0 ? description : undefined;
163
+ };
164
+ /**
165
+ * Enrich the TOP-LEVEL properties of an OpenAPI/JSON-Schema object fragment
166
+ * (a `requestBodySchema` / `responseBodySchema` produced by
167
+ * `convertZodSchemaToOpenApiSchema`) with authored field documentation.
168
+ * Mutates `fragment` in place.
169
+ *
170
+ * Rules:
171
+ * - Property `description` is composed from the field's docs ONLY when the
172
+ * property has no existing `description` — a Zod `.describe()` authored on
173
+ * the DTO field wins (it is closer to the wire contract).
174
+ * - `x-enum-descriptions` (value → meaning) is set whenever enum meanings
175
+ * exist, independent of the description-clobber rule, for machine consumers.
176
+ *
177
+ * Only top-level properties are walked: the field-spec extractor produces
178
+ * specs for top-level schema fields only, so nested-object properties have no
179
+ * authored docs to apply. No-op for non-object fragments (`null`, scalars).
180
+ * Joins by EXACT property-name match — DTO property names that diverge from
181
+ * the resource's field names (renamed / computed / backend-stripped fields)
182
+ * are simply left undocumented rather than mis-documented.
183
+ */
184
+ export const injectFieldDocsIntoJsonSchema = (fragment, fieldDocsByName) => {
185
+ if (!isPlainObject(fragment)) {
186
+ return;
187
+ }
188
+ const properties = fragment['properties'];
189
+ if (!isPlainObject(properties)) {
190
+ return;
191
+ }
192
+ for (const [propertyName, rawPropertySchema] of Object.entries(properties)) {
193
+ if (!isPlainObject(rawPropertySchema)) {
194
+ continue;
195
+ }
196
+ const doc = fieldDocsByName[propertyName];
197
+ if (doc === undefined) {
198
+ continue;
199
+ }
200
+ if (rawPropertySchema['description'] === undefined) {
201
+ const description = composeFieldDescription(doc);
202
+ if (description !== undefined) {
203
+ rawPropertySchema['description'] = description;
204
+ }
205
+ }
206
+ if (doc.enumValueMeanings !== undefined) {
207
+ const cleanedEnumMeanings = cleanEnumMeanings(doc.enumValueMeanings);
208
+ if (Object.keys(cleanedEnumMeanings).length > 0) {
209
+ rawPropertySchema['x-enum-descriptions'] = cleanedEnumMeanings;
210
+ }
211
+ }
212
+ }
213
+ };
214
+ const asNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
215
+ /**
216
+ * Like `asNonEmptyString`, but for PROSE fields surfaced verbatim to API
217
+ * consumers (response-status `meaning`, error-scenario `when`, example `title`,
218
+ * `rateLimitNote`): additionally strips implementation-only noise (see
219
+ * `stripImplementationNoise`) and returns `undefined` if the value is empty
220
+ * before OR after cleaning. Do NOT use for code-like fields (`code`,
221
+ * `errorCode`, `forStatus`) — those pass through untouched via `asNonEmptyString`.
222
+ */
223
+ const asCleanProse = (value) => {
224
+ const raw = asNonEmptyString(value);
225
+ if (raw === undefined) {
226
+ return undefined;
227
+ }
228
+ const cleaned = stripImplementationNoise(raw);
229
+ return cleaned.length > 0 ? cleaned : undefined;
230
+ };
231
+ /**
232
+ * Extract + normalize the Tier-3 HTTP-documentation fields from a raw authored
233
+ * operation spec into the projection shape. Variant examples are a complete
234
+ * overlay: when `variants.<key>.examples` is present it replaces the base
235
+ * operation examples, rather than merging a payload that may target a different
236
+ * response schema. Defensive: a malformed entry is dropped rather than
237
+ * poisoning the whole projection (the generator re-validates via Zod). Returns
238
+ * `{}` when the operation authored no HTTP docs.
239
+ *
240
+ * Prose fields (`meaning` / `when` / `title` / `rateLimitNote`) are cleaned of
241
+ * implementation-only noise via `asCleanProse`, consistent with the Tier-1
242
+ * `description` and Tier-2 field descriptions. `code` / `forStatus` /
243
+ * `errorCode` are `HttpResponseStatusCode` / error-code enum values whose
244
+ * runtime string value (`'200'`, …) is the OpenAPI `responses` key / a machine
245
+ * code, so they pass through untouched as strings.
246
+ */
247
+ export const buildOperationHttpDocFromSpec = (operationSpec, variantKey) => {
248
+ if (!isPlainObject(operationSpec)) {
249
+ return {};
250
+ }
251
+ const doc = {};
252
+ const responseStatuses = operationSpec['responseStatuses'];
253
+ if (Array.isArray(responseStatuses)) {
254
+ const normalized = responseStatuses
255
+ .map((entry) => {
256
+ if (!isPlainObject(entry))
257
+ return undefined;
258
+ const code = asNonEmptyString(entry['code']);
259
+ const meaning = asCleanProse(entry['meaning']);
260
+ return code !== undefined && meaning !== undefined ? { code, meaning } : undefined;
261
+ })
262
+ .filter((entry) => entry !== undefined);
263
+ if (normalized.length > 0)
264
+ doc.responseStatuses = normalized;
265
+ }
266
+ const errorScenarios = operationSpec['errorScenarios'];
267
+ if (Array.isArray(errorScenarios)) {
268
+ const normalized = errorScenarios
269
+ .map((entry) => {
270
+ if (!isPlainObject(entry))
271
+ return undefined;
272
+ const code = asNonEmptyString(entry['code']);
273
+ const when = asCleanProse(entry['when']);
274
+ if (code === undefined || when === undefined)
275
+ return undefined;
276
+ const scenario = { code, when };
277
+ const errorCode = asNonEmptyString(entry['errorCode']);
278
+ if (errorCode !== undefined)
279
+ scenario.errorCode = errorCode;
280
+ return scenario;
281
+ })
282
+ .filter((entry) => entry !== undefined);
283
+ if (normalized.length > 0)
284
+ doc.errorScenarios = normalized;
285
+ }
286
+ if (typeof operationSpec['idempotent'] === 'boolean') {
287
+ doc.idempotent = operationSpec['idempotent'];
288
+ }
289
+ const rateLimitNote = asCleanProse(operationSpec['rateLimitNote']);
290
+ if (rateLimitNote !== undefined) {
291
+ doc.rateLimitNote = rateLimitNote;
292
+ }
293
+ const variants = operationSpec['variants'];
294
+ const variant = isPlainObject(variants) ? variants[variantKey ?? 'default'] : undefined;
295
+ // A present variant `examples` member deliberately suppresses the base even
296
+ // when it is malformed or empty: falling back would publish a payload under a
297
+ // schema the variant does not own. The normalizer below emits no example for
298
+ // that invalid/empty override, which is safer than a misleading one.
299
+ const examples = isPlainObject(variant) && Object.prototype.hasOwnProperty.call(variant, 'examples')
300
+ ? variant['examples']
301
+ : operationSpec['examples'];
302
+ if (Array.isArray(examples)) {
303
+ const normalized = examples
304
+ .map((entry) => {
305
+ if (!isPlainObject(entry))
306
+ return undefined;
307
+ const title = asCleanProse(entry['title']);
308
+ if (title === undefined)
309
+ return undefined;
310
+ const example = { title };
311
+ if (entry['request'] !== undefined)
312
+ example.request = entry['request'];
313
+ if (entry['response'] !== undefined)
314
+ example.response = entry['response'];
315
+ const forStatus = asNonEmptyString(entry['forStatus']);
316
+ if (forStatus !== undefined)
317
+ example.forStatus = forStatus;
318
+ return example;
319
+ })
320
+ .filter((entry) => entry !== undefined);
321
+ if (normalized.length > 0)
322
+ doc.examples = normalized;
323
+ }
324
+ return doc;
325
+ };
326
+ //# sourceMappingURL=spec-to-operation-doc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-to-operation-doc.js","sourceRoot":"","sources":["../../../../src/companion/spec-to-operation-doc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-technical-doc/companion (spec → OpenAPI enrichment)\n *\n * Pure mappers from a resource SPECIFICATION's authored prose into OpenAPI:\n * - **Tier 1** `buildOperationDocFromSpec` — operation `purpose` / `outcome` /\n * `whenToUse` (with per-variant overrides) → operation `description` on an\n * `OperationProjection`. The `summary` (operation TITLE) is intentionally NOT\n * produced here: it is a short verb-first label the generator derives\n * (`buildDefaultSummary`), kept separate from the authored prose.\n * - **Tier 2** `injectFieldDocsIntoJsonSchema` — per-field `meaning` /\n * `whyItMatters` / `relationshipContext` / enum-value meanings → property\n * `description` + `x-enum-descriptions` on a request/response JSON-Schema\n * fragment.\n *\n * See `.claude/plans/technical-doc-openapi-spec-integration.md`. Without authored\n * spec prose the description is empty and the operation falls back to the\n * generator's humanized verb-first title alone (e.g. `\"List users\"`) with no\n * field documentation — the \"quality will be very poor\" failure mode this\n * integration removes.\n *\n * @wildo-boundary\n * Imports NOTHING — pure string composition. It deliberately declares its\n * own NARROW structural input types instead of importing\n * `ResourceOperationSpecification` / `ResourceOperationVariantSpecification`\n * from `@wildo-ai/saas-specifications`. Same K-3 boundary reasoning the rest\n * of `companion/` follows (see `operation-projection.schemas.ts`): pulling\n * the spec package would drag a heavy type graph into the decorator-free\n * technical-doc companion bundle. The types below MUST remain a structural\n * SUBSET of the authored spec types — only the prose fields this mapper\n * reads. If the spec layer renames a prose field, update these in lockstep.\n */\n\n/**\n * Narrow structural view of a per-variant operation spec — the prose fields\n * a variant may override. Subset of `ResourceOperationVariantSpecification`.\n */\nexport interface OperationSpecVariantProse {\n purpose?: string;\n outcome?: string;\n whenToUse?: string;\n /** Complete OpenAPI example replacement for this response-shape variant. */\n examples?: Array<{ title: string; request?: unknown; response?: unknown; forStatus?: string }>;\n}\n\n/**\n * Narrow structural view of an operation spec — the base prose plus the\n * per-variant overrides map. Subset of `ResourceOperationSpecification`.\n * `variants` is keyed by the named variant key, with the reserved `'default'`\n * key for the no-`variantKey` (default-variant) path.\n */\nexport interface OperationSpecProse {\n purpose?: string;\n outcome?: string;\n whenToUse?: string;\n variants?: Record<string, OperationSpecVariantProse | undefined>;\n}\n\n/** The OpenAPI prose this mapper produces. Both fields optional. */\nexport interface OperationDoc {\n summary?: string;\n description?: string;\n}\n\n/** OperationProjection description cap: ≤2000 (Zod-enforced downstream). The\n * summary (title) is derived by the generator, not built here. */\nconst DESCRIPTION_MAX_LENGTH = 2000;\n\n/** Collapse all whitespace runs to single spaces and trim (single-line summary). */\nconst toSingleLine = (value: string): string => value.replace(/\\s+/gu, ' ').trim();\n\n/** Clamp to `max` chars, appending an ellipsis when truncated. */\nconst clampLength = (value: string, max: number): string =>\n value.length <= max ? value : `${value.slice(0, max - 1).trimEnd()}…`;\n\n/**\n * Strip implementation-only noise that authored spec prose carries for the\n * FRAMEWORK-KNOWLEDGE audience but that means nothing to an API consumer\n * reading the generated docs:\n * - Parentheticals naming a runtime variant — `(API_CALL in factory)`,\n * `(REPOSITORY_ONLY create in factory — …)`.\n * - Dangling `in factory` / `in the factory` phrases.\n * Then normalise the leftover spacing/punctuation. Conservative by design — it\n * only removes parentheticals that actually contain a variant marker, so\n * ordinary parenthetical prose survives.\n *\n * This is the single canonical \"spec prose → consumer prose\" cleaner, applied\n * to EVERY prose surface that reaches the OpenAPI output: the Tier-1 operation\n * `description` (here), Tier-2 field descriptions + `x-enum-descriptions`\n * (`composeFieldDescription` / `cleanEnumMeanings`), Tier-3 response/error/\n * example/rate-limit prose (`asCleanProse`), and the resource `tags[].description`\n * (applied by the generator — `openapi-generator.ts` imports this). Exported so\n * those out-of-module surfaces clean prose identically rather than diverging.\n */\nexport const stripImplementationNoise = (value: string): string =>\n value\n .replace(\n /\\s*\\([^()]*\\b(?:API_CALL_WITH_CALLBACK|API_CALL|REPOSITORY_ONLY|INTERNAL_CALL|CRON_JOB|BATCH_JOB)\\b[^()]*\\)/gu,\n '',\n )\n .replace(/\\s+in\\s+(?:the\\s+)?factory\\b/giu, '')\n .replace(/\\(\\s*\\)/gu, '')\n .replace(/\\s+([.,;:])/gu, '$1')\n .replace(/\\s{2,}/gu, ' ')\n .trim();\n\n/**\n * Resolve the EFFECTIVE prose for an operation, applying variant overlay\n * semantics: a present variant field REPLACES the base field; an absent\n * variant field falls back to the base. `variantKey === undefined` resolves\n * the reserved `'default'` variant.\n *\n * Returns `{}` when the spec is absent or carries no prose at all — callers\n * leave `OperationProjection.summary` / `.description` unset so the\n * generator's humanized fallback applies (graceful degradation for\n * operations / resources with no authored spec).\n */\nexport const buildOperationDocFromSpec = (\n operationSpec: OperationSpecProse | undefined,\n variantKey: string | undefined,\n): OperationDoc => {\n if (operationSpec === undefined) {\n return {};\n }\n\n const variant = operationSpec.variants?.[variantKey ?? 'default'];\n const effectivePurpose = variant?.purpose ?? operationSpec.purpose;\n const effectiveOutcome = variant?.outcome ?? operationSpec.outcome;\n const effectiveWhenToUse = variant?.whenToUse ?? operationSpec.whenToUse;\n\n const doc: OperationDoc = {};\n\n // The OpenAPI `summary` (operation TITLE / nav label / breadcrumb) is\n // intentionally NOT taken from `purpose`. `purpose` is impl-aware intent prose\n // authored for the framework-knowledge layer — a full sentence, often with\n // runtime detail like \"(API_CALL in factory)\" — which is far too long and too\n // internal to be a title. The generator derives a short verb-first title\n // (`buildDefaultSummary`); here we only build the DESCRIPTION from the authored\n // prose. The description LEADS with `purpose` (the intent), then the observable\n // `outcome`, then a \"When to use\" clause — each cleaned of implementation noise.\n const descriptionParts: string[] = [];\n if (effectivePurpose !== undefined) {\n const purpose = stripImplementationNoise(toSingleLine(effectivePurpose));\n if (purpose.length > 0) descriptionParts.push(purpose);\n }\n if (effectiveOutcome !== undefined) {\n const outcome = stripImplementationNoise(effectiveOutcome.trim());\n if (outcome.length > 0) descriptionParts.push(outcome);\n }\n if (effectiveWhenToUse !== undefined) {\n const whenToUse = stripImplementationNoise(effectiveWhenToUse.trim());\n if (whenToUse.length > 0) descriptionParts.push(`**When to use:** ${whenToUse}`);\n }\n if (descriptionParts.length > 0) {\n doc.description = clampLength(descriptionParts.join('\\n\\n'), DESCRIPTION_MAX_LENGTH);\n }\n\n return doc;\n};\n\n// ---------------------------------------------------------------------------\n// Tier 2 — field / enum documentation\n// ---------------------------------------------------------------------------\n\n/**\n * Narrow structural view of one field's authored documentation. Built by the\n * companion projector from the extracted resource field specs and consumed by\n * `injectFieldDocsIntoJsonSchema`. Boundary-clean (no `@wildo-ai/saas-specifications`\n * import): the projector maps `ExtractedFieldSpec.params.{meaning,whyItMatters,\n * relationshipContext,values}` into this shape before calling the injector.\n */\nexport interface FieldDoc {\n /** Business meaning of the field (becomes the lead of the property description). */\n meaning?: string;\n /** Why the field is included (appended to the description). */\n whyItMatters?: string;\n /** For foreign keys: the relationship this field expresses. */\n relationshipContext?: string;\n /** For enum fields: enum value string → authored meaning. */\n enumValueMeanings?: Record<string, string>;\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Clean an enum value→meaning record for consumer-facing output: strip\n * implementation-only noise from each meaning (see `stripImplementationNoise`)\n * and drop any entry that is non-string or empty after cleaning. Used for both\n * the human-readable \"Values:\" legend and the machine `x-enum-descriptions`\n * extension so the two never diverge.\n */\nconst cleanEnumMeanings = (enumValueMeanings: Record<string, string>): Record<string, string> => {\n const cleaned: Record<string, string> = {};\n for (const [value, meaning] of Object.entries(enumValueMeanings)) {\n if (typeof meaning !== 'string') {\n continue;\n }\n const cleanedMeaning = stripImplementationNoise(meaning.trim());\n if (cleanedMeaning.length > 0) {\n cleaned[value] = cleanedMeaning;\n }\n }\n return cleaned;\n};\n\n/**\n * Compose a single property `description` from a field's authored docs:\n * meaning + whyItMatters + relationshipContext as space-joined sentences,\n * then an optional markdown \"Values:\" legend from enum meanings. Each authored\n * sentence is run through `stripImplementationNoise` — the field-spec prose is\n * authored for the framework-knowledge audience and may carry runtime markers\n * (`(API_CALL in factory)`) that mean nothing to an API consumer. Returns\n * `undefined` when the field carries no usable prose.\n */\nconst composeFieldDescription = (doc: FieldDoc): string | undefined => {\n const sentences: string[] = [];\n for (const candidate of [doc.meaning, doc.whyItMatters, doc.relationshipContext]) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n const cleaned = stripImplementationNoise(candidate.trim());\n if (cleaned.length > 0) {\n sentences.push(cleaned);\n }\n }\n }\n let description = sentences.join(' ');\n\n if (doc.enumValueMeanings !== undefined) {\n const legend = Object.entries(cleanEnumMeanings(doc.enumValueMeanings)).map(\n ([value, meaning]) => `- \\`${value}\\`: ${meaning}`,\n );\n if (legend.length > 0) {\n const block = `Values:\\n${legend.join('\\n')}`;\n description = description.length > 0 ? `${description}\\n\\n${block}` : block;\n }\n }\n\n return description.length > 0 ? description : undefined;\n};\n\n/**\n * Enrich the TOP-LEVEL properties of an OpenAPI/JSON-Schema object fragment\n * (a `requestBodySchema` / `responseBodySchema` produced by\n * `convertZodSchemaToOpenApiSchema`) with authored field documentation.\n * Mutates `fragment` in place.\n *\n * Rules:\n * - Property `description` is composed from the field's docs ONLY when the\n * property has no existing `description` — a Zod `.describe()` authored on\n * the DTO field wins (it is closer to the wire contract).\n * - `x-enum-descriptions` (value → meaning) is set whenever enum meanings\n * exist, independent of the description-clobber rule, for machine consumers.\n *\n * Only top-level properties are walked: the field-spec extractor produces\n * specs for top-level schema fields only, so nested-object properties have no\n * authored docs to apply. No-op for non-object fragments (`null`, scalars).\n * Joins by EXACT property-name match — DTO property names that diverge from\n * the resource's field names (renamed / computed / backend-stripped fields)\n * are simply left undocumented rather than mis-documented.\n */\nexport const injectFieldDocsIntoJsonSchema = (\n fragment: unknown,\n fieldDocsByName: Record<string, FieldDoc>,\n): void => {\n if (!isPlainObject(fragment)) {\n return;\n }\n const properties = fragment['properties'];\n if (!isPlainObject(properties)) {\n return;\n }\n for (const [propertyName, rawPropertySchema] of Object.entries(properties)) {\n if (!isPlainObject(rawPropertySchema)) {\n continue;\n }\n const doc = fieldDocsByName[propertyName];\n if (doc === undefined) {\n continue;\n }\n if (rawPropertySchema['description'] === undefined) {\n const description = composeFieldDescription(doc);\n if (description !== undefined) {\n rawPropertySchema['description'] = description;\n }\n }\n if (doc.enumValueMeanings !== undefined) {\n const cleanedEnumMeanings = cleanEnumMeanings(doc.enumValueMeanings);\n if (Object.keys(cleanedEnumMeanings).length > 0) {\n rawPropertySchema['x-enum-descriptions'] = cleanedEnumMeanings;\n }\n }\n }\n};\n\n// ---------------------------------------------------------------------------\n// Tier 3 — operation HTTP documentation (response statuses / errors / examples)\n// ---------------------------------------------------------------------------\n\n/**\n * Projection-shaped HTTP documentation for one operation (Tier 3). Mirrors the\n * authored `ResourceOperationSpecification` HTTP fields, normalized to plain\n * JSON the OpenAPI generator emits as `responses` / `examples` / extensions.\n */\nexport interface OperationHttpDoc {\n responseStatuses?: Array<{ code: string; meaning: string }>;\n errorScenarios?: Array<{ code: string; when: string; errorCode?: string }>;\n idempotent?: boolean;\n rateLimitNote?: string;\n examples?: Array<{ title: string; request?: unknown; response?: unknown; forStatus?: string }>;\n}\n\nconst asNonEmptyString = (value: unknown): string | undefined =>\n typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n\n/**\n * Like `asNonEmptyString`, but for PROSE fields surfaced verbatim to API\n * consumers (response-status `meaning`, error-scenario `when`, example `title`,\n * `rateLimitNote`): additionally strips implementation-only noise (see\n * `stripImplementationNoise`) and returns `undefined` if the value is empty\n * before OR after cleaning. Do NOT use for code-like fields (`code`,\n * `errorCode`, `forStatus`) — those pass through untouched via `asNonEmptyString`.\n */\nconst asCleanProse = (value: unknown): string | undefined => {\n const raw = asNonEmptyString(value);\n if (raw === undefined) {\n return undefined;\n }\n const cleaned = stripImplementationNoise(raw);\n return cleaned.length > 0 ? cleaned : undefined;\n};\n\n/**\n * Extract + normalize the Tier-3 HTTP-documentation fields from a raw authored\n * operation spec into the projection shape. Variant examples are a complete\n * overlay: when `variants.<key>.examples` is present it replaces the base\n * operation examples, rather than merging a payload that may target a different\n * response schema. Defensive: a malformed entry is dropped rather than\n * poisoning the whole projection (the generator re-validates via Zod). Returns\n * `{}` when the operation authored no HTTP docs.\n *\n * Prose fields (`meaning` / `when` / `title` / `rateLimitNote`) are cleaned of\n * implementation-only noise via `asCleanProse`, consistent with the Tier-1\n * `description` and Tier-2 field descriptions. `code` / `forStatus` /\n * `errorCode` are `HttpResponseStatusCode` / error-code enum values whose\n * runtime string value (`'200'`, …) is the OpenAPI `responses` key / a machine\n * code, so they pass through untouched as strings.\n */\nexport const buildOperationHttpDocFromSpec = (operationSpec: unknown, variantKey?: string): OperationHttpDoc => {\n if (!isPlainObject(operationSpec)) {\n return {};\n }\n const doc: OperationHttpDoc = {};\n\n const responseStatuses = operationSpec['responseStatuses'];\n if (Array.isArray(responseStatuses)) {\n const normalized = responseStatuses\n .map((entry) => {\n if (!isPlainObject(entry)) return undefined;\n const code = asNonEmptyString(entry['code']);\n const meaning = asCleanProse(entry['meaning']);\n return code !== undefined && meaning !== undefined ? { code, meaning } : undefined;\n })\n .filter((entry): entry is { code: string; meaning: string } => entry !== undefined);\n if (normalized.length > 0) doc.responseStatuses = normalized;\n }\n\n const errorScenarios = operationSpec['errorScenarios'];\n if (Array.isArray(errorScenarios)) {\n const normalized = errorScenarios\n .map((entry) => {\n if (!isPlainObject(entry)) return undefined;\n const code = asNonEmptyString(entry['code']);\n const when = asCleanProse(entry['when']);\n if (code === undefined || when === undefined) return undefined;\n const scenario: { code: string; when: string; errorCode?: string } = { code, when };\n const errorCode = asNonEmptyString(entry['errorCode']);\n if (errorCode !== undefined) scenario.errorCode = errorCode;\n return scenario;\n })\n .filter((entry): entry is { code: string; when: string; errorCode?: string } => entry !== undefined);\n if (normalized.length > 0) doc.errorScenarios = normalized;\n }\n\n if (typeof operationSpec['idempotent'] === 'boolean') {\n doc.idempotent = operationSpec['idempotent'];\n }\n\n const rateLimitNote = asCleanProse(operationSpec['rateLimitNote']);\n if (rateLimitNote !== undefined) {\n doc.rateLimitNote = rateLimitNote;\n }\n\n const variants = operationSpec['variants'];\n const variant = isPlainObject(variants) ? variants[variantKey ?? 'default'] : undefined;\n // A present variant `examples` member deliberately suppresses the base even\n // when it is malformed or empty: falling back would publish a payload under a\n // schema the variant does not own. The normalizer below emits no example for\n // that invalid/empty override, which is safer than a misleading one.\n const examples =\n isPlainObject(variant) && Object.prototype.hasOwnProperty.call(variant, 'examples')\n ? variant['examples']\n : operationSpec['examples'];\n if (Array.isArray(examples)) {\n const normalized = examples\n .map((entry) => {\n if (!isPlainObject(entry)) return undefined;\n const title = asCleanProse(entry['title']);\n if (title === undefined) return undefined;\n const example: { title: string; request?: unknown; response?: unknown; forStatus?: string } = { title };\n if (entry['request'] !== undefined) example.request = entry['request'];\n if (entry['response'] !== undefined) example.response = entry['response'];\n const forStatus = asNonEmptyString(entry['forStatus']);\n if (forStatus !== undefined) example.forStatus = forStatus;\n return example;\n })\n .filter(\n (entry): entry is { title: string; request?: unknown; response?: unknown; forStatus?: string } =>\n entry !== undefined,\n );\n if (normalized.length > 0) doc.examples = normalized;\n }\n\n return doc;\n};\n"]}
@@ -0,0 +1,16 @@
1
+ import { TechnicalDocumentationAssetKind } from '@wildo-ai/saas-specifications/technical-documentation';
2
+ /**
3
+ * Encodes the complete semantic asset ref as a portable, case-fold-safe filename stem.
4
+ *
5
+ * Byte digests never participate: an asset keeps this path when its bytes change.
6
+ * Every non-lowercase-alphanumeric member is represented by a distinct token, so
7
+ * two valid namespaced refs cannot collapse to the same stem on Windows or macOS.
8
+ */
9
+ export declare function technicalDocumentationAssetFileStem(assetRefInput: string): string;
10
+ /** Canonical managed path for every asset kind implemented by the current reusable pipeline. */
11
+ export declare function technicalDocumentationCanonicalAssetRelativePath(input: {
12
+ readonly assetRef: string;
13
+ readonly kind: TechnicalDocumentationAssetKind;
14
+ readonly mediaType: string;
15
+ }): string;
16
+ //# sourceMappingURL=technical-documentation-asset-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-asset-path.d.ts","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-asset-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,+BAA+B,EAChC,MAAM,uDAAuD,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,gGAAgG;AAChG,wBAAgB,gDAAgD,CAAC,KAAK,EAAE;IACtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GAAG,MAAM,CAIT"}
@@ -0,0 +1,19 @@
1
+ import { technicalDocumentationCanonicalAssetRelativePathContract, technicalDocumentationAssetRefPortableStem, } from '@wildo-ai/saas-specifications/technical-documentation';
2
+ /**
3
+ * Encodes the complete semantic asset ref as a portable, case-fold-safe filename stem.
4
+ *
5
+ * Byte digests never participate: an asset keeps this path when its bytes change.
6
+ * Every non-lowercase-alphanumeric member is represented by a distinct token, so
7
+ * two valid namespaced refs cannot collapse to the same stem on Windows or macOS.
8
+ */
9
+ export function technicalDocumentationAssetFileStem(assetRefInput) {
10
+ return technicalDocumentationAssetRefPortableStem(assetRefInput);
11
+ }
12
+ /** Canonical managed path for every asset kind implemented by the current reusable pipeline. */
13
+ export function technicalDocumentationCanonicalAssetRelativePath(input) {
14
+ const path = technicalDocumentationCanonicalAssetRelativePathContract(input);
15
+ if (path !== null)
16
+ return path;
17
+ throw new Error(`technical-documentation asset path policy does not support ${input.kind}/${input.mediaType}`);
18
+ }
19
+ //# sourceMappingURL=technical-documentation-asset-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-asset-path.js","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-asset-path.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wDAAwD,EACxD,0CAA0C,GAE3C,MAAM,uDAAuD,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,UAAU,mCAAmC,CAAC,aAAqB;IACvE,OAAO,0CAA0C,CAAC,aAAa,CAAC,CAAC;AACnE,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,gDAAgD,CAAC,KAIhE;IACC,MAAM,IAAI,GAAG,wDAAwD,CAAC,KAAK,CAAC,CAAC;IAC7E,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,KAAK,CAAC,8DAA8D,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;AACjH,CAAC","sourcesContent":["import {\n technicalDocumentationCanonicalAssetRelativePathContract,\n technicalDocumentationAssetRefPortableStem,\n TechnicalDocumentationAssetKind,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\n/**\n * Encodes the complete semantic asset ref as a portable, case-fold-safe filename stem.\n *\n * Byte digests never participate: an asset keeps this path when its bytes change.\n * Every non-lowercase-alphanumeric member is represented by a distinct token, so\n * two valid namespaced refs cannot collapse to the same stem on Windows or macOS.\n */\nexport function technicalDocumentationAssetFileStem(assetRefInput: string): string {\n return technicalDocumentationAssetRefPortableStem(assetRefInput);\n}\n\n/** Canonical managed path for every asset kind implemented by the current reusable pipeline. */\nexport function technicalDocumentationCanonicalAssetRelativePath(input: {\n readonly assetRef: string;\n readonly kind: TechnicalDocumentationAssetKind;\n readonly mediaType: string;\n}): string {\n const path = technicalDocumentationCanonicalAssetRelativePathContract(input);\n if (path !== null) return path;\n throw new Error(`technical-documentation asset path policy does not support ${input.kind}/${input.mediaType}`);\n}\n\n"]}
@@ -0,0 +1,14 @@
1
+ import type { TechnicalDocumentationCaptureExecutionRequestV1, TechnicalDocumentationCaptureReceiptV1 } from '@wildo-ai/saas-specifications/technical-documentation';
2
+ /**
3
+ * Result returned before the application-owned publication transaction writes any byte. Keeping bytes in memory at this seam prevents the reusable capture
4
+ * implementation from choosing an application output root or bypassing complete-tree staging.
5
+ */
6
+ export interface TechnicalDocumentationCaptureExecutionResult {
7
+ readonly receipt: TechnicalDocumentationCaptureReceiptV1;
8
+ readonly outputBytes: Uint8Array;
9
+ }
10
+ /** Production-neutral execution port implemented by the Node-only Playwright capture package. */
11
+ export interface TechnicalDocumentationCaptureExecutionPort {
12
+ execute(request: TechnicalDocumentationCaptureExecutionRequestV1): Promise<TechnicalDocumentationCaptureExecutionResult>;
13
+ }
14
+ //# sourceMappingURL=technical-documentation-capture-execution-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-capture-execution-port.d.ts","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-capture-execution-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,+CAA+C,EAC/C,sCAAsC,EACvC,MAAM,uDAAuD,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,4CAA4C;IAC3D,QAAQ,CAAC,OAAO,EAAE,sCAAsC,CAAC;IACzD,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;CAClC;AAED,iGAAiG;AACjG,MAAM,WAAW,0CAA0C;IACzD,OAAO,CAAC,OAAO,EAAE,+CAA+C,GAAG,OAAO,CAAC,4CAA4C,CAAC,CAAC;CAC1H"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=technical-documentation-capture-execution-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-capture-execution-port.js","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-capture-execution-port.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n TechnicalDocumentationCaptureExecutionRequestV1,\n TechnicalDocumentationCaptureReceiptV1,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\n/**\n * Result returned before the application-owned publication transaction writes any byte. Keeping bytes in memory at this seam prevents the reusable capture\n * implementation from choosing an application output root or bypassing complete-tree staging.\n */\nexport interface TechnicalDocumentationCaptureExecutionResult {\n readonly receipt: TechnicalDocumentationCaptureReceiptV1;\n readonly outputBytes: Uint8Array;\n}\n\n/** Production-neutral execution port implemented by the Node-only Playwright capture package. */\nexport interface TechnicalDocumentationCaptureExecutionPort {\n execute(request: TechnicalDocumentationCaptureExecutionRequestV1): Promise<TechnicalDocumentationCaptureExecutionResult>;\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { type TechnicalDocumentationAssetRequestV1 } from '@wildo-ai/saas-specifications/technical-documentation';
2
+ export interface TechnicalDocumentationDiagramNode {
3
+ readonly nodeRef: string;
4
+ readonly label: string;
5
+ }
6
+ export interface TechnicalDocumentationDiagramEdge {
7
+ readonly fromNodeRef: string;
8
+ readonly toNodeRef: string;
9
+ readonly label: string;
10
+ }
11
+ export interface TechnicalDocumentationDiagramDefinition {
12
+ readonly diagramRef: string;
13
+ readonly title: string;
14
+ readonly nodes: readonly TechnicalDocumentationDiagramNode[];
15
+ readonly edges: readonly TechnicalDocumentationDiagramEdge[];
16
+ }
17
+ export declare const TECHNICAL_DOCUMENTATION_APPLICATION_REQUEST_DIAGRAM_V1: TechnicalDocumentationDiagramDefinition;
18
+ export declare function createTechnicalDocumentationApplicationRequestDiagramAssetRequest(): TechnicalDocumentationAssetRequestV1;
19
+ /** A dependency-free deterministic SVG materializer; no layout engine or runtime font metrics participate in its bytes. */
20
+ export declare function materializeTechnicalDocumentationDiagram(definition: TechnicalDocumentationDiagramDefinition): {
21
+ readonly bytes: Uint8Array;
22
+ readonly byteDigest: string;
23
+ readonly mediaType: 'image/svg+xml';
24
+ };
25
+ //# sourceMappingURL=technical-documentation-diagram-materializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-diagram-materializer.d.ts","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-diagram-materializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,oCAAoC,EAC1C,MAAM,uDAAuD,CAAC;AAI/D,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uCAAuC;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,iCAAiC,EAAE,CAAC;IAC7D,QAAQ,CAAC,KAAK,EAAE,SAAS,iCAAiC,EAAE,CAAC;CAC9D;AAED,eAAO,MAAM,sDAAsD,EAAE,uCAYnE,CAAC;AAEH,wBAAgB,iEAAiE,IAAI,oCAAoC,CAwBxH;AAMD,2HAA2H;AAC3H,wBAAgB,wCAAwC,CAAC,UAAU,EAAE,uCAAuC,GAAG;IAC7G,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;CACrC,CA+BA"}
@@ -0,0 +1,86 @@
1
+ import { TechnicalDocumentationAssetAccessClass, TechnicalDocumentationAssetKind, TechnicalDocumentationAssetOrigin, TechnicalDocumentationAssetRequiredness, TechnicalDocumentationAssetRightsBasis, } from '@wildo-ai/saas-specifications/technical-documentation';
2
+ import { technicalDocumentationSha256Bytes } from './rendering/technical-documentation-render-model.js';
3
+ export const TECHNICAL_DOCUMENTATION_APPLICATION_REQUEST_DIAGRAM_V1 = Object.freeze({
4
+ diagramRef: 'technical-documentation:diagram/application-api-request',
5
+ title: 'From client to application API',
6
+ nodes: Object.freeze([
7
+ { nodeRef: 'technical-documentation:diagram-node/client', label: 'Client or service' },
8
+ { nodeRef: 'technical-documentation:diagram-node/application-api', label: 'Application API' },
9
+ { nodeRef: 'technical-documentation:diagram-node/resource', label: 'Documented resource' },
10
+ ]),
11
+ edges: Object.freeze([
12
+ { fromNodeRef: 'technical-documentation:diagram-node/client', toNodeRef: 'technical-documentation:diagram-node/application-api', label: 'Authenticated request' },
13
+ { fromNodeRef: 'technical-documentation:diagram-node/application-api', toNodeRef: 'technical-documentation:diagram-node/resource', label: 'Authorized operation' },
14
+ ]),
15
+ });
16
+ export function createTechnicalDocumentationApplicationRequestDiagramAssetRequest() {
17
+ return {
18
+ assetRef: 'technical-documentation:asset/application-api-request-diagram',
19
+ kind: TechnicalDocumentationAssetKind.DIAGRAM,
20
+ origin: TechnicalDocumentationAssetOrigin.DETERMINISTIC_RENDER,
21
+ accessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,
22
+ requiredness: TechnicalDocumentationAssetRequiredness.REQUIRED,
23
+ rights: {
24
+ basis: TechnicalDocumentationAssetRightsBasis.ENGINE_AUTHORED,
25
+ authorityRef: 'technical-documentation:rights/engine-authored-diagram',
26
+ },
27
+ sourceAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,
28
+ viewedTenantAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,
29
+ roleAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,
30
+ captureOrRenderProfileRef: 'technical-documentation:renderer/deterministic-svg-v1',
31
+ /*
32
+ * The definition this diagram depicts, NAMED. It was a sha256 of the definition until
33
+ * 2026-08-27 — computed here by calling the materializer, then compared by the pilot against
34
+ * the value the same materializer returned over the same frozen constant. A value compared
35
+ * with itself.
36
+ */
37
+ semanticInputRef: TECHNICAL_DOCUMENTATION_APPLICATION_REQUEST_DIAGRAM_V1.diagramRef,
38
+ altText: 'A client sends an authenticated request through the application API to a documented resource.',
39
+ };
40
+ }
41
+ function escapeXml(value) {
42
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
43
+ }
44
+ /** A dependency-free deterministic SVG materializer; no layout engine or runtime font metrics participate in its bytes. */
45
+ export function materializeTechnicalDocumentationDiagram(definition) {
46
+ if (definition.nodes.length < 2)
47
+ throw new Error('technical-documentation diagram requires at least two nodes');
48
+ const nodes = [...definition.nodes].sort((left, right) => left.nodeRef.localeCompare(right.nodeRef));
49
+ const nodeRefs = nodes.map((node) => node.nodeRef);
50
+ if (new Set(nodeRefs).size !== nodeRefs.length)
51
+ throw new Error('technical-documentation diagram node refs must be unique');
52
+ const nodeRefSet = new Set(nodeRefs);
53
+ const edges = [...definition.edges].sort((left, right) => `${left.fromNodeRef}\0${left.toNodeRef}\0${left.label}`.localeCompare(`${right.fromNodeRef}\0${right.toNodeRef}\0${right.label}`));
54
+ for (const edge of edges) {
55
+ if (!nodeRefSet.has(edge.fromNodeRef) || !nodeRefSet.has(edge.toNodeRef))
56
+ throw new Error('technical-documentation diagram edge references an unknown node');
57
+ }
58
+ const width = 960;
59
+ const nodeWidth = 180;
60
+ const gap = (width - nodes.length * nodeWidth) / (nodes.length + 1);
61
+ const xByRef = new Map(nodes.map((node, index) => [node.nodeRef, Math.round(gap + index * (nodeWidth + gap))]));
62
+ const nodeSvg = nodes.map((node) => {
63
+ const x = xByRef.get(node.nodeRef);
64
+ return `<g><rect x="${x}" y="96" width="${nodeWidth}" height="72" rx="12" fill="#f7f8fb" stroke="#263247" stroke-width="2"/><text x="${x + nodeWidth / 2}" y="138" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#172033">${escapeXml(node.label)}</text></g>`;
65
+ }).join('');
66
+ const edgeSvg = edges.map((edge, index) => {
67
+ const fromX = xByRef.get(edge.fromNodeRef) + nodeWidth;
68
+ const toX = xByRef.get(edge.toNodeRef);
69
+ const y = 112 + index * 14;
70
+ return `<g><path d="M ${fromX} ${y} L ${toX} ${y}" stroke="#54657e" stroke-width="2" marker-end="url(#arrow)"/><text x="${Math.round((fromX + toX) / 2)}" y="${y - 6}" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#34445d">${escapeXml(edge.label)}</text></g>`;
71
+ }).join('');
72
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="240" viewBox="0 0 ${width} 240" role="img" aria-labelledby="title"><title id="title">${escapeXml(definition.title)}</title><defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#54657e"/></marker></defs><text x="480" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="22" font-weight="600" fill="#172033">${escapeXml(definition.title)}</text>${edgeSvg}${nodeSvg}</svg>\n`;
73
+ const bytes = Buffer.from(svg, 'utf8');
74
+ return Object.freeze({
75
+ bytes,
76
+ byteDigest: technicalDocumentationSha256Bytes(bytes),
77
+ mediaType: 'image/svg+xml',
78
+ });
79
+ }
80
+ /*
81
+ * `technicalDocumentationDiagramProducerImplementationDigest()` lived here until 2026-08-27. It
82
+ * hashed `escapeXml.toString()` and this materializer's own source text, and rode into every
83
+ * resolved asset as `producerImplementationDigest`. Nothing compared it; the producer is named by
84
+ * `producerRef` + `producerVersion`, which a reader can act on.
85
+ */
86
+ //# sourceMappingURL=technical-documentation-diagram-materializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"technical-documentation-diagram-materializer.js","sourceRoot":"","sources":["../../../../src/companion/technical-documentation-diagram-materializer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n TechnicalDocumentationAssetAccessClass,\n TechnicalDocumentationAssetKind,\n TechnicalDocumentationAssetOrigin,\n TechnicalDocumentationAssetRequiredness,\n TechnicalDocumentationAssetRightsBasis,\n type TechnicalDocumentationAssetRequestV1,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\nimport { technicalDocumentationSha256Bytes } from './rendering/technical-documentation-render-model';\n\nexport interface TechnicalDocumentationDiagramNode {\n readonly nodeRef: string;\n readonly label: string;\n}\n\nexport interface TechnicalDocumentationDiagramEdge {\n readonly fromNodeRef: string;\n readonly toNodeRef: string;\n readonly label: string;\n}\n\nexport interface TechnicalDocumentationDiagramDefinition {\n readonly diagramRef: string;\n readonly title: string;\n readonly nodes: readonly TechnicalDocumentationDiagramNode[];\n readonly edges: readonly TechnicalDocumentationDiagramEdge[];\n}\n\nexport const TECHNICAL_DOCUMENTATION_APPLICATION_REQUEST_DIAGRAM_V1: TechnicalDocumentationDiagramDefinition = Object.freeze({\n diagramRef: 'technical-documentation:diagram/application-api-request',\n title: 'From client to application API',\n nodes: Object.freeze([\n { nodeRef: 'technical-documentation:diagram-node/client', label: 'Client or service' },\n { nodeRef: 'technical-documentation:diagram-node/application-api', label: 'Application API' },\n { nodeRef: 'technical-documentation:diagram-node/resource', label: 'Documented resource' },\n ]),\n edges: Object.freeze([\n { fromNodeRef: 'technical-documentation:diagram-node/client', toNodeRef: 'technical-documentation:diagram-node/application-api', label: 'Authenticated request' },\n { fromNodeRef: 'technical-documentation:diagram-node/application-api', toNodeRef: 'technical-documentation:diagram-node/resource', label: 'Authorized operation' },\n ]),\n});\n\nexport function createTechnicalDocumentationApplicationRequestDiagramAssetRequest(): TechnicalDocumentationAssetRequestV1 {\n return {\n assetRef: 'technical-documentation:asset/application-api-request-diagram',\n kind: TechnicalDocumentationAssetKind.DIAGRAM,\n origin: TechnicalDocumentationAssetOrigin.DETERMINISTIC_RENDER,\n accessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,\n requiredness: TechnicalDocumentationAssetRequiredness.REQUIRED,\n rights: {\n basis: TechnicalDocumentationAssetRightsBasis.ENGINE_AUTHORED,\n authorityRef: 'technical-documentation:rights/engine-authored-diagram',\n },\n sourceAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,\n viewedTenantAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,\n roleAccessClass: TechnicalDocumentationAssetAccessClass.PUBLIC,\n captureOrRenderProfileRef: 'technical-documentation:renderer/deterministic-svg-v1',\n /*\n * The definition this diagram depicts, NAMED. It was a sha256 of the definition until\n * 2026-08-27 — computed here by calling the materializer, then compared by the pilot against\n * the value the same materializer returned over the same frozen constant. A value compared\n * with itself.\n */\n semanticInputRef: TECHNICAL_DOCUMENTATION_APPLICATION_REQUEST_DIAGRAM_V1.diagramRef,\n altText: 'A client sends an authenticated request through the application API to a documented resource.',\n };\n}\n\nfunction escapeXml(value: string): string {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('\"', '&quot;').replaceAll(\"'\", '&apos;');\n}\n\n/** A dependency-free deterministic SVG materializer; no layout engine or runtime font metrics participate in its bytes. */\nexport function materializeTechnicalDocumentationDiagram(definition: TechnicalDocumentationDiagramDefinition): {\n readonly bytes: Uint8Array;\n readonly byteDigest: string;\n readonly mediaType: 'image/svg+xml';\n} {\n if (definition.nodes.length < 2) throw new Error('technical-documentation diagram requires at least two nodes');\n const nodes = [...definition.nodes].sort((left, right) => left.nodeRef.localeCompare(right.nodeRef));\n const nodeRefs = nodes.map((node) => node.nodeRef);\n if (new Set(nodeRefs).size !== nodeRefs.length) throw new Error('technical-documentation diagram node refs must be unique');\n const nodeRefSet = new Set(nodeRefs);\n const edges = [...definition.edges].sort((left, right) => `${left.fromNodeRef}\\0${left.toNodeRef}\\0${left.label}`.localeCompare(`${right.fromNodeRef}\\0${right.toNodeRef}\\0${right.label}`));\n for (const edge of edges) {\n if (!nodeRefSet.has(edge.fromNodeRef) || !nodeRefSet.has(edge.toNodeRef)) throw new Error('technical-documentation diagram edge references an unknown node');\n }\n const width = 960;\n const nodeWidth = 180;\n const gap = (width - nodes.length * nodeWidth) / (nodes.length + 1);\n const xByRef = new Map(nodes.map((node, index) => [node.nodeRef, Math.round(gap + index * (nodeWidth + gap))]));\n const nodeSvg = nodes.map((node) => {\n const x = xByRef.get(node.nodeRef) as number;\n return `<g><rect x=\"${x}\" y=\"96\" width=\"${nodeWidth}\" height=\"72\" rx=\"12\" fill=\"#f7f8fb\" stroke=\"#263247\" stroke-width=\"2\"/><text x=\"${x + nodeWidth / 2}\" y=\"138\" text-anchor=\"middle\" font-family=\"system-ui,sans-serif\" font-size=\"16\" fill=\"#172033\">${escapeXml(node.label)}</text></g>`;\n }).join('');\n const edgeSvg = edges.map((edge, index) => {\n const fromX = (xByRef.get(edge.fromNodeRef) as number) + nodeWidth;\n const toX = xByRef.get(edge.toNodeRef) as number;\n const y = 112 + index * 14;\n return `<g><path d=\"M ${fromX} ${y} L ${toX} ${y}\" stroke=\"#54657e\" stroke-width=\"2\" marker-end=\"url(#arrow)\"/><text x=\"${Math.round((fromX + toX) / 2)}\" y=\"${y - 6}\" text-anchor=\"middle\" font-family=\"system-ui,sans-serif\" font-size=\"12\" fill=\"#34445d\">${escapeXml(edge.label)}</text></g>`;\n }).join('');\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"240\" viewBox=\"0 0 ${width} 240\" role=\"img\" aria-labelledby=\"title\"><title id=\"title\">${escapeXml(definition.title)}</title><defs><marker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"7\" refY=\"4\" orient=\"auto\"><path d=\"M0,0 L8,4 L0,8 z\" fill=\"#54657e\"/></marker></defs><text x=\"480\" y=\"42\" text-anchor=\"middle\" font-family=\"system-ui,sans-serif\" font-size=\"22\" font-weight=\"600\" fill=\"#172033\">${escapeXml(definition.title)}</text>${edgeSvg}${nodeSvg}</svg>\\n`;\n const bytes = Buffer.from(svg, 'utf8');\n return Object.freeze({\n bytes,\n byteDigest: technicalDocumentationSha256Bytes(bytes),\n mediaType: 'image/svg+xml' as const,\n });\n}\n\n/*\n * `technicalDocumentationDiagramProducerImplementationDigest()` lived here until 2026-08-27. It\n * hashed `escapeXml.toString()` and this materializer's own source text, and rode into every\n * resolved asset as `producerImplementationDigest`. Nothing compared it; the producer is named by\n * `producerRef` + `producerVersion`, which a reader can act on.\n */\n"]}