@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,184 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Closed metric catalog for the Warpgogol observability port (RFC-0337). Every metric emitted anywhere in the ecosystem MUST be declared here.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node: modules — must be Workers-compatible.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0337: initial implementation with smoke metric only. RFC-0340/0341/0343 append entries.</item>
10
+ <item>RFC-0807: add back prefix metrics for service health monitoring.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ export type WarpgogolMetricKind = "counter" | "gauge" | "histogram";
15
+
16
+ export interface WarpgogolMetricSpec {
17
+ name: string;
18
+ kind: WarpgogolMetricKind;
19
+ help: string;
20
+ labelKeys: readonly string[];
21
+ unit?: string;
22
+ buckets?: readonly number[];
23
+ }
24
+
25
+ export const METRIC_NAME_PATTERN = /^warpgogol_(factory|probe|delivery|workers|back)_[a-z0-9_]+$/;
26
+
27
+ export const FORBIDDEN_LABEL_KEYS: readonly string[] = [
28
+ "user_id",
29
+ "session_id",
30
+ "request_id",
31
+ "run_id",
32
+ "url",
33
+ "path",
34
+ "email",
35
+ ];
36
+
37
+ export const WARPGOGOL_METRIC_REGISTRY: readonly WarpgogolMetricSpec[] = [
38
+ {
39
+ name: "warpgogol_factory_smoke_total",
40
+ kind: "counter",
41
+ help: "Smoke-test counter emitted by observability.factory.smoke to verify the OTLP pipe end-to-end (RFC-0340).",
42
+ labelKeys: [],
43
+ unit: "1",
44
+ },
45
+ {
46
+ name: "warpgogol_factory_command_runs_total",
47
+ kind: "counter",
48
+ help: "Total kernel command executions by command name and outcome (RFC-0340).",
49
+ labelKeys: ["command", "status", "site_id"],
50
+ unit: "1",
51
+ },
52
+ {
53
+ name: "warpgogol_factory_command_duration_seconds",
54
+ kind: "histogram",
55
+ help: "Kernel command execution duration in seconds (RFC-0340).",
56
+ labelKeys: ["command", "site_id"],
57
+ unit: "s",
58
+ buckets: [0.5, 1, 2, 5, 10, 30, 60, 120, 300, 600],
59
+ },
60
+ {
61
+ name: "warpgogol_factory_diagnostics_total",
62
+ kind: "counter",
63
+ help: "Total diagnostics emitted by kernel commands by severity (RFC-0340).",
64
+ labelKeys: ["command", "severity", "site_id"],
65
+ unit: "1",
66
+ },
67
+ {
68
+ name: "warpgogol_probe_up",
69
+ kind: "gauge",
70
+ help: "1 = site route is up (status 2xx/3xx AND sentinel ok); else 0 (RFC-0341).",
71
+ labelKeys: ["site_id", "route"],
72
+ unit: "1",
73
+ },
74
+ {
75
+ name: "warpgogol_probe_ttfb_seconds",
76
+ kind: "gauge",
77
+ help: "Last observed time-to-first-byte in seconds (RFC-0341).",
78
+ labelKeys: ["site_id", "route"],
79
+ unit: "s",
80
+ },
81
+ {
82
+ name: "warpgogol_probe_http_status_class_total",
83
+ kind: "counter",
84
+ help: "Total probe requests by HTTP status class (RFC-0341).",
85
+ labelKeys: ["site_id", "route", "status_class"],
86
+ unit: "1",
87
+ },
88
+ {
89
+ name: "warpgogol_probe_content_ok",
90
+ kind: "gauge",
91
+ help: "1 = content sentinel matched; 0 = sentinel failed (RFC-0341).",
92
+ labelKeys: ["site_id", "route"],
93
+ unit: "1",
94
+ },
95
+ {
96
+ name: "warpgogol_probe_cert_expiry_days",
97
+ kind: "gauge",
98
+ help: "Days until TLS certificate expiry (RFC-0341).",
99
+ labelKeys: ["site_id"],
100
+ unit: "d",
101
+ },
102
+ {
103
+ name: "warpgogol_probe_deep_ok",
104
+ kind: "gauge",
105
+ help: "Phase 2 deep probe result: 1 = ok, 0 = failed (RFC-0341 Phase 2).",
106
+ labelKeys: ["site_id"],
107
+ unit: "1",
108
+ },
109
+ {
110
+ name: "warpgogol_delivery_requests_total",
111
+ kind: "counter",
112
+ help: "Total HTTP requests served by Cloudflare per site, cache status, and status class (RFC-0343).",
113
+ labelKeys: ["site_id", "cache_status", "status_class"],
114
+ unit: "1",
115
+ },
116
+ {
117
+ name: "warpgogol_delivery_bytes_total",
118
+ kind: "counter",
119
+ help: "Total bytes served by Cloudflare per site (RFC-0343).",
120
+ labelKeys: ["site_id"],
121
+ unit: "By",
122
+ },
123
+ {
124
+ name: "warpgogol_workers_requests_total",
125
+ kind: "counter",
126
+ help: "Total Cloudflare Worker invocations per site (RFC-0343).",
127
+ labelKeys: ["site_id"],
128
+ unit: "1",
129
+ },
130
+ {
131
+ name: "warpgogol_workers_errors_total",
132
+ kind: "counter",
133
+ help: "Total Cloudflare Worker errors per site (RFC-0343).",
134
+ labelKeys: ["site_id"],
135
+ unit: "1",
136
+ },
137
+ {
138
+ name: "warpgogol_back_requests_total",
139
+ kind: "counter",
140
+ help: "Total HTTP requests served by backend services (RFC-0807).",
141
+ labelKeys: ["service", "status_class"],
142
+ unit: "1",
143
+ },
144
+ {
145
+ name: "warpgogol_back_up",
146
+ kind: "gauge",
147
+ help: "1 = service self-reports healthy; 0 = unhealthy (RFC-0807).",
148
+ labelKeys: ["service"],
149
+ unit: "1",
150
+ },
151
+ {
152
+ name: "warpgogol_back_last_run_total",
153
+ kind: "counter",
154
+ help: "Total scheduled runs by outcome (success/failure) (RFC-0807).",
155
+ labelKeys: ["service", "status"],
156
+ unit: "1",
157
+ },
158
+ {
159
+ name: "warpgogol_back_last_error_total",
160
+ kind: "counter",
161
+ help: "Total errors encountered by the service (RFC-0807).",
162
+ labelKeys: ["service"],
163
+ unit: "1",
164
+ },
165
+ {
166
+ name: "warpgogol_back_queue_depth",
167
+ kind: "gauge",
168
+ help: "Current queue depth (items pending processing) (RFC-0807).",
169
+ labelKeys: ["service"],
170
+ unit: "1",
171
+ },
172
+ ];
173
+
174
+ export function findMetricSpec(name: string): WarpgogolMetricSpec | undefined {
175
+ return WARPGOGOL_METRIC_REGISTRY.find((spec) => spec.name === name);
176
+ }
177
+
178
+ export function isMetricNameValid(name: string): boolean {
179
+ return METRIC_NAME_PATTERN.test(name);
180
+ }
181
+
182
+ export function isLabelKeyForbidden(key: string): boolean {
183
+ return FORBIDDEN_LABEL_KEYS.includes(key);
184
+ }
@@ -0,0 +1,125 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Pure conversion from accumulated metric points to OTLP metric points (RFC-0337). Extracted from pusher.ts for testability without HTTP I/O.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node: modules — must be Workers-compatible.</item>
6
+ <item>Do not perform HTTP — that lives in pusher.ts.</item>
7
+ <item>Do not validate metric names/labels — pusher.ts validates at accumulation time.</item>
8
+ </non-goals>
9
+ @ai-invariant: Histogram bucket boundaries are inclusive — a value exactly on a bound goes into the lower bucket (value <= bounds[i]).
10
+ </MODULE_CONTRACT>
11
+ <CHANGE_SUMMARY>
12
+ <item>Extract pure OTLP conversion pipeline from pusher.ts flush() for interface-level testability.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import { findMetricSpec } from "./metric-registry.ts";
17
+ import {
18
+ nowUnixNano,
19
+ type OtlpGaugePoint,
20
+ type OtlpHistogramPoint,
21
+ type OtlpMetricPoint,
22
+ type OtlpSumPoint,
23
+ type UnixNanoString,
24
+ } from "./otlp-json.ts";
25
+ import type { OtlpKeyValue } from "./conventions.ts";
26
+
27
+ export interface AccumulatedPoint {
28
+ name: string;
29
+ labels: Record<string, string>;
30
+ value: number;
31
+ kind: "counter" | "gauge" | "histogram";
32
+ }
33
+
34
+ function labelsToAttributes(labels: Record<string, string>): OtlpKeyValue[] {
35
+ return Object.entries(labels).map(([key, value]) => ({
36
+ key,
37
+ value: { stringValue: value },
38
+ }));
39
+ }
40
+
41
+ export function convertAccumulatedToOtlp(
42
+ points: readonly AccumulatedPoint[],
43
+ startTimeNano: UnixNanoString,
44
+ ): OtlpMetricPoint[] {
45
+ const byName = new Map<string, AccumulatedPoint[]>();
46
+ for (const pt of points) {
47
+ const existing = byName.get(pt.name);
48
+ if (existing) {
49
+ existing.push(pt);
50
+ } else {
51
+ byName.set(pt.name, [pt]);
52
+ }
53
+ }
54
+
55
+ const otlpPoints: OtlpMetricPoint[] = [];
56
+
57
+ for (const [name, group] of byName) {
58
+ const spec = findMetricSpec(name);
59
+ if (!spec) continue;
60
+
61
+ if (spec.kind === "counter") {
62
+ const sumPoints: OtlpSumPoint[] = group.map((pt) => ({
63
+ asDouble: pt.value,
64
+ startTimeUnixNano: startTimeNano,
65
+ timeUnixNano: nowUnixNano(),
66
+ attributes: labelsToAttributes(pt.labels),
67
+ }));
68
+ otlpPoints.push({
69
+ name,
70
+ kind: "sum",
71
+ unit: spec.unit,
72
+ isMonotonic: true,
73
+ sumPoints,
74
+ });
75
+ } else if (spec.kind === "gauge") {
76
+ const gaugePoints: OtlpGaugePoint[] = group.map((pt) => ({
77
+ asDouble: pt.value,
78
+ timeUnixNano: nowUnixNano(),
79
+ attributes: labelsToAttributes(pt.labels),
80
+ }));
81
+ otlpPoints.push({
82
+ name,
83
+ kind: "gauge",
84
+ unit: spec.unit,
85
+ gaugePoints,
86
+ });
87
+ } else if (spec.kind === "histogram") {
88
+ const bounds = spec.buckets ?? [];
89
+ const bucketCounts = new Array(bounds.length + 1).fill(0) as number[];
90
+ let sum = 0;
91
+ for (const pt of group) {
92
+ sum += pt.value;
93
+ let placed = false;
94
+ for (let i = 0; i < bounds.length; i++) {
95
+ if (pt.value <= bounds[i]!) {
96
+ bucketCounts[i]!++;
97
+ placed = true;
98
+ break;
99
+ }
100
+ }
101
+ if (!placed) {
102
+ bucketCounts[bucketCounts.length - 1]!++;
103
+ }
104
+ }
105
+ const histogramPoints: OtlpHistogramPoint[] = [
106
+ {
107
+ startTimeUnixNano: startTimeNano,
108
+ timeUnixNano: nowUnixNano() as UnixNanoString,
109
+ count: String(group.length),
110
+ sum,
111
+ bucketCounts: bucketCounts.map(String),
112
+ explicitBounds: [...bounds],
113
+ },
114
+ ];
115
+ otlpPoints.push({
116
+ name,
117
+ kind: "histogram",
118
+ unit: spec.unit,
119
+ histogramPoints,
120
+ });
121
+ }
122
+ }
123
+
124
+ return otlpPoints;
125
+ }
@@ -0,0 +1,135 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>OTLP/HTTP JSON envelope encoding for metrics (RFC-0337). Produces the exact wire format consumed by SigNoz.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node: modules — must be Workers-compatible.</item>
6
+ <item>Do not perform HTTP — that lives in pusher.ts.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0337: initial implementation.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ import type { OtlpKeyValue } from "./conventions.ts";
15
+
16
+ export type UnixNanoString = string;
17
+
18
+ export function nowUnixNano(): UnixNanoString {
19
+ return String(Date.now() * 1_000_000);
20
+ }
21
+
22
+ export interface OtlpGaugePoint {
23
+ asDouble: number;
24
+ timeUnixNano: UnixNanoString;
25
+ attributes?: OtlpKeyValue[];
26
+ }
27
+
28
+ export interface OtlpSumPoint {
29
+ asDouble: number;
30
+ startTimeUnixNano: UnixNanoString;
31
+ timeUnixNano: UnixNanoString;
32
+ attributes?: OtlpKeyValue[];
33
+ }
34
+
35
+ export interface OtlpHistogramPoint {
36
+ startTimeUnixNano: UnixNanoString;
37
+ timeUnixNano: UnixNanoString;
38
+ count: string;
39
+ sum: number;
40
+ bucketCounts: string[];
41
+ explicitBounds: number[];
42
+ attributes?: OtlpKeyValue[];
43
+ }
44
+
45
+ export interface OtlpMetricPoint {
46
+ name: string;
47
+ kind: "gauge" | "sum" | "histogram";
48
+ unit?: string;
49
+ isMonotonic?: boolean;
50
+ gaugePoints?: OtlpGaugePoint[];
51
+ sumPoints?: OtlpSumPoint[];
52
+ histogramPoints?: OtlpHistogramPoint[];
53
+ buckets?: readonly number[];
54
+ }
55
+
56
+ export interface OtlpMetricsEnvelope {
57
+ resourceMetrics: Array<{
58
+ resource: { attributes: OtlpKeyValue[] };
59
+ scopeMetrics: Array<{
60
+ scope: { name: string; version: string };
61
+ metrics: unknown[];
62
+ }>;
63
+ }>;
64
+ }
65
+
66
+ export function encodeOtlpMetrics(
67
+ resourceAttributes: OtlpKeyValue[],
68
+ points: OtlpMetricPoint[],
69
+ scopeName: string,
70
+ scopeVersion: string,
71
+ ): OtlpMetricsEnvelope {
72
+ const metrics: unknown[] = [];
73
+
74
+ for (const point of points) {
75
+ if (point.kind === "gauge" && point.gaugePoints) {
76
+ metrics.push({
77
+ name: point.name,
78
+ ...(point.unit ? { unit: point.unit } : {}),
79
+ gauge: {
80
+ dataPoints: point.gaugePoints.map((dp) => ({
81
+ asDouble: dp.asDouble,
82
+ timeUnixNano: dp.timeUnixNano,
83
+ ...(dp.attributes ? { attributes: dp.attributes } : {}),
84
+ })),
85
+ },
86
+ });
87
+ } else if (point.kind === "sum" && point.sumPoints) {
88
+ metrics.push({
89
+ name: point.name,
90
+ ...(point.unit ? { unit: point.unit } : {}),
91
+ sum: {
92
+ aggregationTemporality: 1,
93
+ isMonotonic: point.isMonotonic ?? true,
94
+ dataPoints: point.sumPoints.map((dp) => ({
95
+ asDouble: dp.asDouble,
96
+ startTimeUnixNano: dp.startTimeUnixNano,
97
+ timeUnixNano: dp.timeUnixNano,
98
+ ...(dp.attributes ? { attributes: dp.attributes } : {}),
99
+ })),
100
+ },
101
+ });
102
+ } else if (point.kind === "histogram" && point.histogramPoints) {
103
+ metrics.push({
104
+ name: point.name,
105
+ ...(point.unit ? { unit: point.unit } : {}),
106
+ histogram: {
107
+ aggregationTemporality: 1,
108
+ dataPoints: point.histogramPoints.map((dp) => ({
109
+ startTimeUnixNano: dp.startTimeUnixNano,
110
+ timeUnixNano: dp.timeUnixNano,
111
+ count: dp.count,
112
+ sum: dp.sum,
113
+ bucketCounts: dp.bucketCounts,
114
+ explicitBounds: dp.explicitBounds,
115
+ ...(dp.attributes ? { attributes: dp.attributes } : {}),
116
+ })),
117
+ },
118
+ });
119
+ }
120
+ }
121
+
122
+ return {
123
+ resourceMetrics: [
124
+ {
125
+ resource: { attributes: resourceAttributes },
126
+ scopeMetrics: [
127
+ {
128
+ scope: { name: scopeName, version: scopeVersion },
129
+ metrics,
130
+ },
131
+ ],
132
+ },
133
+ ],
134
+ };
135
+ }
@@ -0,0 +1,68 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ OTLP transport port — abstracts the HTTP POST delivery of OTLP JSON.
5
+ Extracted from pusher.ts so the transport can be mocked in tests
6
+ without touching the network.
7
+ </purpose>
8
+ <non-goals>
9
+ <item>Do not accumulate or convert metrics — that stays in pusher.ts.</item>
10
+ </non-goals>
11
+ </MODULE_CONTRACT>
12
+ <CHANGE_SUMMARY>
13
+ <item>Architecture review 2026-07-14: extract OtlpTransport port from pusher.ts.</item>
14
+ </CHANGE_SUMMARY>
15
+ */
16
+
17
+ export interface OtlpTransport {
18
+ send(body: string, signal?: AbortSignal): Promise<{ delivered: boolean; reason?: string }>;
19
+ }
20
+
21
+ export interface OtlpHttpTransportOptions {
22
+ endpoint: string;
23
+ token: string;
24
+ timeoutMs?: number;
25
+ }
26
+
27
+ export function createOtlpHttpTransport(options: OtlpHttpTransportOptions): OtlpTransport {
28
+ const url = `${options.endpoint.replace(/\/$/, "")}/v1/metrics`;
29
+ const timeoutMs = options.timeoutMs ?? 2000;
30
+
31
+ return {
32
+ async send(body, signal) {
33
+ try {
34
+ const controller = new AbortController();
35
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
36
+
37
+ // If caller provided a signal, link it
38
+ if (signal) {
39
+ signal.addEventListener("abort", () => controller.abort(), { once: true });
40
+ }
41
+
42
+ try {
43
+ const response = await fetch(url, {
44
+ method: "POST",
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ Authorization: `Bearer ${options.token}`,
48
+ },
49
+ body,
50
+ signal: controller.signal,
51
+ });
52
+ if (!response.ok) {
53
+ return {
54
+ delivered: false,
55
+ reason: `HTTP ${response.status} ${response.statusText}`,
56
+ };
57
+ }
58
+ return { delivered: true };
59
+ } finally {
60
+ clearTimeout(timer);
61
+ }
62
+ } catch (error) {
63
+ const reason = error instanceof Error ? error.message : String(error);
64
+ return { delivered: false, reason };
65
+ }
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,142 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Minimal OTLP/HTTP JSON push client for metrics (RFC-0337). Zero-dependency, Workers-compatible, fire-and-forget.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node: modules — must be Workers-compatible.</item>
6
+ <item>Do not retry — one flush = at most one HTTP request.</item>
7
+ <item>Do not throw from flush — always resolve with delivery status.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0337: initial implementation.</item>
12
+ <item>Deepening: extract OTLP conversion to otlp-converter.ts pure function.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import { buildResourceAttributes, type WarpgogolResourceInput } from "./conventions.ts";
17
+ import { findMetricSpec, isLabelKeyForbidden, isMetricNameValid } from "./metric-registry.ts";
18
+ import { encodeOtlpMetrics, nowUnixNano } from "./otlp-json.ts";
19
+ import { convertAccumulatedToOtlp, type AccumulatedPoint } from "./otlp-converter.ts";
20
+ import type { OtlpTransport } from "./otlp-transport.ts";
21
+ import { createOtlpHttpTransport } from "./otlp-transport.ts";
22
+ import type { MetricsPusherEnv } from "./env-resolver.ts";
23
+ import { resolvePusherEnv, detectEnvironment } from "./env-resolver.ts";
24
+
25
+ export type { MetricsPusherEnv } from "./env-resolver.ts";
26
+
27
+ export interface MetricsPusher {
28
+ counterAdd(name: string, value: number, labels?: Record<string, string>): void;
29
+ gaugeSet(name: string, value: number, labels?: Record<string, string>): void;
30
+ histogramRecord(name: string, value: number, labels?: Record<string, string>): void;
31
+ flush(): Promise<{ delivered: boolean; reason?: string }>;
32
+ }
33
+
34
+ const SCOPE_NAME = "@warpgogol/werkstatt-shared/observability";
35
+ const SCOPE_VERSION = "1";
36
+ const DEFAULT_TIMEOUT_MS = 2000;
37
+
38
+ export function createMetricsPusher(
39
+ resource: WarpgogolResourceInput,
40
+ env?: MetricsPusherEnv,
41
+ options?: { timeoutMs?: number },
42
+ ): MetricsPusher | null {
43
+ const resolved = resolvePusherEnv(env);
44
+ if (!resolved.endpoint || !resolved.token) {
45
+ return null;
46
+ }
47
+
48
+ const endpoint = resolved.endpoint;
49
+ const token = resolved.token;
50
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
51
+ const environment = resource.environment ?? detectEnvironment();
52
+ const layer = resource.layer;
53
+
54
+ const resourceInput: WarpgogolResourceInput = {
55
+ ...resource,
56
+ environment,
57
+ layer,
58
+ };
59
+
60
+ const points: AccumulatedPoint[] = [];
61
+ const startTimeNano = nowUnixNano();
62
+ let _droppedCount = 0;
63
+
64
+ const isStrict = environment === "development" || environment === "ci";
65
+
66
+ function validateMetric(name: string, labels: Record<string, string>): void {
67
+ if (!isMetricNameValid(name)) {
68
+ const msg = `[observability] metric name "${name}" does not match the naming grammar`;
69
+ if (isStrict) throw new Error(msg);
70
+ _droppedCount++;
71
+ return;
72
+ }
73
+ const spec = findMetricSpec(name);
74
+ if (!spec) {
75
+ const msg = `[observability] metric name "${name}" is not declared in WARPGOGOL_METRIC_REGISTRY`;
76
+ if (isStrict) throw new Error(msg);
77
+ _droppedCount++;
78
+ return;
79
+ }
80
+ for (const key of Object.keys(labels)) {
81
+ if (isLabelKeyForbidden(key)) {
82
+ const msg = `[observability] label key "${key}" is forbidden for metric "${name}"`;
83
+ if (isStrict) throw new Error(msg);
84
+ _droppedCount++;
85
+ return;
86
+ }
87
+ if (!spec.labelKeys.includes(key)) {
88
+ const msg = `[observability] label key "${key}" is not declared for metric "${name}"`;
89
+ if (isStrict) throw new Error(msg);
90
+ _droppedCount++;
91
+ return;
92
+ }
93
+ }
94
+ }
95
+
96
+ function addPoint(
97
+ name: string,
98
+ value: number,
99
+ labels: Record<string, string>,
100
+ kind: AccumulatedPoint["kind"],
101
+ ): void {
102
+ try {
103
+ validateMetric(name, labels);
104
+ } catch {
105
+ return;
106
+ }
107
+ points.push({ name, labels, value, kind });
108
+ }
109
+
110
+ const transport: OtlpTransport = createOtlpHttpTransport({ endpoint, token, timeoutMs });
111
+
112
+ const pusher: MetricsPusher = {
113
+ counterAdd(name, value, labels = {}) {
114
+ addPoint(name, value, labels, "counter");
115
+ },
116
+ gaugeSet(name, value, labels = {}) {
117
+ addPoint(name, value, labels, "gauge");
118
+ },
119
+ histogramRecord(name, value, labels = {}) {
120
+ addPoint(name, value, labels, "histogram");
121
+ },
122
+ async flush() {
123
+ if (points.length === 0) {
124
+ return { delivered: true };
125
+ }
126
+
127
+ const resourceAttrs = buildResourceAttributes(resourceInput);
128
+
129
+ const otlpPoints = convertAccumulatedToOtlp(points, startTimeNano);
130
+
131
+ if (otlpPoints.length === 0) {
132
+ return { delivered: true };
133
+ }
134
+
135
+ const body = encodeOtlpMetrics(resourceAttrs, otlpPoints, SCOPE_NAME, SCOPE_VERSION);
136
+
137
+ return transport.send(JSON.stringify(body));
138
+ },
139
+ };
140
+
141
+ return pusher;
142
+ }
@@ -0,0 +1,27 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Maintains packages/observability/src/redact.ts as an authored observability authored module so agents can evolve it without rediscovering local boundaries.</purpose>
4
+ <non-goals>
5
+ <item>Do not import node: modules — must be Workers-compatible.</item>
6
+ <item>Do not redact at the collector level — this is a convenience for emitters.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0337: initial implementation.</item>
11
+ <item>Fix 2026-07-11: only trim the URL root slash so redaction is idempotent for multi-slash paths.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ export function redactUrl(url: string): string {
16
+ try {
17
+ const parsed = new URL(url);
18
+ parsed.hash = "";
19
+ parsed.search = "";
20
+ parsed.hostname = parsed.hostname.toLowerCase();
21
+ const redacted = parsed.toString();
22
+ if (parsed.pathname === "/") return redacted.replace(/\/$/, "");
23
+ return redacted;
24
+ } catch {
25
+ return url;
26
+ }
27
+ }