paxman 0.1.0__tar.gz

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 (463) hide show
  1. paxman-0.1.0/.github/workflows/ci.yml +72 -0
  2. paxman-0.1.0/.github/workflows/opencode.yml +33 -0
  3. paxman-0.1.0/.github/workflows/publish.yml +88 -0
  4. paxman-0.1.0/.gitignore +37 -0
  5. paxman-0.1.0/AGENTS.md +108 -0
  6. paxman-0.1.0/ARCHITECTURE.md +258 -0
  7. paxman-0.1.0/CONTEXT.md +1013 -0
  8. paxman-0.1.0/CONTRIBUTING.md +156 -0
  9. paxman-0.1.0/HOW_TO_ADD_NEW_CAPABILITY.md +1158 -0
  10. paxman-0.1.0/HOW_TO_ADD_NEW_GRAMMAR.md +288 -0
  11. paxman-0.1.0/LICENSE.md +21 -0
  12. paxman-0.1.0/PKG-INFO +713 -0
  13. paxman-0.1.0/QUICKSTART.md +107 -0
  14. paxman-0.1.0/README.md +697 -0
  15. paxman-0.1.0/SECURITY.md +43 -0
  16. paxman-0.1.0/TESTING_STRATEGY.md +131 -0
  17. paxman-0.1.0/benchmarks/README.md +11 -0
  18. paxman-0.1.0/benchmarks/__init__.py +0 -0
  19. paxman-0.1.0/benchmarks/baseline.json +95 -0
  20. paxman-0.1.0/benchmarks/harness.py +97 -0
  21. paxman-0.1.0/benchmarks/scenarios.py +241 -0
  22. paxman-0.1.0/capability_homogeneity_audit.md +408 -0
  23. paxman-0.1.0/docs/adr/0001-clean-architecture-pipeline.md +283 -0
  24. paxman-0.1.0/docs/adr/0002-remove-replay-hash.md +147 -0
  25. paxman-0.1.0/docs/adr/0003-semantic-affinity-routing.md +260 -0
  26. paxman-0.1.0/docs/adr/0004-single-value-invariant.md +162 -0
  27. paxman-0.1.0/docs/adr/0005-si-unit-multi-solidus-and-parens.md +90 -0
  28. paxman-0.1.0/docs/adr/0006-si-unit-split-prefixes.md +94 -0
  29. paxman-0.1.0/docs/adr/0007-contract-surface-unification.md +40 -0
  30. paxman-0.1.0/docs/adr/0008-staged-recognition-pipeline.md +277 -0
  31. paxman-0.1.0/docs/development/AGENTS.md +44 -0
  32. paxman-0.1.0/docs/development/MILESTONE.md +74 -0
  33. paxman-0.1.0/docs/development/plans/2026-07-26-contract-params-hypothesis-tests-revised.md +2051 -0
  34. paxman-0.1.0/docs/development/plans/2026-07-26-contract-params-hypothesis-tests.md +1594 -0
  35. paxman-0.1.0/docs/development/plans/2026-07-26-email-capability.md +2613 -0
  36. paxman-0.1.0/docs/development/plans/2026-07-28-country-capability-implementation.md +3685 -0
  37. paxman-0.1.0/docs/development/plans/2026-08-01-phone-number-capability.md +3414 -0
  38. paxman-0.1.0/docs/development/plans/2026-08-02-capability-surface-homogeneity.md +220 -0
  39. paxman-0.1.0/docs/development/plans/2026-08-03-f1-grammar-rule-affinity.md +298 -0
  40. paxman-0.1.0/docs/development/plans/2026-08-03-f2-include-feature-gating.md +275 -0
  41. paxman-0.1.0/docs/development/plans/2026-08-03-f3-recognition-validation-boundary.md +495 -0
  42. paxman-0.1.0/docs/development/plans/2026-08-04-centralize-output-format.md +431 -0
  43. paxman-0.1.0/docs/development/plans/2026-08-04-recognition-homogeneity.md +1580 -0
  44. paxman-0.1.0/docs/development/plans/2026-08-05-isbn-capability.md +1310 -0
  45. paxman-0.1.0/docs/development/plans/2026-08-05-money-capability.md +5099 -0
  46. paxman-0.1.0/docs/development/plans/2026-08-06-url-capability.md +379 -0
  47. paxman-0.1.0/docs/development/plans/2026-08-08-currency-capability.md +1317 -0
  48. paxman-0.1.0/docs/development/plans/2026-08-09-si-units-capability.md +2389 -0
  49. paxman-0.1.0/docs/development/plans/2026-08-10-adr0002-remove-replay-hash.md +574 -0
  50. paxman-0.1.0/docs/development/plans/2026-08-11-adr0003-semantic-affinity-routing.md +772 -0
  51. paxman-0.1.0/docs/development/plans/2026-08-17-capability-scaffolder.md +450 -0
  52. paxman-0.1.0/docs/development/plans/2026-08-17-register-all-shipped-bootstrap.md +368 -0
  53. paxman-0.1.0/docs/development/plans/2026-08-17-segmentation-recipe.md +208 -0
  54. paxman-0.1.0/docs/development/plans/2026-08-17-stale-surface-cleanup.md +473 -0
  55. paxman-0.1.0/docs/development/plans/2026-08-20-staged-recognition-pipeline.md +1697 -0
  56. paxman-0.1.0/docs/development/plans/2026-08-21-issn-capability.md +766 -0
  57. paxman-0.1.0/docs/development/plans/2026-08-22-iban-capability.md +1127 -0
  58. paxman-0.1.0/docs/development/release/checklist.md +226 -0
  59. paxman-0.1.0/docs/development/reports/2026-08-07-architecture-review.md +254 -0
  60. paxman-0.1.0/docs/development/reports/2026-08-22-first-time-user-experience.md +312 -0
  61. paxman-0.1.0/docs/development/reports/architecture-review-2026-07-26.md +221 -0
  62. paxman-0.1.0/docs/development/reports/recognition-handling-library-research.md +538 -0
  63. paxman-0.1.0/docs/development/research/2026-08-05-isbn-canonicalization.md +584 -0
  64. paxman-0.1.0/docs/development/research/2026-08-05-money-canonicalization.md +818 -0
  65. paxman-0.1.0/docs/development/research/2026-08-06-url-canonicalization.md +415 -0
  66. paxman-0.1.0/docs/development/research/2026-08-09-si-unit-canonicalization.md +561 -0
  67. paxman-0.1.0/docs/development/research/2026-08-21-issn-canonicalization.md +644 -0
  68. paxman-0.1.0/docs/development/research/2026-08-22-iban-canonicalization.md +832 -0
  69. paxman-0.1.0/docs/development/specs/2026-07-28-country-capability-design.md +652 -0
  70. paxman-0.1.0/docs/development/specs/2026-08-03-f3-recognition-validation-boundary-design.md +118 -0
  71. paxman-0.1.0/docs/recipes/segmentation.md +161 -0
  72. paxman-0.1.0/docs/user/api-reference.md +241 -0
  73. paxman-0.1.0/docs/user/capabilities/country.md +132 -0
  74. paxman-0.1.0/docs/user/capabilities/currency.md +133 -0
  75. paxman-0.1.0/docs/user/capabilities/date.md +125 -0
  76. paxman-0.1.0/docs/user/capabilities/email.md +140 -0
  77. paxman-0.1.0/docs/user/capabilities/index.md +80 -0
  78. paxman-0.1.0/docs/user/capabilities/ip.md +105 -0
  79. paxman-0.1.0/docs/user/capabilities/isbn.md +114 -0
  80. paxman-0.1.0/docs/user/capabilities/money.md +137 -0
  81. paxman-0.1.0/docs/user/capabilities/phone.md +125 -0
  82. paxman-0.1.0/docs/user/capabilities/si-unit.md +116 -0
  83. paxman-0.1.0/docs/user/capabilities/url.md +115 -0
  84. paxman-0.1.0/docs/user/concepts/candidates-and-ambiguity.md +147 -0
  85. paxman-0.1.0/docs/user/concepts/capabilities.md +108 -0
  86. paxman-0.1.0/docs/user/concepts/contracts.md +175 -0
  87. paxman-0.1.0/docs/user/concepts/errors.md +126 -0
  88. paxman-0.1.0/docs/user/concepts/execution-result.md +149 -0
  89. paxman-0.1.0/docs/user/concepts/index.md +90 -0
  90. paxman-0.1.0/docs/user/concepts/pipeline.md +151 -0
  91. paxman-0.1.0/docs/user/concepts/provenance.md +79 -0
  92. paxman-0.1.0/docs/user/extending.md +239 -0
  93. paxman-0.1.0/docs/user/getting-started.md +221 -0
  94. paxman-0.1.0/docs/user/index.md +111 -0
  95. paxman-0.1.0/docs/user/migration.md +122 -0
  96. paxman-0.1.0/ocs/development/plans/2026-08-19-mid-term-recommendations.md +1551 -0
  97. paxman-0.1.0/paxman/__init__.py +16 -0
  98. paxman-0.1.0/paxman/__main__.py +6 -0
  99. paxman-0.1.0/paxman/api/__init__.py +3 -0
  100. paxman-0.1.0/paxman/api/bootstrap.py +76 -0
  101. paxman-0.1.0/paxman/api/canonicalize.py +31 -0
  102. paxman-0.1.0/paxman/capabilities/AGENTS.md +68 -0
  103. paxman-0.1.0/paxman/capabilities/Country/__init__.py +7 -0
  104. paxman-0.1.0/paxman/capabilities/Country/capability.py +150 -0
  105. paxman-0.1.0/paxman/capabilities/Country/contract.py +41 -0
  106. paxman-0.1.0/paxman/capabilities/Country/grammar/__init__.py +1 -0
  107. paxman-0.1.0/paxman/capabilities/Country/grammar/alpha2_recognition.py +39 -0
  108. paxman-0.1.0/paxman/capabilities/Country/grammar/alpha3_recognition.py +39 -0
  109. paxman-0.1.0/paxman/capabilities/Country/grammar/data/__init__.py +12 -0
  110. paxman-0.1.0/paxman/capabilities/Country/grammar/data/chinese_names.py +100 -0
  111. paxman-0.1.0/paxman/capabilities/Country/grammar/data/english_names.py +334 -0
  112. paxman-0.1.0/paxman/capabilities/Country/grammar/data/historical_names.py +57 -0
  113. paxman-0.1.0/paxman/capabilities/Country/grammar/data/localized_names.py +213 -0
  114. paxman-0.1.0/paxman/capabilities/Country/grammar/name_recognition.py +59 -0
  115. paxman-0.1.0/paxman/capabilities/Country/grammar/numeric_recognition.py +39 -0
  116. paxman-0.1.0/paxman/capabilities/Country/notation.py +68 -0
  117. paxman-0.1.0/paxman/capabilities/Country/rules/__init__.py +1 -0
  118. paxman-0.1.0/paxman/capabilities/Country/rules/cldr_localized_ed2025.py +78 -0
  119. paxman-0.1.0/paxman/capabilities/Country/rules/data/__init__.py +1 -0
  120. paxman-0.1.0/paxman/capabilities/Country/rules/data/cldr_ed2025.py +198 -0
  121. paxman-0.1.0/paxman/capabilities/Country/rules/data/iso_3166_ed2020_part3.py +101 -0
  122. paxman-0.1.0/paxman/capabilities/Country/rules/data/iso_3166_ed2024.py +1890 -0
  123. paxman-0.1.0/paxman/capabilities/Country/rules/iso_3166_ed2024.py +238 -0
  124. paxman-0.1.0/paxman/capabilities/Country/rules/iso_3166_historical_ed2020.py +122 -0
  125. paxman-0.1.0/paxman/capabilities/Currency/__init__.py +9 -0
  126. paxman-0.1.0/paxman/capabilities/Currency/capability.py +93 -0
  127. paxman-0.1.0/paxman/capabilities/Currency/contract.py +83 -0
  128. paxman-0.1.0/paxman/capabilities/Currency/grammar/__init__.py +1 -0
  129. paxman-0.1.0/paxman/capabilities/Currency/grammar/code_recognition.py +50 -0
  130. paxman-0.1.0/paxman/capabilities/Currency/grammar/data/__init__.py +1 -0
  131. paxman-0.1.0/paxman/capabilities/Currency/grammar/data/currency_symbols.py +83 -0
  132. paxman-0.1.0/paxman/capabilities/Currency/grammar/data/currency_words.py +80 -0
  133. paxman-0.1.0/paxman/capabilities/Currency/grammar/symbol_recognition.py +59 -0
  134. paxman-0.1.0/paxman/capabilities/Currency/grammar/word_recognition.py +55 -0
  135. paxman-0.1.0/paxman/capabilities/Currency/notation.py +35 -0
  136. paxman-0.1.0/paxman/capabilities/Currency/rules/__init__.py +1 -0
  137. paxman-0.1.0/paxman/capabilities/Currency/rules/cldr_currencies_ed2025.py +199 -0
  138. paxman-0.1.0/paxman/capabilities/Currency/rules/data/__init__.py +1 -0
  139. paxman-0.1.0/paxman/capabilities/Currency/rules/data/cldr_currencies.py +190 -0
  140. paxman-0.1.0/paxman/capabilities/Currency/rules/data/iso4217_list_one.py +204 -0
  141. paxman-0.1.0/paxman/capabilities/Currency/rules/iso_4217_ed2015.py +72 -0
  142. paxman-0.1.0/paxman/capabilities/Date/__init__.py +6 -0
  143. paxman-0.1.0/paxman/capabilities/Date/capability.py +103 -0
  144. paxman-0.1.0/paxman/capabilities/Date/contract.py +25 -0
  145. paxman-0.1.0/paxman/capabilities/Date/grammar/__init__.py +1 -0
  146. paxman-0.1.0/paxman/capabilities/Date/grammar/european_recognition.py +54 -0
  147. paxman-0.1.0/paxman/capabilities/Date/grammar/iso8601_recognition.py +41 -0
  148. paxman-0.1.0/paxman/capabilities/Date/grammar/slash_iso_recognition.py +48 -0
  149. paxman-0.1.0/paxman/capabilities/Date/grammar/us_recognition.py +52 -0
  150. paxman-0.1.0/paxman/capabilities/Date/notation.py +20 -0
  151. paxman-0.1.0/paxman/capabilities/Date/rules/__init__.py +1 -0
  152. paxman-0.1.0/paxman/capabilities/Date/rules/en_50160_ed2010.py +76 -0
  153. paxman-0.1.0/paxman/capabilities/Date/rules/iso_8601_ed2019.py +57 -0
  154. paxman-0.1.0/paxman/capabilities/Date/rules/us_federal_rules_ed2023.py +76 -0
  155. paxman-0.1.0/paxman/capabilities/Email/__init__.py +9 -0
  156. paxman-0.1.0/paxman/capabilities/Email/capability.py +64 -0
  157. paxman-0.1.0/paxman/capabilities/Email/contract.py +29 -0
  158. paxman-0.1.0/paxman/capabilities/Email/grammar/__init__.py +1 -0
  159. paxman-0.1.0/paxman/capabilities/Email/grammar/localhost_recognition.py +35 -0
  160. paxman-0.1.0/paxman/capabilities/Email/grammar/obfuscated_recognition.py +53 -0
  161. paxman-0.1.0/paxman/capabilities/Email/grammar/standard_recognition.py +34 -0
  162. paxman-0.1.0/paxman/capabilities/Email/notation.py +13 -0
  163. paxman-0.1.0/paxman/capabilities/Email/rules/__init__.py +1 -0
  164. paxman-0.1.0/paxman/capabilities/Email/rules/rfc_5322_ed2008.py +46 -0
  165. paxman-0.1.0/paxman/capabilities/Email/rules/rfc_6761_ed2012.py +43 -0
  166. paxman-0.1.0/paxman/capabilities/IBAN/__init__.py +9 -0
  167. paxman-0.1.0/paxman/capabilities/IBAN/capability.py +51 -0
  168. paxman-0.1.0/paxman/capabilities/IBAN/contract.py +18 -0
  169. paxman-0.1.0/paxman/capabilities/IBAN/grammar/__init__.py +5 -0
  170. paxman-0.1.0/paxman/capabilities/IBAN/grammar/iban_recognition.py +53 -0
  171. paxman-0.1.0/paxman/capabilities/IBAN/notation.py +22 -0
  172. paxman-0.1.0/paxman/capabilities/IBAN/rules/__init__.py +3 -0
  173. paxman-0.1.0/paxman/capabilities/IBAN/rules/iso_13616_1_ed2020.py +166 -0
  174. paxman-0.1.0/paxman/capabilities/IP/__init__.py +5 -0
  175. paxman-0.1.0/paxman/capabilities/IP/capability.py +56 -0
  176. paxman-0.1.0/paxman/capabilities/IP/contract.py +26 -0
  177. paxman-0.1.0/paxman/capabilities/IP/grammar/__init__.py +1 -0
  178. paxman-0.1.0/paxman/capabilities/IP/grammar/ipv4_recognition.py +31 -0
  179. paxman-0.1.0/paxman/capabilities/IP/grammar/ipv6_recognition.py +70 -0
  180. paxman-0.1.0/paxman/capabilities/IP/notation.py +12 -0
  181. paxman-0.1.0/paxman/capabilities/IP/rules/__init__.py +1 -0
  182. paxman-0.1.0/paxman/capabilities/IP/rules/rfc_5952_ed2010.py +54 -0
  183. paxman-0.1.0/paxman/capabilities/IP/rules/rfc_791_ed1981.py +55 -0
  184. paxman-0.1.0/paxman/capabilities/ISBN/__init__.py +1 -0
  185. paxman-0.1.0/paxman/capabilities/ISBN/capability.py +129 -0
  186. paxman-0.1.0/paxman/capabilities/ISBN/contract.py +46 -0
  187. paxman-0.1.0/paxman/capabilities/ISBN/grammar/__init__.py +1 -0
  188. paxman-0.1.0/paxman/capabilities/ISBN/grammar/isbn10_recognition.py +42 -0
  189. paxman-0.1.0/paxman/capabilities/ISBN/grammar/isbn13_recognition.py +37 -0
  190. paxman-0.1.0/paxman/capabilities/ISBN/notation.py +16 -0
  191. paxman-0.1.0/paxman/capabilities/ISBN/rules/__init__.py +1 -0
  192. paxman-0.1.0/paxman/capabilities/ISBN/rules/data/__init__.py +1 -0
  193. paxman-0.1.0/paxman/capabilities/ISBN/rules/data/range_message.py +2250 -0
  194. paxman-0.1.0/paxman/capabilities/ISBN/rules/data/range_message_2026-08-05.xml +9216 -0
  195. paxman-0.1.0/paxman/capabilities/ISBN/rules/isbn_range_message_ed2026.py +75 -0
  196. paxman-0.1.0/paxman/capabilities/ISBN/rules/isbn_users_manual_ed2012.py +50 -0
  197. paxman-0.1.0/paxman/capabilities/ISBN/rules/iso_2108_ed2017.py +66 -0
  198. paxman-0.1.0/paxman/capabilities/ISSN/__init__.py +9 -0
  199. paxman-0.1.0/paxman/capabilities/ISSN/capability.py +67 -0
  200. paxman-0.1.0/paxman/capabilities/ISSN/contract.py +16 -0
  201. paxman-0.1.0/paxman/capabilities/ISSN/grammar/__init__.py +1 -0
  202. paxman-0.1.0/paxman/capabilities/ISSN/grammar/issn_recognition.py +34 -0
  203. paxman-0.1.0/paxman/capabilities/ISSN/notation.py +15 -0
  204. paxman-0.1.0/paxman/capabilities/ISSN/rules/__init__.py +1 -0
  205. paxman-0.1.0/paxman/capabilities/ISSN/rules/iso_3297_ed2022.py +47 -0
  206. paxman-0.1.0/paxman/capabilities/Money/__init__.py +9 -0
  207. paxman-0.1.0/paxman/capabilities/Money/capability.py +130 -0
  208. paxman-0.1.0/paxman/capabilities/Money/contract.py +97 -0
  209. paxman-0.1.0/paxman/capabilities/Money/grammar/__init__.py +45 -0
  210. paxman-0.1.0/paxman/capabilities/Money/grammar/code_recognition.py +58 -0
  211. paxman-0.1.0/paxman/capabilities/Money/grammar/data/__init__.py +1 -0
  212. paxman-0.1.0/paxman/capabilities/Money/grammar/data/currency_symbols.py +83 -0
  213. paxman-0.1.0/paxman/capabilities/Money/grammar/data/currency_words.py +77 -0
  214. paxman-0.1.0/paxman/capabilities/Money/grammar/symbol_recognition.py +67 -0
  215. paxman-0.1.0/paxman/capabilities/Money/grammar/word_recognition.py +62 -0
  216. paxman-0.1.0/paxman/capabilities/Money/notation.py +47 -0
  217. paxman-0.1.0/paxman/capabilities/Money/parsing.py +131 -0
  218. paxman-0.1.0/paxman/capabilities/Money/rules/__init__.py +1 -0
  219. paxman-0.1.0/paxman/capabilities/Money/rules/cldr_currencies_ed2025.py +245 -0
  220. paxman-0.1.0/paxman/capabilities/Money/rules/data/__init__.py +1 -0
  221. paxman-0.1.0/paxman/capabilities/Money/rules/data/cldr_currencies.py +183 -0
  222. paxman-0.1.0/paxman/capabilities/Money/rules/data/iso4217_list_one.py +353 -0
  223. paxman-0.1.0/paxman/capabilities/Money/rules/iso_4217_ed2015.py +121 -0
  224. paxman-0.1.0/paxman/capabilities/Phone/__init__.py +9 -0
  225. paxman-0.1.0/paxman/capabilities/Phone/capability.py +151 -0
  226. paxman-0.1.0/paxman/capabilities/Phone/contract.py +92 -0
  227. paxman-0.1.0/paxman/capabilities/Phone/grammar/__init__.py +1 -0
  228. paxman-0.1.0/paxman/capabilities/Phone/grammar/_common.py +28 -0
  229. paxman-0.1.0/paxman/capabilities/Phone/grammar/e164_recognition.py +106 -0
  230. paxman-0.1.0/paxman/capabilities/Phone/grammar/international_00_recognition.py +56 -0
  231. paxman-0.1.0/paxman/capabilities/Phone/grammar/national_recognition.py +59 -0
  232. paxman-0.1.0/paxman/capabilities/Phone/grammar/tel_uri_recognition.py +54 -0
  233. paxman-0.1.0/paxman/capabilities/Phone/notation.py +27 -0
  234. paxman-0.1.0/paxman/capabilities/Phone/rules/__init__.py +1 -0
  235. paxman-0.1.0/paxman/capabilities/Phone/rules/data/__init__.py +1 -0
  236. paxman-0.1.0/paxman/capabilities/Phone/rules/data/e164_country_codes.py +262 -0
  237. paxman-0.1.0/paxman/capabilities/Phone/rules/data/nanp_tables.py +18 -0
  238. paxman-0.1.0/paxman/capabilities/Phone/rules/e164_ed2010.py +138 -0
  239. paxman-0.1.0/paxman/capabilities/Phone/rules/nanp_ed2024.py +191 -0
  240. paxman-0.1.0/paxman/capabilities/Phone/rules/rfc_3966_ed2004.py +63 -0
  241. paxman-0.1.0/paxman/capabilities/SIUnit/__init__.py +9 -0
  242. paxman-0.1.0/paxman/capabilities/SIUnit/capability.py +113 -0
  243. paxman-0.1.0/paxman/capabilities/SIUnit/contract.py +34 -0
  244. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/__init__.py +1 -0
  245. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/compound_recognition.py +69 -0
  246. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/data/__init__.py +1 -0
  247. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/data/compound_tokens.py +16 -0
  248. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/data/prefix_tokens.py +71 -0
  249. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/data/unit_name_tokens.py +922 -0
  250. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/data/unit_symbol_tokens.py +942 -0
  251. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/name_recognition.py +83 -0
  252. paxman-0.1.0/paxman/capabilities/SIUnit/grammar/symbol_recognition.py +93 -0
  253. paxman-0.1.0/paxman/capabilities/SIUnit/notation.py +46 -0
  254. paxman-0.1.0/paxman/capabilities/SIUnit/rules/__init__.py +1 -0
  255. paxman-0.1.0/paxman/capabilities/SIUnit/rules/bipm_si_brochure_ed2019.py +187 -0
  256. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/__init__.py +1 -0
  257. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/prefixed_unit_names.py +876 -0
  258. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/prefixed_units.py +878 -0
  259. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/si_base_units.py +8 -0
  260. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/si_derived_units.py +38 -0
  261. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/si_nonsi_units.py +38 -0
  262. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/si_prefixes.py +59 -0
  263. paxman-0.1.0/paxman/capabilities/SIUnit/rules/data/unit_names.py +70 -0
  264. paxman-0.1.0/paxman/capabilities/SIUnit/rules/iso_80000_ed2022.py +218 -0
  265. paxman-0.1.0/paxman/capabilities/SIUnit/rules/split_prefixes.py +71 -0
  266. paxman-0.1.0/paxman/capabilities/URL/__init__.py +7 -0
  267. paxman-0.1.0/paxman/capabilities/URL/capability.py +56 -0
  268. paxman-0.1.0/paxman/capabilities/URL/contract.py +24 -0
  269. paxman-0.1.0/paxman/capabilities/URL/grammar/__init__.py +0 -0
  270. paxman-0.1.0/paxman/capabilities/URL/grammar/absolute_uri_recognition.py +84 -0
  271. paxman-0.1.0/paxman/capabilities/URL/notation.py +17 -0
  272. paxman-0.1.0/paxman/capabilities/URL/parsing.py +837 -0
  273. paxman-0.1.0/paxman/capabilities/URL/rules/__init__.py +0 -0
  274. paxman-0.1.0/paxman/capabilities/URL/rules/data/__init__.py +0 -0
  275. paxman-0.1.0/paxman/capabilities/URL/rules/data/idna_uts46_mapping.py +15019 -0
  276. paxman-0.1.0/paxman/capabilities/URL/rules/data/idna_uts46_mapping.txt +9026 -0
  277. paxman-0.1.0/paxman/capabilities/URL/rules/whatwg_url_standard.py +77 -0
  278. paxman-0.1.0/paxman/capabilities/__init__.py +104 -0
  279. paxman-0.1.0/paxman/cli.py +275 -0
  280. paxman-0.1.0/paxman/core/AGENTS.md +41 -0
  281. paxman-0.1.0/paxman/core/__init__.py +69 -0
  282. paxman-0.1.0/paxman/core/capability.py +87 -0
  283. paxman-0.1.0/paxman/core/capability_contract.py +95 -0
  284. paxman-0.1.0/paxman/core/contract.py +124 -0
  285. paxman-0.1.0/paxman/core/discovery.py +83 -0
  286. paxman-0.1.0/paxman/core/domain.py +264 -0
  287. paxman-0.1.0/paxman/core/errors.py +56 -0
  288. paxman-0.1.0/paxman/core/extensions.py +117 -0
  289. paxman-0.1.0/paxman/core/grammar/__init__.py +31 -0
  290. paxman-0.1.0/paxman/core/grammar/boundary.py +110 -0
  291. paxman-0.1.0/paxman/core/grammar/composer.py +108 -0
  292. paxman-0.1.0/paxman/core/grammar/lexicon.py +42 -0
  293. paxman-0.1.0/paxman/core/grammar/pipeline.py +45 -0
  294. paxman-0.1.0/paxman/core/grammar/stages.py +195 -0
  295. paxman-0.1.0/paxman/engine/__init__.py +0 -0
  296. paxman-0.1.0/paxman/engine/orchestrator.py +525 -0
  297. paxman-0.1.0/paxman/py.typed +0 -0
  298. paxman-0.1.0/paxman/shared_data/README.md +10 -0
  299. paxman-0.1.0/paxman/shared_data/currency_snapshot.json +1609 -0
  300. paxman-0.1.0/pyproject.toml +129 -0
  301. paxman-0.1.0/tests/AGENTS.md +47 -0
  302. paxman-0.1.0/tests/__init__.py +0 -0
  303. paxman-0.1.0/tests/capabilities/__init__.py +0 -0
  304. paxman-0.1.0/tests/capabilities/country/__init__.py +1 -0
  305. paxman-0.1.0/tests/capabilities/country/test_capability.py +297 -0
  306. paxman-0.1.0/tests/capabilities/country/test_data_consistency.py +153 -0
  307. paxman-0.1.0/tests/capabilities/country/test_grammar.py +483 -0
  308. paxman-0.1.0/tests/capabilities/country/test_rules.py +654 -0
  309. paxman-0.1.0/tests/capabilities/currency/__init__.py +0 -0
  310. paxman-0.1.0/tests/capabilities/currency/test_capability.py +149 -0
  311. paxman-0.1.0/tests/capabilities/currency/test_contract.py +68 -0
  312. paxman-0.1.0/tests/capabilities/currency/test_data.py +207 -0
  313. paxman-0.1.0/tests/capabilities/currency/test_data_consistency.py +87 -0
  314. paxman-0.1.0/tests/capabilities/currency/test_grammar.py +166 -0
  315. paxman-0.1.0/tests/capabilities/currency/test_notation.py +43 -0
  316. paxman-0.1.0/tests/capabilities/currency/test_rules.py +315 -0
  317. paxman-0.1.0/tests/capabilities/date/__init__.py +0 -0
  318. paxman-0.1.0/tests/capabilities/date/test_capability.py +149 -0
  319. paxman-0.1.0/tests/capabilities/date/test_grammar.py +240 -0
  320. paxman-0.1.0/tests/capabilities/date/test_rules.py +204 -0
  321. paxman-0.1.0/tests/capabilities/email/__init__.py +0 -0
  322. paxman-0.1.0/tests/capabilities/email/test_capability.py +124 -0
  323. paxman-0.1.0/tests/capabilities/email/test_grammar.py +177 -0
  324. paxman-0.1.0/tests/capabilities/email/test_rules.py +163 -0
  325. paxman-0.1.0/tests/capabilities/iban/__init__.py +1 -0
  326. paxman-0.1.0/tests/capabilities/iban/test_capability.py +43 -0
  327. paxman-0.1.0/tests/capabilities/iban/test_contract.py +42 -0
  328. paxman-0.1.0/tests/capabilities/iban/test_grammar.py +119 -0
  329. paxman-0.1.0/tests/capabilities/iban/test_notation.py +32 -0
  330. paxman-0.1.0/tests/capabilities/iban/test_rules.py +96 -0
  331. paxman-0.1.0/tests/capabilities/ip/__init__.py +0 -0
  332. paxman-0.1.0/tests/capabilities/ip/test_capability.py +159 -0
  333. paxman-0.1.0/tests/capabilities/ip/test_grammar.py +137 -0
  334. paxman-0.1.0/tests/capabilities/ip/test_rules.py +204 -0
  335. paxman-0.1.0/tests/capabilities/isbn/__init__.py +1 -0
  336. paxman-0.1.0/tests/capabilities/isbn/test_capability.py +109 -0
  337. paxman-0.1.0/tests/capabilities/isbn/test_contract.py +51 -0
  338. paxman-0.1.0/tests/capabilities/isbn/test_data.py +85 -0
  339. paxman-0.1.0/tests/capabilities/isbn/test_grammar.py +196 -0
  340. paxman-0.1.0/tests/capabilities/isbn/test_notation.py +35 -0
  341. paxman-0.1.0/tests/capabilities/isbn/test_rules.py +228 -0
  342. paxman-0.1.0/tests/capabilities/issn/__init__.py +1 -0
  343. paxman-0.1.0/tests/capabilities/issn/test_capability.py +68 -0
  344. paxman-0.1.0/tests/capabilities/issn/test_contract.py +56 -0
  345. paxman-0.1.0/tests/capabilities/issn/test_grammar.py +143 -0
  346. paxman-0.1.0/tests/capabilities/issn/test_notation.py +43 -0
  347. paxman-0.1.0/tests/capabilities/issn/test_rules.py +99 -0
  348. paxman-0.1.0/tests/capabilities/money/__init__.py +1 -0
  349. paxman-0.1.0/tests/capabilities/money/test_capability.py +179 -0
  350. paxman-0.1.0/tests/capabilities/money/test_contract.py +107 -0
  351. paxman-0.1.0/tests/capabilities/money/test_data.py +214 -0
  352. paxman-0.1.0/tests/capabilities/money/test_data_consistency.py +69 -0
  353. paxman-0.1.0/tests/capabilities/money/test_grammar.py +337 -0
  354. paxman-0.1.0/tests/capabilities/money/test_notation.py +79 -0
  355. paxman-0.1.0/tests/capabilities/money/test_parsing.py +161 -0
  356. paxman-0.1.0/tests/capabilities/money/test_rules.py +390 -0
  357. paxman-0.1.0/tests/capabilities/phone/__init__.py +1 -0
  358. paxman-0.1.0/tests/capabilities/phone/test_capability.py +331 -0
  359. paxman-0.1.0/tests/capabilities/phone/test_data.py +70 -0
  360. paxman-0.1.0/tests/capabilities/phone/test_grammar.py +443 -0
  361. paxman-0.1.0/tests/capabilities/phone/test_rules.py +482 -0
  362. paxman-0.1.0/tests/capabilities/si_unit/test_capability.py +240 -0
  363. paxman-0.1.0/tests/capabilities/si_unit/test_contract.py +54 -0
  364. paxman-0.1.0/tests/capabilities/si_unit/test_data.py +199 -0
  365. paxman-0.1.0/tests/capabilities/si_unit/test_data_consistency.py +113 -0
  366. paxman-0.1.0/tests/capabilities/si_unit/test_grammar.py +238 -0
  367. paxman-0.1.0/tests/capabilities/si_unit/test_notation.py +31 -0
  368. paxman-0.1.0/tests/capabilities/si_unit/test_rules.py +305 -0
  369. paxman-0.1.0/tests/capabilities/url/__init__.py +0 -0
  370. paxman-0.1.0/tests/capabilities/url/test_capability.py +135 -0
  371. paxman-0.1.0/tests/capabilities/url/test_contract.py +59 -0
  372. paxman-0.1.0/tests/capabilities/url/test_data.py +58 -0
  373. paxman-0.1.0/tests/capabilities/url/test_data_consistency.py +121 -0
  374. paxman-0.1.0/tests/capabilities/url/test_grammar.py +106 -0
  375. paxman-0.1.0/tests/capabilities/url/test_notation.py +31 -0
  376. paxman-0.1.0/tests/capabilities/url/test_parsing.py +178 -0
  377. paxman-0.1.0/tests/capabilities/url/test_parsing_conformance.py +298 -0
  378. paxman-0.1.0/tests/capabilities/url/test_rule.py +234 -0
  379. paxman-0.1.0/tests/conftest.py +9 -0
  380. paxman-0.1.0/tests/e2e/__init__.py +0 -0
  381. paxman-0.1.0/tests/e2e/test_bootstrap.py +25 -0
  382. paxman-0.1.0/tests/e2e/test_canonicalize.py +621 -0
  383. paxman-0.1.0/tests/integration/__init__.py +0 -0
  384. paxman-0.1.0/tests/integration/test_ambiguity.py +59 -0
  385. paxman-0.1.0/tests/integration/test_benchmark_harness.py +40 -0
  386. paxman-0.1.0/tests/integration/test_country_pipeline.py +593 -0
  387. paxman-0.1.0/tests/integration/test_currency_pipeline.py +173 -0
  388. paxman-0.1.0/tests/integration/test_date_capability.py +154 -0
  389. paxman-0.1.0/tests/integration/test_feature_gating.py +423 -0
  390. paxman-0.1.0/tests/integration/test_format_value_seam.py +361 -0
  391. paxman-0.1.0/tests/integration/test_grammar_extensions.py +338 -0
  392. paxman-0.1.0/tests/integration/test_iban_capability.py +141 -0
  393. paxman-0.1.0/tests/integration/test_issn_capability.py +211 -0
  394. paxman-0.1.0/tests/integration/test_money_pipeline.py +254 -0
  395. paxman-0.1.0/tests/integration/test_phone_pipeline.py +259 -0
  396. paxman-0.1.0/tests/integration/test_pipeline.py +756 -0
  397. paxman-0.1.0/tests/integration/test_recognition_seam.py +432 -0
  398. paxman-0.1.0/tests/integration/test_si_unit_pipeline.py +233 -0
  399. paxman-0.1.0/tests/integration/test_single_value_invariant.py +111 -0
  400. paxman-0.1.0/tests/integration/test_span_exposure.py +55 -0
  401. paxman-0.1.0/tests/integration/test_temporal.py +61 -0
  402. paxman-0.1.0/tests/integration/test_url_pipeline.py +166 -0
  403. paxman-0.1.0/tests/property/__init__.py +0 -0
  404. paxman-0.1.0/tests/property/_legacy_currency_grammars.py +117 -0
  405. paxman-0.1.0/tests/property/_legacy_money_grammars.py +153 -0
  406. paxman-0.1.0/tests/property/_legacy_phone_url_grammars.py +218 -0
  407. paxman-0.1.0/tests/property/_legacy_remaining_grammars.py +523 -0
  408. paxman-0.1.0/tests/property/_legacy_siunit_grammars.py +145 -0
  409. paxman-0.1.0/tests/property/grammar_stage_parity.py +31 -0
  410. paxman-0.1.0/tests/property/test_currency_properties.py +80 -0
  411. paxman-0.1.0/tests/property/test_domain_properties.py +182 -0
  412. paxman-0.1.0/tests/property/test_format_value_properties.py +275 -0
  413. paxman-0.1.0/tests/property/test_grammar_properties.py +115 -0
  414. paxman-0.1.0/tests/property/test_grammar_stage_parity.py +952 -0
  415. paxman-0.1.0/tests/property/test_iban_properties.py +82 -0
  416. paxman-0.1.0/tests/property/test_isbn_properties.py +130 -0
  417. paxman-0.1.0/tests/property/test_issn_properties.py +160 -0
  418. paxman-0.1.0/tests/property/test_money_properties.py +135 -0
  419. paxman-0.1.0/tests/property/test_rule_properties.py +140 -0
  420. paxman-0.1.0/tests/property/test_si_unit_properties.py +119 -0
  421. paxman-0.1.0/tests/property/test_url_properties.py +111 -0
  422. paxman-0.1.0/tests/unit/__init__.py +0 -0
  423. paxman-0.1.0/tests/unit/test_api_coverage_fix.py +36 -0
  424. paxman-0.1.0/tests/unit/test_bootstrap.py +81 -0
  425. paxman-0.1.0/tests/unit/test_boundary_guards.py +75 -0
  426. paxman-0.1.0/tests/unit/test_candidate.py +114 -0
  427. paxman-0.1.0/tests/unit/test_capability.py +136 -0
  428. paxman-0.1.0/tests/unit/test_capability_contract.py +181 -0
  429. paxman-0.1.0/tests/unit/test_capability_exports.py +184 -0
  430. paxman-0.1.0/tests/unit/test_capability_lazy_import.py +91 -0
  431. paxman-0.1.0/tests/unit/test_capability_surface.py +567 -0
  432. paxman-0.1.0/tests/unit/test_contract.py +197 -0
  433. paxman-0.1.0/tests/unit/test_contract_surface.py +129 -0
  434. paxman-0.1.0/tests/unit/test_currency_data_regeneration.py +21 -0
  435. paxman-0.1.0/tests/unit/test_discovery.py +214 -0
  436. paxman-0.1.0/tests/unit/test_errors.py +58 -0
  437. paxman-0.1.0/tests/unit/test_extensions.py +253 -0
  438. paxman-0.1.0/tests/unit/test_grammar_semantic_purity.py +78 -0
  439. paxman-0.1.0/tests/unit/test_grammar_semantics_consistency.py +325 -0
  440. paxman-0.1.0/tests/unit/test_grammar_semantics_metadata.py +152 -0
  441. paxman-0.1.0/tests/unit/test_lexicon_alternation.py +60 -0
  442. paxman-0.1.0/tests/unit/test_new_capability_tool.py +272 -0
  443. paxman-0.1.0/tests/unit/test_package_install.py +158 -0
  444. paxman-0.1.0/tests/unit/test_phone_helpers.py +43 -0
  445. paxman-0.1.0/tests/unit/test_pipeline_stages.py +66 -0
  446. paxman-0.1.0/tests/unit/test_provenance.py +72 -0
  447. paxman-0.1.0/tests/unit/test_recognized_rep.py +148 -0
  448. paxman-0.1.0/tests/unit/test_removed_surfaces.py +75 -0
  449. paxman-0.1.0/tests/unit/test_resolution.py +29 -0
  450. paxman-0.1.0/tests/unit/test_rule_metadata.py +153 -0
  451. paxman-0.1.0/tests/unit/test_rule_output_format_purity.py +50 -0
  452. paxman-0.1.0/tests/unit/test_status_computation_invariant.py +99 -0
  453. paxman-0.1.0/tests/unit/test_version_stamp.py +41 -0
  454. paxman-0.1.0/tools/__init__.py +1 -0
  455. paxman-0.1.0/tools/canonicalize_si_unit.py +55 -0
  456. paxman-0.1.0/tools/generate_readme_table.py +100 -0
  457. paxman-0.1.0/tools/new_capability.py +773 -0
  458. paxman-0.1.0/tools/regenerate_currency_data.py +445 -0
  459. paxman-0.1.0/tools/regenerate_idna_uts46_data.py +142 -0
  460. paxman-0.1.0/tools/regenerate_isbn_range_data.py +106 -0
  461. paxman-0.1.0/tools/regenerate_si_prefix_data.py +270 -0
  462. paxman-0.1.0/tools/si_unit_canonicalize.py +32 -0
  463. paxman-0.1.0/uv.lock +527 -0
