@warpgogol/werkstatt-shared 0.14.0 → 0.15.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 (437) hide show
  1. package/README.md +31 -13
  2. package/README.uk.md +25 -15
  3. package/package.json +519 -762
  4. package/src/{share/agent → agent}/ard-catalog.ts +6 -1
  5. package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
  6. package/src/{share/agent → agent}/manifest.ts +15 -1
  7. package/src/{share/agent → agent}/search.ts +6 -1
  8. package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
  9. package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
  10. package/src/{share → agent}/tests/capability.test.ts +1 -1
  11. package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
  12. package/src/{share → agent}/tests/knowledge.test.ts +2 -2
  13. package/src/{share → agent}/tests/manifest.test.ts +1 -1
  14. package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
  15. package/src/agent/tests/omit-empty.test.ts +33 -0
  16. package/src/{share → agent}/tests/openapi.test.ts +2 -2
  17. package/src/{share → agent}/tests/proof.test.ts +1 -1
  18. package/src/{share → attribution}/material-credits.ts +4 -4
  19. package/src/{share → checks}/check-hints.ts +6 -1
  20. package/src/checks/index.ts +4 -1
  21. package/src/checks/lib/astro-site-url.ts +6 -1
  22. package/src/checks/lib/i18n.ts +14 -0
  23. package/src/checks/result-helpers.ts +22 -5
  24. package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
  25. package/src/{share → client-scripts}/counter-utils.ts +6 -2
  26. package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
  27. package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
  28. package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
  29. package/src/client-scripts/index.ts +35 -0
  30. package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
  31. package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
  32. package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
  33. package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
  34. package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
  35. package/src/client-scripts/scroll-spy.ts +106 -0
  36. package/src/client-scripts/tests/external-link-qr.test.ts +8 -0
  37. package/src/client-scripts/tests/external-links.test.ts +25 -0
  38. package/src/client-scripts/tests/gsap-counter.test.ts +8 -0
  39. package/src/client-scripts/tests/gsap-mountain-journey.test.ts +8 -0
  40. package/src/client-scripts/tests/gsap-parallax.test.ts +8 -0
  41. package/src/client-scripts/tests/gsap-reveal.test.ts +8 -0
  42. package/src/client-scripts/tests/gsap-stagger.test.ts +8 -0
  43. package/src/client-scripts/tests/inline-number-animation.test.ts +8 -0
  44. package/src/client-scripts/tests/lenis.test.ts +8 -0
  45. package/src/client-scripts/tests/live-photos.test.ts +8 -0
  46. package/src/client-scripts/tests/lordicon.test.ts +8 -0
  47. package/src/client-scripts/tests/orchestrator.test.ts +47 -0
  48. package/src/client-scripts/tests/scheduler.test.ts +23 -0
  49. package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
  50. package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
  51. package/src/component/contracts.ts +206 -0
  52. package/src/component/index.ts +11 -0
  53. package/src/{share → content}/content-reference.ts +3 -3
  54. package/src/{share/content → content}/entity-id.ts +6 -3
  55. package/src/content/index.ts +3 -0
  56. package/src/{share/content → content}/merge.ts +6 -1
  57. package/src/content/system-manifest.test.ts +107 -0
  58. package/src/content/system-manifest.ts +12 -94
  59. package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
  60. package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
  61. package/src/content-discipline/index.ts +19 -0
  62. package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
  63. package/src/{share/env.d.ts → env.d.ts} +6 -3
  64. package/src/fingerprint/canonical-json.ts +731 -0
  65. package/src/fingerprint/index.ts +12 -0
  66. package/src/fingerprint/primitives.ts +72 -0
  67. package/src/{share/i18n → i18n}/localization.ts +6 -2
  68. package/src/integration/adapters.ts +10 -71
  69. package/src/integration/dispatch.ts +1 -1
  70. package/src/integration/funnel.ts +6 -1
  71. package/src/integration/index.ts +101 -4
  72. package/src/integration/lagebild-destination-adapter.ts +108 -0
  73. package/src/integration/lagebild-ingress.ts +7 -12
  74. package/src/integration/orchestration.ts +11 -10
  75. package/src/integration/tests/deliver-event.test.ts +1 -1
  76. package/src/integration/tests/dispatch.test.ts +4 -4
  77. package/src/integration/tests/hub-runtime.test.ts +22 -14
  78. package/src/integration/vitest.config.ts +8 -0
  79. package/src/kernel/desired-state.ts +227 -0
  80. package/src/kernel/diagnostic.ts +289 -0
  81. package/src/kernel/fs-atomic.ts +131 -0
  82. package/src/kernel/index.ts +15 -0
  83. package/src/kernel/tests/fs-atomic.test.ts +150 -0
  84. package/src/kernel/tests/workspace-io.test.ts +187 -0
  85. package/src/kernel/types.ts +459 -0
  86. package/src/kernel/workspace-io.ts +248 -0
  87. package/src/{share → knowledge}/tests/claim.test.ts +1 -1
  88. package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
  89. package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
  90. package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
  91. package/src/{share → knowledge}/tests/plan.test.ts +1 -1
  92. package/src/{share → knowledge}/tests/source.test.ts +1 -1
  93. package/src/legal/index.ts +15 -0
  94. package/src/{share/middleware → middleware}/access-protection.ts +8 -8
  95. package/src/{share → node}/dev-props-validator.ts +6 -1
  96. package/src/{share → node}/import-scan.ts +23 -3
  97. package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
  98. package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
  99. package/src/node/tests/derived-prices-loader.test.ts +59 -0
  100. package/src/node/tests/markdown-twin-provenance.test.ts +152 -0
  101. package/src/observability/env-resolver.ts +6 -1
  102. package/src/observability/index.ts +6 -1
  103. package/src/observability/otlp-converter.ts +6 -1
  104. package/src/observability/otlp-transport.ts +6 -1
  105. package/src/observability/pusher.ts +6 -1
  106. package/src/observability/redact.ts +6 -1
  107. package/src/observability/typed-refs.ts +6 -2
  108. package/src/observability/vitest.config.ts +1 -0
  109. package/src/{share → offers}/formula-eval.ts +3 -3
  110. package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
  111. package/src/onboarding/index.ts +1 -0
  112. package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
  113. package/src/ontology/archetype-registry.ts +6 -1
  114. package/src/ontology/archetypes/index.json +33 -1
  115. package/src/ontology/archetypes/index.yaml +27 -1
  116. package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
  117. package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
  118. package/src/ontology/biome-token-projection.ts +6 -1
  119. package/src/ontology/cosmic/index.ts +6 -1
  120. package/src/ontology/cosmic/moon-catalog.ts +6 -1
  121. package/src/ontology/cosmic/planet-catalog.ts +6 -1
  122. package/src/ontology/cosmic/star-catalog.ts +6 -1
  123. package/src/ontology/enums.ts +6 -1
  124. package/src/ontology/external-surfaces/index.ts +1 -1
  125. package/src/ontology/index.ts +6 -2
  126. package/src/ontology/manifest.ts +26 -4
  127. package/src/ontology/operations/artifact-store.ts +45 -0
  128. package/src/ontology/operations/dht.ts +98 -0
  129. package/src/ontology/operations/handoff.ts +92 -0
  130. package/src/ontology/operations/index.ts +187 -1
  131. package/src/ontology/operations/leitstand.ts +99 -0
  132. package/src/ontology/operations/materialization.ts +90 -0
  133. package/src/ontology/operations/mission.ts +111 -0
  134. package/src/ontology/operations/naming-policy.ts +132 -0
  135. package/src/ontology/operations/notausgang.ts +64 -0
  136. package/src/ontology/operations/release.ts +121 -0
  137. package/src/ontology/operations/sternsystem.ts +192 -0
  138. package/src/ontology/operations/werkstatt.ts +48 -0
  139. package/src/ontology/schemas/biome.ts +7 -2
  140. package/src/ontology/schemas/capability.ts +1 -1
  141. package/src/ontology/schemas/constellation.ts +6 -1
  142. package/src/ontology/schemas/index.ts +6 -2
  143. package/src/ontology/schemas/manifest-resolver.ts +6 -1
  144. package/src/ontology/schemas/page-entry.ts +9 -5
  145. package/src/ontology/schemas/system/growth.ts +6 -1
  146. package/src/ontology/schemas/system/manifest.ts +40 -1
  147. package/src/ontology/schemas/system/page-output.ts +7 -6
  148. package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
  149. package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
  150. package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
  151. package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
  152. package/src/ontology/schemas/system/tests/release.test.ts +22 -0
  153. package/src/ontology/schemas/system/tests/text.test.ts +18 -0
  154. package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
  155. package/src/ontology/schemas/system/text.ts +15 -0
  156. package/src/ontology/schemas/system.ts +6 -2
  157. package/src/ontology/schemas/tests/biome.test.ts +19 -0
  158. package/src/ontology/schemas/tests/capability.test.ts +22 -0
  159. package/src/ontology/schemas/tests/constellation.test.ts +22 -0
  160. package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
  161. package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
  162. package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
  163. package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
  164. package/src/ontology/schemas/tests/site-family.test.ts +18 -0
  165. package/src/ontology/schemas/tests/system.test.ts +20 -0
  166. package/src/ontology/shared-section-props/body-fragments.ts +2 -0
  167. package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
  168. package/src/ontology/vitest.config.ts +1 -0
  169. package/src/{share → page}/page.ts +13 -9
  170. package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
  171. package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
  172. package/src/passport/index.ts +6 -1
  173. package/src/passport/key-rotate.ts +10 -2
  174. package/src/passport/schema.ts +6 -1
  175. package/src/passport/sign.ts +8 -3
  176. package/src/passport/vitest.config.ts +1 -0
  177. package/src/pbt/index.test.ts +95 -0
  178. package/src/pbt/index.ts +251 -0
  179. package/src/plugin/plugin-contract.ts +1 -1
  180. package/src/{share → policy}/entitlement.ts +24 -4
  181. package/src/{share → policy}/feature-policy.ts +8 -3
  182. package/src/{share → policy}/visibility.ts +7 -2
  183. package/src/redirects/redirects.ts +117 -0
  184. package/src/{share → redirects/tests}/redirects.test.ts +1 -1
  185. package/src/remediation/remediation-catalog.ts +316 -0
  186. package/src/{share/routes → routes}/template-filter.ts +1 -1
  187. package/src/{share → runtime}/runtime-context.ts +7 -2
  188. package/src/{share → runtime}/shared-context.ts +1 -1
  189. package/src/{share/schemas → schemas}/claims.ts +2 -2
  190. package/src/{share/schemas → schemas}/effects.ts +6 -1
  191. package/src/{share/schemas → schemas}/features.ts +1 -1
  192. package/src/{share/schemas → schemas}/index.ts +3 -3
  193. package/src/{share/schemas → schemas}/navigation.ts +6 -1
  194. package/src/{share/schemas → schemas}/page-base.ts +6 -2
  195. package/src/{share/schemas → schemas}/person.ts +1 -1
  196. package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
  197. package/src/schemas/tests/claim-records.test.ts +52 -0
  198. package/src/schemas/tests/claims.test.ts +23 -0
  199. package/src/schemas/tests/effects.test.ts +116 -0
  200. package/src/schemas/tests/features.test.ts +61 -0
  201. package/src/schemas/tests/horizontal-align.test.ts +16 -0
  202. package/src/schemas/tests/icon-color.test.ts +16 -0
  203. package/src/schemas/tests/live-photo.test.ts +40 -0
  204. package/src/schemas/tests/material-credit.test.ts +51 -0
  205. package/src/schemas/tests/media.test.ts +26 -0
  206. package/src/schemas/tests/navigation.test.ts +58 -0
  207. package/src/schemas/tests/page-base.test.ts +19 -0
  208. package/src/schemas/tests/participant.test.ts +26 -0
  209. package/src/schemas/tests/person.test.ts +12 -0
  210. package/src/schemas/tests/print.test.ts +30 -0
  211. package/src/schemas/tests/section-background.test.ts +31 -0
  212. package/src/schemas/tests/section-body.test.ts +16 -0
  213. package/src/schemas/tests/section-cards.test.ts +17 -0
  214. package/src/schemas/tests/section-cta.test.ts +41 -0
  215. package/src/schemas/tests/section-header.test.ts +31 -0
  216. package/src/schemas/tests/section-image.test.ts +24 -0
  217. package/src/schemas/tests/section-motion.test.ts +29 -0
  218. package/src/schemas/tests/section-shell.test.ts +42 -0
  219. package/src/schemas/tests/section-stats.test.ts +17 -0
  220. package/src/schemas/tests/site-background.test.ts +33 -0
  221. package/src/schemas/tests/standard-list-item.test.ts +16 -0
  222. package/src/{share → sections}/rfc0042-utils.ts +1 -1
  223. package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
  224. package/src/{share/semantic → semantic}/business-projection.ts +95 -1
  225. package/src/semantic/canonical-uri.ts +54 -0
  226. package/src/{share/semantic → semantic}/extract.ts +100 -1
  227. package/src/semantic/fact-extraction.test.ts +47 -0
  228. package/src/semantic/fact-extraction.ts +46 -0
  229. package/src/semantic/freshness.ts +67 -0
  230. package/src/{share/semantic → semantic}/ids.ts +6 -1
  231. package/src/{share/semantic → semantic}/index.ts +9 -2
  232. package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
  233. package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
  234. package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
  235. package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
  236. package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
  237. package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
  238. package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
  239. package/src/semantic/jsonld/shared.ts +36 -0
  240. package/src/semantic/jsonld/tests/article.test.ts +89 -0
  241. package/src/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
  242. package/src/semantic/jsonld/tests/collection-list.test.ts +39 -0
  243. package/src/semantic/jsonld/tests/faq.test.ts +60 -0
  244. package/src/semantic/jsonld/tests/initiative.test.ts +56 -0
  245. package/src/semantic/jsonld/tests/organization.test.ts +123 -0
  246. package/src/semantic/jsonld/tests/person.test.ts +89 -0
  247. package/src/semantic/jsonld/tests/service.test.ts +65 -0
  248. package/src/semantic/jsonld/tests/shared.test.ts +41 -0
  249. package/src/semantic/jsonld/tests/video.test.ts +84 -0
  250. package/src/semantic/jsonld/tests/webpage.test.ts +93 -0
  251. package/src/semantic/jsonld/tests/website.test.ts +27 -0
  252. package/src/semantic/jsonld/types.ts +24 -0
  253. package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
  254. package/src/semantic/jsonld/website.ts +32 -0
  255. package/src/{share/semantic → semantic}/jsonld.ts +6 -1
  256. package/src/{share/semantic → semantic}/llms.ts +41 -3
  257. package/src/{share/semantic → semantic}/models.ts +27 -38
  258. package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
  259. package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
  260. package/src/{share/semantic → semantic}/page-utils.ts +6 -3
  261. package/src/semantic/tests/ai.test.ts +55 -0
  262. package/src/semantic/tests/block-extraction.test.ts +34 -0
  263. package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
  264. package/src/semantic/tests/breadcrumbs.test.ts +127 -0
  265. package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
  266. package/src/semantic/tests/build-page.test.ts +122 -0
  267. package/src/semantic/tests/business-projection.test.ts +367 -0
  268. package/src/semantic/tests/canonical-uri.test.ts +56 -0
  269. package/src/semantic/tests/extract.test.ts +209 -0
  270. package/src/semantic/tests/feed.test.ts +113 -0
  271. package/src/semantic/tests/freshness.pbt.test.ts +108 -0
  272. package/src/semantic/tests/helpers.ts +32 -0
  273. package/src/semantic/tests/ids.test.ts +118 -0
  274. package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
  275. package/src/semantic/tests/image-sitemap.test.ts +109 -0
  276. package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
  277. package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
  278. package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
  279. package/src/semantic/tests/jsonld.test.ts +61 -0
  280. package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
  281. package/src/semantic/tests/llms-policy.test.ts +60 -0
  282. package/src/semantic/tests/llms.test.ts +164 -0
  283. package/src/semantic/tests/markdown-hygiene.test.ts +72 -0
  284. package/src/semantic/tests/markdown-page.test.ts +97 -0
  285. package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
  286. package/src/semantic/tests/organization-profile.test.ts +107 -0
  287. package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
  288. package/src/semantic/tests/output-projection.test.ts +77 -0
  289. package/src/semantic/tests/page-markdown.test.ts +98 -0
  290. package/src/semantic/tests/page-utils.test.ts +88 -0
  291. package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
  292. package/src/semantic/tests/price-marker-resolver.test.ts +90 -0
  293. package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
  294. package/src/semantic/tests/robots.test.ts +70 -0
  295. package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
  296. package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
  297. package/src/semantic/tests/update-stamp.test.ts +102 -0
  298. package/src/signing/index.ts +13 -0
  299. package/src/signing/key.ts +139 -0
  300. package/src/signing/sign.ts +66 -0
  301. package/src/signing/types.ts +29 -0
  302. package/src/{share/slug → slug}/slug-url.ts +4 -1
  303. package/src/stack/run-tool.ts +108 -0
  304. package/src/stack/stack-checks.ts +124 -0
  305. package/src/stack/tests/run-tool.test.ts +138 -0
  306. package/src/stack/tests/stack-checks.test.ts +141 -0
  307. package/src/stack/tests/walk-files.test.ts +131 -0
  308. package/src/stack/walk-files.ts +114 -0
  309. package/src/surface/blueprint.ts +342 -66
  310. package/src/surface/decision-composer.ts +6 -1
  311. package/src/surface/geo.ts +6 -1
  312. package/src/surface/index.ts +11 -7
  313. package/src/surface/types.ts +2 -2
  314. package/src/surface/vitest.config.ts +1 -0
  315. package/src/tests/signing-core.test.ts +129 -0
  316. package/src/{share → text}/css-value-normalize.ts +6 -1
  317. package/src/text/string-utils.ts +24 -0
  318. package/src/{share → text}/tests/text-normalize.test.ts +5 -0
  319. package/src/{share → text}/text-normalize.ts +4 -3
  320. package/src/{share → text}/wrap-inline-numbers.ts +1 -1
  321. package/src/types/axiom-study.d.ts +36 -0
  322. package/src/types/hls-js-light.d.ts +21 -0
  323. package/src/types/qrcode-browser.d.ts +23 -0
  324. package/src/typography/fix.pbt.test.ts +208 -0
  325. package/src/typography/fix.ts +148 -0
  326. package/src/typography/index.ts +54 -0
  327. package/src/typography/locale-defaults.ts +97 -0
  328. package/src/typography/rules-tier1.test.ts +459 -0
  329. package/src/typography/rules-tier1.ts +925 -0
  330. package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
  331. package/src/typography/rules-tier2-locale.test.ts +286 -0
  332. package/src/typography/rules-tier2-locale.ts +399 -0
  333. package/src/typography/rules-tier2-structure.test.ts +330 -0
  334. package/src/typography/rules-tier2-structure.ts +378 -0
  335. package/src/typography/rules-tier3-advisory.test.ts +290 -0
  336. package/src/typography/rules-tier3-advisory.ts +239 -0
  337. package/src/typography/text-surface.test.ts +244 -0
  338. package/src/typography/text-surface.ts +448 -0
  339. package/src/index.ts +0 -14
  340. package/src/integration/port-barrel.ts +0 -105
  341. package/src/share/content/index.ts +0 -16
  342. package/src/share/content-discipline/index.ts +0 -14
  343. package/src/share/index.ts +0 -86
  344. package/src/share/legal/index.ts +0 -1
  345. package/src/share/redirects.ts +0 -54
  346. package/src/share/scripts/index.ts +0 -26
  347. package/src/share/semantic/jsonld/shared.ts +0 -31
  348. package/src/share/semantic/jsonld/types.ts +0 -18
  349. package/src/share/semantic/jsonld/website.ts +0 -27
  350. package/src/share/string-utils.ts +0 -19
  351. package/src/share/types/axiom-study.d.ts +0 -22
  352. package/src/share/types/hls-js-light.d.ts +0 -16
  353. package/src/share/types/qrcode-browser.d.ts +0 -18
  354. package/src/share/utility-registry.yaml +0 -36
  355. package/src/share/vitest.config.ts +0 -8
  356. package/src/surface/blueprint-schema.ts +0 -314
  357. package/src/surface/blueprint-types.ts +0 -256
  358. /package/src/{share/agent → agent}/api-catalog.ts +0 -0
  359. /package/src/{share/agent → agent}/capability.ts +0 -0
  360. /package/src/{share/agent → agent}/dns-aid.ts +0 -0
  361. /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
  362. /package/src/{share/agent → agent}/index.ts +0 -0
  363. /package/src/{share/agent → agent}/knowledge.ts +0 -0
  364. /package/src/{share/agent → agent}/mcp-card.ts +0 -0
  365. /package/src/{share/agent → agent}/omit-empty.ts +0 -0
  366. /package/src/{share/agent → agent}/openapi.ts +0 -0
  367. /package/src/{share/agent → agent}/proof.ts +0 -0
  368. /package/src/{share/agent → agent}/search.test.ts +0 -0
  369. /package/src/{share → attribution}/attribution-display.ts +0 -0
  370. /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
  371. /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
  372. /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
  373. /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
  374. /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
  375. /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
  376. /package/src/{share/content → content}/resolve-field-path.ts +0 -0
  377. /package/src/{share/content → content}/substitute-deep.ts +0 -0
  378. /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
  379. /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
  380. /package/src/{share → image-provider}/image-provider.ts +0 -0
  381. /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
  382. /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
  383. /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
  384. /package/src/{share/knowledge → knowledge}/index.ts +0 -0
  385. /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
  386. /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
  387. /package/src/{share/knowledge → knowledge}/source.ts +0 -0
  388. /package/src/{share/legal → legal}/translation-policy.ts +0 -0
  389. /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
  390. /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
  391. /package/src/{share → node}/fs/index.ts +0 -0
  392. /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
  393. /package/src/{share → offers}/offer-capacity.ts +0 -0
  394. /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
  395. /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
  396. /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
  397. /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
  398. /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
  399. /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
  400. /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
  401. /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
  402. /package/src/{share/schemas → schemas}/media.ts +0 -0
  403. /package/src/{share/schemas → schemas}/participant.ts +0 -0
  404. /package/src/{share/schemas → schemas}/print.ts +0 -0
  405. /package/src/{share/schemas → schemas}/section-background.ts +0 -0
  406. /package/src/{share/schemas → schemas}/section-body.ts +0 -0
  407. /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
  408. /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
  409. /package/src/{share/schemas → schemas}/section-header.ts +0 -0
  410. /package/src/{share/schemas → schemas}/section-image.ts +0 -0
  411. /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
  412. /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
  413. /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
  414. /package/src/{share/schemas → schemas}/site-background.ts +0 -0
  415. /package/src/{share/semantic → semantic}/ai.ts +0 -0
  416. /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
  417. /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
  418. /package/src/{share/semantic → semantic}/build-page.ts +0 -0
  419. /package/src/{share/semantic → semantic}/feed.ts +0 -0
  420. /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
  421. /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
  422. /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
  423. /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
  424. /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
  425. /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
  426. /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
  427. /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
  428. /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
  429. /package/src/{share/semantic → semantic}/robots.ts +0 -0
  430. /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
  431. /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
  432. /package/src/{share/slug → slug}/index.ts +0 -0
  433. /package/src/{share/slug → slug}/slug-id.ts +0 -0
  434. /package/src/{share/slug → slug}/strategies.ts +0 -0
  435. /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
  436. /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
  437. /package/src/{share → text}/text-position.ts +0 -0
