@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,314 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ [RFC-0152] The Image Provider Port — the rendering/optimization analogue of the
5
+ RFC-0141 content-source port. Defines "how an authored image becomes src + srcset"
6
+ as a swappable provider so the optimization backend (Cloudflare runtime today, a
7
+ CMS/DAM or build-time pipeline later) can change without touching any section or
8
+ component. Ships the `cloudflare-runtime` provider (Cloudflare Image Resizing via
9
+ /cdn-cgi/image) as the default.
10
+ [RFC-0204] Adds the `build-portable` provider that emits srcset from pre-generated
11
+ static width variants (no Cloudflare Image Transformations required).
12
+ </purpose>
13
+ <non-goals>
14
+ <item>Do not run sharp or any build-time optimization here — providers emit URLs only.</item>
15
+ <item>Do not resolve asset tokens — that is resolveImage / the content-source port.</item>
16
+ <item>Do not implement the cms-native provider yet (later phase).</item>
17
+ </non-goals>
18
+ </MODULE_CONTRACT>
19
+ <CHANGE_SUMMARY>
20
+ <item>RFC-0152: introduced the Image Provider Port and the cloudflare-runtime provider.</item>
21
+ <item>RFC-0204: added ImageVariantManifest types and createBuildPortableProvider factory.</item>
22
+ </CHANGE_SUMMARY>
23
+ */
24
+
25
+ /** A resolved image to render. Astro's ImageMetadata is structurally assignable. */
26
+ export interface ImageDescriptor {
27
+ /** Origin path: root-relative for in-repo assets ("/_astro/..") or an absolute URL (CMS/DAM). */
28
+ src: string;
29
+ /** Intrinsic width, when known — used to avoid upscaling the responsive ladder. */
30
+ width?: number;
31
+ /** Intrinsic height, when known. */
32
+ height?: number;
33
+ }
34
+
35
+ /** Output format requested from the provider. */
36
+ export type ImageFormat = "webp" | "avif" | "auto";
37
+
38
+ /** Quality presets. NOTE: Cloudflare expects a numeric quality — never the literal "max". */
39
+ export const IMAGE_QUALITY_PRESETS = { low: 30, mid: 60, high: 80, max: 100 } as const;
40
+ export type ImageQualityPreset = keyof typeof IMAGE_QUALITY_PRESETS;
41
+
42
+ /** Default no-upscale responsive width ladder. */
43
+ export const DEFAULT_IMAGE_WIDTHS: readonly number[] = [320, 480, 640, 768, 1024, 1280, 1536, 1920];
44
+
45
+ export interface ImageRequest {
46
+ /** Candidate widths; defaults to DEFAULT_IMAGE_WIDTHS. Filtered to never exceed the intrinsic width. */
47
+ widths?: readonly number[];
48
+ /** <img sizes> hint; defaults to "100vw". */
49
+ sizes?: string;
50
+ /** Numeric quality OR a preset; resolved to a number for the provider. */
51
+ quality?: number | ImageQualityPreset;
52
+ /** Output format; defaults to "webp". */
53
+ format?: ImageFormat;
54
+ }
55
+
56
+ export interface ImageSources {
57
+ /** Fallback src (largest candidate). */
58
+ src: string;
59
+ /** Responsive srcset (`url 320w, url 640w, ...`); omitted when a single candidate. */
60
+ srcset?: string;
61
+ /** <img sizes>. */
62
+ sizes?: string;
63
+ /**
64
+ * Width of the fallback `src` variant in CSS pixels.
65
+ * Set by providers that select a specific variant (e.g. build-portable).
66
+ * When present, <ResponsiveImage> uses this instead of the intrinsic ImageMetadata width.
67
+ */
68
+ width?: number;
69
+ /**
70
+ * Height of the fallback `src` variant in CSS pixels.
71
+ * Derived from the selected variant width + original aspect ratio.
72
+ */
73
+ height?: number;
74
+ }
75
+
76
+ /** The Image Provider Port. Every optimization backend implements this single interface. */
77
+ export interface ImageProvider {
78
+ readonly id: "cloudflare-runtime" | "cms-native" | "build-portable";
79
+ buildSources(descriptor: ImageDescriptor, request?: ImageRequest): ImageSources;
80
+ }
81
+
82
+ const clamp = (n: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, n));
83
+
84
+ /** Resolve a preset or numeric quality to a Cloudflare-valid integer (1..100). */
85
+ export function resolveImageQuality(quality?: number | ImageQualityPreset): number {
86
+ if (typeof quality === "number") return clamp(Math.round(quality), 1, 100);
87
+ if (quality && quality in IMAGE_QUALITY_PRESETS) return IMAGE_QUALITY_PRESETS[quality];
88
+ return IMAGE_QUALITY_PRESETS.high;
89
+ }
90
+
91
+ /** Build a sorted, de-duplicated, no-upscale candidate width list. */
92
+ export function candidateWidths(
93
+ intrinsic?: number,
94
+ widths: readonly number[] = DEFAULT_IMAGE_WIDTHS,
95
+ ): number[] {
96
+ const base = widths.filter((w) => !intrinsic || w < intrinsic);
97
+ const set = new Set(base);
98
+ if (intrinsic && intrinsic > 0) set.add(intrinsic);
99
+ const sorted = [...set].sort((a, b) => a - b);
100
+ return sorted.length > 0 ? sorted : intrinsic ? [intrinsic] : [...widths];
101
+ }
102
+
103
+ function isAbsoluteUrl(src: string): boolean {
104
+ return /^https?:\/\//i.test(src) || src.startsWith("data:");
105
+ }
106
+
107
+ /**
108
+ * cloudflare-runtime provider (RFC-0152 default).
109
+ * When Cloudflare Image Transformations are enabled (PUBLIC_CF_IMAGE_TRANSFORM=on),
110
+ * emits /cdn-cgi/image/<opts>/<origin> URLs resolved at request time by Cloudflare —
111
+ * a responsive srcset for in-repo assets and remote CMS URLs. `onerror=redirect` only
112
+ * helps when the feature IS enabled but a specific transform errors; it does NOT save a
113
+ * zone where the feature is OFF (those URLs 404). Hence the safe passthrough default.
114
+ */
115
+ /**
116
+ * Whether to emit /cdn-cgi/image transform URLs (baked at build time).
117
+ *
118
+ * IMPORTANT: /cdn-cgi/image only works when Cloudflare Image Transformations are
119
+ * ENABLED on the zone. When the feature is OFF, those URLs return 404 with NO
120
+ * `onerror` fallback — so emitting them unconditionally breaks every image on a
121
+ * zone that has not enabled the feature. Therefore the safe default is OFF: serve
122
+ * the raw origin asset (always 200, no resize, no srcset). Opt in per app by
123
+ * setting `PUBLIC_CF_IMAGE_TRANSFORM=on` once Transformations are enabled on the
124
+ * zone — then the provider emits the responsive /cdn-cgi/image srcset.
125
+ */
126
+ function cloudflareTransformEnabled(): boolean {
127
+ try {
128
+ const env = (import.meta as ImportMeta & { env?: Record<string, string | undefined> }).env;
129
+ return env?.PUBLIC_CF_IMAGE_TRANSFORM === "on";
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ export const cloudflareRuntimeProvider: ImageProvider = {
136
+ id: "cloudflare-runtime",
137
+ buildSources(descriptor, request = {}) {
138
+ // data: URLs cannot be resized — pass straight through.
139
+ if (descriptor.src.startsWith("data:")) {
140
+ return { src: descriptor.src };
141
+ }
142
+ // Safe default: until Cloudflare Image Transformations are enabled on the zone
143
+ // (PUBLIC_CF_IMAGE_TRANSFORM=on), serve the raw origin asset — it always deploys
144
+ // to dist/client/_astro and is served 200. No /cdn-cgi/image, no srcset, no resize.
145
+ if (!cloudflareTransformEnabled()) {
146
+ return { src: descriptor.src };
147
+ }
148
+ const quality = resolveImageQuality(request.quality);
149
+ const format: ImageFormat = request.format ?? "webp";
150
+ const origin = isAbsoluteUrl(descriptor.src)
151
+ ? descriptor.src
152
+ : descriptor.src.replace(/^\//, "");
153
+
154
+ const url = (w: number): string => {
155
+ const opts = `onerror=redirect,width=${w},quality=${quality},format=${format}`;
156
+ return `/cdn-cgi/image/${opts}/${origin}`;
157
+ };
158
+
159
+ const widths = candidateWidths(descriptor.width, request.widths);
160
+ const largest = widths[widths.length - 1] ?? descriptor.width ?? 1280;
161
+ const srcset = widths.length > 1 ? widths.map((w) => `${url(w)} ${w}w`).join(", ") : undefined;
162
+
163
+ return {
164
+ src: url(largest),
165
+ srcset,
166
+ sizes: srcset ? (request.sizes ?? "100vw") : undefined,
167
+ };
168
+ },
169
+ };
170
+
171
+ let activeProvider: ImageProvider = cloudflareRuntimeProvider;
172
+
173
+ /** Return the active default image provider. */
174
+ export function getDefaultImageProvider(): ImageProvider {
175
+ return activeProvider;
176
+ }
177
+
178
+ /** Override the active default image provider (app/deploy-target wiring, tests). */
179
+ export function setDefaultImageProvider(provider: ImageProvider): void {
180
+ activeProvider = provider;
181
+ }
182
+
183
+ /** Convenience: build sources via the active default provider. */
184
+ export function buildImageSources(
185
+ descriptor: ImageDescriptor,
186
+ request?: ImageRequest,
187
+ ): ImageSources {
188
+ return activeProvider.buildSources(descriptor, request);
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // RFC-0204: build-portable provider — pre-generated responsive variants.
193
+ // ---------------------------------------------------------------------------
194
+
195
+ /** A single pre-generated width variant for one source image. */
196
+ export interface ImageVariant {
197
+ /** CSS pixel width of this variant. */
198
+ width: number;
199
+ /** Root-relative URL served from public/_img/<hash>/<width>.webp (always deployed). */
200
+ url: string;
201
+ }
202
+
203
+ /** Manifest entry for one source image origin. */
204
+ export interface ImageVariantEntry {
205
+ /**
206
+ * Origin src key — matches the exact `descriptor.src` value emitted by resolveImage
207
+ * (e.g. "/_astro/katrin-hennings.<hash>.webp"). Used as the lookup key.
208
+ */
209
+ origin: string;
210
+ /** Intrinsic width of the source, when known. */
211
+ intrinsicWidth?: number;
212
+ /** Intrinsic height of the source, when known. */
213
+ intrinsicHeight?: number;
214
+ /** Pre-generated variants sorted ascending by width, never exceeding intrinsicWidth. */
215
+ variants: ImageVariant[];
216
+ /**
217
+ * SHA-256 hex digest of the source file bytes at generation time.
218
+ * Used by `image.variants.generate` to detect source changes and invalidate
219
+ * stale derived variants (same contract as video.variants.generate RFC-0210).
220
+ */
221
+ sourceHash?: string;
222
+ }
223
+
224
+ /** Top-level manifest written by `image.variants.generate` and read by createBuildPortableProvider. */
225
+ export interface ImageVariantManifest {
226
+ version: 1;
227
+ /** Map from origin src → entry (stable content-path key, e.g. /src/content/business-profile/de/assets/portrait.webp). */
228
+ byOrigin: Record<string, ImageVariantEntry>;
229
+ /**
230
+ * Secondary lookup: maps the bare image basename (no hash, no ext) to the entry origin key.
231
+ * Allows the provider to resolve Astro-hashed descriptor.src values like
232
+ * "/_astro/katrin-hennings.BIwCI837.webp" → entry via "katrin-hennings".
233
+ */
234
+ byBasename: Record<string, string>;
235
+ }
236
+
237
+ /**
238
+ * Extract the bare basename (no hash suffix, no extension) from an Astro-hashed URL.
239
+ * "/_astro/katrin-hennings.CrtmuSkJ.webp" → "katrin-hennings"
240
+ * "/src/content/business-profile/de/assets/katrin-hennings.webp" → "katrin-hennings"
241
+ *
242
+ * Vite/Rollup content hashes use base64url characters (a-z A-Z 0-9 - _), not pure hex.
243
+ * The hash is always exactly 8 characters before the final extension.
244
+ */
245
+ function extractBasename(src: string): string {
246
+ const filename = src.split("/").pop() ?? "";
247
+ // Strip extension
248
+ const noExt = filename.replace(/\.[a-z0-9]+$/i, "");
249
+ // Strip Vite/Rollup hash suffix: ".<8-char alphanumeric-dash-underscore>"
250
+ return noExt.replace(/\.[a-zA-Z0-9_-]{8}$/, "");
251
+ }
252
+
253
+ /**
254
+ * RFC-0204: build-portable provider factory.
255
+ *
256
+ * Reads a pre-generated `ImageVariantManifest` (produced by `image.variants.generate`)
257
+ * and emits a responsive `srcset` from static width-variant URLs. Falls back to
258
+ * `{ src: descriptor.src }` for images not in the manifest (SVG, data:, unmanaged).
259
+ *
260
+ * Lookup strategy: primary by exact `byOrigin` key (content-relative path);
261
+ * fallback by `byBasename` to handle Astro-hashed `/_astro/<name>.<hash>.webp` URLs.
262
+ *
263
+ * IMPORTANT: synchronous — do NOT import Astro `astro:assets` / `<Image>` / `getImage`.
264
+ */
265
+ export function createBuildPortableProvider(manifest: ImageVariantManifest): ImageProvider {
266
+ return {
267
+ id: "build-portable",
268
+ buildSources(descriptor, request = {}) {
269
+ if (!descriptor.src || descriptor.src.startsWith("data:")) {
270
+ return { src: descriptor.src };
271
+ }
272
+
273
+ // Primary: exact origin key match (e.g. /src/content/.../portrait.webp)
274
+ let entry = manifest.byOrigin[descriptor.src];
275
+
276
+ // Secondary: match by stripped basename for Astro-hashed URLs (/_astro/<name>.<hash>.webp)
277
+ if (!entry && manifest.byBasename) {
278
+ const basename = extractBasename(descriptor.src);
279
+ const originKey = manifest.byBasename[basename];
280
+ if (originKey) entry = manifest.byOrigin[originKey];
281
+ }
282
+
283
+ if (!entry || entry.variants.length === 0) {
284
+ return { src: descriptor.src };
285
+ }
286
+
287
+ const widths = candidateWidths(entry.intrinsicWidth, request.widths);
288
+ const available = entry.variants.filter((v) => widths.includes(v.width));
289
+ const sorted = available.length > 0 ? available : entry.variants;
290
+
291
+ const largest = sorted[sorted.length - 1];
292
+ if (!largest) return { src: descriptor.src };
293
+
294
+ const srcset =
295
+ sorted.length > 1 ? sorted.map((v) => `${v.url} ${v.width}w`).join(", ") : undefined;
296
+
297
+ // Derive variant height from the original aspect ratio
298
+ const aspectRatio =
299
+ entry.intrinsicWidth && entry.intrinsicHeight
300
+ ? entry.intrinsicHeight / entry.intrinsicWidth
301
+ : undefined;
302
+ const variantHeight =
303
+ aspectRatio !== undefined ? Math.round(largest.width * aspectRatio) : undefined;
304
+
305
+ return {
306
+ src: largest.url,
307
+ srcset,
308
+ sizes: srcset ? (request.sizes ?? "100vw") : undefined,
309
+ width: largest.width,
310
+ height: variantHeight,
311
+ };
312
+ },
313
+ };
314
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Deprecated compatibility barrel for @warpgogol/werkstatt-shared/share (RFC-0264). New code MUST
5
+ import from the domain subpath instead — see the map in
6
+ packages/share/AGENTS.md. This root barrel re-exports each subpath module
7
+ unchanged (never new logic) so existing consumers keep working while they
8
+ migrate. The `page` and `i18n` domains have completed their migration wave:
9
+ their re-export blocks are deleted here — import them from
10
+ `@warpgogol/werkstatt-shared/share/page` / `@warpgogol/werkstatt-shared/share/i18n`.
11
+ </purpose>
12
+ <non-goals>
13
+ <item>Do not add business logic here — keep as a pure re-export barrel.</item>
14
+ <item>Do not re-add the page or i18n domains — those are fully migrated; import the subpath directly.</item>
15
+ </non-goals>
16
+ </MODULE_CONTRACT>
17
+ <CHANGE_SUMMARY>
18
+ <item>Initial barrel created alongside package extraction.</item>
19
+ <item>RFC-0264: split into subpath entry points. Root barrel shrunk to a deprecated compatibility surface; page and i18n domains fully migrated (root re-export blocks deleted, all consumers rewritten).</item>
20
+ <item>C6: image-utils.ts shim deleted; root barrel re-exports from @warpgogol/werkstatt-site/content-source directly. page-handler.ts shim deleted; export map points to resolve-route.ts.</item>
21
+ </CHANGE_SUMMARY>
22
+ */
23
+
24
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/content" instead */
25
+ export * from "./content/index.ts";
26
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/semantic" instead */
27
+ export * from "./semantic/index.ts";
28
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/middleware" instead */
29
+ export * from "./middleware/language-redirect.ts";
30
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/entitlement" instead */
31
+ export * from "./entitlement.ts";
32
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/offer-capacity" instead */
33
+ export * from "./offer-capacity.ts";
34
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/knowledge" instead */
35
+ export * from "./knowledge/index.ts";
36
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/schemas" instead */
37
+ export * from "./schemas/index.ts";
38
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/feature-policy" instead */
39
+ export * from "./feature-policy.ts";
40
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/counter-utils" instead */
41
+ export * from "./counter-utils.ts";
42
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/image-provider" instead */
43
+ export * from "./image-provider.ts";
44
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/runtime-context" instead */
45
+ export { EMPTY_RUNTIME_CONTEXT } from "./runtime-context.ts";
46
+ export type { RuntimeContext } from "./runtime-context.ts";
47
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/visibility" instead */
48
+ export * from "./visibility.ts";
49
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/wrap-inline-numbers" instead */
50
+ export * from "./wrap-inline-numbers.ts";
51
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/text-normalize" instead */
52
+ export * from "./text-normalize.ts";
53
+ // dev-props-validator is NOT re-exported from the root barrel. It imports
54
+ // node:fs/promises and @warpgogol/werkstatt-shared/ontology/schemas (which also imports node:fs),
55
+ // so re-exporting it here pulls Node-only modules into the Vite client bundle.
56
+ // The sole consumer (resolve-route.ts) imports it via relative path.
57
+ // Import from "@warpgogol/werkstatt-shared/share/dev-props-validator" subpath directly if needed.
58
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/rfc0042-utils" instead */
59
+ export { need, cast, withDefault } from "./rfc0042-utils.ts";
60
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/shared-context" instead */
61
+ export * from "./shared-context.ts";
62
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/content-discipline" instead */
63
+ export * from "./content-discipline/types.ts";
64
+ export * from "./content-discipline/parsers.ts";
65
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/legal" instead */
66
+ export { resolveTranslationContext, TRANSLATION_STATUSES } from "./legal/translation-policy.ts";
67
+ export type {
68
+ TranslationStatus,
69
+ PageTranslationPolicy,
70
+ ResolvedTranslationContext,
71
+ } from "./legal/translation-policy.ts";
72
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/material-credits" instead */
73
+ export {
74
+ parseMaterialCreditMap,
75
+ findMaterialCredit,
76
+ creditByTarget,
77
+ buildMaterialCreditJsonLd,
78
+ langFromCreditPath,
79
+ } from "./material-credits.ts";
80
+ export type { MaterialCreditRecord, RawMaterialCreditMap } from "./material-credits.ts";
81
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/attribution-display" instead */
82
+ export * from "./attribution-display.ts";
83
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/string-utils" instead */
84
+ export { toKebabCase } from "./string-utils.ts";
85
+ /** @deprecated import from "@warpgogol/werkstatt-shared/share/css-value-normalize" instead */
86
+ export { normalizeCssValue } from "./css-value-normalize.ts";
@@ -0,0 +1,129 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-0211: the Content Knowledge Lifecycle (CKL) claim model. A claim is the
5
+ atomic unit of durable site truth — the binding of a value to a subject field,
6
+ qualified by provenance and time. Framework-free types + the canonical subject
7
+ address parser/formatter shared by every CKL command and validator (RFC-0212..0218).
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Do not read the filesystem, evaluate freshness, hash derivations, or emit Diagnostics — those live in the kernel checks and later CKL modules.</item>
11
+ <item>Do not define the source descriptor (RFC-0214) or the ledger event (RFC-0217) — only the in-content claim shape.</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>RFC-0211: initial claim model + subject address codec.</item>
16
+ </CHANGE_SUMMARY>
17
+ */
18
+
19
+ /** RFC-0211: how a value is known. */
20
+ export type ClaimProvenanceKind = "external" | "derived" | "asserted" | "generated";
21
+
22
+ export const CLAIM_PROVENANCE_KINDS: readonly ClaimProvenanceKind[] = [
23
+ "external",
24
+ "derived",
25
+ "asserted",
26
+ "generated",
27
+ ] as const;
28
+
29
+ /** Confidence an author/agent attaches to an asserted value. */
30
+ export type ClaimConfidence = "high" | "medium" | "low";
31
+
32
+ /**
33
+ * A content address — the subject a claim is about. Reuses RFC-0045 coordinates:
34
+ * a `collection`, a locale-independent `file` stem (RFC-0044), and a dotted
35
+ * `fieldPath` into the record. `lang` is present for language-scoped collections
36
+ * (e.g. `business/{lang}/…`).
37
+ */
38
+ export interface ClaimSubject {
39
+ collection: string;
40
+ file: string;
41
+ fieldPath: string[];
42
+ lang?: string;
43
+ }
44
+
45
+ /** The temporal validity window of a claim (RFC-0212/0213). */
46
+ export interface ClaimValidity {
47
+ /** ISO date the value was last verified / asserted. */
48
+ asOf: string;
49
+ /** ISO date after which the claim is stale. */
50
+ validUntil?: string;
51
+ /** ISO 8601 duration, e.g. "P3M" — recurring review cadence. */
52
+ reviewEvery?: string;
53
+ }
54
+
55
+ /**
56
+ * A claim: the binding of a value to a subject field, qualified by provenance and
57
+ * time. Stored per-field in a `<record>.claims.yaml` sidecar (RFC-0212); the value
58
+ * itself stays in the record body — a claim annotates, it never wraps the value.
59
+ */
60
+ export interface Claim {
61
+ subject: ClaimSubject;
62
+ provenance: ClaimProvenanceKind;
63
+ validity?: ClaimValidity;
64
+ /** → source descriptor id (RFC-0214) when provenance = external. */
65
+ sourceRef?: string;
66
+ /** Source subject (RFC-0215) when provenance = derived. */
67
+ derivedFrom?: ClaimSubject;
68
+ /** Normalized hash of the source value at derivation time (RFC-0215). */
69
+ sourceHash?: string;
70
+ /** Responsible agent/human handle — routing (RFC-0216). */
71
+ owner?: string;
72
+ confidence?: ClaimConfidence;
73
+ }
74
+
75
+ // ─── Canonical subject address codec ────────────────────────────────────────
76
+ // String form: `<collection>/[<lang>/]<file>#<dotted.field.path>`
77
+ // business/de/location#residents
78
+ // business/uk/offer#price.monthly
79
+ // surface#someField (no lang, no nested path)
80
+ // `lang` is recognized as a 2-letter segment immediately after the collection.
81
+
82
+ const TWO_LETTER_LANG = /^[a-z]{2}$/;
83
+
84
+ export class ClaimSubjectParseError extends Error {}
85
+
86
+ /** Parse the canonical subject string into a ClaimSubject. Throws on malformed input. */
87
+ export function parseClaimSubject(input: string): ClaimSubject {
88
+ const hashIndex = input.indexOf("#");
89
+ if (hashIndex < 0) {
90
+ throw new ClaimSubjectParseError(
91
+ `Subject "${input}" is missing the "#fieldPath" part (expected <collection>/[<lang>/]<file>#<field>)`,
92
+ );
93
+ }
94
+ const pathPart = input.slice(0, hashIndex);
95
+ const fieldPart = input.slice(hashIndex + 1);
96
+ const segments = pathPart.split("/").filter((s) => s.length > 0);
97
+ if (segments.length < 2) {
98
+ throw new ClaimSubjectParseError(
99
+ `Subject "${input}" must have at least <collection>/<file> before "#"`,
100
+ );
101
+ }
102
+ if (fieldPart.length === 0) {
103
+ throw new ClaimSubjectParseError(`Subject "${input}" has an empty fieldPath after "#"`);
104
+ }
105
+ const collection = segments[0]!;
106
+ let lang: string | undefined;
107
+ let fileSegments = segments.slice(1);
108
+ if (fileSegments.length >= 2 && TWO_LETTER_LANG.test(fileSegments[0]!)) {
109
+ lang = fileSegments[0]!;
110
+ fileSegments = fileSegments.slice(1);
111
+ }
112
+ return {
113
+ collection,
114
+ lang,
115
+ file: fileSegments.join("/"),
116
+ fieldPath: fieldPart.split("."),
117
+ };
118
+ }
119
+
120
+ /** Render a ClaimSubject back to its canonical string. Inverse of parseClaimSubject. */
121
+ export function formatClaimSubject(subject: ClaimSubject): string {
122
+ const langPart = subject.lang ? `${subject.lang}/` : "";
123
+ return `${subject.collection}/${langPart}${subject.file}#${subject.fieldPath.join(".")}`;
124
+ }
125
+
126
+ /** Structural equality on subjects (ignores undefined-vs-absent lang differences). */
127
+ export function claimSubjectsEqual(a: ClaimSubject, b: ClaimSubject): boolean {
128
+ return formatClaimSubject(a) === formatClaimSubject(b);
129
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-0215: derived-content staleness. A translation/copy is a `derived` claim that
5
+ remembers which source it came from (derivedFrom) and a normalized hash of that
6
+ source at derivation time (sourceHash). When the source changes, the stored hash
7
+ no longer matches → the derivative is `outdated`. Framework-free hashing + compare,
8
+ shared with the enriched-content frozen-provenance model (RFC-0197/0207).
9
+ </purpose>
10
+ <non-goals>
11
+ <item>Do not read the filesystem or parse sidecars — the kernel command does that.</item>
12
+ <item>Do not translate or regenerate content — only detect staleness.</item>
13
+ </non-goals>
14
+ </MODULE_CONTRACT>
15
+ <CHANGE_SUMMARY>
16
+ <item>RFC-0215: initial normalized hasher + state compare.</item>
17
+ </CHANGE_SUMMARY>
18
+ */
19
+
20
+ import { createHash } from "node:crypto";
21
+
22
+ export type DerivedState = "current" | "outdated" | "source-missing";
23
+
24
+ /**
25
+ * Normalize a source value so cosmetic edits (whitespace reflow, markdown emphasis)
26
+ * do not falsely mark a derivative outdated, while wording/number changes do.
27
+ * Whitespace is collapsed; common markdown punctuation is stripped; result trimmed.
28
+ */
29
+ export function normalizeForHash(value: string): string {
30
+ return value
31
+ .replace(/[*_`~#>]/g, "")
32
+ .replace(/[[\]()]/g, "")
33
+ .replace(/\s+/g, " ")
34
+ .trim();
35
+ }
36
+
37
+ /** Hash a value (after normalization) into a stable "sha256:<hex>" string. */
38
+ export function hashSourceValue(value: string): string {
39
+ const hex = createHash("sha256").update(normalizeForHash(value), "utf8").digest("hex");
40
+ return `sha256:${hex}`;
41
+ }
42
+
43
+ /**
44
+ * Compare a derivative's stored sourceHash against a freshly computed hash of the
45
+ * current source value. A missing stored hash counts as `outdated` (never stamped).
46
+ */
47
+ export function derivedState(
48
+ storedHash: string | undefined,
49
+ currentSourceValue: string | undefined,
50
+ ): { state: DerivedState; currentHash?: string } {
51
+ if (currentSourceValue === undefined) return { state: "source-missing" };
52
+ const currentHash = hashSourceValue(currentSourceValue);
53
+ if (!storedHash || storedHash !== currentHash) return { state: "outdated", currentHash };
54
+ return { state: "current", currentHash };
55
+ }