@@ -0,0 +1,72 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ types: [opened, synchronize, reopened]
7
+ push:
8
+ branches: [main, "feature/**", "refactor/**"]
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ ci:
19
+ runs-on: ubuntu-latest
20
+
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ python-version: ["3.11", "3.12", "3.13"]
25
+
26
+ steps:
27
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
28
+ with:
29
+ persist-credentials: false
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
33
+
34
+ - name: Set up Python
35
+ run: uv python install ${{ matrix.python-version }}
36
+
37
+ - name: Install dependencies
38
+ run: uv sync --all-extras --python ${{ matrix.python-version }}
39
+
40
+ - name: Ruff lint
41
+ run: uv run ruff check paxman/ tests/
42
+
43
+ - name: Ruff format check
44
+ run: uv run ruff format --check paxman/ tests/
45
+
46
+ - name: Pyright type check
47
+ run: uv run pyright
48
+
49
+ - name: Import lint
50
+ run: uv run import-linter lint
51
+
52
+ - name: Check generated Currency/Money data drift
53
+ run: uv run python tools/regenerate_currency_data.py --check
54
+
55
+ - name: Check generated SIUnit data drift
56
+ run: uv run python tools/regenerate_si_prefix_data.py --check
57
+
58
+ - name: Benchmark (informational, non-blocking)
59
+ continue-on-error: true
60
+ run: uv run python -m benchmarks.harness --iterations 50 --output /tmp/bench.json && cat /tmp/bench.json
61
+
62
+ - name: Tests with coverage
63
+ run: uv run pytest --cov=paxman --cov-report=term-missing --cov-report=html --tb=short -q
64
+
65
+ - name: Enforce per-package coverage
66
+ run: |
67
+ echo "Checking per-package coverage thresholds..."
68
+ uv run coverage report --include="paxman/core/*" --fail-under=95
69
+ uv run coverage report --include="paxman/capabilities/*" --fail-under=95
70
+ uv run coverage report --include="paxman/engine/*" --fail-under=95
71
+ uv run coverage report --include="paxman/api/*" --fail-under=95
72
+ echo "All packages meet 95% coverage threshold."
@@ -0,0 +1,33 @@
1
+ name: opencode
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+
9
+ jobs:
10
+ opencode:
11
+ if: |
12
+ contains(github.event.comment.body, ' /oc') ||
13
+ startsWith(github.event.comment.body, '/oc') ||
14
+ contains(github.event.comment.body, ' /opencode') ||
15
+ startsWith(github.event.comment.body, '/opencode')
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ id-token: write
19
+ contents: read
20
+ pull-requests: read
21
+ issues: read
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v6
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - name: Run opencode
29
+ uses: anomalyco/opencode/github@latest
30
+ env:
31
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
32
+ with:
33
+ model: ${{ secrets.OPENCODE_MODEL_ID }}
@@ -0,0 +1,88 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ name: Build distributions
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
18
+ with:
19
+ fetch-depth: 0
20
+ persist-credentials: false
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
24
+
25
+ - name: Set up Python
26
+ run: uv python install 3.11
27
+
28
+ - name: Validate version matches tag
29
+ if: startsWith(github.ref, 'refs/tags/')
30
+ run: |
31
+ TAG_RAW="${GITHUB_REF#refs/tags/}"
32
+ if [[ ! "$TAG_RAW" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
33
+ echo "::error::Tag $TAG_RAW does not match expected format vX.Y.Z"
34
+ exit 1
35
+ fi
36
+ TAG="${TAG_RAW#v}"
37
+ VERSION="$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
38
+ echo "Tag: $TAG pyproject version: $VERSION"
39
+ if [ "$TAG" != "$VERSION" ]; then
40
+ echo "::error::Tag v$TAG does not match pyproject.toml version $VERSION"
41
+ exit 1
42
+ fi
43
+
44
+ - name: Build sdist and wheel
45
+ run: uv build
46
+
47
+ - name: Twine check
48
+ run: uv run --with twine twine check dist/*
49
+
50
+ - name: Verify py.typed in wheel
51
+ run: |
52
+ uv run python -c "
53
+ import zipfile, pathlib, sys
54
+ whls = list(pathlib.Path('dist').glob('*.whl'))
55
+ assert whls, 'no wheel built'
56
+ for whl in whls:
57
+ with zipfile.ZipFile(whl) as z:
58
+ names = z.namelist()
59
+ assert any(n.endswith('paxman/py.typed') for n in names), f'py.typed missing in {whl.name}: {names[:20]}'
60
+ print(f'py.typed Ships in {whl.name}')
61
+ "
62
+
63
+ - name: Upload artifacts
64
+ uses: actions/upload-artifact@v4
65
+ with:
66
+ name: dist
67
+ path: dist/
68
+
69
+ publish:
70
+ name: Publish to PyPI
71
+ needs: build
72
+ runs-on: ubuntu-latest
73
+ if: startsWith(github.ref, 'refs/tags/v')
74
+ environment: pypi
75
+ permissions:
76
+ id-token: write
77
+ steps:
78
+ - name: Download distributions
79
+ uses: actions/download-artifact@v4
80
+ with:
81
+ name: dist
82
+ path: dist/
83
+
84
+ - name: Publish to PyPI via Trusted Publishing
85
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1
86
+ with:
87
+ verbose: true
88
+ print-hash: true
@@ -0,0 +1,37 @@
1
+ .omo
2
+ .worktrees/
3
+ # Python
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+ .pytest_cache/
28
+ .mypy_cache/
29
+ .ruff_cache/
30
+ .pyright/
31
+ htmlcov/
32
+ .coverage
33
+ .coverage.*
34
+ coverage.xml
35
+ *.cover
36
+ *.py,cover
37
+ .hypothesis/
paxman-0.1.0/AGENTS.md ADDED
@@ -0,0 +1,108 @@
1
+ # PROJECT KNOWLEDGE BASE
2
+
3
+ **Generated:** 2026-08-22
4
+ **Commit:** d7737f0
5
+ **Branch:** chores/pre-release-housekeeping
6
+
7
+ ## OVERVIEW
8
+ Paxman is a Python 3.11+ canonicalization library with a small CLI: takes ambiguous human input, returns what authoritative specs say it means, with full provenance. Deterministic, provenance-first. 12 capabilities (Country, Currency, Date, Email, IBAN, IP, ISBN, ISSN, Money, Phone, SI Unit, URL). Toolchain: uv + hatchling, ruff, strict pyright, import-linter, pytest at 95% coverage.
9
+
10
+ ## STRUCTURE
11
+ ```text
12
+ paxman/
13
+ ├── api/ # canonicalize() + bootstrap (register_all_shipped, list_shipped_capabilities)
14
+ ├── cli.py # CLI: `paxman` console script / `python -m paxman` (--list, --json, stdin)
15
+ ├── __main__.py # python -m paxman entry
16
+ ├── engine/ # run_capability() pipeline orchestrator
17
+ ├── core/ # domain objects, Contract protocol, registry, extensions, errors (+ grammar/ shared machinery)
18
+ ├── capabilities/ # 12 self-contained capability packages
19
+ ├── shared_data/ # cross-capability source snapshots (currency_snapshot.json → Currency + Money data)
20
+ └── py.typed # PEP 561 marker
21
+ benchmarks/ # harness.py (CI-run), grammar_stage_parity.py, baseline.json
22
+ tests/ # unit / capabilities/<cap> / integration / property / e2e
23
+ tools/ # new_capability.py (scaffolder), generate_readme_table.py,
24
+ # regenerate_{isbn_range,si_prefix,idna_uts46,currency}_data.py
25
+ docs/ # adr/, development/, recipes/, user/
26
+ ```
27
+
28
+ ## WHERE TO LOOK
29
+ | Task | Location |
30
+ |------|----------|
31
+ | Trace pipeline flow | `paxman/engine/orchestrator.py` → `run_capability()` |
32
+ | Domain vocabulary (Rule, Provenance, Candidate…) | `paxman/core/domain.py` |
33
+ | Contract protocol | `paxman/core/contract.py`, `paxman/core/capability_contract.py` |
34
+ | Capability registration | `paxman/core/discovery.py` (explicit, never auto) |
35
+ | Community extensions | `paxman/core/extensions.py` → `register_grammar` / `register_rule` + `extra_grammars` on contracts |
36
+ | Error hierarchy | `paxman/core/errors.py` |
37
+ | Add a capability | `HOW_TO_ADD_NEW_CAPABILITY.md` (62KB spec — read first). Scaffold first with `tools/new_capability.py` (see HOW_TO_ADD_NEW_CAPABILITY.md Step 0); then fill in the domain. |
38
+ | Recognition (per cap) | `paxman/capabilities/<Name>/grammar/` |
39
+ | Validation (per cap) | `paxman/capabilities/<Name>/rules/` |
40
+ | Presentation seam | `paxman/capabilities/<Name>/capability.py` → `format_value()` |
41
+ | Regenerate generated data | `tools/regenerate_isbn_range_data.py` (ISBN range), `tools/regenerate_si_prefix_data.py` (SIUnit prefixed units), `tools/regenerate_idna_uts46_data.py` (URL IDNA mapping), `tools/regenerate_currency_data.py` (Currency + Money from `paxman/shared_data/currency_snapshot.json`) |
42
+ | CLI behavior | `paxman/cli.py` (`--list`, `--json`, stdin; contract flags are API-only) |
43
+ | Merge-blocking commands | `.github/workflows/ci.yml` (authoritative) |
44
+
45
+ ## CODE MAP
46
+ | Symbol | Type | Location | Role |
47
+ |--------|------|----------|------|
48
+ | `canonicalize()` | function | `paxman/api/canonicalize.py` | Sole user entry point → `run_capability()` |
49
+ | `register_all_shipped()` / `list_shipped_capabilities()` | functions | `paxman/api/bootstrap.py` | One-call registration of the 12 shipped capabilities; deterministic name list |
50
+ | `list_registered_capabilities()` | function | `paxman/core/discovery.py` | Introspection of the live registry |
51
+ | `register_capability()` | function | `paxman/core/discovery.py` | Registry add; freezes on first run |
52
+ | `register_grammar()` / `register_rule()` | functions | `paxman/core/extensions.py` | Community extension seam (opt-in via contract `extra_grammars`) |
53
+ | `run_capability()` | function | `paxman/engine/orchestrator.py` | Full pipeline (recognize→validate→resolve→hash) |
54
+ | `ExecutionResult` | dataclass | `paxman/engine/orchestrator.py` | Return type of `canonicalize()` |
55
+ | `Capability` | ABC | `paxman/core/capability.py` | `get_grammars`/`get_rules`/`format_value` |
56
+ | `CapabilityContract` | dataclass | `paxman/core/capability_contract.py` | Frozen contract base (no slots) |
57
+ | `Contract` | Protocol | `paxman/core/contract.py` | Structural contract interface |
58
+ | `Rule` / `Grammar` | ABCs | `paxman/core/domain.py` | Validation / recognition units |
59
+ | `Resolution`, `Provenance`, `Candidate`, `RecognizedRep`, `VersionStamp` | dataclasses | `paxman/core/domain.py` | Pipeline value objects |
60
+ | `main()` | function | `paxman/cli.py` | CLI entry (`[project.scripts] paxman` + `python -m paxman`) |
61
+
62
+ ## CONVENTIONS
63
+ - **uv only** — no Makefile/tox/nox. Every command via `uv run`.
64
+ - Per-capability layout: `notation.py`, `contract.py`, `capability.py`, `grammar/`, `rules/`.
65
+ - Rule file = ONE publication (`rfc_5322_ed2008.py`); class = one section; rule `name` = `"Section 3.4.1-addr-spec"`.
66
+ - Grammars recognize only: span-bearing `RecognitionMatch`, never bare notation, never validate/dedup/order.
67
+ - Rules never read `output_format` (CI source-scan enforced), never raise, never gate on `include_*` (declared as `requires_features`).
68
+ - `format_value()` is the ONLY presentation seam; `output_format` resolved in `CapabilityContract.__post_init__`.
69
+ - Domain objects: `@dataclass(frozen=True, slots=True)`. Contracts: `@dataclass(frozen=True)` **without** slots.
70
+ - Test doubles local to the test file/conftest — no shared mock libraries.
71
+ - Registry is module-level and freezes per pipeline run; tests use autouse `_clean_registry` fixture (integration/e2e).
72
+ - TDD: failing test first. No skipped tests without justification.
73
+
74
+ ## ANTI-PATTERNS (THIS PROJECT)
75
+ - **No `# type: ignore` / `# noqa` / `# pyright: ignore` in `paxman/` source** — fix root cause or use scoped ruff `per-file-ignores` (sanctioned pattern in pyproject). Tests may use `# type: ignore[misc]` for immutability checks.
76
+ - Deterministic by construction: given the same input, the same contract, and the same library snapshot (fixed library version, registry contents, and rule-data tables), the pipeline always yields the same canonical output — no world-knowledge, no clock, no environment-dependent ordering, no fuzzy logic, no network inference across recognition, validation, and canonicalization.
77
+ - No cross-capability imports; capabilities import only from `paxman.core`; `paxman.core` imports nothing from `paxman.*`.
78
+ - Grammars must not map tokens to canonical values or import rule-layer data.
79
+ - Rules never contain the token `output_format` (code, comments, or docstrings).
80
+ - No `as any`, no broad exception suppression.
81
+
82
+ ## COMMANDS
83
+ ```bash
84
+ uv sync --all-extras # install
85
+ uv run ruff check paxman/ tests/ # lint
86
+ uv run ruff format --check paxman/ tests/ # format check
87
+ uv run pyright # strict typecheck
88
+ uv run import-linter lint # layer boundaries
89
+ uv run pytest # all tests
90
+ uv run pytest -m "unit or capability or integration or e2e" # by marker (also: property, benchmark, country, currency, isbn, issn, money, url, si_unit)
91
+ uv run pytest --cov=paxman --cov-report=term-missing --tb=short -q
92
+ uv run coverage report --include="paxman/core/*,paxman/capabilities/*,paxman/engine/*,paxman/api/*" --fail-under=95
93
+ uv run python tools/regenerate_isbn_range_data.py # regenerate ISBN range message module
94
+ uv run python tools/regenerate_si_prefix_data.py # regenerate SIUnit prefixed-unit modules
95
+ uv run python tools/regenerate_idna_uts46_data.py # regenerate URL IDNA UTS #46 mapping
96
+ uv run python tools/regenerate_currency_data.py # regenerate Currency + Money data from shared snapshot
97
+ uv run python -m paxman email "user@example.com" # CLI smoke test
98
+ ```
99
+ Full pre-PR gate: `uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run import-linter lint && uv run pytest`
100
+
101
+ ## NOTES
102
+ - `paxman/capabilities/__init__.py` exports all twelve shipped capabilities (Country, Currency, Date, Email, IBAN, IP, ISBN, ISSN, Money, Phone, SI Unit, URL); export completeness is enforced by `tests/unit/test_capability_exports.py`.
103
+ - CONTEXT.md is the domain glossary for the full shipped set (twelve capabilities). It is kept in sync with the code; when adding a capability, update its Notation/table entries there too.
104
+ - No `pyrightconfig.json` — pyright config is inline `[tool.pyright]` in pyproject.toml. No `.editorconfig`.
105
+ - Data modules live under `rules/data/` and `grammar/data/` — plain module-level tables separating data from logic. Generated modules (edit via snapshot + regenerate, never by hand): ISBN range message (`tools/regenerate_isbn_range_data.py`), URL IDNA UTS #46 mapping (`tools/regenerate_idna_uts46_data.py`), SIUnit prefixed-unit and grammar token tables (`tools/regenerate_si_prefix_data.py`), and the Currency + Money data set (`tools/regenerate_currency_data.py`, from `paxman/shared_data/currency_snapshot.json`). Unmarked data files are edited directly.
106
+ - Library + CLI: `[project.scripts] paxman = "paxman.cli:main"` and `python -m paxman`; CLI supports `--list`, `--json`, stdin input. Version 0.1.0.
107
+ - Publishing: `.github/workflows/publish.yml` uses PyPI Trusted Publishing (OIDC) with a Git-tag ↔ `pyproject.toml` version safety check; `paxman/py.typed` ships PEP 561 conformance.
108
+ - Coverage: global `fail_under = 95`; `paxman/cli.py` and `paxman/__main__.py` are omitted from coverage (smoke-tested via e2e).
@@ -0,0 +1,258 @@
1
+ # Paxman Architecture
2
+
3
+ Paxman is a canonicalization authority resolver — a library that takes ambiguous human input and returns what authoritative specifications say that input means, with full provenance. This document describes the architectural principles, structural layers, and design decisions that shape the system.
4
+
5
+ ---
6
+
7
+ ## Core Principles
8
+
9
+ ### Determinism
10
+
11
+ Paxman never guesses. Given the same input, the same contract configuration, and the same library snapshot (fixed library version, registry contents, and rule-data tables), the pipeline always yields the same canonical output. This property holds by construction: every pipeline stage — grammar recognition, rule validation, capability formatting, candidate deduplication, and status determination — is a pure function of its inputs with no world-knowledge, no clock, no environment-dependent ordering, no fuzzy logic, and no network inference. Identical inputs therefore always produce identical outputs, enabling auditability and reproducibility. Across library snapshots, provenance or rule-routing changes may alter the resulting metadata; the determinism guarantee is scoped to a fixed snapshot.
12
+
13
+ ### Provenance-First
14
+
15
+ Every canonicalized value carries full provenance — a citation of the authoritative specification, registry, or policy that validates it. Provenance is not optional metadata; it is a structural requirement. If no authority can validate a recognized input, the system reports INVALID rather than returning an unvalidated value. This ensures that users always know *why* a value is considered canonical.
16
+
17
+ ### Separation of Recognition and Validation
18
+
19
+ Paxman strictly separates the act of finding values in text (recognition) from the act of determining whether those values are valid (validation). This separation is the foundation of the architecture:
20
+
21
+ - **Recognition** (syntactic): Grammars scan raw text and extract structured representations. They do not validate — they only find.
22
+ - **Validation** (semantic): Rules accept structured representations and determine whether authoritative specifications validate them. They produce canonical values with provenance.
23
+
24
+ This separation means that a single input can be recognized by multiple grammars and validated by multiple rules, enabling ambiguity detection when different authoritative sources disagree.
25
+
26
+ ### Recognition Pipeline Contract
27
+
28
+ Every grammar implements `recognize(text) -> list[RecognitionMatch]`, where
29
+ `RecognitionMatch` carries the notation plus a half-open `[start, end)` span
30
+ and the matched `raw_text`. The grammar produces positions; the engine owns
31
+ all cross-match policy:
32
+
33
+ - **Containment dedup (per grammar):** a match fully contained in a longer
34
+ match from the SAME grammar is dropped ("longer wins"). Matches from the
35
+ same grammar with identical `[start, end)` spans keep the first-emitted
36
+ match. Dedup never runs across grammars, so two grammars agreeing on the
37
+ same span (e.g. US vs European date reading of `01/02/2026`) are both
38
+ preserved and ambiguity stays observable.
39
+ - **Ordering:** recognitions are emitted in the total order
40
+ `(start, end, active-set index, grammar name)`, i.e. document order — where the
41
+ active set is `contract.active_grammars` or, when the contract returns `None`,
42
+ every shipped grammar in `get_grammars()` order.
43
+ - **Candidate dedup** (`value, recognition_rule, validation_rule`) runs
44
+ after validation as a stability net.
45
+
46
+ Grammars perform syntax-level extraction and normalization only; rules own
47
+ semantic validation with provenance. This contract applies identically to
48
+ every capability, built-in or future.
49
+
50
+ ### Capability Isolation
51
+
52
+ Each domain (Email, Date, Country, etc.) is encapsulated as a **Capability** — an independent module that defines its own intermediate representation, recognition rules, and validation rules. Capabilities cannot import from each other. The engine and core domain provide the orchestration layer; capabilities provide the domain expertise.
53
+
54
+ ---
55
+
56
+ ## Structural Layers
57
+
58
+ Paxman is organized into four layers, each with a distinct responsibility. Dependencies flow inward — outer layers depend on inner layers, never the reverse.
59
+
60
+ ### Core Domain
61
+
62
+ The innermost layer defines the shared vocabulary and abstract contracts that all other layers consume. It contains:
63
+
64
+ - **Abstract base classes** for Grammars (recognition) and Rules (validation)
65
+ - **Immutable value objects** representing provenance, candidates, recognized representations, and version stamps
66
+ - **Enums** for resolution status (MISSING, INVALID, SUCCESS, AMBIGUOUS) and rule strategies (REGEX, LOOKUP_TABLE, PARSER)
67
+ - **The Contract protocol** — a structural interface that all capability contracts must satisfy
68
+ - **The Capability abstract class** — a base class that all capability implementations must extend
69
+ - **The discovery registry** — a module-level registry that manages capability registration and lookup
70
+ - **Exception hierarchy** — typed errors for different failure modes
71
+
72
+ The core layer has no knowledge of specific capabilities. It defines *what* a capability is, not *how* any particular capability works.
73
+
74
+ ### Capabilities
75
+
76
+ Each capability is a self-contained domain module that provides:
77
+
78
+ - **A Notation type** — a typed intermediate representation specific to the domain (e.g., email local part and domain part, date N1/N2/N3)
79
+ - **Grammars** — recognition rules that extract the notation from raw text
80
+ - **Validation Rules** — semantic rules that validate the notation against authoritative specifications
81
+ - **A Contract** — a user-facing configuration object that toggles grammars, excludes rules, and passes parameters
82
+
83
+ Capabilities are registered with the discovery registry before the first canonicalization call. The registry freezes on the first `canonicalize()` call and remains frozen for later runs, ensuring that the set of available capabilities is stable during execution. The sanctioned bulk form is `paxman.register_all_shipped()`, which registers all ten shipped capabilities in fixed alphabetical order; registration — single or bootstrap — must complete from a single thread before the first `canonicalize()` call, after which reads are safe from any thread.
84
+
85
+ ### Engine
86
+
87
+ The engine is the orchestration layer that coordinates the full pipeline. It:
88
+
89
+ 1. Freezes the capability registry
90
+ 2. Looks up the requested capability by name
91
+ 3. Runs the recognition phase — iterating over active grammars to extract span-bearing recognition matches
92
+ 4. Runs the validation phase — testing each notation against active rules, which normalize to each capability's default canonical form and never inspect `output_format`
93
+ 5. Formats each validated value through the capability's `format_value()` seam — immediately after normalization and before deduplication and status determination
94
+ 6. Deduplicates identical candidates
95
+ 7. Determines the resolution status based on candidate outcomes
96
+ 8. Assembles the final execution result
97
+
98
+ The engine is capability-agnostic. It does not know what a "grammar" or "rule" does — it only knows that grammars produce span-bearing recognition matches and rules produce candidates.
99
+
100
+ Before the recognition phase, the engine composes each capability's shipped grammars and rules with any community extensions registered for that capability (see "Community Extensions" below). Composition is guarded: duplicate names fail fast, and every rule's declared `target_semantics` must resolve within the composed set.
101
+
102
+ ### Public API
103
+
104
+ The outermost layer exposes the user-facing interface. It is intentionally minimal — a single entry point that accepts input text and a contract, and returns a fully-resolved execution result with full provenance.
105
+
106
+ ---
107
+
108
+ ## Key Architectural Patterns
109
+
110
+ ### Protocol-Based Contracts
111
+
112
+ Contracts are defined as structural protocols (`Contract`), not inheritance-based base classes. Any class that satisfies the structural interface — providing the required attributes and methods — qualifies as a contract. This allows capability authors to design contract objects that fit their domain (using dataclasses, Pydantic models, etc.) without being constrained by a base class hierarchy. This prioritizes **user flexibility** and **decoupling**.
113
+
114
+ ### ABC-Based Capabilities
115
+
116
+ In contrast to contracts, Capabilities are defined as Abstract Base Classes (`Capability`). This prioritizes **internal rigidity** and **reliability**. Since capabilities are internal components managed by the engine's registry, strict inheritance ensures they adhere to the required structure (`get_grammars()`, `get_rules()`) and prevents runtime errors during discovery.
117
+
118
+ ### Capability as Factory
119
+
120
+ Capabilities do not hold state. They are factories that produce grammars and rules on demand. The engine queries a capability for its available grammars and rules, then filters them based on the contract configuration. This design keeps capabilities lightweight and makes the filtering logic centralized in the engine.
121
+
122
+ ### Typed Notation
123
+
124
+ Each capability defines a frozen dataclass Notation with one `str` field per recognized component (e.g., `DateNotation` carries `N1`, `N2`, `N3`). The concrete notation type is the sole type parameter threaded end to end: `Grammar[NotationT]` recognizes it and `Rule[NotationT]` validates it, so the engine and every rule operate on the fully typed object rather than a positional `list[str]`. Rules read notation fields by name (e.g., `notation.N1`); there is no generic list alias and no conversion bridge between the typed notation and a list form.
125
+
126
+ ### Contract Parameters
127
+
128
+ Contracts pass configuration parameters to validation rules, enabling rules to adapt their behavior based on user preferences.
129
+
130
+ **Base Contract Parameters:**
131
+ - **`output_format`**: Controls the canonical value format (e.g., `"ISO"` for `YYYY-MM-DD`, `"US"` for `MM/DD/YYYY`). `CapabilityContract.__post_init__` resolves `None`, `"default"`, and each capability's default format to a concrete string; the capability's `format_value()` seam applies the format to the rule-produced default canonical value. Validation rules never inspect `output_format` — they always normalize to the default canonical form. See "The Formatting Seam" below.
132
+ - **`pinned_rules`**: Pins to specific validation rules by name. When set, ONLY those rules run — `excluded_rules` is ignored. Takes precedence over `excluded_rules`.
133
+ - **`extra_grammars`**: Names community grammars (opt-in) to run alongside the capability's shipped active set — `contract.active_grammars`, or every shipped grammar when the contract returns `None` — in order. Unknown names are silently skipped; shipped names listed here are deduplicated. Registration happens through `paxman.register_grammar` / `paxman.register_rule` (see "Community Extensions" below).
134
+
135
+ **Date-Specific Parameters:**
136
+ - **`two_digit_base_year`**: Specifies the base year for interpreting two-digit years (e.g., `2000` means `"26"` becomes `2026`). Only available on Date contracts, not part of the base Contract protocol. Used by US and European grammars to resolve ambiguous year values.
137
+
138
+ These parameters are passed through the contract to rule methods (`matches()` and `normalize()`), allowing rules to be contract-aware without direct coupling to specific capabilities. `output_format` is the exception: it is a presentation parameter consumed by the capability's formatting seam, never by validation rules.
139
+
140
+ ### The Formatting Seam
141
+
142
+ Validation and presentation are separated at the pipeline level. Rules own validation and default normalization only: `matches()` never consults `output_format`, and `normalize()` always returns the capability's default canonical form (e.g., `YYYY-MM-DD` for Date, E.164 `+CCNSN` for Phone, alpha-2 for Country). The engine then renders each validated value through the capability's `format_value(value, output_format, notation)` method — called immediately after `normalize()` and before candidate deduplication and status determination:
143
+
144
+ **recognition → validation → default normalization → capability formatting → candidate deduplication → status → result**
145
+
146
+ Formatting adds no provenance: `Candidate.provenance`, `recognition_rule`, and `validation_rule` are set from the rule that validated the notation, and the formatter only transforms the value. Date, Phone, and Country implement conversions; Email and IP inherit the identity implementation because they offer no alternative formats.
147
+
148
+ A CI source scan (`tests/unit/test_rule_output_format_purity.py`) rejects any `output_format` token in `paxman/capabilities/*/rules/` modules — in code, comments, or docstrings — so presentation cannot migrate back into rules. In the Country capability, localized names (e.g., `Alemania` → `DE`) are formatted through the current alpha-2 conversion tables for `alpha3`, `numeric`, and `name` while retaining Unicode/CLDR provenance; historical former codes (e.g., `SU`) pass through unchanged for those formats when no current mapping exists, retaining ISO 3166-3 provenance.
149
+
150
+ ### Immutability
151
+
152
+ All domain objects are immutable. Once created, they cannot be modified. This is enforced through `@dataclass(frozen=True, slots=True)` — stdlib dataclasses that prevent attribute assignment and use efficient slot-based storage. Immutability ensures that objects can be safely shared, hashed, and used as dictionary keys without defensive copying.
153
+
154
+ ### Temporal Filtering
155
+
156
+ Rules carry a publication year from their authoritative specification. When a contract specifies a year, the engine filters out rules whose publication year exceeds that year. This allows users to pin to a specific historical version of a specification, excluding rules from newer revisions.
157
+
158
+ ### Determinism by Construction
159
+
160
+ Determinism is a structural property of the layered pipeline, not a post-hoc artifact:
161
+
162
+ - **Recognition layer.** Active grammars emit span-bearing `RecognitionMatch` objects from the input text, matching the `Grammar.recognize()` contract. Grammar output depends only on the input and the grammar itself.
163
+ - **Validation layer.** Rules accept recognized representations and produce candidates, each carrying a canonical value and provenance. A rule's output depends only on the representation and the contract.
164
+ - **Result layer.** The engine deduplicates identical candidates and folds the distinct candidate values into one of the resolution statuses: `SUCCESS` when all candidates agree on a single canonical value, `AMBIGUOUS` when they disagree, `INVALID` when nothing validated, and `MISSING` when no grammar recognized anything.
165
+
166
+ Every stage is a pure function of its inputs — no clocks, no randomness, no environment-dependent ordering, no world-knowledge, no fuzzy logic, no network inference — so the same input, contract configuration, and library snapshot (fixed library version, registry contents, and rule-data tables) always produce the same canonical output. The `VersionStamp` on each execution result records the library version for provenance; the determinism guarantee rests on this determinism-by-construction. Changes to provenance or rule routing across library snapshots can alter result metadata; the determinism guarantee is scoped to a fixed snapshot.
167
+
168
+ ### Community Extensions
169
+
170
+ Capabilities are closed for modification but open for extension. Community contributors register additional grammars (and the rules that validate them) against an existing capability through `paxman.core.extensions` — never by editing the capability package. The registries freeze with the capability registry at the first pipeline run.
171
+
172
+ A contract opts a registered grammar in by naming it in `extra_grammars`, a base `CapabilityContract` field surfaced on every `create_contract` factory. The engine composes the shipped active set with the opted-in extras, deduplicating names while preserving order — shipped slots first, extras after (unknown extra names are silently skipped). The shipped slots are `contract.active_grammars` when the contract implements it (the gated capabilities), or every shipped grammar in `get_grammars()` order when it returns `None` (the base default). Opt-in preserves determinism: a contract that names no extras composes to exactly the shipped set, so non-opt-in behavior is identical (deterministic).
173
+
174
+ Community rules follow the same opt-in discipline: a registered rule runs only when the contract's `extra_grammars` resolve to one of its `target_semantics` ids. An un-opted community rule — even one targeting a shipped grammar's semantics — never affects results, so a default contract resolves with shipped rules only.
175
+
176
+ Composition is guarded at pipeline start: a community grammar name colliding with a shipped name raises `CapabilityError`, and an opted-in community rule whose `target_semantics` names an id no grammar claims raises `ContractError` — failing fast rather than producing a silently wrong result. Community grammars and rules are pure functions of their inputs, and the composed set is fixed once the registries freeze, so the determinism guarantees of "Determinism by Construction" extend unchanged.
177
+
178
+ ---
179
+
180
+ ## Resolution Semantics
181
+
182
+ The system produces one of four resolution statuses:
183
+
184
+ | Status | Meaning |
185
+ |--------|---------|
186
+ | **MISSING** | No grammars recognized anything in the input. The input does not match any known pattern. |
187
+ | **INVALID** | Grammars recognized the input, but no validation rule could validate it against an authoritative specification. |
188
+ | **SUCCESS** | One or more rules validated the input, and all agree on the same canonical value. |
189
+ | **AMBIGUOUS** | Multiple rules validated the input but produced different canonical values. The system cannot determine which is correct. |
190
+
191
+ Ambiguity is detected at the value level, not the candidate level. Multiple candidates with the same canonical value still produce SUCCESS. Ambiguity requires genuinely different canonical outputs from different authoritative sources.
192
+
193
+ For multi-entity input, segmentation is caller-owned — see the [segmentation recipe](docs/recipes/segmentation.md) (ADR-0004 companion).
194
+
195
+ ---
196
+
197
+ ## Error Handling
198
+
199
+ The exception hierarchy separates different failure modes:
200
+
201
+ - **CapabilityError** — the requested capability is unknown or the registry is in an invalid state
202
+ - **ContractError** — the contract configuration is malformed or missing required fields
203
+ - **RecognitionError** — a grammar failed during recognition (e.g., malformed regex), wrapping the original exception
204
+ - **ValidationError** — a rule failed during validation (e.g., unexpected data), wrapping the original exception
205
+
206
+ Recognition and validation errors carry the name of the offending rule and the original exception, enabling targeted debugging without losing context.
207
+
208
+ ---
209
+
210
+ ## Quality Enforcement
211
+
212
+ Paxman enforces architectural invariants through tooling:
213
+
214
+ - **Static type checking** in strict mode ensures type safety across all layers
215
+ - **Import boundary enforcement** prevents capability-to-capability dependencies and ensures the core layer remains independent
216
+ - **Linting and formatting** enforce consistent code style
217
+ - **Rule-purity source scan** fails CI when any validation-rule module under `paxman/capabilities/*/rules/` references `output_format`, enforcing that presentation is owned solely by `Capability.format_value()`
218
+ - **Property-based testing** validates domain object contracts (immutability, equality, hashability)
219
+
220
+ These tools run as part of the development workflow and block merges when invariants are violated.
221
+
222
+ ---
223
+
224
+ ## Date Capability Design
225
+
226
+ The Date capability demonstrates the system's handling of ambiguous inputs through multiple grammars and validation rules.
227
+
228
+ ### Grammars
229
+
230
+ Three grammars recognize date patterns with different position mappings:
231
+
232
+ | Grammar | Delimiter | N1 (first) | N2 (second) | N3 (third) | Notes |
233
+ |---------|-----------|------------|-------------|------------|-------|
234
+ | ISO | `-` | year | month | day | 4-digit year only |
235
+ | US | `/` | month | day | year | Supports 2-digit years |
236
+ | European | `/` | day | month | year | Supports 2-digit years |
237
+
238
+ European and US grammars both use `/` as delimiter. Ambiguity arises from different position mappings, not delimiters.
239
+
240
+ ### Validation Rules
241
+
242
+ Three rules validate date notations against authoritative specifications:
243
+
244
+ | Rule | Standard | Canonical Output |
245
+ |------|----------|------------------|
246
+ | ISO 8601 | ISO 8601:2019 | `YYYY-MM-DD` |
247
+ | US federal | US government standard | `YYYY-MM-DD` |
248
+ | EN 50160 | European EN 50160 | `YYYY-MM-DD` |
249
+
250
+ All rules normalize to ISO 8601 format (`YYYY-MM-DD`) regardless of input grammar; the capability's formatting seam applies a requested alternative (e.g., `output_format="US"`) to that default afterward.
251
+
252
+ ### Ambiguity Detection
253
+
254
+ When the same input is recognized by multiple grammars, each grammar produces notation with different position mappings. For example, `"01/02/2026"` is recognized by both US and European grammars:
255
+ - US grammar: N1=month=01, N2=day=02, N3=year=2026
256
+ - European grammar: N1=day=01, N2=month=02, N3=year=2026
257
+
258
+ Each grammar's notation flows to its corresponding validation rule. If both rules validate and produce different canonical values, the system reports AMBIGUOUS.