@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,328 @@
1
+ import { test, expect } from "vitest";
2
+ import {
3
+ resolveReference,
4
+ resolveReferencesInString,
5
+ resolveReferencesDeep,
6
+ EMPTY_CONTENT_REF_INDEX,
7
+ type ContentRefIndex,
8
+ type SourceRef,
9
+ } from "../content-reference.ts";
10
+
11
+ const TEST_INDEX: ContentRefIndex = {
12
+ version: 1,
13
+ generatedAt: "2026-07-25T00:00:00Z",
14
+ collections: ["business", "pages"],
15
+ entries: {
16
+ business: {
17
+ offer: {
18
+ de: {
19
+ price: { monthly: "70 €/Monat", yearly: "700 €/Jahr", setup: "200 € Einrichtung" },
20
+ growthModules: [{ price: "+19 €/Monat je Stadt" }],
21
+ },
22
+ },
23
+ legal: {
24
+ de: { companyName: "Warpgogol GmbH" },
25
+ en: { companyName: "Warpgogol Ltd." },
26
+ },
27
+ },
28
+ pages: {
29
+ "de/index": {
30
+ de: { title: "Startseite", subtitle: "Willkommen" },
31
+ },
32
+ },
33
+ },
34
+ };
35
+
36
+ test("resolveReference — resolves a simple braceless reference", () => {
37
+ const result = resolveReference(TEST_INDEX, "business.offer.price.monthly", "de", "de");
38
+ expect(result.resolved).toBe(true);
39
+ expect(result.value).toBe("70 €/Monat");
40
+ });
41
+
42
+ test("resolveReference — resolves a nested field path", () => {
43
+ const result = resolveReference(TEST_INDEX, "business.legal.companyName", "de", "de");
44
+ expect(result.resolved).toBe(true);
45
+ expect(result.value).toBe("Warpgogol GmbH");
46
+ });
47
+
48
+ test("resolveReference — falls back to default language", () => {
49
+ const result = resolveReference(TEST_INDEX, "business.legal.companyName", "fr", "de");
50
+ expect(result.resolved).toBe(true);
51
+ expect(result.value).toBe("Warpgogol GmbH");
52
+ });
53
+
54
+ test("resolveReference — uses English when lang is en", () => {
55
+ const result = resolveReference(TEST_INDEX, "business.legal.companyName", "en", "de");
56
+ expect(result.resolved).toBe(true);
57
+ expect(result.value).toBe("Warpgogol Ltd.");
58
+ });
59
+
60
+ test("resolveReference — REF-01: missing collection", () => {
61
+ const result = resolveReference(TEST_INDEX, "unknown.coll.field", "de", "de");
62
+ expect(result.resolved).toBe(false);
63
+ expect(result.error).toContain("REF-01");
64
+ });
65
+
66
+ test("resolveReference — REF-02: missing file", () => {
67
+ const result = resolveReference(TEST_INDEX, "business.unknown.field", "de", "de");
68
+ expect(result.resolved).toBe(false);
69
+ expect(result.error).toContain("REF-02");
70
+ });
71
+
72
+ test("resolveReference — REF-03: missing field", () => {
73
+ const result = resolveReference(TEST_INDEX, "business.offer.price.nonexistent", "de", "de");
74
+ expect(result.resolved).toBe(false);
75
+ expect(result.error).toContain("REF-03");
76
+ });
77
+
78
+ test("resolveReference — invalid syntax", () => {
79
+ const result = resolveReference(TEST_INDEX, "not-a-ref", "de", "de");
80
+ expect(result.resolved).toBe(false);
81
+ expect(result.error).toContain("Invalid reference syntax");
82
+ });
83
+
84
+ test("resolveReferencesInString — pure reference string resolves fully", () => {
85
+ const out = resolveReferencesInString(TEST_INDEX, "business.offer.price.monthly", "de", "de");
86
+ expect(out).toBe("70 €/Monat");
87
+ });
88
+
89
+ test("resolveReferencesInString — embedded references in text", () => {
90
+ const out = resolveReferencesInString(
91
+ TEST_INDEX,
92
+ "Preis: business.offer.price.monthly oder business.offer.price.yearly",
93
+ "de",
94
+ "de",
95
+ );
96
+ expect(out).toBe("Preis: 70 €/Monat oder 700 €/Jahr");
97
+ });
98
+
99
+ test("resolveReferencesInString — leaves non-reference text untouched", () => {
100
+ const text = "Hello world, no references here.";
101
+ const out = resolveReferencesInString(TEST_INDEX, text, "de", "de");
102
+ expect(out).toBe(text);
103
+ });
104
+
105
+ test("resolveReferencesInString — empty index leaves text untouched", () => {
106
+ const text = "business.offer.price.monthly should stay as-is";
107
+ const out = resolveReferencesInString(EMPTY_CONTENT_REF_INDEX, text, "de", "de");
108
+ expect(out).toBe(text);
109
+ });
110
+
111
+ test("resolveReferencesDeep — resolves references in nested objects", async () => {
112
+ const data = {
113
+ title: "business.offer.price.monthly",
114
+ nested: { price: "business.offer.price.yearly" },
115
+ list: ["business.offer.price.setup", "plain text"],
116
+ number: 42,
117
+ };
118
+ const out = await resolveReferencesDeep(TEST_INDEX, data, "de", "de");
119
+ expect(out).toEqual({
120
+ title: "70 €/Monat",
121
+ nested: { price: "700 €/Jahr" },
122
+ list: ["200 € Einrichtung", "plain text"],
123
+ number: 42,
124
+ });
125
+ });
126
+
127
+ test("resolveReferencesDeep — handles arrays of objects", async () => {
128
+ const data = [{ name: "business.legal.companyName" }, { name: "plain" }];
129
+ const out = await resolveReferencesDeep(TEST_INDEX, data, "de", "de");
130
+ expect(out).toEqual([{ name: "Warpgogol GmbH" }, { name: "plain" }]);
131
+ });
132
+
133
+ test("resolveReferencesDeep — empty index returns data unchanged", async () => {
134
+ const data = { text: "business.offer.price.monthly" };
135
+ const out = await resolveReferencesDeep(EMPTY_CONTENT_REF_INDEX, data, "de", "de");
136
+ expect(out).toEqual(data);
137
+ });
138
+
139
+ // RFC-0570: Formula expression integration tests
140
+
141
+ test("resolveReferencesInString — formula with two references and arithmetic", () => {
142
+ const out = resolveReferencesInString(
143
+ TEST_INDEX,
144
+ "=(business.offer.price.setup + business.offer.price.monthly * 12)",
145
+ "de",
146
+ "de",
147
+ );
148
+ expect(out).toBe("1040");
149
+ });
150
+
151
+ test("resolveReferencesInString — formula with surrounding text and unit suffix", () => {
152
+ const out = resolveReferencesInString(
153
+ TEST_INDEX,
154
+ "Total: =(business.offer.price.setup + business.offer.price.monthly * 12) €",
155
+ "de",
156
+ "de",
157
+ );
158
+ expect(out).toBe("Total: 1040 €");
159
+ });
160
+
161
+ test("resolveReferencesInString — formula and braceless refs coexist", () => {
162
+ const out = resolveReferencesInString(
163
+ TEST_INDEX,
164
+ "=(business.offer.price.setup + business.offer.price.monthly) and business.offer.price.yearly",
165
+ "de",
166
+ "de",
167
+ );
168
+ expect(out).toBe("270 and 700 €/Jahr");
169
+ });
170
+
171
+ test("resolveReferencesInString — formula with unresolved ref renders empty", () => {
172
+ const out = resolveReferencesInString(
173
+ TEST_INDEX,
174
+ "Total: =(business.offer.price.nonexistent + 100) €",
175
+ "de",
176
+ "de",
177
+ );
178
+ expect(out).toBe("Total: €");
179
+ });
180
+
181
+ test("resolveReferencesInString — string without formulas is unchanged", () => {
182
+ const text = "business.offer.price.monthly is the price";
183
+ const out = resolveReferencesInString(TEST_INDEX, text, "de", "de");
184
+ expect(out).toBe("70 €/Monat is the price");
185
+ });
186
+
187
+ test("resolveReferencesInString — formula with nested parentheses", () => {
188
+ const out = resolveReferencesInString(
189
+ TEST_INDEX,
190
+ "=((business.offer.price.setup + business.offer.price.monthly) * 2)",
191
+ "de",
192
+ "de",
193
+ );
194
+ expect(out).toBe("540");
195
+ });
196
+
197
+ test("resolveReferencesInString — overlapping prefix references do not corrupt each other", () => {
198
+ const index: ContentRefIndex = {
199
+ ...TEST_INDEX,
200
+ entries: {
201
+ ...TEST_INDEX.entries,
202
+ business: {
203
+ ...TEST_INDEX.entries.business,
204
+ place: {
205
+ de: {
206
+ address: {
207
+ street: "Elly-Heuss-Knapp-Weg",
208
+ streetNumber: "29",
209
+ postalCode: "71522",
210
+ locality: "Backnang",
211
+ },
212
+ },
213
+ },
214
+ },
215
+ },
216
+ };
217
+ const out = resolveReferencesInString(
218
+ index,
219
+ "business.place.address.street business.place.address.streetNumber business.place.address.postalCode business.place.address.locality",
220
+ "de",
221
+ "de",
222
+ );
223
+ expect(out).toBe("Elly-Heuss-Knapp-Weg 29 71522 Backnang");
224
+ });
225
+
226
+ test("resolveReferencesInString — trailing period from sentence punctuation is not included in reference", () => {
227
+ const out = resolveReferencesInString(
228
+ TEST_INDEX,
229
+ "Bei Fragen wenden Sie sich an business.legal.companyName.",
230
+ "de",
231
+ "de",
232
+ );
233
+ expect(out).toBe("Bei Fragen wenden Sie sich an Warpgogol GmbH.");
234
+ });
235
+
236
+ test("resolveReferencesInString — reference followed by comma resolves correctly", () => {
237
+ const out = resolveReferencesInString(
238
+ TEST_INDEX,
239
+ "Preis: business.offer.price.monthly, jährlich.",
240
+ "de",
241
+ "de",
242
+ );
243
+ expect(out).toBe("Preis: 70 €/Monat, jährlich.");
244
+ });
245
+
246
+ test("resolveReferencesInString — reference at end of line without trailing punctuation resolves", () => {
247
+ const out = resolveReferencesInString(
248
+ TEST_INDEX,
249
+ "Kontakt: business.legal.companyName",
250
+ "de",
251
+ "de",
252
+ );
253
+ expect(out).toBe("Kontakt: Warpgogol GmbH");
254
+ });
255
+
256
+ // RFC-0731: this. self-reference tests
257
+
258
+ const OFFER_SOURCE_REF: SourceRef = { collection: "business", file: "offer" };
259
+
260
+ test("RFC-0731: resolveReference with this. prefix and sourceRef — resolves to same-file field", () => {
261
+ const result = resolveReference(TEST_INDEX, "this.price.monthly", "de", "de", OFFER_SOURCE_REF);
262
+ expect(result.resolved).toBe(true);
263
+ expect(result.value).toBe("70 €/Monat");
264
+ });
265
+
266
+ test("RFC-0731: resolveReference with this. prefix but no sourceRef — returns REF-12", () => {
267
+ const result = resolveReference(TEST_INDEX, "this.price.monthly", "de", "de");
268
+ expect(result.resolved).toBe(false);
269
+ expect(result.error).toContain("REF-12");
270
+ });
271
+
272
+ test("RFC-0731: resolveReference with this. prefix, sourceRef provided, but field not found — returns REF-13", () => {
273
+ const result = resolveReference(
274
+ TEST_INDEX,
275
+ "this.nonexistent.field",
276
+ "de",
277
+ "de",
278
+ OFFER_SOURCE_REF,
279
+ );
280
+ expect(result.resolved).toBe(false);
281
+ expect(result.error).toContain("REF-13");
282
+ });
283
+
284
+ test("RFC-0731: resolveReferencesInString with this. as pure reference — resolves correctly", () => {
285
+ const out = resolveReferencesInString(
286
+ TEST_INDEX,
287
+ "this.price.monthly",
288
+ "de",
289
+ "de",
290
+ OFFER_SOURCE_REF,
291
+ );
292
+ expect(out).toBe("70 €/Monat");
293
+ });
294
+
295
+ test("RFC-0731: resolveReferencesInString with this. in embedded text — resolves correctly", () => {
296
+ const out = resolveReferencesInString(
297
+ TEST_INDEX,
298
+ "Preis: this.price.monthly monatlich",
299
+ "de",
300
+ "de",
301
+ OFFER_SOURCE_REF,
302
+ );
303
+ expect(out).toBe("Preis: 70 €/Monat monatlich");
304
+ });
305
+
306
+ test("RFC-0731: resolveReferencesDeep with this. in nested object — resolves correctly", async () => {
307
+ const data = {
308
+ label: "Monatlicher Preis",
309
+ value: "this.price.monthly",
310
+ nested: { description: "Kosten: this.price.monthly" },
311
+ };
312
+ const result = await resolveReferencesDeep(TEST_INDEX, data, "de", "de", OFFER_SOURCE_REF);
313
+ expect(result).toEqual({
314
+ label: "Monatlicher Preis",
315
+ value: "70 €/Monat",
316
+ nested: { description: "Kosten: 70 €/Monat" },
317
+ });
318
+ });
319
+
320
+ test("RFC-0731: resolveReferencesInString with this. but no sourceRef — leaves text unchanged", () => {
321
+ const out = resolveReferencesInString(TEST_INDEX, "this.price.monthly", "de", "de");
322
+ expect(out).toBe("this.price.monthly");
323
+ });
324
+
325
+ test("RFC-0731: regression — existing absolute references still resolve without sourceRef", () => {
326
+ const out = resolveReferencesInString(TEST_INDEX, "business.offer.price.monthly", "de", "de");
327
+ expect(out).toBe("70 €/Monat");
328
+ });
@@ -0,0 +1,38 @@
1
+ import { test, expect } from "vitest";
2
+ import { createDevPropsValidator } from "../dev-props-validator.ts";
3
+
4
+ /*
5
+ <MODULE_CONTRACT>
6
+ <purpose>
7
+ RFC-0262: end-to-end test of createDevPropsValidator against the real
8
+ packages/werkstatt-site/src/domain/ui/sections/hero manifest (this test runs from within the
9
+ actual monorepo checkout, so workspace-root discovery resolves for real).
10
+ </purpose>
11
+ </MODULE_CONTRACT>
12
+ */
13
+
14
+ test("createDevPropsValidator: passes valid Europa (hero-section) props", async () => {
15
+ const validate = createDevPropsValidator();
16
+ await validate("Europa", { header: { heading: "Hello" } }, "hero-block"); // cosmic-literals-ignore: fixture cosmicName exercising the real hero-section manifest
17
+ });
18
+
19
+ test("createDevPropsValidator: throws PAGE-PROPS-01 on an undeclared prop key", async () => {
20
+ const validate = createDevPropsValidator();
21
+ try {
22
+ await validate(
23
+ "Europa", // cosmic-literals-ignore: fixture cosmicName exercising the real hero-section manifest
24
+ { header: { heading: "Hello" }, totallyUnknownField: true },
25
+ "hero-block",
26
+ ); // cosmic-literals-ignore: fixture cosmicName exercising the real hero-section manifest
27
+ expect.fail("should have thrown");
28
+ } catch (error) {
29
+ expect(error).toBeInstanceOf(Error);
30
+ expect((error as Error).message).toMatch(/PAGE-PROPS-01/);
31
+ expect((error as Error).message).toMatch(/hero-block/);
32
+ }
33
+ });
34
+
35
+ test("createDevPropsValidator: an unknown planetName resolves no schema and does not throw", async () => {
36
+ const validate = createDevPropsValidator();
37
+ await validate("NotARealPlanet", { anything: true }, null);
38
+ });
@@ -0,0 +1,39 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0215: tests for normalized hashing + derived-state comparison.</purpose>
4
+ <keywords>RFC-0215, CKL, derivation, test</keywords>
5
+ </MODULE_CONTRACT>
6
+ <MODULE_MAP><entry key="tests">normalize stability, hash sensitivity, derivedState transitions.</entry></MODULE_MAP>
7
+ <CHANGE_SUMMARY><item>RFC-0215: initial derivation tests.</item></CHANGE_SUMMARY>
8
+ */
9
+
10
+ import { test, expect } from "vitest";
11
+ import { normalizeForHash, hashSourceValue, derivedState } from "../knowledge/derivation.ts";
12
+
13
+ test("normalize: whitespace and markdown are insensitive", () => {
14
+ expect(normalizeForHash("Hello world")).toBe("Hello world");
15
+ expect(normalizeForHash("**Hello** _world_")).toBe("Hello world");
16
+ expect(hashSourceValue("Hello world")).toBe(hashSourceValue("**Hello** world"));
17
+ });
18
+
19
+ test("hash: wording change is sensitive", () => {
20
+ expect(hashSourceValue("Price is 70 EUR")).not.toBe(hashSourceValue("Price is 80 EUR"));
21
+ });
22
+
23
+ test("derivedState: current when hash matches", () => {
24
+ const h = hashSourceValue("Digitales Fundament");
25
+ expect(derivedState(h, "Digitales Fundament").state).toBe("current");
26
+ });
27
+
28
+ test("derivedState: outdated when source changed", () => {
29
+ const h = hashSourceValue("old source");
30
+ expect(derivedState(h, "new source").state).toBe("outdated");
31
+ });
32
+
33
+ test("derivedState: outdated when never stamped", () => {
34
+ expect(derivedState(undefined, "any").state).toBe("outdated");
35
+ });
36
+
37
+ test("derivedState: source-missing when source value absent", () => {
38
+ expect(derivedState("sha256:x", undefined).state).toBe("source-missing");
39
+ });
@@ -0,0 +1,88 @@
1
+ import { test, expect } from "vitest";
2
+ import { buildPage, type PageEntry } from "../page.ts";
3
+ import { EMPTY_RUNTIME_CONTEXT } from "../runtime-context.ts";
4
+
5
+ /*
6
+ <MODULE_CONTRACT>
7
+ <purpose>
8
+ RFC-0262: verify buildPage's validateProps hook is called per resolved
9
+ block (with the block id) and that a thrown violation propagates —
10
+ buildPage never swallows a validateProps error (fail-fast contract).
11
+ </purpose>
12
+ <non-goals>
13
+ <item>Do not test real propsSchema resolution here — see dev-props-validator's own schema-shape checks, exercised indirectly via props-contract.test.ts's validateExampleAgainstSchema (identical logic).</item>
14
+ </non-goals>
15
+ </MODULE_CONTRACT>
16
+ */
17
+
18
+ function fixtureEntry(): PageEntry {
19
+ return {
20
+ kind: "page",
21
+ cosmicStar: "TestStar",
22
+ title: "Test",
23
+ description: "Test page",
24
+ lang: "en",
25
+ blocks: [
26
+ {
27
+ id: "fixture-block",
28
+ type: "FixturePlanet",
29
+ props: { extra: "not-in-schema" },
30
+ },
31
+ ],
32
+ };
33
+ }
34
+
35
+ test("buildPage: validateProps is called with planetName, props, and blockId", async () => {
36
+ const entry = fixtureEntry();
37
+ const ctx = EMPTY_RUNTIME_CONTEXT("en");
38
+ const calls: Array<{
39
+ planetName: string;
40
+ props: Record<string, unknown>;
41
+ blockId: string | null;
42
+ }> = [];
43
+
44
+ const page = await buildPage(entry, ctx, {
45
+ resolveImportPath: () => "@warpgogol/werkstatt-site/ui/sections/fixture-section.astro",
46
+ validateProps: async (planetName, props, blockId) => {
47
+ calls.push({ planetName, props, blockId });
48
+ },
49
+ });
50
+
51
+ expect(page.blocks.length).toBe(1);
52
+ expect(calls.length).toBe(1);
53
+ expect(calls[0]?.planetName).toBe("FixturePlanet");
54
+ expect(calls[0]?.blockId).toBe("fixture-block");
55
+ expect(calls[0]?.props).toEqual({ extra: "not-in-schema" });
56
+ });
57
+
58
+ test("buildPage: a validateProps throw (PAGE-PROPS-01) propagates and includes the block id", async () => {
59
+ const entry = fixtureEntry();
60
+ const ctx = EMPTY_RUNTIME_CONTEXT("en");
61
+
62
+ try {
63
+ await buildPage(entry, ctx, {
64
+ resolveImportPath: () => "@warpgogol/werkstatt-site/ui/sections/fixture-section.astro",
65
+ validateProps: async (_planetName, props, blockId) => {
66
+ if ("extra" in props) {
67
+ throw new Error(
68
+ `[PAGE-PROPS-01] block "${blockId}" (FixturePlanet): unknown property "extra"`,
69
+ );
70
+ }
71
+ },
72
+ });
73
+ expect.fail("should have thrown");
74
+ } catch (error) {
75
+ expect(error).toBeInstanceOf(Error);
76
+ expect((error as Error).message).toMatch(/PAGE-PROPS-01/);
77
+ expect((error as Error).message).toMatch(/fixture-block/);
78
+ }
79
+ });
80
+
81
+ test("buildPage: without validateProps, no hook is invoked (opt-in, zero behavior change)", async () => {
82
+ const entry = fixtureEntry();
83
+ const ctx = EMPTY_RUNTIME_CONTEXT("en");
84
+ const page = await buildPage(entry, ctx, {
85
+ resolveImportPath: () => "@warpgogol/werkstatt-site/ui/sections/fixture-section.astro",
86
+ });
87
+ expect(page.blocks.length).toBe(1);
88
+ });
@@ -0,0 +1,134 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0292: determinism + shape tests for the Fleet Agent Catalog builder.</purpose>
4
+ <keywords>RFC-0292, fleet, agent catalog, test</keywords>
5
+ </MODULE_CONTRACT>
6
+ <MODULE_MAP>
7
+ <entry key="tests">determinism, missing-doc entry, sorting, hash stability, empty-input safe default.</entry>
8
+ </MODULE_MAP>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0292: initial builder tests.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ import { test, expect } from "vitest";
15
+ import {
16
+ buildFleetAgentCatalog,
17
+ computeFleetCatalogContentHash,
18
+ FLEET_AGENT_CATALOG_SCHEMA,
19
+ } from "../agent/fleet-catalog.ts";
20
+
21
+ const sampleDoc = {
22
+ surfaceVersion: "1.0.0",
23
+ site: "warpgogol-com",
24
+ baseUrl: "https://warpgogol.com",
25
+ contentHash: "abc123",
26
+ knowledge: [
27
+ { domain: "offer", url: "/api/agent/v1/offer.json", schema: "gogol.agent.knowledge/offer@1" },
28
+ ],
29
+ actions: [
30
+ {
31
+ id: "lead.submit",
32
+ url: "/api/agent/actions/lead.submit",
33
+ title: { de: "Anfrage" },
34
+ inputSchemaRef: "#/x",
35
+ entitlement: "agent.actions",
36
+ },
37
+ ],
38
+ interfaces: {
39
+ llms: "/llms.txt",
40
+ twins: { pattern: "/**.md" },
41
+ openapi: "/.well-known/agent.openapi.json",
42
+ mcp: { url: "/api/agent/mcp", protocolVersion: "1" },
43
+ },
44
+ proof: null,
45
+ };
46
+
47
+ test("buildFleetAgentCatalog: missing doc ⇒ enabled: false entry", () => {
48
+ const catalog = buildFleetAgentCatalog([{ site: "alpha", doc: null }]);
49
+ expect(catalog.sites).toHaveLength(1);
50
+ expect(catalog.sites[0]!.enabled).toBe(false);
51
+ expect(catalog.sites[0]!.signed).toBe(false);
52
+ expect(catalog.sites[0]!.knowledgeDomains).toEqual([]);
53
+ expect(catalog.sites[0]!.actions).toEqual([]);
54
+ expect(catalog.sites[0]!.baseUrl).toBe("");
55
+ });
56
+
57
+ test("buildFleetAgentCatalog: populated doc ⇒ enabled: true with extracted fields", () => {
58
+ const catalog = buildFleetAgentCatalog([{ site: "alpha", doc: sampleDoc }]);
59
+ const entry = catalog.sites[0]!;
60
+ expect(entry.enabled).toBe(true);
61
+ expect(entry.signed).toBe(false);
62
+ expect(entry.baseUrl).toBe("https://warpgogol.com");
63
+ expect(entry.surfaceVersion).toBe("1.0.0");
64
+ expect(entry.contentHash).toBe("abc123");
65
+ expect(entry.knowledgeDomains).toEqual(["offer"]);
66
+ expect(entry.actions).toEqual(["lead.submit"]);
67
+ expect(entry.interfaces).toEqual({ openapi: true, mcp: true });
68
+ });
69
+
70
+ test("buildFleetAgentCatalog: proof present ⇒ signed: true", () => {
71
+ const catalog = buildFleetAgentCatalog([
72
+ {
73
+ site: "alpha",
74
+ doc: {
75
+ ...sampleDoc,
76
+ proof: { type: "Ed25519Signature2020", verificationMethod: "k", proofValue: "v" },
77
+ },
78
+ },
79
+ ]);
80
+ expect(catalog.sites[0]!.signed).toBe(true);
81
+ });
82
+
83
+ test("buildFleetAgentCatalog: sorts sites by site id", () => {
84
+ const catalog = buildFleetAgentCatalog([
85
+ { site: "zeta", doc: null },
86
+ { site: "alpha", doc: null },
87
+ { site: "mid", doc: null },
88
+ ]);
89
+ expect(catalog.sites.map((s) => s.site)).toEqual(["alpha", "mid", "zeta"]);
90
+ });
91
+
92
+ test("buildFleetAgentCatalog: is deterministic across repeated calls (byte-identical)", () => {
93
+ const input = [
94
+ { site: "alpha", doc: sampleDoc },
95
+ { site: "beta", doc: null },
96
+ ];
97
+ const a = buildFleetAgentCatalog(input);
98
+ const b = buildFleetAgentCatalog(input);
99
+ expect(JSON.stringify(a)).toBe(JSON.stringify(b));
100
+ expect(a.contentHash).toBe(b.contentHash);
101
+ });
102
+
103
+ test("buildFleetAgentCatalog: empty input ⇒ valid catalog with empty sites", () => {
104
+ const catalog = buildFleetAgentCatalog([]);
105
+ expect(catalog.sites).toEqual([]);
106
+ expect(catalog.schema).toBe(FLEET_AGENT_CATALOG_SCHEMA);
107
+ expect(catalog.contentHash).toMatch(/^[0-9a-f]{64}$/);
108
+ });
109
+
110
+ test("buildFleetAgentCatalog: contentHash is a 64-char lowercase hex sha256", () => {
111
+ const catalog = buildFleetAgentCatalog([{ site: "alpha", doc: sampleDoc }]);
112
+ expect(catalog.contentHash).toMatch(/^[0-9a-f]{64}$/);
113
+ });
114
+
115
+ test("computeFleetCatalogContentHash: matches the builder's hash for the same sites", () => {
116
+ const catalog = buildFleetAgentCatalog([
117
+ { site: "alpha", doc: sampleDoc },
118
+ { site: "beta", doc: null },
119
+ ]);
120
+ const recomputed = computeFleetCatalogContentHash(catalog.sites);
121
+ expect(recomputed).toBe(catalog.contentHash);
122
+ });
123
+
124
+ test("buildFleetAgentCatalog: input array order does not affect output (sorted)", () => {
125
+ const a = buildFleetAgentCatalog([
126
+ { site: "alpha", doc: sampleDoc },
127
+ { site: "beta", doc: null },
128
+ ]);
129
+ const b = buildFleetAgentCatalog([
130
+ { site: "beta", doc: null },
131
+ { site: "alpha", doc: sampleDoc },
132
+ ]);
133
+ expect(JSON.stringify(a)).toBe(JSON.stringify(b));
134
+ });