@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,172 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0169: the closed paid-feature catalog, the resolved-entitlements shape, and the
4
+ isEntitled() reader. Stripe Entitlements is the source of truth; this module is the framework-
5
+ agnostic contract consumed by the build-time resolver, the feature gates, and runtime endpoints.</purpose>
6
+ <non-goals>
7
+ <item>Do not call Stripe here — the resolver command (site OS) performs network I/O and passes
8
+ the mapped lookup keys to resolveEntitlements().</item>
9
+ <item>Do not build a custom entitlements store — Stripe is the source of truth.</item>
10
+ </non-goals>
11
+ </MODULE_CONTRACT>
12
+ <CHANGE_SUMMARY>
13
+ <item>RFC-0169: initial implementation.</item>
14
+ <item>RFC-0706: add nachweis feature for Nachweisregister commercial module.</item>
15
+ <item>RFC-0741: add multi-currency feature for multi-currency build pipeline.</item>
16
+ </CHANGE_SUMMARY>
17
+ */
18
+
19
+ /** Closed catalog of paid features that can gate a site's compiled modules. */
20
+ export const ENTITLED_FEATURES = [
21
+ "blog",
22
+ "integrations.channels",
23
+ "integrations.crm",
24
+ "integrations.chat",
25
+ "analytics",
26
+ "pseo",
27
+ "team.profiles",
28
+ // RFC-0240: Angebot modular productization
29
+ "offer",
30
+ "booking",
31
+ "trust",
32
+ "i18n-extra",
33
+ "automation",
34
+ // RFC-0288: Agent Surface action tier (AI-agent-invocable capabilities)
35
+ "agent.actions",
36
+ // RFC-0706 / ADR-0028: Nachweisregister commercial module
37
+ "nachweis",
38
+ // RFC-0741: multi-currency entitled feature
39
+ "multi-currency",
40
+ ] as const;
41
+
42
+ export type EntitledFeature = (typeof ENTITLED_FEATURES)[number];
43
+
44
+ /** Stripe Feature `lookup_key` → EntitledFeature. The single mapping point. */
45
+ export const STRIPE_FEATURE_LOOKUP_MAP: Record<string, EntitledFeature> = {
46
+ feature_blog: "blog",
47
+ feature_integrations_channels: "integrations.channels",
48
+ feature_integrations_crm: "integrations.crm",
49
+ feature_integrations_chat: "integrations.chat",
50
+ feature_analytics: "analytics",
51
+ feature_pseo: "pseo",
52
+ feature_team_profiles: "team.profiles",
53
+ // RFC-0240
54
+ feature_offer: "offer",
55
+ feature_booking: "booking",
56
+ feature_trust: "trust",
57
+ feature_i18n_extra: "i18n-extra",
58
+ feature_automation: "automation",
59
+ // RFC-0288
60
+ feature_agent_actions: "agent.actions",
61
+ // RFC-0706
62
+ feature_nachweis: "nachweis",
63
+ // RFC-0741
64
+ feature_multi_currency: "multi-currency",
65
+ };
66
+
67
+ /**
68
+ * RFC-0196: Stripe Feature `lookup_key` → Programmatic Surface index budget (top-K indexable pages
69
+ * by substance). A site's tier is the highest budget among its active entitlement lookup-keys.
70
+ */
71
+ export const PSEO_TIER_BUDGET: Record<string, number> = {
72
+ feature_pseo: 12, // base tier "Быть найденным" — ≈12 target pages
73
+ feature_pseo_regional: 500, // regional-hub upsell — unlocks d3–d4
74
+ feature_pseo_pro: 5000,
75
+ feature_pseo_scale: 50000,
76
+ };
77
+
78
+ /** RFC-0240: tiers that unlock the d3 (region) and d4 levels of the local family. */
79
+ export const PSEO_REGIONAL_TIERS = [
80
+ "feature_pseo_regional",
81
+ "feature_pseo_pro",
82
+ "feature_pseo_scale",
83
+ ] as const;
84
+
85
+ /** Resolve the index budget for a set of active Stripe lookup-keys (max tier; undefined ⇒ none). */
86
+ export function resolvePseoBudget(lookupKeys: readonly string[]): number | undefined {
87
+ let budget: number | undefined;
88
+ for (const key of lookupKeys) {
89
+ const tier = PSEO_TIER_BUDGET[key];
90
+ if (tier !== undefined) budget = budget === undefined ? tier : Math.max(budget, tier);
91
+ }
92
+ return budget;
93
+ }
94
+
95
+ export type EntitlementSource = "stripe" | "override" | "none";
96
+
97
+ /** The generated, content-driven feature set written by `entitlements.resolve`. */
98
+ export interface ResolvedEntitlements {
99
+ customerId: string | null;
100
+ features: EntitledFeature[];
101
+ source: EntitlementSource;
102
+ /**
103
+ * RFC-0196: Programmatic Surface tier. `indexBudget` caps the number of indexable generated
104
+ * pages (top-K by substance score); absent ⇒ unbounded (fail-open). Set by the tier mapping.
105
+ */
106
+ pseo?: {
107
+ indexBudget?: number;
108
+ /** RFC-0240: whether the resolved tier is the regional-hub tier or higher (unlocks d3–d4). */
109
+ regionalUnlocked?: boolean;
110
+ };
111
+ }
112
+
113
+ export function isValidFeature(value: string): value is EntitledFeature {
114
+ return (ENTITLED_FEATURES as readonly string[]).includes(value);
115
+ }
116
+
117
+ /**
118
+ * Pure resolver. The site-OS command performs the Stripe network call and passes the
119
+ * mapped lookup keys as `stripeFeatures`; an offline `override` wins for dev/CI.
120
+ */
121
+ export function resolveEntitlements(input: {
122
+ customerId: string | null;
123
+ override?: string[];
124
+ stripeFeatures?: string[];
125
+ /** RFC-0196: optional Programmatic Surface index budget (tier-derived). */
126
+ pseoIndexBudget?: number;
127
+ /**
128
+ * RFC-0240: explicit regional-hub-or-higher unlock for offline/override mode, where no Stripe
129
+ * lookup key is available to derive it from.
130
+ */
131
+ pseoRegionalUnlocked?: boolean;
132
+ /** RFC-0240: raw Stripe lookup keys (Stripe mode only) — used to derive the regional-tier unlock. */
133
+ stripeLookupKeys?: string[];
134
+ }): ResolvedEntitlements {
135
+ const dedupe = (values: string[]): EntitledFeature[] =>
136
+ Array.from(new Set(values.filter(isValidFeature)));
137
+ const regionalUnlocked =
138
+ input.pseoRegionalUnlocked ??
139
+ (input.stripeLookupKeys ?? []).some((key) =>
140
+ (PSEO_REGIONAL_TIERS as readonly string[]).includes(key),
141
+ );
142
+ const pseo =
143
+ typeof input.pseoIndexBudget === "number" || regionalUnlocked
144
+ ? { pseo: { indexBudget: input.pseoIndexBudget, regionalUnlocked } }
145
+ : {};
146
+
147
+ if (input.override && input.override.length > 0) {
148
+ return {
149
+ customerId: input.customerId,
150
+ features: dedupe(input.override),
151
+ source: "override",
152
+ ...pseo,
153
+ };
154
+ }
155
+ if (input.stripeFeatures) {
156
+ return {
157
+ customerId: input.customerId,
158
+ features: dedupe(input.stripeFeatures),
159
+ source: "stripe",
160
+ ...pseo,
161
+ };
162
+ }
163
+ return { customerId: input.customerId, features: [], source: "none", ...pseo };
164
+ }
165
+
166
+ /** Reader used by build gates (which modules compile) and runtime endpoints. */
167
+ export function isEntitled(
168
+ resolved: ResolvedEntitlements | null | undefined,
169
+ feature: EntitledFeature,
170
+ ): boolean {
171
+ return !!resolved?.features.includes(feature);
172
+ }
@@ -0,0 +1,73 @@
1
+ /***********************************************
2
+ <MODULE_CONTRACT>
3
+ <purpose>Defines type declarations for middleware and content management in the Astro framework.</purpose>
4
+ <non-goals>
5
+ <item>Do not implement middleware logic or content retrieval.</item>
6
+ <item>Do not handle raw data parsing or transformation.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
11
+ </CHANGE_SUMMARY>
12
+ ***********************************************/
13
+
14
+ /// <reference types="astro/client" />
15
+ declare module "astro:middleware" {
16
+ export function defineMiddleware(fn: any): any;
17
+ export function sequence(...middlewares: any[]): any;
18
+ }
19
+ declare module "astro:content" {
20
+ export function getEntry(collection: string, id: string): Promise<any>;
21
+ export function getCollection(collection: string, filter?: any): Promise<any[]>;
22
+ }
23
+ // Ambient shim for the per-app GENERATED env schema (api.routes.generate writes the
24
+ // real schema into apps/*/src/env.schema.generated.mjs). In the package-typecheck
25
+ // context there is no app schema, so we declare the secret names the section API
26
+ // handlers import. Keep in sync with the Integration Port secret catalog
27
+ // (@warpgogol/werkstatt-site/integration) + section `api[].secrets`. All optional — a missing
28
+ // value degrades to the handler's error path, never a build/runtime throw.
29
+ declare module "astro:env/server" {
30
+ // RFC-0168 Integration Port — channels + CRM (send-message handler).
31
+ export const INTEGRATION_TELEGRAM_BOT_TOKEN: string | undefined;
32
+ export const INTEGRATION_TELEGRAM_CHAT_ID: string | undefined;
33
+ // RFC-0181: email is sent via Cloudflare Email Routing (no Resend API key). TO is a
34
+ // verified destination address; FROM a verified sender on the zone domain.
35
+ export const INTEGRATION_EMAIL_TO: string | undefined;
36
+ export const INTEGRATION_EMAIL_FROM: string | undefined;
37
+ export const INTEGRATION_WHATSAPP_TOKEN: string | undefined;
38
+ export const INTEGRATION_WHATSAPP_PHONE_ID: string | undefined;
39
+ export const INTEGRATION_WHATSAPP_TO: string | undefined;
40
+ export const INTEGRATION_PIPEDRIVE_API_TOKEN: string | undefined;
41
+ export const INTEGRATION_PIPEDRIVE_DOMAIN: string | undefined;
42
+ // RFC-0176 inbound hub (chat-widget inbound handler).
43
+ export const INTEGRATION_INBOUND_SECRET: string | undefined;
44
+ // RFC-0181 EU-resident delivery — Upstash QStash + Redis (eu-central-1).
45
+ export const UPSTASH_QSTASH_URL: string | undefined;
46
+ export const UPSTASH_QSTASH_TOKEN: string | undefined;
47
+ export const UPSTASH_QSTASH_CURRENT_SIGNING_KEY: string | undefined;
48
+ export const UPSTASH_QSTASH_NEXT_SIGNING_KEY: string | undefined;
49
+ export const UPSTASH_REDIS_REST_URL: string | undefined;
50
+ export const UPSTASH_REDIS_REST_TOKEN: string | undefined;
51
+ // Lagebild MVP: Supabase CRM buffer (self-enabling — adapter skips when absent).
52
+ export const SUPABASE_BUFFER_URL: string | undefined;
53
+ export const SUPABASE_BUFFER_SERVICE_KEY: string | undefined;
54
+ export const SUPABASE_BUFFER_TENANT_ID: string | undefined;
55
+ // RFC-0191: Stripe billing — the webhook signing secret (stripe-webhook route) and the
56
+ // server API key (billing client). Per-tenant; self-enabling — handlers fail-closed when absent.
57
+ export const STRIPE_WEBHOOK_SECRET: string | undefined;
58
+ export const STRIPE_SECRET_KEY: string | undefined;
59
+ }
60
+
61
+ // RFC-0176/0181: Cloudflare Workers runtime bindings. Astro v6 removed
62
+ // Astro.locals.runtime.env — handlers read bindings from this global instead.
63
+ // RFC-0181 uses the send_email binding for Cloudflare Email Routing.
64
+ declare module "cloudflare:workers" {
65
+ export const env: Record<string, unknown>;
66
+ }
67
+
68
+ // RFC-0181: Cloudflare Email Routing message type (send_email binding).
69
+ declare module "cloudflare:email" {
70
+ export class EmailMessage {
71
+ constructor(from: string, to: string, raw: string);
72
+ }
73
+ }
@@ -0,0 +1,367 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Runtime resolver for RFC-0183 Feature Policy embedded in RFC-0047 content domains.</purpose>
4
+ <non-goals>
5
+ <item>Do not import from astro:content or other framework-specific modules.</item>
6
+ <item>Do not modify content files.</item>
7
+ <item>Do not handle remote feature flag services.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0183: Initial runtime resolver for Feature Policy without Feature Graph dependency.</item>
12
+ <item>Removed false async from resolveFeaturePolicy; resolveFeaturePolicySync is now a thin alias.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import type {
17
+ FeaturePolicy,
18
+ FeaturePolicyBehaviorValue,
19
+ FeaturePolicyTargetRef,
20
+ FeaturePolicyVisibility,
21
+ ResolvedFeaturePolicy,
22
+ } from "./schemas/features.ts";
23
+
24
+ // Re-export types for consumers
25
+ export type {
26
+ FeaturePolicy,
27
+ FeaturePolicyBehaviorValue,
28
+ FeaturePolicyTargetKind,
29
+ FeaturePolicyTargetRef,
30
+ FeaturePolicyVisibility,
31
+ ResolvedFeaturePolicy,
32
+ } from "./schemas/features.ts";
33
+
34
+ export interface FeaturePolicyResolverOptions {
35
+ /**
36
+ * Default language code for policy resolution when localized policy absent.
37
+ */
38
+ defaultLanguage?: string;
39
+ /**
40
+ * Whether to cache resolved policy. Default true.
41
+ */
42
+ cache?: boolean;
43
+ /**
44
+ * Platform default visibility when no policy found anywhere.
45
+ * RFC-0183 default is "enabled".
46
+ */
47
+ platformDefaultVisibility?: FeaturePolicyVisibility;
48
+ }
49
+
50
+ /**
51
+ * Narrow interface for content nodes that may carry policy.
52
+ * Resolver works with any object matching this shape, allowing
53
+ * framework-neutral resolution from Astro content, plain objects, etc.
54
+ */
55
+ export interface PolicyCarryingNode {
56
+ id?: string;
57
+ policy?: FeaturePolicy;
58
+ blocks?: PolicyCarryingNode[];
59
+ items?: PolicyCarryingNode[];
60
+ components?: PolicyCarryingNode[];
61
+ [key: string]: unknown;
62
+ }
63
+
64
+ /**
65
+ * Content context passed to resolver.
66
+ * Contains already-loaded content from RFC-0047 domains.
67
+ */
68
+ export interface FeaturePolicyContentContext {
69
+ /** Site-level policy defaults from system.md */
70
+ systemPolicy?: FeaturePolicy;
71
+ /** Site-level defaults from site/{lang}/config or similar */
72
+ siteDefaults?: FeaturePolicy;
73
+ /** Current page content node */
74
+ page?: PolicyCarryingNode;
75
+ /** Current language */
76
+ lang?: string;
77
+ /** Default language for fallback */
78
+ defaultLanguage?: string;
79
+ }
80
+
81
+ // Resolution is pure given (context, target, lang, platformDefault). The cache is
82
+ // therefore scoped to the content CONTEXT — a WeakMap keyed by context identity — so two
83
+ // different contexts never share resolved policy for the same target+lang.
84
+ // Bug history (RFC-0183): a module-global array keyed by target+lang ALONE silently
85
+ // returned one context's result for another, clobbering page-level policy. Do not
86
+ // reintroduce a context-agnostic global cache.
87
+ const MAX_CACHE_SIZE = 100;
88
+ let contextCaches = new WeakMap<FeaturePolicyContentContext, Map<string, ResolvedFeaturePolicy>>();
89
+
90
+ function getCacheKey(
91
+ target: FeaturePolicyTargetRef,
92
+ lang: string,
93
+ platformDefault: FeaturePolicyVisibility,
94
+ ): string {
95
+ const parts = [target.kind, lang, `d:${platformDefault}`];
96
+ if (target.pageId) parts.push(`p:${target.pageId}`);
97
+ if (target.blockId) parts.push(`b:${target.blockId}`);
98
+ if (target.componentId) parts.push(`c:${target.componentId}`);
99
+ if (target.itemId) parts.push(`i:${target.itemId}`);
100
+ return parts.join("|");
101
+ }
102
+
103
+ function getCached(
104
+ context: FeaturePolicyContentContext,
105
+ key: string,
106
+ ): ResolvedFeaturePolicy | undefined {
107
+ return contextCaches.get(context)?.get(key);
108
+ }
109
+
110
+ function setCached(
111
+ context: FeaturePolicyContentContext,
112
+ key: string,
113
+ result: ResolvedFeaturePolicy,
114
+ ): void {
115
+ let cache = contextCaches.get(context);
116
+ if (!cache) {
117
+ cache = new Map();
118
+ contextCaches.set(context, cache);
119
+ }
120
+ // Evict oldest (Map preserves insertion order) when at capacity.
121
+ if (cache.size >= MAX_CACHE_SIZE) {
122
+ const oldest = cache.keys().next().value;
123
+ if (oldest !== undefined) cache.delete(oldest);
124
+ }
125
+ cache.set(key, result);
126
+ }
127
+
128
+ /** Drops all cached resolutions. Per-context caches are also GC'd with their context. */
129
+ export function clearFeaturePolicyCache(): void {
130
+ contextCaches = new WeakMap();
131
+ }
132
+
133
+ /**
134
+ * Search for policy in a node tree by id path.
135
+ * Handles nested structures: page → blocks → components → items.
136
+ */
137
+ function findPolicyInNode(
138
+ node: PolicyCarryingNode,
139
+ target: FeaturePolicyTargetRef,
140
+ ): FeaturePolicy | null {
141
+ // Direct match on current node
142
+ if (
143
+ (target.blockId && node.id === target.blockId) ||
144
+ (target.componentId && node.id === target.componentId) ||
145
+ (target.itemId && node.id === target.itemId)
146
+ ) {
147
+ return node.policy ?? null;
148
+ }
149
+
150
+ // Search in blocks
151
+ if (target.blockId && node.blocks) {
152
+ for (const block of node.blocks) {
153
+ if (block.id === target.blockId) {
154
+ // If looking for nested component/item within this block
155
+ if (target.componentId && block.components) {
156
+ const comp = block.components.find((c) => c.id === target.componentId);
157
+ if (comp) {
158
+ if (target.itemId && comp.items) {
159
+ const item = comp.items.find((i) => i.id === target.itemId);
160
+ if (item?.policy) return item.policy;
161
+ }
162
+ return comp.policy ?? null;
163
+ }
164
+ }
165
+ return block.policy ?? null;
166
+ }
167
+ // Deep search if not matched by id
168
+ const deep = findPolicyInNode(block, target);
169
+ if (deep) return deep;
170
+ }
171
+ }
172
+
173
+ // Search in items (for shared components)
174
+ if (target.itemId && node.items) {
175
+ const item = node.items.find((i) => i.id === target.itemId);
176
+ if (item?.policy) return item.policy;
177
+ }
178
+
179
+ // Search in components
180
+ if (target.componentId && node.components) {
181
+ const comp = node.components.find((c) => c.id === target.componentId);
182
+ if (comp) {
183
+ if (target.itemId && comp.items) {
184
+ const item = comp.items.find((i) => i.id === target.itemId);
185
+ if (item?.policy) return item.policy;
186
+ }
187
+ return comp.policy ?? null;
188
+ }
189
+ }
190
+
191
+ // Recursive search in any nested structure
192
+ const nestedArrays = [node.blocks, node.components, node.items].filter(Boolean);
193
+ for (const arr of nestedArrays) {
194
+ for (const child of arr ?? []) {
195
+ const found = findPolicyInNode(child, target);
196
+ if (found) return found;
197
+ }
198
+ }
199
+
200
+ return null;
201
+ }
202
+
203
+ /**
204
+ * RFC-0183 resolution order:
205
+ * 1. Explicit policy on content node
206
+ * 2. Page-level policy (via page content context)
207
+ * 3. System-level policy for matching ids
208
+ * 4. Site-level defaults
209
+ * 5. Platform default: visibility=enabled, empty behavior
210
+ */
211
+ function resolvePolicyInternal(
212
+ target: FeaturePolicyTargetRef,
213
+ context: FeaturePolicyContentContext,
214
+ platformDefault: FeaturePolicyVisibility,
215
+ ): ResolvedFeaturePolicy {
216
+ const lang = target.lang ?? context.lang;
217
+ const defaultLang = context.defaultLanguage;
218
+ if (!lang || !defaultLang) {
219
+ throw new Error("[feature-policy] lang and defaultLanguage are required.");
220
+ }
221
+
222
+ // 1. Try explicit node policy via page context
223
+ let foundPolicy: FeaturePolicy | null = null;
224
+ let inheritedFrom: FeaturePolicyTargetRef | undefined;
225
+
226
+ if (context.page) {
227
+ foundPolicy = findPolicyInNode(context.page, target);
228
+ if (foundPolicy) {
229
+ inheritedFrom = { ...target, lang };
230
+ }
231
+ }
232
+
233
+ // 2. Try system-level policy if id-based lookup exists there
234
+ // (System policy stored as flat record keyed by "page:home/block:hero")
235
+ if (!foundPolicy && context.systemPolicy) {
236
+ const systemKey = buildSystemKey(target);
237
+ if (systemKey && (context.systemPolicy as Record<string, FeaturePolicy>)[systemKey]) {
238
+ foundPolicy = (context.systemPolicy as Record<string, FeaturePolicy>)[systemKey];
239
+ inheritedFrom = { kind: "page" as const, lang: defaultLang }; // system is lang-agnostic
240
+ }
241
+ }
242
+
243
+ // 3. Site-level defaults
244
+ if (!foundPolicy && context.siteDefaults) {
245
+ foundPolicy = context.siteDefaults;
246
+ inheritedFrom = { kind: "page" as const, lang: defaultLang };
247
+ }
248
+
249
+ // 4. Build resolved result with defaults
250
+ const resolved: ResolvedFeaturePolicy = {
251
+ target,
252
+ visibility: foundPolicy?.visibility ?? platformDefault,
253
+ behavior: foundPolicy?.behavior ?? {},
254
+ sourcePath: buildSourcePath(foundPolicy, inheritedFrom),
255
+ inheritedFrom,
256
+ };
257
+
258
+ return resolved;
259
+ }
260
+
261
+ function buildSystemKey(target: FeaturePolicyTargetRef): string | null {
262
+ const parts: string[] = [];
263
+ if (target.pageId) parts.push(`page:${target.pageId}`);
264
+ if (target.blockId) parts.push(`block:${target.blockId}`);
265
+ if (target.componentId) parts.push(`component:${target.componentId}`);
266
+ if (target.itemId) parts.push(`item:${target.itemId}`);
267
+ return parts.length > 0 ? parts.join("/") : null;
268
+ }
269
+
270
+ function buildSourcePath(policy: FeaturePolicy | null, inherited?: FeaturePolicyTargetRef): string {
271
+ if (!policy) return "platform-default";
272
+ if (inherited) {
273
+ const parts: string[] = [inherited.kind];
274
+ if (inherited.pageId) parts.push(inherited.pageId);
275
+ if (inherited.blockId) parts.push(inherited.blockId);
276
+ if (inherited.componentId) parts.push(inherited.componentId);
277
+ if (inherited.itemId) parts.push(inherited.itemId);
278
+ return parts.join("/");
279
+ }
280
+ return "node-explicit";
281
+ }
282
+
283
+ /**
284
+ * Resolve feature policy for a target.
285
+ * Resolution is synchronous given loaded content context.
286
+ * Kept as the canonical name; resolveFeaturePolicySync is an alias.
287
+ */
288
+ export function resolveFeaturePolicy(
289
+ target: FeaturePolicyTargetRef,
290
+ context: FeaturePolicyContentContext,
291
+ options: FeaturePolicyResolverOptions = {},
292
+ ): ResolvedFeaturePolicy {
293
+ const { cache = true, platformDefaultVisibility = "enabled" } = options;
294
+ const lang = target.lang ?? context.lang;
295
+ if (!lang) {
296
+ throw new Error("[feature-policy] lang is required.");
297
+ }
298
+
299
+ if (cache) {
300
+ const key = getCacheKey(target, lang, platformDefaultVisibility);
301
+ const cached = getCached(context, key);
302
+ if (cached) return cached;
303
+
304
+ const result = resolvePolicyInternal(target, context, platformDefaultVisibility);
305
+ setCached(context, key, result);
306
+ return result;
307
+ }
308
+
309
+ return resolvePolicyInternal(target, context, platformDefaultVisibility);
310
+ }
311
+
312
+ /**
313
+ * Alias for resolveFeaturePolicy (kept for call sites that explicitly want the
314
+ * synchronous signature). Resolution was always synchronous.
315
+ */
316
+ export function resolveFeaturePolicySync(
317
+ target: FeaturePolicyTargetRef,
318
+ context: FeaturePolicyContentContext,
319
+ options: FeaturePolicyResolverOptions = {},
320
+ ): ResolvedFeaturePolicy {
321
+ return resolveFeaturePolicy(target, context, options);
322
+ }
323
+
324
+ /**
325
+ * Factory for creating a configured resolver bound to specific content context.
326
+ */
327
+ export function createFeaturePolicyResolver(
328
+ context: FeaturePolicyContentContext,
329
+ options: FeaturePolicyResolverOptions = {},
330
+ ) {
331
+ return {
332
+ resolve: (target: FeaturePolicyTargetRef) => resolveFeaturePolicy(target, context, options),
333
+ resolveSync: (target: FeaturePolicyTargetRef) =>
334
+ resolveFeaturePolicySync(target, context, options),
335
+ isEnabled: (target: FeaturePolicyTargetRef) =>
336
+ isPolicyEnabled(resolveFeaturePolicySync(target, context, options)),
337
+ getBehavior: (
338
+ target: FeaturePolicyTargetRef,
339
+ key: string,
340
+ defaultValue?: FeaturePolicyBehaviorValue,
341
+ ) => getPolicyBehavior(resolveFeaturePolicySync(target, context, options), key, defaultValue),
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Predicate: is the resolved policy effectively enabled?
347
+ * Treats "enabled" as true, "disabled"/"hidden" as false, "draft" depends on environment.
348
+ */
349
+ export function isPolicyEnabled(policy: ResolvedFeaturePolicy, isDraftAllowed = false): boolean {
350
+ if (policy.visibility === "enabled") return true;
351
+ if (policy.visibility === "disabled" || policy.visibility === "hidden") return false;
352
+ if (policy.visibility === "draft") return isDraftAllowed;
353
+ return true; // unknown defaults to enabled per RFC-0183
354
+ }
355
+
356
+ /**
357
+ * Extract behavior value with optional default.
358
+ */
359
+ export function getPolicyBehavior(
360
+ policy: ResolvedFeaturePolicy,
361
+ key: string,
362
+ defaultValue?: FeaturePolicyBehaviorValue,
363
+ ): FeaturePolicyBehaviorValue {
364
+ const value = policy.behavior[key];
365
+ if (value === undefined) return defaultValue ?? null;
366
+ return value as FeaturePolicyBehaviorValue;
367
+ }