@warpgogol/werkstatt-shared 0.2.0

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 (403) hide show
  1. package/package.json +352 -0
  2. package/src/checks/index.ts +24 -0
  3. package/src/checks/lib/astro-site-url.ts +30 -0
  4. package/src/checks/lib/i18n.ts +30 -0
  5. package/src/checks/result-helpers.ts +102 -0
  6. package/src/checks/suppressions-config.ts +195 -0
  7. package/src/index.ts +14 -0
  8. package/src/integration/README.md +38 -0
  9. package/src/integration/adapters.ts +143 -0
  10. package/src/integration/crm-buffer.ts +491 -0
  11. package/src/integration/dispatch.ts +106 -0
  12. package/src/integration/funnel.ts +379 -0
  13. package/src/integration/index.ts +56 -0
  14. package/src/integration/lifecycle.ts +155 -0
  15. package/src/integration/orchestration.ts +439 -0
  16. package/src/integration/port-barrel.ts +124 -0
  17. package/src/integration/port.ts +143 -0
  18. package/src/integration/qstash.ts +134 -0
  19. package/src/integration/sharding.ts +133 -0
  20. package/src/integration/tests/deliver-event.test.ts +52 -0
  21. package/src/integration/tests/dispatch.test.ts +88 -0
  22. package/src/integration/tests/funnel-bridge.test.ts +46 -0
  23. package/src/integration/tests/hub-runtime.test.ts +128 -0
  24. package/src/integration/tests/lifecycle.test.ts +69 -0
  25. package/src/integration/tests/qstash.test.ts +83 -0
  26. package/src/integration/tests/sharding.test.ts +64 -0
  27. package/src/integration/vitest.config.ts +8 -0
  28. package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
  29. package/src/integration-adapter-supabase-crm/client.ts +605 -0
  30. package/src/integration-adapter-supabase-crm/index.ts +28 -0
  31. package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
  32. package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
  33. package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
  34. package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
  35. package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
  36. package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
  37. package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
  38. package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
  39. package/src/integration-adapter-supabase-crm/worker.ts +223 -0
  40. package/src/observability/conventions.ts +93 -0
  41. package/src/observability/env-resolver.ts +57 -0
  42. package/src/observability/index.ts +57 -0
  43. package/src/observability/metric-registry.ts +184 -0
  44. package/src/observability/otlp-converter.ts +125 -0
  45. package/src/observability/otlp-json.ts +135 -0
  46. package/src/observability/otlp-transport.ts +68 -0
  47. package/src/observability/pusher.ts +142 -0
  48. package/src/observability/redact.ts +27 -0
  49. package/src/observability/tests/conventions.test.ts +100 -0
  50. package/src/observability/tests/metric-registry.test.ts +117 -0
  51. package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
  52. package/src/observability/tests/otlp-converter.test.ts +157 -0
  53. package/src/observability/tests/otlp-json.test.ts +120 -0
  54. package/src/observability/tests/pusher.test.ts +75 -0
  55. package/src/observability/tests/redact.pbt.test.ts +71 -0
  56. package/src/observability/tests/redact.test.ts +37 -0
  57. package/src/observability/typed-refs.ts +137 -0
  58. package/src/observability/vitest.config.ts +8 -0
  59. package/src/ontology/analytics/matomo-binding.yaml +172 -0
  60. package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
  61. package/src/ontology/analytics/messkanon.yaml +151 -0
  62. package/src/ontology/archetype-registry.ts +61 -0
  63. package/src/ontology/archetypes/components/brand-label.yaml +19 -0
  64. package/src/ontology/archetypes/components/copyright.yaml +19 -0
  65. package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
  66. package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
  67. package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
  68. package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
  69. package/src/ontology/archetypes/components/layout.yaml +19 -0
  70. package/src/ontology/archetypes/components/live-photo.yaml +20 -0
  71. package/src/ontology/archetypes/components/material-credit.yaml +20 -0
  72. package/src/ontology/archetypes/components/media.yaml +21 -0
  73. package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
  74. package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
  75. package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
  76. package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
  77. package/src/ontology/archetypes/components/not-found.yaml +22 -0
  78. package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
  79. package/src/ontology/archetypes/components/passport-header.yaml +19 -0
  80. package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
  81. package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
  82. package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
  83. package/src/ontology/archetypes/components/person-profile.yaml +20 -0
  84. package/src/ontology/archetypes/components/pulsar.yaml +20 -0
  85. package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
  86. package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
  87. package/src/ontology/archetypes/components/social-meta.yaml +20 -0
  88. package/src/ontology/archetypes/components/structured-data.yaml +19 -0
  89. package/src/ontology/archetypes/index.json +1003 -0
  90. package/src/ontology/archetypes/index.yaml +821 -0
  91. package/src/ontology/archetypes/sections/approach.yaml +23 -0
  92. package/src/ontology/archetypes/sections/article-list.yaml +30 -0
  93. package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
  94. package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
  95. package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
  96. package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
  97. package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
  98. package/src/ontology/archetypes/sections/credits.yaml +29 -0
  99. package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
  100. package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
  101. package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
  102. package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
  103. package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
  104. package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
  105. package/src/ontology/archetypes/sections/hero.yaml +50 -0
  106. package/src/ontology/archetypes/sections/impact.yaml +23 -0
  107. package/src/ontology/archetypes/sections/markdown.yaml +43 -0
  108. package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
  109. package/src/ontology/archetypes/sections/navigation.yaml +20 -0
  110. package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
  111. package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
  112. package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
  113. package/src/ontology/archetypes/sections/people.yaml +48 -0
  114. package/src/ontology/archetypes/sections/price-card.yaml +50 -0
  115. package/src/ontology/archetypes/sections/problem.yaml +23 -0
  116. package/src/ontology/archetypes/sections/send-message.yaml +63 -0
  117. package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
  118. package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
  119. package/src/ontology/archetypes/sections/transparency.yaml +32 -0
  120. package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
  121. package/src/ontology/archetypes/sections/video-section.yaml +27 -0
  122. package/src/ontology/archetypes/sections/women.yaml +29 -0
  123. package/src/ontology/biome-token-projection.ts +229 -0
  124. package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
  125. package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
  126. package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
  127. package/src/ontology/blueprints/offer.yaml +39 -0
  128. package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
  129. package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
  130. package/src/ontology/blueprints/ratgeber.yaml +62 -0
  131. package/src/ontology/blueprints/website-local.yaml +309 -0
  132. package/src/ontology/blueprints/website-service.yaml +84 -0
  133. package/src/ontology/capabilities/lead.submit.yaml +44 -0
  134. package/src/ontology/cosmic/index.ts +48 -0
  135. package/src/ontology/cosmic/moon-catalog.ts +173 -0
  136. package/src/ontology/cosmic/planet-catalog.ts +82 -0
  137. package/src/ontology/cosmic/star-catalog.ts +337 -0
  138. package/src/ontology/enums.ts +159 -0
  139. package/src/ontology/external-surfaces/index.ts +158 -0
  140. package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
  141. package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
  142. package/src/ontology/external-surfaces/url-schema.yaml +95 -0
  143. package/src/ontology/growth/events/contact-email-click.yaml +21 -0
  144. package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
  145. package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
  146. package/src/ontology/growth/events/contact-route-click.yaml +21 -0
  147. package/src/ontology/growth/events/contact-submit.yaml +28 -0
  148. package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
  149. package/src/ontology/growth/events/cta-click.yaml +30 -0
  150. package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
  151. package/src/ontology/growth/events/donation-form-start.yaml +21 -0
  152. package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
  153. package/src/ontology/growth/events/donation-intent.yaml +29 -0
  154. package/src/ontology/growth/events/form-error.yaml +27 -0
  155. package/src/ontology/growth/events/form-start.yaml +21 -0
  156. package/src/ontology/growth/events/form-submit.yaml +21 -0
  157. package/src/ontology/growth/events/outbound-click.yaml +25 -0
  158. package/src/ontology/growth/events/page-view.yaml +24 -0
  159. package/src/ontology/growth/events/passport-view.yaml +28 -0
  160. package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
  161. package/src/ontology/index.ts +165 -0
  162. package/src/ontology/manifest.ts +344 -0
  163. package/src/ontology/operations/index.ts +1 -0
  164. package/src/ontology/schemas/biome.ts +303 -0
  165. package/src/ontology/schemas/capability.ts +104 -0
  166. package/src/ontology/schemas/constellation.ts +114 -0
  167. package/src/ontology/schemas/dns-records.ts +53 -0
  168. package/src/ontology/schemas/index.ts +95 -0
  169. package/src/ontology/schemas/manifest-resolver.ts +123 -0
  170. package/src/ontology/schemas/page-entry.ts +213 -0
  171. package/src/ontology/schemas/section-archetype.ts +109 -0
  172. package/src/ontology/schemas/site-family.ts +98 -0
  173. package/src/ontology/schemas/system/growth.ts +80 -0
  174. package/src/ontology/schemas/system/integrations.ts +143 -0
  175. package/src/ontology/schemas/system/manifest.ts +492 -0
  176. package/src/ontology/schemas/system/page-output.ts +99 -0
  177. package/src/ontology/schemas/system/release.ts +58 -0
  178. package/src/ontology/schemas/system/text.ts +58 -0
  179. package/src/ontology/schemas/system.ts +53 -0
  180. package/src/ontology/shared-section-props/body-fragments.ts +182 -0
  181. package/src/ontology/shared-section-props/catalog.ts +195 -0
  182. package/src/ontology/shared-section-props/common.ts +178 -0
  183. package/src/ontology/shared-section-props/index.ts +34 -0
  184. package/src/ontology/shared-section-props/visual-header.ts +163 -0
  185. package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
  186. package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
  187. package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
  188. package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
  189. package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
  190. package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
  191. package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
  192. package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
  193. package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
  194. package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
  195. package/src/ontology/tests/enums.test.ts +203 -0
  196. package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
  197. package/src/ontology/vitest.config.ts +8 -0
  198. package/src/passport/dht-sign.test.ts +99 -0
  199. package/src/passport/dht-sign.ts +73 -0
  200. package/src/passport/identity-sign.test.ts +134 -0
  201. package/src/passport/identity-sign.ts +85 -0
  202. package/src/passport/index.ts +53 -0
  203. package/src/passport/key-rotate.ts +133 -0
  204. package/src/passport/schema.ts +198 -0
  205. package/src/passport/sign.ts +225 -0
  206. package/src/passport/tests/schema.pbt.test.ts +93 -0
  207. package/src/passport/tests/schema.test.ts +222 -0
  208. package/src/passport/vitest.config.ts +8 -0
  209. package/src/share/agent/api-catalog.ts +77 -0
  210. package/src/share/agent/capability.ts +68 -0
  211. package/src/share/agent/dns-aid.test.ts +74 -0
  212. package/src/share/agent/dns-aid.ts +53 -0
  213. package/src/share/agent/fleet-catalog.ts +149 -0
  214. package/src/share/agent/index.ts +29 -0
  215. package/src/share/agent/knowledge.ts +96 -0
  216. package/src/share/agent/manifest.ts +126 -0
  217. package/src/share/agent/mcp-card.ts +63 -0
  218. package/src/share/agent/omit-empty.ts +77 -0
  219. package/src/share/agent/openapi.ts +157 -0
  220. package/src/share/agent/proof.ts +61 -0
  221. package/src/share/attribution-display.ts +86 -0
  222. package/src/share/check-hints.ts +38 -0
  223. package/src/share/content/entity-id.ts +65 -0
  224. package/src/share/content/index.ts +16 -0
  225. package/src/share/content/merge.ts +86 -0
  226. package/src/share/content/resolve-field-path.ts +39 -0
  227. package/src/share/content/substitute-deep.ts +47 -0
  228. package/src/share/content-discipline/index.ts +14 -0
  229. package/src/share/content-discipline/parsers.ts +66 -0
  230. package/src/share/content-discipline/types.ts +118 -0
  231. package/src/share/content-reference.ts +333 -0
  232. package/src/share/counter-utils.ts +75 -0
  233. package/src/share/css-value-normalize.ts +48 -0
  234. package/src/share/dev-props-validator.ts +131 -0
  235. package/src/share/entitlement.ts +172 -0
  236. package/src/share/env.d.ts +73 -0
  237. package/src/share/feature-policy.ts +367 -0
  238. package/src/share/formula-eval.ts +364 -0
  239. package/src/share/fs/index.ts +96 -0
  240. package/src/share/i18n/localization.ts +57 -0
  241. package/src/share/image-provider.ts +314 -0
  242. package/src/share/index.ts +86 -0
  243. package/src/share/knowledge/claim.ts +129 -0
  244. package/src/share/knowledge/derivation.ts +55 -0
  245. package/src/share/knowledge/freshness.ts +127 -0
  246. package/src/share/knowledge/index.ts +23 -0
  247. package/src/share/knowledge/ledger.ts +222 -0
  248. package/src/share/knowledge/plan.ts +122 -0
  249. package/src/share/knowledge/source.ts +170 -0
  250. package/src/share/legal/translation-policy.ts +133 -0
  251. package/src/share/material-credits.ts +181 -0
  252. package/src/share/middleware/language-redirect.ts +49 -0
  253. package/src/share/offer-capacity.ts +149 -0
  254. package/src/share/onboarding-yaml/index.ts +162 -0
  255. package/src/share/page.ts +351 -0
  256. package/src/share/redirects.test.ts +62 -0
  257. package/src/share/redirects.ts +54 -0
  258. package/src/share/rfc0042-utils.ts +95 -0
  259. package/src/share/runtime-context.ts +70 -0
  260. package/src/share/schemas/claim-records.ts +69 -0
  261. package/src/share/schemas/claims.ts +150 -0
  262. package/src/share/schemas/effects.ts +212 -0
  263. package/src/share/schemas/features.ts +147 -0
  264. package/src/share/schemas/horizontal-align.ts +22 -0
  265. package/src/share/schemas/icon-color.ts +27 -0
  266. package/src/share/schemas/index.ts +43 -0
  267. package/src/share/schemas/live-photo.ts +53 -0
  268. package/src/share/schemas/material-credit.ts +292 -0
  269. package/src/share/schemas/media.ts +220 -0
  270. package/src/share/schemas/navigation.ts +74 -0
  271. package/src/share/schemas/page-base.ts +33 -0
  272. package/src/share/schemas/participant.ts +247 -0
  273. package/src/share/schemas/person.ts +24 -0
  274. package/src/share/schemas/print.ts +93 -0
  275. package/src/share/schemas/section-background.ts +106 -0
  276. package/src/share/schemas/section-body.ts +138 -0
  277. package/src/share/schemas/section-cards.ts +37 -0
  278. package/src/share/schemas/section-cta.ts +74 -0
  279. package/src/share/schemas/section-header.ts +77 -0
  280. package/src/share/schemas/section-image.ts +77 -0
  281. package/src/share/schemas/section-motion.ts +52 -0
  282. package/src/share/schemas/section-shell.ts +63 -0
  283. package/src/share/schemas/section-stats.ts +37 -0
  284. package/src/share/schemas/site-background.ts +78 -0
  285. package/src/share/schemas/standard-list-item.ts +38 -0
  286. package/src/share/scripts/external-links.ts +45 -0
  287. package/src/share/scripts/gsap-counter.ts +168 -0
  288. package/src/share/scripts/gsap-mountain-journey.ts +236 -0
  289. package/src/share/scripts/gsap-parallax.ts +50 -0
  290. package/src/share/scripts/gsap-reveal.ts +65 -0
  291. package/src/share/scripts/gsap-stagger.ts +54 -0
  292. package/src/share/scripts/index.ts +25 -0
  293. package/src/share/scripts/inline-number-animation.ts +120 -0
  294. package/src/share/scripts/lenis.ts +109 -0
  295. package/src/share/scripts/live-photos.ts +250 -0
  296. package/src/share/scripts/lordicon.ts +94 -0
  297. package/src/share/scripts/orchestrator.ts +138 -0
  298. package/src/share/scripts/scheduler.ts +53 -0
  299. package/src/share/scripts/video-player.ts +116 -0
  300. package/src/share/semantic/ai.ts +140 -0
  301. package/src/share/semantic/block-extraction.ts +52 -0
  302. package/src/share/semantic/block-extractors/index.ts +502 -0
  303. package/src/share/semantic/breadcrumbs.ts +120 -0
  304. package/src/share/semantic/build-page.ts +309 -0
  305. package/src/share/semantic/business-projection.ts +278 -0
  306. package/src/share/semantic/derived-prices-loader.ts +36 -0
  307. package/src/share/semantic/extract.ts +118 -0
  308. package/src/share/semantic/feed.ts +124 -0
  309. package/src/share/semantic/ids.ts +76 -0
  310. package/src/share/semantic/image-sitemap.ts +153 -0
  311. package/src/share/semantic/index.ts +36 -0
  312. package/src/share/semantic/jsonld/article.ts +47 -0
  313. package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
  314. package/src/share/semantic/jsonld/collection-list.ts +30 -0
  315. package/src/share/semantic/jsonld/context.ts +46 -0
  316. package/src/share/semantic/jsonld/faq.ts +49 -0
  317. package/src/share/semantic/jsonld/initiative.ts +53 -0
  318. package/src/share/semantic/jsonld/organization.ts +101 -0
  319. package/src/share/semantic/jsonld/person.ts +71 -0
  320. package/src/share/semantic/jsonld/service.ts +80 -0
  321. package/src/share/semantic/jsonld/shared.ts +31 -0
  322. package/src/share/semantic/jsonld/types.ts +18 -0
  323. package/src/share/semantic/jsonld/webpage.ts +98 -0
  324. package/src/share/semantic/jsonld/website.ts +27 -0
  325. package/src/share/semantic/jsonld.ts +75 -0
  326. package/src/share/semantic/llms-policy.ts +74 -0
  327. package/src/share/semantic/llms.ts +394 -0
  328. package/src/share/semantic/markdown-hygiene.ts +68 -0
  329. package/src/share/semantic/markdown-twin-provenance.ts +242 -0
  330. package/src/share/semantic/models.ts +402 -0
  331. package/src/share/semantic/organization-profile.ts +160 -0
  332. package/src/share/semantic/output-projection.ts +131 -0
  333. package/src/share/semantic/page-builders/markdown-page.ts +84 -0
  334. package/src/share/semantic/page-markdown.ts +234 -0
  335. package/src/share/semantic/page-utils.ts +125 -0
  336. package/src/share/semantic/price-marker-resolver.ts +92 -0
  337. package/src/share/semantic/robots.ts +127 -0
  338. package/src/share/semantic/update-stamp.ts +136 -0
  339. package/src/share/shared-context.ts +177 -0
  340. package/src/share/string-utils.ts +19 -0
  341. package/src/share/tests/api-catalog.test.ts +97 -0
  342. package/src/share/tests/breadcrumbs.test.ts +126 -0
  343. package/src/share/tests/build-page-price-markers.test.ts +160 -0
  344. package/src/share/tests/capability.test.ts +107 -0
  345. package/src/share/tests/claim.test.ts +90 -0
  346. package/src/share/tests/content-ref-index.test.ts +328 -0
  347. package/src/share/tests/create-dev-props-validator.test.ts +38 -0
  348. package/src/share/tests/derivation.test.ts +39 -0
  349. package/src/share/tests/dev-props-validator.test.ts +88 -0
  350. package/src/share/tests/fleet-catalog.test.ts +134 -0
  351. package/src/share/tests/formula-eval.test.ts +619 -0
  352. package/src/share/tests/freshness.test.ts +60 -0
  353. package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
  354. package/src/share/tests/image-sitemap.test.ts +101 -0
  355. package/src/share/tests/jsonld-service.test.ts +282 -0
  356. package/src/share/tests/knowledge.test.ts +86 -0
  357. package/src/share/tests/ledger.test.ts +125 -0
  358. package/src/share/tests/llms-0789.test.ts +63 -0
  359. package/src/share/tests/manifest.test.ts +100 -0
  360. package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
  361. package/src/share/tests/mcp-card.test.ts +57 -0
  362. package/src/share/tests/onboarding-yaml.test.ts +111 -0
  363. package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
  364. package/src/share/tests/openapi.test.ts +116 -0
  365. package/src/share/tests/organization-jsonld.test.ts +74 -0
  366. package/src/share/tests/output-projection.test.ts +61 -0
  367. package/src/share/tests/plan.test.ts +97 -0
  368. package/src/share/tests/price-marker-resolver.test.ts +141 -0
  369. package/src/share/tests/proof.test.ts +72 -0
  370. package/src/share/tests/resolve-field-path.test.ts +52 -0
  371. package/src/share/tests/source.test.ts +81 -0
  372. package/src/share/tests/substitute-deep.test.ts +65 -0
  373. package/src/share/tests/text-normalize.pbt.test.ts +143 -0
  374. package/src/share/tests/text-normalize.test.ts +285 -0
  375. package/src/share/tests/twin-path.test.ts +43 -0
  376. package/src/share/text-normalize.ts +548 -0
  377. package/src/share/text-position.ts +19 -0
  378. package/src/share/types/hls-js-light.d.ts +16 -0
  379. package/src/share/visibility.ts +167 -0
  380. package/src/share/vitest.config.ts +8 -0
  381. package/src/share/wrap-inline-numbers.ts +139 -0
  382. package/src/surface/blueprint-schema.ts +314 -0
  383. package/src/surface/blueprint-types.ts +256 -0
  384. package/src/surface/blueprint.ts +300 -0
  385. package/src/surface/breaker.ts +57 -0
  386. package/src/surface/decision-composer.ts +156 -0
  387. package/src/surface/eligibility.ts +372 -0
  388. package/src/surface/evidence-records.ts +97 -0
  389. package/src/surface/fleet.ts +60 -0
  390. package/src/surface/geo.ts +170 -0
  391. package/src/surface/governance.ts +146 -0
  392. package/src/surface/index.ts +216 -0
  393. package/src/surface/io/index.ts +18 -0
  394. package/src/surface/io/surface-module-context-io.ts +60 -0
  395. package/src/surface/io/visibility-outcomes-io.ts +46 -0
  396. package/src/surface/module-context.ts +120 -0
  397. package/src/surface/substance.ts +161 -0
  398. package/src/surface/tests/eligibility.pbt.test.ts +137 -0
  399. package/src/surface/tests/eligibility.test.ts +302 -0
  400. package/src/surface/types.ts +289 -0
  401. package/src/surface/visibility.ts +57 -0
  402. package/src/surface/vitest.config.ts +8 -0
  403. package/tsconfig.json +17 -0
