@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,351 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Build resolved page models from entry data, executing the block composition
5
+ pipeline for each block defined in a page's content.
6
+ </purpose>
7
+ <non-goals>
8
+ <item>Do not own content loading or route file dispatch.</item>
9
+ <item>Do not handle growth, passport, or other cross-cutting concerns
10
+ (those are wired in the route or layout).</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>Wave 1 (RFC-0026): Initial creation — ResolvedPage/ResolvedBlock types and buildPage pipeline.</item>
15
+ <item>Moved SectionProps into shared package for cross-app consumption.</item>
16
+ <item>RFC-0091: PLANET_IMPORT_PATHS and BLOCK_TYPE_TO_COSMIC_NAME now derived from registry; import from @warpgogol/werkstatt-shared/ontology/archetypes instead of literal constants.</item>
17
+ <item>RFC-0262: add the optional BuildPageOptions.validateProps dev-time fail-fast hook, called once per resolved shell and content block.</item>
18
+ <item>RFC-0263: hideSectionNumber injection now keyed off registry-derived roleByCosmicName[planetName] === "hero" instead of the hardcoded UNNUMBERED_HERO_PLANETS literal set (deleted).</item>
19
+ <item>RFC-0264: relocated SectionProps/SectionPageOverride here from the @warpgogol/werkstatt-shared/share root barrel (its natural domain owner); consumers now import from @warpgogol/werkstatt-shared/share/page.</item>
20
+ </CHANGE_SUMMARY>
21
+ */
22
+
23
+ import type { RuntimeContext } from "./runtime-context.ts";
24
+ import { evalVisibility, type ResolvedFeatureGraph, EMPTY_FEATURE_GRAPH } from "./visibility.ts";
25
+ import {
26
+ planetImportPaths as registryPlanetImportPaths,
27
+ blockTypeToCosmicName as registryBlockTypeToCosmicName,
28
+ moonImportPaths as registryMoonImportPaths,
29
+ roleByCosmicName as registryRoleByCosmicName,
30
+ } from "@warpgogol/werkstatt-shared/ontology/archetypes";
31
+
32
+ // @ai-invariant: PageEntry and BlockEntry shapes MUST stay structurally
33
+ // compatible with @warpgogol/werkstatt-shared/ontology/schemas PageEntrySchema / BlockEntrySchema.
34
+ // Any field change here requires a matching change there and in every
35
+ // system.md pages[] block that references the changed field.
36
+
37
+ export interface PageEntry {
38
+ kind: "page";
39
+ cosmicStar: string;
40
+ title: string;
41
+ description: string;
42
+ lang: string;
43
+ blocks: BlockEntry[];
44
+ }
45
+
46
+ export interface BlockEntry {
47
+ id?: string;
48
+ type?: string; // CMS-facing archetype slug — validated by page.block.validate
49
+ use?: string; // PlanetName or MoonName — normalized from type for internal resolution
50
+ props: Record<string, unknown>;
51
+ visibility?: import("./visibility.ts").VisibilityExpr;
52
+ /** Block layer — shell blocks render before content blocks (RFC-0036). */
53
+ layer?: "shell" | "section";
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Shell block types (RFC-0036)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ export interface ShellBlockConfig {
61
+ enabled?: boolean;
62
+ cosmicMoon: string; // MoonName from MoonCatalog
63
+ pin: string;
64
+ props?: Record<string, unknown>;
65
+ }
66
+
67
+ export interface ShellConfig {
68
+ background?: ShellBlockConfig;
69
+ header?: ShellBlockConfig;
70
+ footer?: ShellBlockConfig;
71
+ }
72
+
73
+ /**
74
+ * A single block after visibility evaluation and prop validation.
75
+ * All blocks in ResolvedPage.blocks passed visibility — invisible blocks are dropped.
76
+ */
77
+ export interface ResolvedBlock {
78
+ /** Stable kebab-case block id from the content entry (null if not declared). */
79
+ readonly id: string | null;
80
+ /** The PlanetName or MoonName identifying this block's archetype. */
81
+ readonly planetName: string;
82
+ /**
83
+ * Stable import path into packages/ui for the component.
84
+ * Used by the page route to dynamically load the component:
85
+ * const sections = import.meta.glob("@warpgogol/werkstatt-site/ui/sections/*")
86
+ * const Component = sections[block.componentImportPath]
87
+ */
88
+ readonly componentImportPath: string;
89
+ /** Props already evaluated and ready to spread into the component. */
90
+ readonly props: Record<string, unknown>;
91
+ /** The raw visibility expression (retained for attribution / debugging). null if absent. */
92
+ readonly visibility: import("./visibility.ts").VisibilityExpr | null;
93
+ /** Block layer — shell blocks are prepended and render before content blocks (RFC-0036). */
94
+ readonly layer?: "shell" | "section";
95
+ }
96
+
97
+ /**
98
+ * A fully resolved page: title/description from the entry, filtered block list,
99
+ * and the RuntimeContext used during resolution.
100
+ */
101
+ export interface ResolvedPage {
102
+ /** The IAU star name identifying this page in the cosmic overlay. */
103
+ readonly star: string;
104
+ readonly title: string;
105
+ readonly description: string;
106
+ readonly lang: string;
107
+ /** Blocks that passed visibility evaluation, in document order. */
108
+ readonly blocks: readonly ResolvedBlock[];
109
+ /** The RuntimeContext used during resolution — retained for attribution. */
110
+ readonly ctx: RuntimeContext;
111
+ }
112
+
113
+ // RFC-0035: Universal Section Props Contract — every resolved content block's
114
+ // props extend this shape. RFC-0264: relocated from the @warpgogol/werkstatt-shared/share root
115
+ // barrel to page.ts (its natural domain owner); re-exported from the `./page`
116
+ // subpath.
117
+ export interface SectionProps {
118
+ /** Active locale for i18n rendering */
119
+ lang: string;
120
+ /** App default locale for RFC-0008 site/content fallback */
121
+ defaultLanguageCode: string;
122
+ /** Zero-padded section index (01, 02, ...) for anchors and styling */
123
+ sectionNumber: string;
124
+ /** Optional link registry for CTA/link resolution */
125
+ linkRegistry?: Record<string, string | null>;
126
+ /** Complete block.props as declared in page frontmatter */
127
+ pageOverride: Record<string, unknown>;
128
+ /** RFC-0048: Stable pageId for localized anchor resolution */
129
+ pageId?: string;
130
+ }
131
+
132
+ /** Type helper for sections to declare their specific pageOverride shape */
133
+ export type SectionPageOverride<T extends Record<string, unknown>> = SectionProps & {
134
+ pageOverride: T;
135
+ };
136
+
137
+ // @ai-invariant: PLANET_IMPORT_PATHS and MOON_IMPORT_PATHS are the single
138
+ // source of truth for cosmicName → import path resolution. Every name added
139
+ // to a manifest MUST also appear in one of these maps (via the archetype
140
+ // registry), and vice versa. Silent mismatches cause
141
+ // "[buildPage] No component import path registered for ..." at runtime.
142
+ // Three-way alignment: manifest.yaml cosmicName ↔ import-paths map ↔ system.md pin.
143
+
144
+ // Default PlanetName → import path table
145
+ // Derived from the archetype registry via @warpgogol/werkstatt-shared/ontology/archetypes
146
+ // (RFC-0091). The archetype.registry.build command regenerates
147
+ // packages/werkstatt-site/src/domain/ontology/archetypes/index.yaml from manifests.
148
+ // ---------------------------------------------------------------------------
149
+
150
+ /**
151
+ * RFC-0091 fallback: entries that cannot be derived from manifests but must
152
+ * remain registered. Empty since RFC-0175 — Amalthea (formerly a reserved
153
+ * placeholder mapped to hero) is now claimed by the chat-widget section, so the
154
+ * registry-derived mapping is authoritative. The registry is spread LAST so a
155
+ * real manifest mapping always wins over any future placeholder fallback.
156
+ */
157
+ const PLANET_IMPORT_PATHS_FALLBACK: Record<string, string> = {};
158
+
159
+ /** @internal */
160
+ export const PLANET_IMPORT_PATHS: Record<string, string> = {
161
+ ...PLANET_IMPORT_PATHS_FALLBACK,
162
+ ...registryPlanetImportPaths,
163
+ };
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // MoonCatalog → import path table (RFC-0036)
167
+ // Components that can be used as shell-level blocks.
168
+ // RFC-0097: derived from the archetype registry just like PLANET_IMPORT_PATHS.
169
+ // `packages/werkstatt-site/src/domain/ontology/archetypes/index.yaml` is regenerated by
170
+ // `archetype.registry.build` from every shell.* manifest under
171
+ // `packages/werkstatt-site/src/domain/ui/components/shell/`. The previous
172
+ // MOON_IMPORT_PATHS_FALLBACK (Hermippe → site-background) was retired by
173
+ // RFC-0108 Proposal D once the registry began discovering the shell archetype
174
+ // directly; the registry is now the single source of truth.
175
+ // ---------------------------------------------------------------------------
176
+
177
+ /** @internal */
178
+ export const MOON_IMPORT_PATHS: Record<string, string> = {
179
+ ...registryMoonImportPaths,
180
+ };
181
+
182
+ /** @internal */
183
+ export const BLOCK_TYPE_TO_COSMIC_NAME: Record<string, string> = registryBlockTypeToCosmicName;
184
+
185
+ export function normalizeBlockType(block: BlockEntry): string {
186
+ const selector = block.type ?? block.use;
187
+ if (!selector) {
188
+ throw new Error("[buildPage] Block is missing required author-facing `type`.");
189
+ }
190
+ return BLOCK_TYPE_TO_COSMIC_NAME[selector] ?? selector;
191
+ }
192
+
193
+ /**
194
+ * Resolves a PlanetName to the stable import path of its section component.
195
+ * Returns null if no mapping is registered.
196
+ */
197
+ export function resolveComponentPath(planetName: string): string | null {
198
+ return PLANET_IMPORT_PATHS[planetName] ?? null;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // BuildPageOptions
203
+ // ---------------------------------------------------------------------------
204
+
205
+ export interface BuildPageOptions {
206
+ /**
207
+ * Override the default PlanetName → import path resolver.
208
+ * Useful when the caller can resolve paths from the live uni.registry.yaml.
209
+ */
210
+ resolveImportPath?: (planetName: string) => string | null;
211
+ /**
212
+ * Provide a resolved feature graph for visibility evaluation.
213
+ * Defaults to EMPTY_FEATURE_GRAPH (all features enabled) when omitted.
214
+ */
215
+ featureGraph?: ResolvedFeatureGraph;
216
+ /**
217
+ * Shell-level blocks from system.yaml prepended to content blocks (RFC-0036).
218
+ * These bypass visibility evaluation and are always included if enabled.
219
+ */
220
+ shellBlocks?: ShellBlockConfig[];
221
+ /**
222
+ * RFC-0262: dev-only fail-fast prop validation. When supplied, buildPage
223
+ * calls this once per resolved block (shell and content) with the block's
224
+ * PlanetName/MoonName, its evaluated props, and its stable block id (null
225
+ * for shell blocks). A violation should throw — buildPage does not catch
226
+ * it, matching its existing "never silently drop a block" contract.
227
+ * page.block.validate (build.check) remains the deploy-time gate; this
228
+ * hook exists purely so an agent iterating in `astro dev` sees the same
229
+ * violation immediately instead of only at the next build:check run.
230
+ */
231
+ validateProps?: (
232
+ planetName: string,
233
+ props: Record<string, unknown>,
234
+ blockId: string | null,
235
+ ) => void | Promise<void>;
236
+ }
237
+
238
+ // @ai-invariant: buildPage is the SINGLE page pipeline (DNA-25). Every page
239
+ // route MUST call it once per locale; no route may hand-assemble block
240
+ // composition. Routes that bypass buildPage fail page.pipeline.contract.
241
+ // buildPage throws on missing import paths — it never silently drops blocks.
242
+
243
+ /**
244
+ * The single build-time page pipeline (DNA-25, RFC-0026).
245
+ *
246
+ * Call this once per page route, once per locale:
247
+ * ```ts
248
+ * const ctx = EMPTY_RUNTIME_CONTEXT(lang);
249
+ * const page = await buildPage(entry.data, ctx);
250
+ * ```
251
+ *
252
+ * buildPage guarantees:
253
+ * - All blocks with `visibility` evaluating false are dropped from ResolvedPage.blocks.
254
+ * - All remaining blocks have a non-null componentImportPath.
255
+ * - No block is skipped silently due to an error — errors throw.
256
+ *
257
+ * Cross-reference checks (star/route coherence, system.yaml pin list,
258
+ * propsSchema strict validation) run in page.block.validate (build.check),
259
+ * not inside buildPage. buildPage is intentionally lenient at render time
260
+ * so that validators provide precise error messages.
261
+ *
262
+ * @throws {Error} if a block's `use` has no registered component import path.
263
+ */
264
+ /**
265
+ * Combined resolver that checks both PlanetCatalog and MoonCatalog (RFC-0036).
266
+ * MoonCatalog is checked first for shell-level components.
267
+ */
268
+ export function resolveComponentPathUnified(name: string): string | null {
269
+ return MOON_IMPORT_PATHS[name] ?? PLANET_IMPORT_PATHS[name] ?? null;
270
+ }
271
+
272
+ export async function buildPage(
273
+ entry: PageEntry,
274
+ ctx: RuntimeContext,
275
+ options?: BuildPageOptions,
276
+ ): Promise<ResolvedPage> {
277
+ const resolver = options?.resolveImportPath ?? resolveComponentPathUnified;
278
+ const featureGraph: ResolvedFeatureGraph = options?.featureGraph ?? EMPTY_FEATURE_GRAPH;
279
+ const shellBlocks = options?.shellBlocks ?? [];
280
+
281
+ const resolvedBlocks: ResolvedBlock[] = [];
282
+
283
+ // Process shell blocks first (RFC-0036) — these bypass visibility
284
+ for (const shellBlock of shellBlocks) {
285
+ if (shellBlock.enabled === false) continue;
286
+
287
+ const componentImportPath = resolver(shellBlock.cosmicMoon);
288
+ if (componentImportPath === null) {
289
+ throw new Error(
290
+ `[buildPage] No component import path registered for moon "${shellBlock.cosmicMoon}". ` +
291
+ `Register it in MOON_IMPORT_PATHS or supply a custom resolveImportPath option.`,
292
+ );
293
+ }
294
+
295
+ const shellProps = shellBlock.props ?? {};
296
+ if (options?.validateProps)
297
+ await options.validateProps(shellBlock.cosmicMoon, shellProps, null);
298
+
299
+ resolvedBlocks.push({
300
+ id: null,
301
+ planetName: shellBlock.cosmicMoon,
302
+ componentImportPath,
303
+ props: shellProps,
304
+ visibility: null,
305
+ layer: "shell",
306
+ });
307
+ }
308
+
309
+ // Process content blocks with visibility evaluation
310
+ for (const block of entry.blocks) {
311
+ // Evaluate visibility — drop blocks that evaluate false
312
+ const visible = evalVisibility(block.visibility ?? null, ctx, featureGraph);
313
+ if (!visible) continue;
314
+
315
+ // Resolve the component import path
316
+ const planetName = normalizeBlockType(block);
317
+ const componentImportPath = resolver(planetName);
318
+ if (componentImportPath === null) {
319
+ throw new Error(
320
+ `[buildPage] No component import path registered for block "${planetName}". ` +
321
+ `Register it in PLANET_IMPORT_PATHS or supply a custom resolveImportPath option.`,
322
+ );
323
+ }
324
+
325
+ const contentProps =
326
+ registryRoleByCosmicName[planetName] === "hero"
327
+ ? { ...block.props, hideSectionNumber: true }
328
+ : block.props;
329
+ if (options?.validateProps) {
330
+ await options.validateProps(planetName, contentProps, block.id ?? null);
331
+ }
332
+
333
+ resolvedBlocks.push({
334
+ id: block.id ?? null,
335
+ planetName,
336
+ componentImportPath,
337
+ props: contentProps,
338
+ visibility: block.visibility ?? null,
339
+ layer: block.layer ?? "section",
340
+ });
341
+ }
342
+
343
+ return {
344
+ star: entry.cosmicStar,
345
+ title: entry.title,
346
+ description: entry.description,
347
+ lang: entry.lang,
348
+ blocks: resolvedBlocks,
349
+ ctx,
350
+ };
351
+ }
@@ -0,0 +1,62 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0595: unit tests for extractRedirectTarget helper.</purpose>
4
+ </MODULE_CONTRACT>
5
+ <CHANGE_SUMMARY>
6
+ <item>RFC-0595: initial extractRedirectTarget tests.</item>
7
+ </CHANGE_SUMMARY>
8
+ */
9
+
10
+ import { test, expect } from "vitest";
11
+ import { extractRedirectTarget, parseRedirectRules } from "./redirects.ts";
12
+
13
+ test("extractRedirectTarget: extracts url from standard meta-refresh tag", () => {
14
+ const html = `<html><head><meta http-equiv="refresh" content="0;url=/de/agb"></head><body></body></html>`;
15
+ expect(extractRedirectTarget(html)).toBe("/de/agb");
16
+ });
17
+
18
+ test("extractRedirectTarget: extracts url with single quotes", () => {
19
+ const html = `<html><head><meta http-equiv='refresh' content='0;url=/en/page'></head></html>`;
20
+ expect(extractRedirectTarget(html)).toBe("/en/page");
21
+ });
22
+
23
+ test("extractRedirectTarget: extracts absolute URL", () => {
24
+ const html = `<meta http-equiv="refresh" content="0;url=https://example.com/target">`;
25
+ expect(extractRedirectTarget(html)).toBe("https://example.com/target");
26
+ });
27
+
28
+ test("extractRedirectTarget: handles delay before url", () => {
29
+ const html = `<meta http-equiv="refresh" content="5;url=/delayed">`;
30
+ expect(extractRedirectTarget(html)).toBe("/delayed");
31
+ });
32
+
33
+ test("extractRedirectTarget: returns null when no meta-refresh tag", () => {
34
+ const html = `<html><head><title>Normal page</title></head><body>content</body></html>`;
35
+ expect(extractRedirectTarget(html)).toBeNull();
36
+ });
37
+
38
+ test("extractRedirectTarget: returns null for malformed content attribute", () => {
39
+ const html = `<meta http-equiv="refresh" content="just text no url">`;
40
+ expect(extractRedirectTarget(html)).toBeNull();
41
+ });
42
+
43
+ test("extractRedirectTarget: returns null for empty html", () => {
44
+ expect(extractRedirectTarget("")).toBeNull();
45
+ });
46
+
47
+ test("parseRedirectRules: still works correctly alongside extractRedirectTarget", () => {
48
+ const rules = parseRedirectRules("/old /new 301\n/de/* / 308");
49
+ expect(rules).toHaveLength(2);
50
+ expect(rules[0].from).toBe("/old");
51
+ expect(rules[1].from).toBe("/de/*");
52
+ });
53
+
54
+ test("extractRedirectTarget: returns first hop only (multi-hop scenario)", () => {
55
+ const html = `<meta http-equiv="refresh" content="0;url=/intermediate">`;
56
+ expect(extractRedirectTarget(html)).toBe("/intermediate");
57
+ });
58
+
59
+ test("extractRedirectTarget: handles reversed attribute order (content before http-equiv)", () => {
60
+ const html = `<meta content="0;url=/target" http-equiv="refresh">`;
61
+ expect(extractRedirectTarget(html)).toBe("/target");
62
+ });
@@ -0,0 +1,54 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>App-agnostic _redirects file parsing for reuse across site-kernel-checks and site-kernel-handoff (RFC-0588).</purpose>
4
+ <non-goals>
5
+ <item>Do not handle advanced Cloudflare Pages syntax (query parameters, placeholders) — only whitespace-delimited `from to status` lines.</item>
6
+ <item>Do not perform glob-to-regex conversion — that is the consumer's responsibility.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0588: extracted parseRedirectRules and RedirectRule from site-kernel-checks/managed-public.ts into @warpgogol/werkstatt-shared/share/redirects subpath.</item>
11
+ <item>RFC-0595: add extractRedirectTarget helper for parsing url= from meta-refresh tags.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ export type RedirectRule = {
16
+ from: string;
17
+ to: string | undefined;
18
+ status: number;
19
+ line: string;
20
+ };
21
+
22
+ export function parseRedirectRules(body: string): RedirectRule[] {
23
+ return body
24
+ .split(/\r?\n/)
25
+ .map((line) => line.trim())
26
+ .filter((line) => line && !line.startsWith("#"))
27
+ .map((line) => {
28
+ const [from, to, statusRaw] = line.split(/\s+/);
29
+ return {
30
+ from: from ?? "",
31
+ to,
32
+ status: Number(statusRaw ?? 301),
33
+ line,
34
+ };
35
+ })
36
+ .filter((rule) => rule.from.length > 0);
37
+ }
38
+
39
+ /**
40
+ * Extract the redirect target URL from a `<meta http-equiv="refresh" content="0;url=...">` tag.
41
+ * Returns the immediate target only (first hop). Returns null if the tag is
42
+ * absent or the url= value cannot be parsed.
43
+ */
44
+ export function extractRedirectTarget(html: string): string | null {
45
+ const m = html.match(
46
+ /<meta[^>]+http-equiv=["']refresh["'][^>]*content=["']\s*\d+\s*;\s*url=([^"']+)['"][^>]*>/i,
47
+ );
48
+ if (m?.[1]) return m[1].trim();
49
+ const m2 = html.match(
50
+ /<meta[^>]+content=["']\s*\d+\s*;\s*url=([^"']+)['"][^>]*http-equiv=["']refresh["'][^>]*>/i,
51
+ );
52
+ if (m2?.[1]) return m2[1].trim();
53
+ return null;
54
+ }
@@ -0,0 +1,95 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0042 shared utilities for section components — NEED_THIS_* markers and type casting.</purpose>
4
+ <non-goals>
5
+ <item>Do not implement content fetching — RFC-0042 removed stub-based loading.</item>
6
+ <item>Do not handle i18n logic — lang is passed separately via SectionProps.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0042: Created shared utilities to centralize NEED_THIS_* pattern and type casting.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ /**
15
+ * [RFC-0042] Helper for NEED_THIS_* markers — shows explicit placeholder when field is missing.
16
+ * Use this in section components to indicate required fields that were not provided in blocks[].props.
17
+ *
18
+ * @param fieldName - The name of the field (will be uppercased in the marker)
19
+ * @param value - The value from pageOverride.props
20
+ * @returns The value if present and non-empty, otherwise `NEED_THIS_${fieldName.toUpperCase()}`
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const content = {
25
+ * heading: need("heading", props.heading),
26
+ * description: need("description", props.description),
27
+ * };
28
+ * ```
29
+ */
30
+ export function need<T>(fieldName: string, value: T | undefined | null): T | string {
31
+ if (value === undefined || value === null) {
32
+ return `NEED_THIS_${fieldName.toUpperCase()}`;
33
+ }
34
+ if (typeof value === "string") {
35
+ return value.trim() !== "" ? value : `NEED_THIS_${fieldName.toUpperCase()}`;
36
+ }
37
+ if (Array.isArray(value)) {
38
+ return value.length > 0 ? value : (`NEED_THIS_${fieldName.toUpperCase()}` as unknown as T);
39
+ }
40
+ if (typeof value === "object") {
41
+ return Object.keys(value as object).length > 0
42
+ ? value
43
+ : (`NEED_THIS_${fieldName.toUpperCase()}` as unknown as T);
44
+ }
45
+ return value;
46
+ }
47
+
48
+ /**
49
+ * [RFC-0042] Type-safe casting function for pageOverride to section content types.
50
+ * Use this instead of `as unknown as T` to make type casting explicit and searchable.
51
+ *
52
+ * @ai-invariant Use this function when casting pageOverride to a specific section content type.
53
+ *
54
+ * @param pageOverride - The pageOverride from SectionProps (blocks[].props)
55
+ * @returns The same object cast to type T
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const props = cast<TeamSectionContent>(pageOverride);
60
+ * ```
61
+ */
62
+ export function cast<T extends Record<string, unknown>>(pageOverride: Record<string, unknown>): T {
63
+ return pageOverride as unknown as T;
64
+ }
65
+
66
+ /**
67
+ * [RFC-0042] Default value helper with NEED_THIS_* marker fallback.
68
+ * Returns the value if present, otherwise returns the provided default or a NEED_THIS marker.
69
+ *
70
+ * @param value - The value from pageOverride.props
71
+ * @param defaultValue - Default value to use if value is missing
72
+ * @param fieldName - Field name for NEED_THIS marker (only used if defaultValue is not provided)
73
+ * @returns The value, defaultValue, or NEED_THIS marker
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const content = {
78
+ * texture: withDefault(props.texture, false),
79
+ * heading: withDefault(props.heading, null, "heading"), // becomes NEED_THIS_HEADING if missing
80
+ * };
81
+ * ```
82
+ */
83
+ export function withDefault<T>(
84
+ value: T | undefined,
85
+ defaultValue: T | null,
86
+ fieldName?: string,
87
+ ): T | string {
88
+ if (value !== undefined && value !== null) {
89
+ return value;
90
+ }
91
+ if (defaultValue !== null) {
92
+ return defaultValue;
93
+ }
94
+ return fieldName ? `NEED_THIS_${fieldName.toUpperCase()}` : "NEED_THIS_VALUE";
95
+ }
@@ -0,0 +1,70 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Defines RuntimeContext — the single context object threaded through the buildPage
5
+ pipeline and every block-level rendering decision (DNA-26, RFC-0026).
6
+ At MVP exactly three fields are present; segment and flags are reserved no-ops that
7
+ become active when RFC-0027 activates the Growth Layer.
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Do not add new fields without a superseding RFC (RFC-0026 nonGoal).</item>
11
+ <item>Do not allow non-null segment or non-empty flags at MVP — runtime.context.shape enforces this.</item>
12
+ <item>Do not add rendering logic here.</item>
13
+ </non-goals>
14
+ </MODULE_CONTRACT>
15
+ <CHANGE_SUMMARY>
16
+ <item>Wave 1 (RFC-0026): Initial creation — RuntimeContext shape and EMPTY_RUNTIME_CONTEXT factory.</item>
17
+ </CHANGE_SUMMARY>
18
+ */
19
+
20
+ /**
21
+ * The single context object threaded through the buildPage pipeline.
22
+ *
23
+ * At MVP (RFC-0026):
24
+ * - `locale` — the active BCP-47 language code; evaluated by block visibility expressions.
25
+ * - `segment` — RESERVED. Always null at MVP. RFC-0027 will populate this from edge-runtime
26
+ * persona detection. Content authors may write `visibility: { segment: "..." }`
27
+ * today; the expression evaluates false until RFC-0027 activates it.
28
+ * - `flags` — RESERVED. Always {} at MVP. RFC-0027 will populate this from feature-flag
29
+ * edge evaluation. Content authors may write `visibility: { flag: "..." }` today;
30
+ * the expression evaluates false until RFC-0027 activates it.
31
+ *
32
+ * `runtime.context.shape` verifies that no workspace code constructs a RuntimeContext
33
+ * with non-null segment or non-empty flags before RFC-0027 supersedes this file.
34
+ */
35
+ export interface RuntimeContext {
36
+ /** Active BCP-47 locale (e.g. "de", "en"). Evaluated by { locale: "de" } expressions. */
37
+ readonly locale: string;
38
+
39
+ /**
40
+ * RESERVED — always null at MVP.
41
+ * Do NOT construct a RuntimeContext with a non-null segment (violation caught by runtime.context.shape).
42
+ * RFC-0027 will fill this from an edge-runtime persona-detection result.
43
+ */
44
+ readonly segment: string | null;
45
+
46
+ /**
47
+ * RESERVED — always {} at MVP.
48
+ * Do NOT construct a RuntimeContext with non-empty flags (violation caught by runtime.context.shape).
49
+ * RFC-0027 will fill this from an edge-runtime feature-flag evaluation result.
50
+ */
51
+ readonly flags: Readonly<Record<string, boolean>>;
52
+ }
53
+
54
+ /**
55
+ * The only legal way to construct a RuntimeContext at MVP.
56
+ *
57
+ * Usage in page routes:
58
+ * ```ts
59
+ * import { EMPTY_RUNTIME_CONTEXT } from "@warpgogol/werkstatt-shared/share";
60
+ * const ctx = EMPTY_RUNTIME_CONTEXT(lang);
61
+ * const page = await buildPage(entry, ctx);
62
+ * ```
63
+ *
64
+ * @param locale — the active BCP-47 language code from the route params.
65
+ */
66
+ export const EMPTY_RUNTIME_CONTEXT = (locale: string): RuntimeContext => ({
67
+ locale,
68
+ segment: null,
69
+ flags: Object.freeze({}) as Readonly<Record<string, boolean>>,
70
+ });