@@ -0,0 +1,190 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Property-based tests for Tier 2 locale typography rules (RFC-1070).
4
+ Tests algebraic properties of the pure rule functions using fast-check (DNA-101).</purpose>
5
+ <non-goals>
6
+ <item>Do not test specific edge cases — those are in rules-tier2-locale.test.ts.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-1070: initial creation with property-based tests for Tier 2 locale rules.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ import { describe, it, expect } from "vitest";
15
+ import fc from "fast-check";
16
+ import { TIER2_LOCALE_RULES } from "./rules-tier2-locale.ts";
17
+ import type { TextSegment } from "./text-surface.ts";
18
+ import {
19
+ createTypographyContext,
20
+ type TypographyContext,
21
+ type TypographyRule,
22
+ type TypographyFinding,
23
+ } from "./rules-tier1.ts";
24
+ import { fcAssert, composeWord, safeText } from "../pbt/index.ts";
25
+
26
+ function makeSegment(text: string, locale: string): TextSegment {
27
+ return {
28
+ file: `test.${locale}.md`,
29
+ line: 1,
30
+ path: "$body",
31
+ source: "body",
32
+ text,
33
+ raw: text,
34
+ isTableRow: false,
35
+ locale,
36
+ };
37
+ }
38
+
39
+ function getRule(ruleId: string): TypographyRule {
40
+ const rule = TIER2_LOCALE_RULES.find((r) => r.id === ruleId);
41
+ if (!rule) throw new Error(`Rule ${ruleId} not found`);
42
+ return rule;
43
+ }
44
+
45
+ function runRule(
46
+ ruleId: string,
47
+ text: string,
48
+ locale: string,
49
+ ctx?: TypographyContext,
50
+ ): TypographyFinding[] {
51
+ const rule = getRule(ruleId);
52
+ const segment = makeSegment(text, locale);
53
+ return rule.check(segment, ctx ?? createTypographyContext(locale, new Set()));
54
+ }
55
+
56
+ // Use bounded-length strings for fast generation (RFC-1070 PBT helpers)
57
+ const FAST_TEXT = safeText(80);
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Properties for locale-specific rules: non-matching locale → zero findings
61
+ // ---------------------------------------------------------------------------
62
+
63
+ describe("PBT: locale-specific rules never fire on wrong locale", () => {
64
+ it("TYPO-NUM-01 never fires on non-de locale", () => {
65
+ fcAssert(
66
+ fc.property(FAST_TEXT, fc.constantFrom("uk", "en", "fr", "es"), (text, locale) => {
67
+ expect(runRule("TYPO-NUM-01", text, locale)).toHaveLength(0);
68
+ }),
69
+ );
70
+ });
71
+
72
+ it("TYPO-NUM-02 never fires on non-uk locale", () => {
73
+ fcAssert(
74
+ fc.property(FAST_TEXT, fc.constantFrom("de", "en", "fr", "es"), (text, locale) => {
75
+ expect(runRule("TYPO-NUM-02", text, locale)).toHaveLength(0);
76
+ }),
77
+ );
78
+ });
79
+
80
+ it("TYPO-NUM-03 never fires on non-uk locale", () => {
81
+ fcAssert(
82
+ fc.property(FAST_TEXT, fc.constantFrom("de", "en", "fr", "es"), (text, locale) => {
83
+ expect(runRule("TYPO-NUM-03", text, locale)).toHaveLength(0);
84
+ }),
85
+ );
86
+ });
87
+
88
+ it("TYPO-ABBR-02 never fires on non-de locale", () => {
89
+ fcAssert(
90
+ fc.property(FAST_TEXT, fc.constantFrom("uk", "en", "fr", "es"), (text, locale) => {
91
+ expect(runRule("TYPO-ABBR-02", text, locale)).toHaveLength(0);
92
+ }),
93
+ );
94
+ });
95
+
96
+ it("TYPO-APOS-01 never fires on non-uk locale", () => {
97
+ fcAssert(
98
+ fc.property(FAST_TEXT, fc.constantFrom("de", "en", "fr", "es"), (text, locale) => {
99
+ expect(runRule("TYPO-APOS-01", text, locale)).toHaveLength(0);
100
+ }),
101
+ );
102
+ });
103
+
104
+ it("TYPO-APOS-02 never fires on non-uk locale", () => {
105
+ fcAssert(
106
+ fc.property(FAST_TEXT, fc.constantFrom("de", "en", "fr", "es"), (text, locale) => {
107
+ expect(runRule("TYPO-APOS-02", text, locale)).toHaveLength(0);
108
+ }),
109
+ );
110
+ });
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Properties: text without target patterns → zero findings
115
+ // Uses conditional inside property body instead of filter (much faster)
116
+ // ---------------------------------------------------------------------------
117
+
118
+ describe("PBT: text without target patterns yields zero findings", () => {
119
+ it("TYPO-NUM-01: text without comma-thousands.dot-decimal → zero findings", () => {
120
+ fcAssert(
121
+ fc.property(FAST_TEXT, (text) => {
122
+ if (/\d{1,3}(?:,\d{3})*\.\d{2}/.test(text)) return; // skip if pattern present
123
+ expect(runRule("TYPO-NUM-01", text, "de")).toHaveLength(0);
124
+ }),
125
+ );
126
+ });
127
+
128
+ it("TYPO-NUM-02: text without dot-thousands.comma-decimal → zero findings", () => {
129
+ fcAssert(
130
+ fc.property(FAST_TEXT, (text) => {
131
+ if (/\d{1,3}(?:\.\d{3})*,\d{2}/.test(text)) return;
132
+ expect(runRule("TYPO-NUM-02", text, "uk")).toHaveLength(0);
133
+ }),
134
+ );
135
+ });
136
+
137
+ it("TYPO-APOS-01: text without U+2019 → zero findings", () => {
138
+ fcAssert(
139
+ fc.property(FAST_TEXT, (text) => {
140
+ if (text.includes("\u2019")) return;
141
+ expect(runRule("TYPO-APOS-01", text, "uk")).toHaveLength(0);
142
+ }),
143
+ );
144
+ });
145
+
146
+ it("TYPO-APOS-02: text without U+02BC → zero findings", () => {
147
+ fcAssert(
148
+ fc.property(FAST_TEXT, (text) => {
149
+ if (text.includes("\u02BC")) return;
150
+ expect(runRule("TYPO-APOS-02", text, "uk")).toHaveLength(0);
151
+ }),
152
+ );
153
+ });
154
+ });
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Properties: allowedTokens exclusion is stable
158
+ // Constructs strings guaranteed to contain the pattern by composition
159
+ // ---------------------------------------------------------------------------
160
+
161
+ // Compose: letter + "'" + letter using efficient letterString (no filter)
162
+ const STRAIGHT_APOS_WORD = composeWord("'");
163
+
164
+ describe("PBT: allowedTokens exclusion", () => {
165
+ it("TYPO-APOS-01: curly-apostrophe words in allowedTokens are never flagged", () => {
166
+ fcAssert(
167
+ fc.property(STRAIGHT_APOS_WORD, (word) => {
168
+ const curlyWord = word.replace(/'/g, "\u2019");
169
+ const match = curlyWord.match(/\p{L}+\u2019\p{L}+/u);
170
+ if (!match) return;
171
+ const allowedToken = match[0];
172
+ const ctx = createTypographyContext("uk", new Set([allowedToken]));
173
+ expect(runRule("TYPO-APOS-01", curlyWord, "uk", ctx)).toHaveLength(0);
174
+ }),
175
+ );
176
+ });
177
+
178
+ it("TYPO-APOS-02: modifier-apostrophe words in allowedTokens are never flagged", () => {
179
+ fcAssert(
180
+ fc.property(STRAIGHT_APOS_WORD, (word) => {
181
+ const modWord = word.replace(/'/g, "\u02BC");
182
+ const match = modWord.match(/\p{L}+\u02BC\p{L}+/u);
183
+ if (!match) return;
184
+ const allowedToken = match[0];
185
+ const ctx = createTypographyContext("uk", new Set([allowedToken]));
186
+ expect(runRule("TYPO-APOS-02", modWord, "uk", ctx)).toHaveLength(0);
187
+ }),
188
+ );
189
+ });
190
+ });
@@ -0,0 +1,286 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Unit tests for Tier 2 locale typography rules (RFC-1070). Tests cover
4
+ all 7 rules: TYPO-NUM-01..03, TYPO-ABBR-01..02, TYPO-APOS-01..02. Each rule
5
+ is tested for positive detection, negative (no finding), and key exclusions.</purpose>
6
+ <non-goals>
7
+ <item>Do not test text-surface extraction — that is text-surface.test.ts.</item>
8
+ <item>Do not test command adapter — that is typography.test.ts in werkstatt-site.</item>
9
+ </non-goals>
10
+ </MODULE_CONTRACT>
11
+ <CHANGE_SUMMARY>
12
+ <item>RFC-1070: initial creation with unit tests for 7 Tier 2 locale rules.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import { describe, it, expect } from "vitest";
17
+ import { TIER2_LOCALE_RULES, type Tier2LocaleRuleId } from "./rules-tier2-locale.ts";
18
+ import type { TextSegment } from "./text-surface.ts";
19
+ import type { TypographyContext, TypographyRule, TypographyFinding } from "./rules-tier1.ts";
20
+ import { createTypographyContext } from "./rules-tier1.ts";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function makeSegment(
27
+ text: string,
28
+ locale: string,
29
+ source: "frontmatter" | "body" = "body",
30
+ ): TextSegment {
31
+ return {
32
+ file: `test.${locale}.md`,
33
+ line: 1,
34
+ path: "$body",
35
+ source,
36
+ text,
37
+ raw: text,
38
+ isTableRow: false,
39
+ locale,
40
+ };
41
+ }
42
+
43
+ function makeCtx(
44
+ locale: string,
45
+ allowedTokens: ReadonlySet<string> = new Set(),
46
+ ): TypographyContext {
47
+ return createTypographyContext(locale, allowedTokens);
48
+ }
49
+
50
+ function runRule(
51
+ ruleId: Tier2LocaleRuleId,
52
+ text: string,
53
+ locale: string,
54
+ source: "frontmatter" | "body" = "body",
55
+ ): TypographyFinding[] {
56
+ const rule = TIER2_LOCALE_RULES.find((r) => r.id === ruleId) as TypographyRule;
57
+ const segment = makeSegment(text, locale, source);
58
+ const ctx = makeCtx(locale);
59
+ return rule.check(segment, ctx);
60
+ }
61
+
62
+ function expectFinding(ruleId: Tier2LocaleRuleId, text: string, locale: string): void {
63
+ const findings = runRule(ruleId, text, locale);
64
+ expect(findings.length).toBeGreaterThanOrEqual(1);
65
+ expect(findings[0].ruleId).toBe(ruleId);
66
+ }
67
+
68
+ function expectNoFinding(ruleId: Tier2LocaleRuleId, text: string, locale: string): void {
69
+ const findings = runRule(ruleId, text, locale);
70
+ expect(findings).toHaveLength(0);
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // TYPO-NUM-01: German number with dot decimal separator
75
+ // ---------------------------------------------------------------------------
76
+
77
+ describe("TYPO-NUM-01 — German dot decimal", () => {
78
+ it("detects 1,234.56 in German text", () => {
79
+ expectFinding("TYPO-NUM-01", "Der Preis betraegt 1,234.56 Euro.", "de");
80
+ });
81
+
82
+ it("does not fire on correct German format 1.234,56", () => {
83
+ expectNoFinding("TYPO-NUM-01", "Der Preis betraegt 1.234,56 Euro.", "de");
84
+ });
85
+
86
+ it("does not fire on dates (12.04.2026)", () => {
87
+ expectNoFinding("TYPO-NUM-01", "Am 12.04.2026 fand das Treffen statt.", "de");
88
+ });
89
+
90
+ it("does not fire on version numbers (v1.23)", () => {
91
+ expectNoFinding("TYPO-NUM-01", "Wir nutzen v1.23 des Tools.", "de");
92
+ });
93
+
94
+ it("does not fire on time values (10.30 Uhr)", () => {
95
+ expectNoFinding("TYPO-NUM-01", "Das Treningen beginnt um 10.30 Uhr.", "de");
96
+ });
97
+
98
+ it("does not fire on Ukrainian text", () => {
99
+ expectNoFinding("TYPO-NUM-01", "Цiна 1,234.56 евро.", "uk");
100
+ });
101
+
102
+ it("does not fire on English text", () => {
103
+ expectNoFinding("TYPO-NUM-01", "The price is 1,234.56 dollars.", "en");
104
+ });
105
+ });
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // TYPO-NUM-02: Ukrainian number with dot as thousands separator
109
+ // ---------------------------------------------------------------------------
110
+
111
+ describe("TYPO-NUM-02 — Ukrainian dot thousands", () => {
112
+ it("detects 1.234,56 in Ukrainian text", () => {
113
+ expectFinding("TYPO-NUM-02", "Цiна 1.234,56 евро.", "uk");
114
+ });
115
+
116
+ it("does not fire on correct Ukrainian format with NNBSP", () => {
117
+ expectNoFinding("TYPO-NUM-02", "Цiна 1\u202F234,56 евро.", "uk");
118
+ });
119
+
120
+ it("does not fire on German text", () => {
121
+ expectNoFinding("TYPO-NUM-02", "Der Preis betraegt 1.234,56 Euro.", "de");
122
+ });
123
+
124
+ it("does not fire on dates", () => {
125
+ expectNoFinding("TYPO-NUM-02", "12.04.2026", "uk");
126
+ });
127
+ });
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // TYPO-NUM-03: Regular space as thousands separator in Ukrainian
131
+ // ---------------------------------------------------------------------------
132
+
133
+ describe("TYPO-NUM-03 — Regular space thousands in Ukrainian", () => {
134
+ it("detects 1 234,56 with regular space in Ukrainian text", () => {
135
+ const text = "Цiна 1 234,56 евро.";
136
+ expectFinding("TYPO-NUM-03", text, "uk");
137
+ });
138
+
139
+ it("does not fire on NBSP (U+00A0) as thousands separator", () => {
140
+ expectNoFinding("TYPO-NUM-03", "Цiна 1\u00A0234,56 евро.", "uk");
141
+ });
142
+
143
+ it("does not fire on NNBSP (U+202F) as thousands separator", () => {
144
+ expectNoFinding("TYPO-NUM-03", "Цiна 1\u202F234,56 евро.", "uk");
145
+ });
146
+
147
+ it("does not fire on German text", () => {
148
+ expectNoFinding("TYPO-NUM-03", "Der Preis 1 234,56 Euro.", "de");
149
+ });
150
+ });
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // TYPO-ABBR-01: Known abbreviation missing required trailing period
154
+ // ---------------------------------------------------------------------------
155
+
156
+ describe("TYPO-ABBR-01 — Missing trailing period on abbreviation", () => {
157
+ it("detects Nr without period in German text", () => {
158
+ expectFinding("TYPO-ABBR-01", "Siehe Nr 5 fuer Details.", "de");
159
+ });
160
+
161
+ it("does not fire on Nr. with period", () => {
162
+ expectNoFinding("TYPO-ABBR-01", "Siehe Nr. 5 fuer Details.", "de");
163
+ });
164
+
165
+ it("detects usw without period in German text", () => {
166
+ expectFinding("TYPO-ABBR-01", "Artikel, Preise usw werden gelistet.", "de");
167
+ });
168
+
169
+ it("does not fire on usw. with period", () => {
170
+ expectNoFinding("TYPO-ABBR-01", "Artikel, Preise usw. werden gelistet.", "de");
171
+ });
172
+
173
+ it("detects напр without period in Ukrainian text", () => {
174
+ expectFinding("TYPO-ABBR-01", "Див напр роздiл 5.", "uk");
175
+ });
176
+
177
+ it("does not fire on напр. with period", () => {
178
+ expectNoFinding("TYPO-ABBR-01", "Наприклад напр. роздiл 5.", "uk");
179
+ });
180
+ });
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // TYPO-ABBR-02: German abbreviation with wrong internal spacing
184
+ // ---------------------------------------------------------------------------
185
+
186
+ describe("TYPO-ABBR-02 — Wrong internal spacing in German abbreviations", () => {
187
+ it("detects z.B. without space", () => {
188
+ expectFinding("TYPO-ABBR-02", "Das ist z.B. ein Beispiel.", "de");
189
+ });
190
+
191
+ it("does not fire on z. B. with space", () => {
192
+ expectNoFinding("TYPO-ABBR-02", "Das ist z. B. ein Beispiel.", "de");
193
+ });
194
+
195
+ it("detects u.a. without space", () => {
196
+ expectFinding("TYPO-ABBR-02", "Es gibt u.a. viele Optionen.", "de");
197
+ });
198
+
199
+ it("detects d.h. without space", () => {
200
+ expectFinding("TYPO-ABBR-02", "Das ist d.h. wichtig.", "de");
201
+ });
202
+
203
+ it("does not fire on Ukrainian text", () => {
204
+ expectNoFinding("TYPO-ABBR-02", "Це z.B. приклад.", "uk");
205
+ });
206
+ });
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // TYPO-APOS-01: Ukrainian word with curly apostrophe (U+2019)
210
+ // ---------------------------------------------------------------------------
211
+
212
+ describe("TYPO-APOS-01 — Curly apostrophe in Ukrainian", () => {
213
+ it("detects curly apostrophe in Ukrainian word", () => {
214
+ const text = "Слово iм\u2019я тут.";
215
+ expectFinding("TYPO-APOS-01", text, "uk");
216
+ });
217
+
218
+ it("does not fire on straight apostrophe", () => {
219
+ expectNoFinding("TYPO-APOS-01", "Слово iм'я тут.", "uk");
220
+ });
221
+
222
+ it("does not fire on German text", () => {
223
+ expectNoFinding("TYPO-APOS-01", "Das ist ein\u2019Test.", "de");
224
+ });
225
+
226
+ it("respects allowedTokens exclusion", () => {
227
+ const text = "Слово i\u043C\u2019\u044F тут.";
228
+ const token = "i\u043C\u2019\u044F";
229
+ const segment = makeSegment(text, "uk");
230
+ const ctx = makeCtx("uk", new Set([token]));
231
+ const rule = TIER2_LOCALE_RULES.find((r) => r.id === "TYPO-APOS-01") as TypographyRule;
232
+ const findings = rule.check(segment, ctx);
233
+ expect(findings).toHaveLength(0);
234
+ });
235
+ });
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // TYPO-APOS-02: Ukrainian word with modifier letter apostrophe (U+02BC)
239
+ // ---------------------------------------------------------------------------
240
+
241
+ describe("TYPO-APOS-02 — Modifier letter apostrophe in Ukrainian", () => {
242
+ it("detects modifier letter apostrophe in Ukrainian word", () => {
243
+ const text = "Слово iм\u02BCя тут.";
244
+ expectFinding("TYPO-APOS-02", text, "uk");
245
+ });
246
+
247
+ it("does not fire on straight apostrophe", () => {
248
+ expectNoFinding("TYPO-APOS-02", "Слово iм'я тут.", "uk");
249
+ });
250
+
251
+ it("does not fire on German text", () => {
252
+ expectNoFinding("TYPO-APOS-02", "Das ist ein\u02BCtest.", "de");
253
+ });
254
+
255
+ it("respects allowedTokens exclusion", () => {
256
+ const text = "Слово i\u043C\u02BC\u044F тут.";
257
+ const token = "i\u043C\u02BC\u044F";
258
+ const segment = makeSegment(text, "uk");
259
+ const ctx = makeCtx("uk", new Set([token]));
260
+ const rule = TIER2_LOCALE_RULES.find((r) => r.id === "TYPO-APOS-02") as TypographyRule;
261
+ const findings = rule.check(segment, ctx);
262
+ expect(findings).toHaveLength(0);
263
+ });
264
+ });
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Export count verification
268
+ // ---------------------------------------------------------------------------
269
+
270
+ describe("TIER2_LOCALE_RULES export", () => {
271
+ it("exports exactly 7 rules", () => {
272
+ expect(TIER2_LOCALE_RULES).toHaveLength(7);
273
+ });
274
+
275
+ it("all rules are tier 2", () => {
276
+ for (const rule of TIER2_LOCALE_RULES) {
277
+ expect(rule.tier).toBe(2);
278
+ }
279
+ });
280
+
281
+ it("all rule IDs use NUM, ABBR, or APOS families", () => {
282
+ for (const rule of TIER2_LOCALE_RULES) {
283
+ expect(["NUM", "ABBR", "APOS"]).toContain(rule.family);
284
+ }
285
+ });
286
+ });