@@ -0,0 +1,167 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Unified VisibilityExpr grammar and evaluator (DNA-26, RFC-0026).
5
+ One grammar, two consumers: block-level visibility in page content and
6
+ feature-graph visibility in RFC-0018.
7
+
8
+ VisibilityExpr is a CLOSED discriminated union (DNA-19). Adding a new case
9
+ requires a superseding RFC.
10
+ </purpose>
11
+ <non-goals>
12
+ <item>Do not add new VisibilityExpr cases (e.g. abTest, experiment) without a superseding RFC.</item>
13
+ <item>Do not implement runtime (edge) evaluation here — evalVisibility is build-time only.</item>
14
+ <item>Do not import from app-local code.</item>
15
+ </non-goals>
16
+ </MODULE_CONTRACT>
17
+ <CHANGE_SUMMARY>
18
+ <item>Wave 1 (RFC-0026): Initial creation — unified VisibilityExpr grammar with build-time evaluator.</item>
19
+ </CHANGE_SUMMARY>
20
+ */
21
+
22
+ import { z } from "zod";
23
+ import type { RuntimeContext } from "./runtime-context.ts";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // VisibilityExpr — closed discriminated union (DNA-19, RFC-0026)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * A closed discriminated union describing when a block or feature is visible.
31
+ *
32
+ * MVP semantics:
33
+ * { feature } — delegates to featureGraph.isEnabled(key)
34
+ * { locale } — checks ctx.locale against the given code(s)
35
+ * { segment } — ALWAYS returns false at MVP (ctx.segment === null). RFC-0027 activates.
36
+ * { flag } — ALWAYS returns false at MVP (ctx.flags === {}). RFC-0027 activates.
37
+ * { all } — logical AND of sub-expressions
38
+ * { any } — logical OR of sub-expressions
39
+ * { not } — logical NOT of a sub-expression
40
+ *
41
+ * Content authors may write segment/flag expressions today. They evaluate false
42
+ * at MVP but will become live without content changes when RFC-0027 ships.
43
+ *
44
+ * Do NOT add a new case without a superseding RFC (DNA-19).
45
+ */
46
+ export type VisibilityExpr =
47
+ | { feature: string }
48
+ | { locale: string | string[] }
49
+ | { segment: string | string[] }
50
+ | { flag: string }
51
+ | { all: VisibilityExpr[] }
52
+ | { any: VisibilityExpr[] }
53
+ | { not: VisibilityExpr };
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // VisibilityExprSchema — Zod validator (recursive via z.lazy)
57
+ // ---------------------------------------------------------------------------
58
+
59
+ const visibilityExprBase: z.ZodType<VisibilityExpr> = z.lazy(() =>
60
+ z.union([
61
+ z.object({ feature: z.string().min(1) }),
62
+ z.object({ locale: z.union([z.string().min(1), z.array(z.string().min(1))]) }),
63
+ z.object({ segment: z.union([z.string().min(1), z.array(z.string().min(1))]) }),
64
+ z.object({ flag: z.string().min(1) }),
65
+ z.object({ all: z.array(visibilityExprBase).min(1) }),
66
+ z.object({ any: z.array(visibilityExprBase).min(1) }),
67
+ z.object({ not: visibilityExprBase }),
68
+ ]),
69
+ );
70
+
71
+ /**
72
+ * Zod schema for VisibilityExpr.
73
+ * Used by page.block.validate and visibility.expr.validate to parse expressions
74
+ * found in page content and feature-graph YAML files.
75
+ */
76
+ export const VisibilityExprSchema: z.ZodType<VisibilityExpr> = visibilityExprBase;
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // ResolvedFeatureGraph — minimal interface for evalVisibility
80
+ // ---------------------------------------------------------------------------
81
+
82
+ /**
83
+ * Minimal interface that evalVisibility depends on for { feature: "..." } clauses.
84
+ * Consumers provide their own implementation (e.g. from getFeatureGraph() in RFC-0018).
85
+ * An empty/no-op implementation returns false for every key — safe for offline/preview.
86
+ */
87
+ export interface ResolvedFeatureGraph {
88
+ /** Returns true when the feature with `key` is enabled in the current graph. */
89
+ isEnabled(key: string): boolean;
90
+ }
91
+
92
+ /** A no-op feature graph where every feature evaluates as disabled. */
93
+ export const EMPTY_FEATURE_GRAPH: ResolvedFeatureGraph = {
94
+ isEnabled: () => true, // MVP: features default to enabled (graph opt-outs via explicit false)
95
+ };
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // exhaustive — compile-time union exhaustion helper
99
+ // ---------------------------------------------------------------------------
100
+
101
+ function exhaustive(x: never): never {
102
+ throw new Error(`[visibility] Unhandled VisibilityExpr case: ${JSON.stringify(x)}`);
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // evalVisibility — build-time evaluator
107
+ // ---------------------------------------------------------------------------
108
+
109
+ /**
110
+ * Evaluates a VisibilityExpr at build time.
111
+ *
112
+ * Returns true → block/feature should be rendered in the output.
113
+ * Returns false → block/feature should be dropped from the output.
114
+ *
115
+ * A null expression (no `visibility:` key) is treated as unconditionally visible.
116
+ *
117
+ * MVP guarantees (enforced by runtime.context.shape):
118
+ * - ctx.segment === null → all { segment } clauses return false.
119
+ * - ctx.flags === {} → all { flag } clauses return false.
120
+ *
121
+ * @param expr The parsed visibility expression (or null if absent).
122
+ * @param ctx The RuntimeContext for this build locale.
123
+ * @param featureGraph The resolved feature graph from RFC-0018.
124
+ */
125
+ export function evalVisibility(
126
+ expr: VisibilityExpr | null | undefined,
127
+ ctx: RuntimeContext,
128
+ featureGraph: ResolvedFeatureGraph,
129
+ ): boolean {
130
+ if (expr == null) return true;
131
+
132
+ if ("feature" in expr) {
133
+ return featureGraph.isEnabled(expr.feature);
134
+ }
135
+
136
+ if ("locale" in expr) {
137
+ const locales = Array.isArray(expr.locale) ? expr.locale : [expr.locale];
138
+ return locales.includes(ctx.locale);
139
+ }
140
+
141
+ if ("segment" in expr) {
142
+ // MVP contract: ctx.segment is always null → segment clauses always return false.
143
+ // RFC-0027 lifts this when persona detection lands.
144
+ if (ctx.segment === null) return false;
145
+ const segments = Array.isArray(expr.segment) ? expr.segment : [expr.segment];
146
+ return segments.includes(ctx.segment);
147
+ }
148
+
149
+ if ("flag" in expr) {
150
+ // MVP contract: ctx.flags is always {} → flag clauses always return false.
151
+ return ctx.flags[expr.flag] === true;
152
+ }
153
+
154
+ if ("all" in expr) {
155
+ return expr.all.every((e) => evalVisibility(e, ctx, featureGraph));
156
+ }
157
+
158
+ if ("any" in expr) {
159
+ return expr.any.some((e) => evalVisibility(e, ctx, featureGraph));
160
+ }
161
+
162
+ if ("not" in expr) {
163
+ return !evalVisibility(expr.not, ctx, featureGraph);
164
+ }
165
+
166
+ return exhaustive(expr);
167
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["src/**/*.test.ts"],
7
+ },
8
+ });
@@ -0,0 +1,139 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Server-side HTML post-processor that wraps qualifying inline numbers in prose HTML with GSAP-ready span elements. Zero DOM access — safe to call in Astro SSR/SSG frontmatter.</purpose>
4
+ <non-goals>
5
+ <item>Do not import or call this from browser scripts — server-only.</item>
6
+ <item>Do not wrap numbers inside h1–h4, code, pre, or existing js-inline-number spans.</item>
7
+ <item>Do not use a full HTML parser — keep dependency-free.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0041: Created as SSR inline number pre-wrap utility in @warpgogol/werkstatt-shared/share.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ // @ai-invariant: RFC-0041. Server-only. Never import in browser scripts.
16
+
17
+ export interface WrapInlineNumbersOptions {
18
+ /** Also wrap year numbers (1200–currentYear+120). Default false — years are excluded by default. */
19
+ animateYears?: boolean;
20
+ /** GSAP tween duration in seconds written into data-duration. Default 3.0. */
21
+ duration?: number;
22
+ }
23
+
24
+ // Matches integers ≥ 2 digits bounded by whitespace, punctuation, or string edges.
25
+ // Does NOT match:
26
+ // - compound tokens: 14-jährig, 1st, e.V., 1.234 (trailing period or hyphen)
27
+ // - paragraph refs: § 53 (lookbehind excludes § via negative lookahead on the preceding char)
28
+ // - registration IDs: VR 1520 BHV (uppercase-letter word before the number)
29
+ // Lookbehind: preceded by start-of-string OR whitespace/opening punctuation,
30
+ // AND NOT immediately preceded by an uppercase letter sequence + space (abbrev/code)
31
+ // Lookahead: followed by end-of-string OR whitespace/closing punctuation
32
+ const NUMBER_RE = /(?<=^|[\s(\[«"–—,;:!?])(\d{2,})(?=[\s).,;:!?»"–—]|$)/gm;
33
+
34
+ // Tags whose text content must not be wrapped.
35
+ const SKIP_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6", "code", "pre", "li", "ol"]);
36
+
37
+ /**
38
+ * Splits an HTML string into alternating text and tag tokens.
39
+ * Tags are strings starting with "<"; text segments are everything between tags.
40
+ */
41
+ function tokenize(html: string): string[] {
42
+ return html.split(/(<[^>]*>)/);
43
+ }
44
+
45
+ /**
46
+ * Extracts the lowercase tag name from a tag token, e.g. "<h2 class=...>" → "h2".
47
+ * Returns empty string for comments, doctypes, etc.
48
+ */
49
+ function tagName(token: string): string {
50
+ const m = token.match(/^<\/?([a-zA-Z][a-zA-Z0-9]*)/);
51
+ return m ? m[1].toLowerCase() : "";
52
+ }
53
+
54
+ function isOpeningTag(token: string): boolean {
55
+ return token.startsWith("<") && !token.startsWith("</") && !token.startsWith("<!--");
56
+ }
57
+
58
+ function isClosingTag(token: string): boolean {
59
+ return token.startsWith("</");
60
+ }
61
+
62
+ /**
63
+ * Accepts a rendered HTML string and returns the same string with qualifying
64
+ * inline numbers wrapped in `<span class="js-inline-number" …>` elements.
65
+ *
66
+ * - Numbers inside h1–h4, code, pre, and existing js-inline-number spans are skipped.
67
+ * - Year numbers (1200–currentYear+120) are always excluded (never wrapped).
68
+ * - `animateYears` is reserved but currently treated as always false.
69
+ * - Each span carries `style="display:inline;min-width:Nch"` to prevent CLS.
70
+ */
71
+ export function wrapInlineNumbers(html: string, options: WrapInlineNumbersOptions = {}): string {
72
+ const { animateYears: _animateYears = false, duration = 3.0 } = options;
73
+ const currentYear = new Date().getFullYear();
74
+ const isYear = (n: number) => n >= 1200 && n <= currentYear + 120;
75
+
76
+ const tokens = tokenize(html);
77
+
78
+ // Stack of tag names currently open (depth tracking to skip nested content)
79
+ const skipStack: string[] = [];
80
+
81
+ const result: string[] = [];
82
+
83
+ for (const token of tokens) {
84
+ if (token.startsWith("<")) {
85
+ const name = tagName(token);
86
+
87
+ if (isOpeningTag(token)) {
88
+ if (SKIP_TAGS.has(name)) {
89
+ skipStack.push(name);
90
+ }
91
+ // Also skip if we're entering a js-inline-number span (no double-wrap)
92
+ if (name === "span" && token.includes("js-inline-number")) {
93
+ skipStack.push("__inline-number__");
94
+ }
95
+ } else if (isClosingTag(token)) {
96
+ const top = skipStack[skipStack.length - 1];
97
+ if (top === name || (top === "__inline-number__" && name === "span")) {
98
+ skipStack.pop();
99
+ }
100
+ }
101
+
102
+ result.push(token);
103
+ continue;
104
+ }
105
+
106
+ // Text segment — only wrap when not inside a skip context
107
+ if (skipStack.length > 0) {
108
+ result.push(token);
109
+ continue;
110
+ }
111
+
112
+ const wrapped = token.replace(
113
+ NUMBER_RE,
114
+ (match, digits: string, offset: number, str: string) => {
115
+ const num = parseInt(digits, 10);
116
+ const digitCount = digits.length;
117
+ const numIsYear = isYear(num);
118
+
119
+ // Never wrap year numbers
120
+ if (numIsYear) {
121
+ return match;
122
+ }
123
+
124
+ // Skip numbers preceded by an uppercase-letter word + space: "VR 1520", "§ 53"
125
+ // Look at the text before the match within the current segment
126
+ const before = str.slice(0, offset);
127
+ if (/(?:^|\s)(?:[A-ZÄÖܧ]{1,}|[A-ZÄÖÜ][a-zäöü]*[A-ZÄÖÜ]+)\s$/.test(before)) {
128
+ return match;
129
+ }
130
+
131
+ return `<span class="js-inline-number" data-numeric="${num}" data-duration="${duration}" style="display:inline;min-width:${digitCount}ch">${digits}</span>`;
132
+ },
133
+ );
134
+
135
+ result.push(wrapped);
136
+ }
137
+
138
+ return result.join("");
139
+ }
@@ -0,0 +1,314 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ [RFC-0193] Zod schema for the Blueprint contract (the declarative surface spec). Co-located with
5
+ the Blueprint type in @warpgogol/werkstatt-shared/surface to avoid a package cycle (the engine owns its own contract).
6
+ The Blueprint YAML data files live in packages/werkstatt-site/src/domain/ontology/blueprints/*.yaml; this schema validates
7
+ them in blueprint.validate and during surface.generate expansion.
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Do not read files (callers supply parsed YAML objects).</item>
11
+ <item>Do not cross-check datasets/constellations (blueprint.validate does that).</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>RFC-0193: initial schema.</item>
16
+ <item>RFC-0496: add serviceSchema (BlueprintServiceConfig) to BlueprintLevel; add linkingParentSchema to linking.</item>
17
+ <item>RFC-0497: add intersectionSchema (BlueprintIntersectionConfig) to BlueprintLevel.</item>
18
+ <item>RFC-0500: add hubSchema (BlueprintHubConfig) to BlueprintLevel; add statusGateSchema (BlueprintStatusGate) to policy.</item>
19
+ </CHANGE_SUMMARY>
20
+ */
21
+
22
+ import { z } from "zod";
23
+ import type { Blueprint } from "./blueprint.ts";
24
+
25
+ const localizedString = z.record(z.string(), z.string());
26
+
27
+ const intRecord = z.record(z.string(), z.number().int());
28
+
29
+ // RFC-0490: pillar-hub configuration for depth-0 hub pages.
30
+ const pillarCtaSchema = z.object({
31
+ label: localizedString,
32
+ target: z.string().min(1),
33
+ });
34
+
35
+ const pillarHeroSchema = z.object({
36
+ eyebrow: localizedString,
37
+ heading: localizedString,
38
+ lead: localizedString,
39
+ primaryCta: pillarCtaSchema,
40
+ secondaryCta: pillarCtaSchema,
41
+ });
42
+
43
+ const pillarAdaptationDimensionSchema = z.object({
44
+ heading: localizedString,
45
+ body: localizedString,
46
+ });
47
+
48
+ const pillarAdaptationSchema = z.object({
49
+ heading: localizedString,
50
+ dimensions: z.array(pillarAdaptationDimensionSchema).min(1),
51
+ });
52
+
53
+ const pillarProductPriceSchema = z.object({
54
+ heading: localizedString,
55
+ body: localizedString,
56
+ priceRef: z.string().min(1),
57
+ });
58
+
59
+ const pillarFinalCtaSchema = z.object({
60
+ heading: localizedString,
61
+ body: localizedString,
62
+ primaryCta: pillarCtaSchema,
63
+ secondaryCta: pillarCtaSchema,
64
+ });
65
+
66
+ const pillarSchema = z.object({
67
+ hero: pillarHeroSchema,
68
+ adaptation: pillarAdaptationSchema,
69
+ productPrice: pillarProductPriceSchema,
70
+ finalCta: pillarFinalCtaSchema,
71
+ catalogHeading: localizedString.optional(),
72
+ });
73
+
74
+ // RFC-0492: dossier configuration for depth-1 industry pages.
75
+ const industryPublicationGateSchema = z.object({
76
+ minServiceCategories: z.number().int().min(0),
77
+ minCustomerJourneys: z.number().int().min(0),
78
+ minTrustSignals: z.number().int().min(0),
79
+ minArchitectureEntries: z.number().int().min(0),
80
+ minModuleMappings: z.number().int().min(0),
81
+ minUniqueFaq: z.number().int().min(0),
82
+ });
83
+
84
+ const dossierSchema = z.object({
85
+ gate: industryPublicationGateSchema,
86
+ claimRestrictions: z.array(z.string().min(1)),
87
+ doorwayMaxFlaggedShare: z.number().min(0).max(1),
88
+ duplicateMaxSimilarity: z.number().min(0).max(1),
89
+ mode: z.enum(["warn", "fail"]),
90
+ });
91
+
92
+ // RFC-0496: service configuration for depth-1 service dossier pages.
93
+ const servicePublicationGateSchema = z.object({
94
+ minServiceVariants: z.number().int().min(0),
95
+ minCustomerQuestions: z.number().int().min(0),
96
+ minPriceModels: z.number().int().min(0),
97
+ minFaq: z.number().int().min(0),
98
+ minPageStructure: z.number().int().min(0),
99
+ });
100
+
101
+ const serviceSchema = z.object({
102
+ gate: servicePublicationGateSchema,
103
+ claimRestrictions: z.array(z.string().min(1)),
104
+ mode: z.enum(["warn", "fail"]),
105
+ });
106
+
107
+ const intersectionGateSchema = z.object({
108
+ minLocalServiceQuestions: z.number().int().min(0),
109
+ minScenarios: z.number().int().min(0),
110
+ minLocalEvidence: z.number().int().min(0),
111
+ minUniqueContentBlocks: z.number().int().min(0),
112
+ minUniqueFaq: z.number().int().min(0),
113
+ minSources: z.number().int().min(0),
114
+ });
115
+
116
+ const intersectionSimilaritySchema = z.object({
117
+ similarityToIndustryPage: z.number().min(0).max(1),
118
+ similarityToCityPage: z.number().min(0).max(1),
119
+ similarityToServicePage: z.number().min(0).max(1),
120
+ similarityToOtherIntersections: z.number().min(0).max(1),
121
+ });
122
+
123
+ const intersectionSchema = z.object({
124
+ gate: intersectionGateSchema,
125
+ similarity: intersectionSimilaritySchema,
126
+ substanceIndependenceThreshold: z.number().min(0).max(1),
127
+ mode: z.enum(["warn", "fail"]),
128
+ });
129
+
130
+ // RFC-0500: hub configuration for depth-0 editorial knowledge hubs (ratgeber).
131
+ const hubSchema = z.object({
132
+ cardFields: z.array(z.string().min(1)).min(1),
133
+ reservedSlugs: z.array(z.string().min(1)),
134
+ });
135
+
136
+ // RFC-0500: status gate — only records with allowed statuses are emitted as surface entries.
137
+ const statusGateSchema = z.object({
138
+ allowedStatuses: z.array(z.string().min(1)),
139
+ excludedStatuses: z.array(z.string().min(1)),
140
+ });
141
+
142
+ // RFC-0496: cross-surface parent linking.
143
+ const linkingParentSchema = z.object({
144
+ surface: z.string().min(1),
145
+ depth: z.number().int().min(0),
146
+ joinField: z.string().min(1),
147
+ });
148
+
149
+ // RFC-0325: static article metadata for a level with no per-record binding (depth-0 hubs).
150
+ const blueprintLevelArticleSchema = z.object({
151
+ publishedAt: z.string().min(1),
152
+ updatedAt: z.string().min(1).optional(),
153
+ author: z.string().min(1).optional(),
154
+ tags: z.array(z.string().min(1)).optional(),
155
+ });
156
+
157
+ const evidencePolicySchema = z.object({
158
+ approvedNarrative: z.enum(["required", "optional"]).optional(),
159
+ requiredRecordFields: z.array(z.string().min(1)).optional(),
160
+ preferredEvidenceSources: z.array(z.string().min(1)).optional(),
161
+ minTupleSpecificFacts: z.number().int().min(0).optional(),
162
+ minWerkEvidence: z.number().int().min(0).optional(),
163
+ existenceSource: z.enum(["records", "works"]).optional(),
164
+ freshness: z.enum(["valid-and-current", "valid"]).optional(),
165
+ duplicate: z
166
+ .object({
167
+ method: z.enum(["shingle", "simhash"]).optional(),
168
+ maxSimilarityWithinCluster: z.number().min(0).max(1).optional(),
169
+ })
170
+ .optional(),
171
+ leadImage: z.enum(["required", "warning", "optional"]).optional(),
172
+ mode: z.enum(["error", "warning"]).optional(),
173
+ });
174
+
175
+ const demandPolicySchema = z.object({
176
+ minVolume: z.number().min(0).optional(),
177
+ allowIntents: z
178
+ .array(z.enum(["informational", "commercial", "transactional", "navigational"]))
179
+ .optional(),
180
+ missing: z.enum(["noindex", "do-not-emit"]).optional(),
181
+ staleAfterDays: z.number().int().min(0).optional(),
182
+ });
183
+
184
+ const depthRolePolicySchema = z.object({
185
+ indexability: z.enum(["index", "navigation-noindex", "evidence-gated"]),
186
+ canonicalTarget: z.union([z.literal("tradeHub"), z.number().int().min(0)]).optional(),
187
+ follow: z.boolean().optional(),
188
+ includeInSitemap: z.boolean().optional(),
189
+ geo: z.enum(["full", "twin-only", "off"]).optional(),
190
+ localEvidence: z
191
+ .object({
192
+ minVerifiedFacts: z.number().int().min(0).optional(),
193
+ minCitySpecificQa: z.number().int().min(0).optional(),
194
+ minUniqueTokenShare: z.number().min(0).max(1).optional(),
195
+ maxBodySimilarityWithinBranch: z.number().min(0).max(1).optional(),
196
+ })
197
+ .optional(),
198
+ });
199
+
200
+ export const blueprintSchema = z.object({
201
+ id: z.string().min(1),
202
+ entitlement: z.string().min(1),
203
+ dataset: z.object({ collection: z.string().min(1), status: z.string().optional() }),
204
+ axes: z
205
+ .array(
206
+ z.object({
207
+ id: z.string().min(1),
208
+ universe: z.union([
209
+ z.object({ collection: z.string().min(1), field: z.string().min(1) }),
210
+ z.object({ provider: z.string().min(1) }),
211
+ ]),
212
+ match: z.object({ recordField: z.string().min(1) }),
213
+ }),
214
+ )
215
+ .min(1),
216
+ levels: z
217
+ .array(
218
+ z.object({
219
+ depth: z.number().int().min(0),
220
+ slug: localizedString,
221
+ redirectToPageId: z.string().min(1).optional(),
222
+ constellation: z.string().min(1),
223
+ geo: z.enum(["full", "twin-only", "off"]).optional(),
224
+ titleTemplate: localizedString.optional(),
225
+ descriptionTemplate: localizedString.optional(),
226
+ intro: localizedString.optional(),
227
+ semanticType: z.string().min(1).optional(),
228
+ article: blueprintLevelArticleSchema.optional(),
229
+ pillar: pillarSchema.optional(),
230
+ dossier: dossierSchema.optional(),
231
+ service: serviceSchema.optional(),
232
+ intersection: intersectionSchema.optional(),
233
+ hub: hubSchema.optional(),
234
+ }),
235
+ )
236
+ .min(1),
237
+ policy: z.object({
238
+ minRecordsPerDepth: intRecord,
239
+ noindexBelowPerDepth: intRecord.optional(),
240
+ redirectPolicy: z.enum(["nearest-ancestor", "root"]).optional(),
241
+ trailingSlash: z.boolean().optional(),
242
+ maxStubDepth: z.number().int().optional(),
243
+ substanceMin: z.number().min(0).max(100).optional(),
244
+ substanceMinPerDepth: intRecord.optional(),
245
+ evidencePerDepth: z.record(z.string(), evidencePolicySchema).optional(),
246
+ demandPerDepth: z.record(z.string(), demandPolicySchema).optional(),
247
+ depthRoles: z.record(z.string(), depthRolePolicySchema).optional(),
248
+ sitemapBudget: z.number().int().min(0).optional(),
249
+ maxThinShare: z.number().min(0).max(1).optional(),
250
+ regionalGateDepths: z.array(z.number().int().min(0)).optional(),
251
+ bake: z.enum(["inline", "lazy"]).optional(),
252
+ statusGate: statusGateSchema.optional(),
253
+ }),
254
+ linking: z
255
+ .object({
256
+ children: z.object({ limit: z.number().int() }).optional(),
257
+ siblings: z.object({ limit: z.number().int() }).optional(),
258
+ teasers: z
259
+ .object({
260
+ relevance: z.array(z.object({ sharedAxis: z.string(), weight: z.number() })).optional(),
261
+ })
262
+ .optional(),
263
+ parent: linkingParentSchema.optional(),
264
+ })
265
+ .optional(),
266
+ rotation: z.object({ variantsByTupleHash: z.boolean() }).optional(),
267
+ projection: z
268
+ .object({
269
+ title: localizedString.optional(),
270
+ description: z.union([localizedString, z.object({ ref: z.string() })]).optional(),
271
+ })
272
+ .optional(),
273
+ freshness: z
274
+ .object({
275
+ slaDaysPerDepth: intRecord,
276
+ field: z.string().min(1),
277
+ mode: z.enum(["any", "all", "median"]).optional(),
278
+ })
279
+ .optional(),
280
+ enrichedFields: z
281
+ .array(
282
+ z.object({
283
+ field: z.string().min(1),
284
+ promptId: z.string().min(1),
285
+ scopeDepth: z.number().int(),
286
+ maxTokens: z.number().int(),
287
+ // RFC-0207: output shape + generation granularity (both default to the original behavior).
288
+ kind: z.enum(["field", "narrative"]).optional(),
289
+ scope: z.enum(["tuple", "record"]).optional(),
290
+ axis: z.string().min(1).optional(),
291
+ }),
292
+ )
293
+ .optional(),
294
+ });
295
+
296
+ export interface ParseBlueprintResult {
297
+ ok: boolean;
298
+ blueprint?: Blueprint;
299
+ errors: string[];
300
+ }
301
+
302
+ /** Validate an unknown value (parsed YAML) into a typed Blueprint. */
303
+ export function parseBlueprint(value: unknown): ParseBlueprintResult {
304
+ const result = blueprintSchema.safeParse(value);
305
+ if (result.success) {
306
+ return { ok: true, blueprint: result.data as Blueprint, errors: [] };
307
+ }
308
+ return {
309
+ ok: false,
310
+ errors: result.error.issues.map(
311
+ (issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`,
312
+ ),
313
+ };
314
+ }