capsule-engine 0.1.0__py3-none-any.whl

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 (288) hide show
  1. capsule_engine/__init__.py +17 -0
  2. capsule_engine/bundle_viewer/__init__.py +21 -0
  3. capsule_engine/bundle_viewer/static/offline_shell.html +1509 -0
  4. capsule_engine/bundle_viewer/viewer.py +57 -0
  5. capsule_engine/cli/__init__.py +5 -0
  6. capsule_engine/cli/bundle_cmd.py +269 -0
  7. capsule_engine/cli/console_cmd.py +70 -0
  8. capsule_engine/cli/constraints_cmd.py +74 -0
  9. capsule_engine/cli/format.py +173 -0
  10. capsule_engine/cli/guard_cmds.py +361 -0
  11. capsule_engine/cli/init_cmds.py +169 -0
  12. capsule_engine/cli/main.py +120 -0
  13. capsule_engine/cli/packs_cmds.py +96 -0
  14. capsule_engine/cli/telemetry_cmd.py +80 -0
  15. capsule_engine/cli/tenant_cmds.py +199 -0
  16. capsule_engine/cli/verify_cmd.py +110 -0
  17. capsule_engine/compiler/__init__.py +13 -0
  18. capsule_engine/compiler/effect_model.py +79 -0
  19. capsule_engine/compiler/vocabulary.py +253 -0
  20. capsule_engine/console/__init__.py +10 -0
  21. capsule_engine/console/api.py +251 -0
  22. capsule_engine/console/components.css +406 -0
  23. capsule_engine/console/console.css +130 -0
  24. capsule_engine/console/console.html +51 -0
  25. capsule_engine/console/console.js +330 -0
  26. capsule_engine/console/gallery.html +274 -0
  27. capsule_engine/console/server.py +137 -0
  28. capsule_engine/console/tokens.css +68 -0
  29. capsule_engine/conversation/__init__.py +88 -0
  30. capsule_engine/conversation/capsules.py +268 -0
  31. capsule_engine/conversation/exchange.py +279 -0
  32. capsule_engine/conversation/merkle.py +80 -0
  33. capsule_engine/conversation/session.py +101 -0
  34. capsule_engine/envcompat.py +15 -0
  35. capsule_engine/events/__init__.py +14 -0
  36. capsule_engine/events/capsule.py +133 -0
  37. capsule_engine/examples/__init__.py +9 -0
  38. capsule_engine/examples/data/tau2_airline/LICENSE-tau2-bench +21 -0
  39. capsule_engine/examples/data/tau2_airline/PROVENANCE.json +70 -0
  40. capsule_engine/examples/data/tau2_airline/hand_labels.json +893 -0
  41. capsule_engine/examples/data/tau2_airline/pilot1_live_gemini-2-5-flash_airline_shift1.jsonl +190 -0
  42. capsule_engine/examples/data/tau2_airline/tau2_committed_claude-3-7-sonnet_airline_trial0.jsonl +556 -0
  43. capsule_engine/examples/data/tau2_airline/tau2_committed_gpt-4-1-mini_airline_trial0.jsonl +480 -0
  44. capsule_engine/examples/data/tau2_airline/tau2_committed_gpt-4-1_airline_trial0.jsonl +496 -0
  45. capsule_engine/examples/data/tau2_airline/tau2_committed_o4-mini_airline_trial0.jsonl +392 -0
  46. capsule_engine/examples/data/tau2_airline/tau2_conversations_claude-3-7-sonnet_airline_4trials.jsonl +200 -0
  47. capsule_engine/examples/tau2_airline_reference.py +460 -0
  48. capsule_engine/examples/tau2_conversation_exchange.py +92 -0
  49. capsule_engine/folds/__init__.py +104 -0
  50. capsule_engine/folds/account_core.py +89 -0
  51. capsule_engine/folds/approval_latency.py +86 -0
  52. capsule_engine/folds/catalog.py +79 -0
  53. capsule_engine/folds/catalog_defs/actions.count_by_developer.yaml +10 -0
  54. capsule_engine/folds/catalog_defs/actions.executed_count.yaml +16 -0
  55. capsule_engine/folds/catalog_defs/actions.last_decision.yaml +12 -0
  56. capsule_engine/folds/catalog_defs/counterparty.seen_before.v2.yaml +34 -0
  57. capsule_engine/folds/catalog_defs/counterparty.seen_before.v3.yaml +38 -0
  58. capsule_engine/folds/catalog_defs/counterparty.seen_before.yaml +28 -0
  59. capsule_engine/folds/catalog_defs/disclosure.class_seen.yaml +29 -0
  60. capsule_engine/folds/catalog_defs/disclosure.statement_seen.yaml +32 -0
  61. capsule_engine/folds/catalog_defs/hold.active_exposure.yaml +44 -0
  62. capsule_engine/folds/catalog_defs/spend.weekly.v2.yaml +38 -0
  63. capsule_engine/folds/catalog_defs/spend.weekly.v3.1.yaml +71 -0
  64. capsule_engine/folds/catalog_defs/spend.weekly.v3.yaml +76 -0
  65. capsule_engine/folds/catalog_defs/spend.weekly.yaml +33 -0
  66. capsule_engine/folds/counterparty_signals.py +179 -0
  67. capsule_engine/folds/definition.py +421 -0
  68. capsule_engine/folds/duration.py +16 -0
  69. capsule_engine/folds/engine.py +436 -0
  70. capsule_engine/folds/errors.py +44 -0
  71. capsule_engine/folds/loader.py +24 -0
  72. capsule_engine/folds/ordering.py +143 -0
  73. capsule_engine/folds/paths.py +25 -0
  74. capsule_engine/folds/record_type_coverage.py +58 -0
  75. capsule_engine/folds/reducers.py +75 -0
  76. capsule_engine/folds/retention_continuity.py +117 -0
  77. capsule_engine/folds/set_membership.py +59 -0
  78. capsule_engine/folds/taxonomy.py +231 -0
  79. capsule_engine/guards/__init__.py +59 -0
  80. capsule_engine/guards/action.py +446 -0
  81. capsule_engine/guards/action_taxonomy.json +27 -0
  82. capsule_engine/guards/action_taxonomy_v2.json +27 -0
  83. capsule_engine/guards/action_taxonomy_v3.json +27 -0
  84. capsule_engine/guards/action_taxonomy_v4.json +27 -0
  85. capsule_engine/guards/action_taxonomy_v5.json +27 -0
  86. capsule_engine/guards/capsule.py +377 -0
  87. capsule_engine/guards/checks/__init__.py +268 -0
  88. capsule_engine/guards/checks/action_class_gate.py +199 -0
  89. capsule_engine/guards/checks/base.py +33 -0
  90. capsule_engine/guards/checks/caps.py +517 -0
  91. capsule_engine/guards/checks/channel_change.py +64 -0
  92. capsule_engine/guards/checks/commitment.py +41 -0
  93. capsule_engine/guards/checks/counterparty_identity_change.py +95 -0
  94. capsule_engine/guards/checks/counterparty_list.py +176 -0
  95. capsule_engine/guards/checks/counterparty_seen_before.py +160 -0
  96. capsule_engine/guards/checks/credential_pattern.py +73 -0
  97. capsule_engine/guards/checks/dedupe.py +259 -0
  98. capsule_engine/guards/checks/destination_rail.py +120 -0
  99. capsule_engine/guards/checks/field_change_count.py +100 -0
  100. capsule_engine/guards/checks/offer_expiry.py +134 -0
  101. capsule_engine/guards/checks/plan_containment.py +180 -0
  102. capsule_engine/guards/checks/price_floor.py +211 -0
  103. capsule_engine/guards/checks/promise_never.py +236 -0
  104. capsule_engine/guards/checks/promise_requires_approval.py +214 -0
  105. capsule_engine/guards/checks/recipient_role.py +117 -0
  106. capsule_engine/guards/checks/recurring_charge.py +49 -0
  107. capsule_engine/guards/checks/refundability.py +46 -0
  108. capsule_engine/guards/checks/release_on_acceptance.py +286 -0
  109. capsule_engine/guards/checks/required_disclosure.py +233 -0
  110. capsule_engine/guards/checks/single_commitment.py +221 -0
  111. capsule_engine/guards/checks/task_authority.py +182 -0
  112. capsule_engine/guards/checks/upfront_amount.py +71 -0
  113. capsule_engine/guards/checks/verify_before_dispatch.py +75 -0
  114. capsule_engine/guards/classes.py +393 -0
  115. capsule_engine/guards/engine.py +926 -0
  116. capsule_engine/guards/history_state.py +127 -0
  117. capsule_engine/guards/plan.py +237 -0
  118. capsule_engine/guards/revocation.py +120 -0
  119. capsule_engine/guards/signing.py +60 -0
  120. capsule_engine/guards/statements.py +62 -0
  121. capsule_engine/guards/tool_call.py +83 -0
  122. capsule_engine/guards/wickets/__init__.py +23 -0
  123. capsule_engine/guards/wickets/catalog.py +75 -0
  124. capsule_engine/guards/wickets/catalog_defs/action_class_gate.v1.1.yaml +38 -0
  125. capsule_engine/guards/wickets/catalog_defs/action_class_gate.v1.2.yaml +52 -0
  126. capsule_engine/guards/wickets/catalog_defs/action_class_gate.yaml +34 -0
  127. capsule_engine/guards/wickets/catalog_defs/caps.v2.yaml +30 -0
  128. capsule_engine/guards/wickets/catalog_defs/caps.v3.yaml +49 -0
  129. capsule_engine/guards/wickets/catalog_defs/caps.v4.yaml +45 -0
  130. capsule_engine/guards/wickets/catalog_defs/caps.v5.1.yaml +53 -0
  131. capsule_engine/guards/wickets/catalog_defs/caps.v5.yaml +43 -0
  132. capsule_engine/guards/wickets/catalog_defs/caps.yaml +17 -0
  133. capsule_engine/guards/wickets/catalog_defs/caps_holds.yaml +18 -0
  134. capsule_engine/guards/wickets/catalog_defs/channel_change.v1.1.yaml +18 -0
  135. capsule_engine/guards/wickets/catalog_defs/channel_change.yaml +10 -0
  136. capsule_engine/guards/wickets/catalog_defs/counterparty_identity_change.yaml +9 -0
  137. capsule_engine/guards/wickets/catalog_defs/counterparty_list.yaml +20 -0
  138. capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v2.yaml +15 -0
  139. capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v3.yaml +23 -0
  140. capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v4.yaml +26 -0
  141. capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.yaml +14 -0
  142. capsule_engine/guards/wickets/catalog_defs/credential_pattern.yaml +14 -0
  143. capsule_engine/guards/wickets/catalog_defs/dedupe.yaml +19 -0
  144. capsule_engine/guards/wickets/catalog_defs/destination_rail.seller.v1.1.yaml +32 -0
  145. capsule_engine/guards/wickets/catalog_defs/destination_rail.seller.yaml +10 -0
  146. capsule_engine/guards/wickets/catalog_defs/destination_rail.yaml +9 -0
  147. capsule_engine/guards/wickets/catalog_defs/hold_reconcile.yaml +14 -0
  148. capsule_engine/guards/wickets/catalog_defs/material_fields_changed.v1.1.yaml +22 -0
  149. capsule_engine/guards/wickets/catalog_defs/material_fields_changed.yaml +14 -0
  150. capsule_engine/guards/wickets/catalog_defs/offer_expiry.v1.1.yaml +31 -0
  151. capsule_engine/guards/wickets/catalog_defs/offer_expiry.yaml +24 -0
  152. capsule_engine/guards/wickets/catalog_defs/offer_fields_changed.v1.1.yaml +22 -0
  153. capsule_engine/guards/wickets/catalog_defs/offer_fields_changed.yaml +10 -0
  154. capsule_engine/guards/wickets/catalog_defs/price_floor.yaml +32 -0
  155. capsule_engine/guards/wickets/catalog_defs/promise_never.v1.1.yaml +64 -0
  156. capsule_engine/guards/wickets/catalog_defs/promise_never.yaml +16 -0
  157. capsule_engine/guards/wickets/catalog_defs/promise_requires_approval.v1.1.yaml +26 -0
  158. capsule_engine/guards/wickets/catalog_defs/promise_requires_approval.yaml +21 -0
  159. capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.v1.1.yaml +23 -0
  160. capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.v1.2.yaml +24 -0
  161. capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.yaml +10 -0
  162. capsule_engine/guards/wickets/catalog_defs/recipient_role.v1.1.yaml +26 -0
  163. capsule_engine/guards/wickets/catalog_defs/recipient_role.yaml +11 -0
  164. capsule_engine/guards/wickets/catalog_defs/recipient_seen_before.yaml +16 -0
  165. capsule_engine/guards/wickets/catalog_defs/recurring_charge.yaml +9 -0
  166. capsule_engine/guards/wickets/catalog_defs/refundability.v1.1.yaml +15 -0
  167. capsule_engine/guards/wickets/catalog_defs/refundability.yaml +7 -0
  168. capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.v1.1.yaml +44 -0
  169. capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.v1.2.yaml +63 -0
  170. capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.yaml +36 -0
  171. capsule_engine/guards/wickets/catalog_defs/required_disclosure.v1.1.yaml +36 -0
  172. capsule_engine/guards/wickets/catalog_defs/required_disclosure.v1.2.yaml +42 -0
  173. capsule_engine/guards/wickets/catalog_defs/required_disclosure.yaml +25 -0
  174. capsule_engine/guards/wickets/catalog_defs/single_commitment.seller.yaml +14 -0
  175. capsule_engine/guards/wickets/catalog_defs/task_authority.v1.1.yaml +20 -0
  176. capsule_engine/guards/wickets/catalog_defs/task_authority.yaml +8 -0
  177. capsule_engine/guards/wickets/catalog_defs/upfront_amount.v1.1.yaml +19 -0
  178. capsule_engine/guards/wickets/catalog_defs/upfront_amount.yaml +11 -0
  179. capsule_engine/guards/wickets/catalog_defs/verify_before_dispatch.yaml +9 -0
  180. capsule_engine/guards/wickets/definition.py +164 -0
  181. capsule_engine/guards/wickets/errors.py +18 -0
  182. capsule_engine/guards/wickets/loader.py +26 -0
  183. capsule_engine/guards/wickets/retired.py +62 -0
  184. capsule_engine/holds/__init__.py +35 -0
  185. capsule_engine/holds/capsules.py +266 -0
  186. capsule_engine/holds/engine.py +476 -0
  187. capsule_engine/holds/errors.py +29 -0
  188. capsule_engine/holds/policy.py +62 -0
  189. capsule_engine/holds/scope.py +47 -0
  190. capsule_engine/mcp/__init__.py +16 -0
  191. capsule_engine/mcp/config.py +117 -0
  192. capsule_engine/mcp/server.py +338 -0
  193. capsule_engine/mcp/tools.py +424 -0
  194. capsule_engine/packaging.py +60 -0
  195. capsule_engine/packs/__init__.py +45 -0
  196. capsule_engine/packs/backfill_coverage.py +238 -0
  197. capsule_engine/packs/catalog/airline-engagement/AI-BOOTSTRAP.md +53 -0
  198. capsule_engine/packs/catalog/airline-engagement/folds/session_close.yaml +8 -0
  199. capsule_engine/packs/catalog/airline-engagement/pack.yaml +146 -0
  200. capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/good_execution.json +36 -0
  201. capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/oversight_declared.json +36 -0
  202. capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/tampered_execution.json +36 -0
  203. capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/trusted_issuer.jwk.json +5 -0
  204. capsule_engine/packs/catalog/eu-ai-act/obligations-pack-note.md +68 -0
  205. capsule_engine/packs/catalog/eu-ai-act/obligations-register.yaml +761 -0
  206. capsule_engine/packs/catalog/eu-ai-act/pack.yaml +88 -0
  207. capsule_engine/packs/catalog/eu-ai-act/register.yaml +96 -0
  208. capsule_engine/packs/catalog/eu-ai-act-deterministic/fixtures/synthetic_internal_assist_sample.jsonl +6 -0
  209. capsule_engine/packs/catalog/eu-ai-act-deterministic/pack.yaml +580 -0
  210. capsule_engine/packs/catalog/everyday/AI-BOOTSTRAP.md +194 -0
  211. capsule_engine/packs/catalog/everyday/fixtures/mini_ledger.jsonl +45 -0
  212. capsule_engine/packs/catalog/everyday/pack.yaml +480 -0
  213. capsule_engine/packs/catalog/payments-safety/AI-BOOTSTRAP.md +83 -0
  214. capsule_engine/packs/catalog/payments-safety/fixtures/mini_ledger.jsonl +8 -0
  215. capsule_engine/packs/catalog/payments-safety/folds/spend_weekly.yaml +29 -0
  216. capsule_engine/packs/catalog/payments-safety/pack.yaml +112 -0
  217. capsule_engine/packs/catalog/seller/fixtures/mini_ledger.jsonl +83 -0
  218. capsule_engine/packs/catalog/seller/pack.yaml +503 -0
  219. capsule_engine/packs/catalog/standard-vendor/AI-BOOTSTRAP.md +70 -0
  220. capsule_engine/packs/catalog/standard-vendor/folds/session_close.yaml +8 -0
  221. capsule_engine/packs/catalog/standard-vendor/pack.yaml +466 -0
  222. capsule_engine/packs/contract_diff.py +809 -0
  223. capsule_engine/packs/contract_validate.py +185 -0
  224. capsule_engine/packs/corpus_verify.py +126 -0
  225. capsule_engine/packs/enforce.py +81 -0
  226. capsule_engine/packs/errors.py +159 -0
  227. capsule_engine/packs/eu_ai_act_trace_field_map.py +46 -0
  228. capsule_engine/packs/install.py +266 -0
  229. capsule_engine/packs/loader.py +1462 -0
  230. capsule_engine/packs/measurability_report.py +280 -0
  231. capsule_engine/packs/obligation_results.py +72 -0
  232. capsule_engine/packs/pins.py +92 -0
  233. capsule_engine/packs/schema.py +1053 -0
  234. capsule_engine/packs/thresholds.py +146 -0
  235. capsule_engine/packs/trace_attainment.py +188 -0
  236. capsule_engine/packs/trace_reader.py +104 -0
  237. capsule_engine/policy/__init__.py +37 -0
  238. capsule_engine/policy/activation.py +87 -0
  239. capsule_engine/policy/catalog_defs/default.yaml +23 -0
  240. capsule_engine/policy/catalog_defs/holds.yaml +27 -0
  241. capsule_engine/policy/errors.py +46 -0
  242. capsule_engine/policy/limits.py +287 -0
  243. capsule_engine/policy/loader.py +26 -0
  244. capsule_engine/policy/manifest.py +289 -0
  245. capsule_engine/policy/profile.py +281 -0
  246. capsule_engine/policy/resolve.py +224 -0
  247. capsule_engine/register/__init__.py +39 -0
  248. capsule_engine/register/compiler.py +219 -0
  249. capsule_engine/register/errors.py +40 -0
  250. capsule_engine/register/loader.py +247 -0
  251. capsule_engine/register/schema.py +129 -0
  252. capsule_engine/registry/__init__.py +19 -0
  253. capsule_engine/registry/conventions.json +162 -0
  254. capsule_engine/registry/conventions.py +188 -0
  255. capsule_engine/registry/cpb_registry.json +78 -0
  256. capsule_engine/report/__init__.py +62 -0
  257. capsule_engine/report/build.py +267 -0
  258. capsule_engine/report/coverage.py +777 -0
  259. capsule_engine/report/errors.py +35 -0
  260. capsule_engine/report/live_check.py +126 -0
  261. capsule_engine/report/live_history.py +420 -0
  262. capsule_engine/report/model.py +95 -0
  263. capsule_engine/report/render.py +467 -0
  264. capsule_engine/report/replay.py +1677 -0
  265. capsule_engine/report/result.py +504 -0
  266. capsule_engine/report/result_from_folds.py +222 -0
  267. capsule_engine/report/result_from_layers.py +670 -0
  268. capsule_engine/report/sale_bundle.py +864 -0
  269. capsule_engine/report/static/verify.js +451 -0
  270. capsule_engine/schemas/evidence-contract-v0.json +383 -0
  271. capsule_engine/schemas/vendor/epistemic-types.json +21 -0
  272. capsule_engine/schemas/vendor/evidence-result-v0.json +463 -0
  273. capsule_engine/telemetry/__init__.py +9 -0
  274. capsule_engine/telemetry/consent.py +39 -0
  275. capsule_engine/telemetry/events.py +93 -0
  276. capsule_engine/telemetry/fixtures/synthetic_events.jsonl +45 -0
  277. capsule_engine/telemetry/funnel.py +147 -0
  278. capsule_engine/telemetry/record.py +106 -0
  279. capsule_engine/telemetry/sink.py +61 -0
  280. capsule_engine/telemetry/state.py +60 -0
  281. capsule_engine/tenants.py +327 -0
  282. capsule_engine-0.1.0.dist-info/METADATA +47 -0
  283. capsule_engine-0.1.0.dist-info/RECORD +288 -0
  284. capsule_engine-0.1.0.dist-info/WHEEL +5 -0
  285. capsule_engine-0.1.0.dist-info/entry_points.txt +2 -0
  286. capsule_engine-0.1.0.dist-info/licenses/LICENSE +202 -0
  287. capsule_engine-0.1.0.dist-info/licenses/capsule_engine/examples/data/tau2_airline/LICENSE-tau2-bench +21 -0
  288. capsule_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,44 @@
1
+ # seller.release_on_acceptance/1.1.0: 1.0.0 (release_on_acceptance.seller.yaml)
2
+ # not applicable to a disclosure to a platform, the marketplace or relay a
3
+ # sale is listed on: it reaches no buyer, so there is no acceptance to cite,
4
+ # and seller.recipient_role/1.1.0 lets it carry the user's email only. 1.0.0
5
+ # stays as it was for the records and packs that pin it.
6
+ #
7
+ # release_on_acceptance, configured for a seller (guards/checks/
8
+ # release_on_acceptance.py). Personal data reaches a buyer only once that
9
+ # buyer's acceptance of the sale is sealed. The rule itself is the
10
+ # `semantics` field below: it is inside the digested body, so rewording it
11
+ # moves the digest. A failure refuses; it never asks, and an approval does
12
+ # not clear it.
13
+ #
14
+ # `release_classes` is the closed set of statement classes, the same list
15
+ # promise_never/1.0.0 and promise_requires_approval/1.0.0 close over, so
16
+ # every class a disclosure may declare is held back until acceptance and
17
+ # any other class is refused. No class is released before acceptance. `acceptance_classes` are the classes whose accepted,
18
+ # non-dry-run record seals the acceptance (the same reading as
19
+ # seller.single_commitment). `action_classes` are the disclosures the rule
20
+ # applies to.
21
+ wicket_id: seller.release_on_acceptance/1.1.0
22
+ check: release_on_acceptance
23
+ semantics: >-
24
+ A disclosure in action_classes passes only when its representation_class
25
+ is in release_classes and it names its task_authority_ref, item_ref and
26
+ target, cites by cited_mandate_capsule_id the sale's acceptance, and that
27
+ acceptance re-verifies and was sealed for the same target. The sale's
28
+ acceptance is the first record in ledger order, among the operator's,
29
+ whose action_class is in acceptance_classes, whose decision was accept,
30
+ which was not a dry run, and which carries the same task_authority_ref and
31
+ item_ref. Every other such disclosure fails: no citation, another sale's
32
+ or another recipient's acceptance, a record that is not the sale's
33
+ acceptance, a missing reference, or a representation_class missing or
34
+ outside release_classes. An action whose action_class is missing or has no
35
+ row in the action taxonomy fails too, since it cannot be shown to be out
36
+ of scope. Any other known action class is not applicable, and so is a
37
+ disclosure whose recipient_role is in except_recipient_roles: it reaches
38
+ no buyer. A failure is refused and no approval clears it. The reason and
39
+ evidence carry no disclosed value, reference, counterparty or capsule id.
40
+ config:
41
+ release_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
42
+ acceptance_classes: [agreement.accept]
43
+ action_classes: [disclosure.personal]
44
+ except_recipient_roles: [platform]
@@ -0,0 +1,63 @@
1
+ # seller.release_on_acceptance/1.2.0: seller.release_on_acceptance/1.1.0
2
+ # (release_on_acceptance.seller.v1.1.yaml) reading what a share gives and
3
+ # binding the acceptance from the history (capsule-engine#179, #186).
4
+ #
5
+ # 1.1.0 keyed on representation_class, a statement class no share carries,
6
+ # and needed a cited, re-verified acceptance no producer sends, so no share
7
+ # to a buyer ever passed. 1.2.0 reads the share's classes of data
8
+ # (disclosed_classes). release_classes are the contact classes of
9
+ # DISCLOSED_CLASSES (guards/action.py); a credential, a verification code, a
10
+ # payment card and an id document are left out, so they still refuse. The
11
+ # listing's public pickup area (pre_acceptance_classes) goes to any buyer
12
+ # before acceptance. Anything else needs the sale's acceptance, the same
13
+ # record as before (the first accepted, non-dry-run agreement.accept of the
14
+ # item), in the share's own deal and by the same buyer: the counterparty
15
+ # fingerprint each check seals, compared on the per-person identity kinds,
16
+ # or on the name and the domain together when neither side seals one (an
17
+ # opening that named the buyer by name and domain only). binding selects this reading in the check (guards/checks/
18
+ # release_on_acceptance.py); 1.0.0 and 1.1.0 have none and decide as they
19
+ # always did. The rule itself is the `semantics` field below: it is inside
20
+ # the digested body, so rewording it moves the digest. A failure refuses; it
21
+ # never asks, and an approval does not clear it.
22
+ wicket_id: seller.release_on_acceptance/1.2.0
23
+ check: release_on_acceptance
24
+ semantics: >-
25
+ A disclosure in action_classes passes only when it names at least one
26
+ class of data (disclosed_classes), every class it names is in
27
+ release_classes, and either every class it names is in
28
+ pre_acceptance_classes, or the sale has a known acceptance in the same
29
+ deal by the same buyer. The sale's acceptance is the first record in
30
+ ledger order, among the operator's, whose action_class is in
31
+ acceptance_classes, whose decision was accept, which was not a dry run,
32
+ and which carries the disclosure's item_ref; a history that may be missing
33
+ acts, an act of the sale that could not be read, an earlier act of an
34
+ acceptance class with no disposition, or an earlier accepted commitment in
35
+ the history that names no item leave it unknown, which is none. The
36
+ same deal is an equal deal_id, both set. The same buyer is the
37
+ counterparty the acceptance's check sealed, carried beside it in the
38
+ history, and the one the disclosure's check sealed, under the same
39
+ fingerprint algorithm, with at least one per-person identity (email,
40
+ phone, profile_id, relay_address) on both sides and every one on both
41
+ sides equal; or, when neither side has any per-person identity, with a
42
+ name and a domain on both sides and both equal. A payee is never
43
+ compared, and a name or a domain is compared only when neither side has a
44
+ per-person identity. A counterparty block on either side with any member
45
+ of another kind or shape, or another algorithm, is no buyer. Every other
46
+ such disclosure fails: no class or a class outside release_classes, no
47
+ item_ref, deal_id or counterparty fingerprint, no known acceptance, or one
48
+ in another deal or by another buyer. An action whose action_class is
49
+ missing or has no row in the action taxonomy fails too, since it cannot
50
+ be shown to be out of scope. Any other known action class is not
51
+ applicable, and so is a disclosure whose recipient_role is in
52
+ except_recipient_roles: it reaches no buyer. A failure is refused and no
53
+ approval clears it. The reason and evidence carry no disclosed value,
54
+ reference, deal, counterparty or capsule id: only the class names read,
55
+ whether the disclosure was bound to an acceptance, and a missing field's
56
+ name.
57
+ config:
58
+ binding: same_deal_same_buyer
59
+ release_classes: [name, phone, email, home_address, address, pickup_location, other_contact, pickup_area]
60
+ pre_acceptance_classes: [pickup_area]
61
+ acceptance_classes: [agreement.accept]
62
+ action_classes: [disclosure.personal]
63
+ except_recipient_roles: [platform]
@@ -0,0 +1,36 @@
1
+ # release_on_acceptance wicket, configured for a seller (guards/checks/
2
+ # release_on_acceptance.py). Personal data reaches a buyer only once that
3
+ # buyer's acceptance of the sale is sealed. The rule itself is the
4
+ # `semantics` field below: it is inside the digested body, so rewording it
5
+ # moves the digest. A failure refuses; it never asks, and an approval does
6
+ # not clear it.
7
+ #
8
+ # `release_classes` is the closed set of statement classes, the same list
9
+ # promise_never/1.0.0 and promise_requires_approval/1.0.0 close over, so
10
+ # every class a disclosure may declare is held back until acceptance and
11
+ # any other class is refused. No class is released before acceptance. `acceptance_classes` are the classes whose accepted,
12
+ # non-dry-run record seals the acceptance (the same reading as
13
+ # seller.single_commitment). `action_classes` are the disclosures the rule
14
+ # applies to.
15
+ wicket_id: seller.release_on_acceptance/1.0.0
16
+ check: release_on_acceptance
17
+ semantics: >-
18
+ A disclosure in action_classes passes only when its representation_class
19
+ is in release_classes and it names its task_authority_ref, item_ref and
20
+ target, cites by cited_mandate_capsule_id the sale's acceptance, and that
21
+ acceptance re-verifies and was sealed for the same target. The sale's
22
+ acceptance is the first record in ledger order, among the operator's,
23
+ whose action_class is in acceptance_classes, whose decision was accept,
24
+ which was not a dry run, and which carries the same task_authority_ref and
25
+ item_ref. Every other such disclosure fails: no citation, another sale's
26
+ or another recipient's acceptance, a record that is not the sale's
27
+ acceptance, a missing reference, or a representation_class missing or
28
+ outside release_classes. An action whose action_class is missing or has no
29
+ row in the action taxonomy fails too, since it cannot be shown to be out
30
+ of scope. Any other known action class is not applicable. A failure is
31
+ refused and no approval clears it. The reason and evidence carry no
32
+ disclosed value, reference, counterparty or capsule id.
33
+ config:
34
+ release_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
35
+ acceptance_classes: [agreement.accept]
36
+ action_classes: [disclosure.personal]
@@ -0,0 +1,36 @@
1
+ # required_disclosure wicket, version 1.1: version 1.0 with one change. A
2
+ # required class also counts as stated when the action's deal already holds
3
+ # a statement of it: a claim the user's agent made to the counterparty in
4
+ # that deal before the action (a capsulectl claim record sealing
5
+ # source_kind agent and the class), counted by
6
+ # disclosure.statement_seen/1.0.0. Under version 1.0 such a claim stated no
7
+ # act, so no decision carried its class and every offer and commit after it
8
+ # failed as if nothing had been said. Version 1.0
9
+ # (required_disclosure.yaml) stays as it was for the packs that pin it.
10
+ #
11
+ # Both `fold_digest` and `statement_fold_digest` are the real
12
+ # definition_digest() of their folds in folds/catalog_defs; the check refuses
13
+ # to run when a catalog fold does not match.
14
+ #
15
+ # Declared disposition: ask.
16
+ wicket_id: required_disclosure/1.1.0
17
+ check: required_disclosure
18
+ semantics: >-
19
+ For an action in action_classes, each class in required_classes must
20
+ already have been stated to the counterparty: by an accepted prior action
21
+ of the operator addressed to the action's target that carried that
22
+ representation_class, leaving dry runs out, or by a claim the operator's
23
+ agent made in the action's deal before the action with source_kind agent
24
+ and that class (a sealed delivery_promise reads as delivery_date). A class
25
+ with neither fails, and an approval never clears it. A class is never read
26
+ from a disclosure record's personal-data fields. An action naming neither
27
+ a target nor a deal is not applicable. Any other action class is out of
28
+ scope.
29
+ config:
30
+ action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
31
+ representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
32
+ required_classes: [condition]
33
+ fold_id: disclosure.class_seen/1.0.0
34
+ fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
35
+ statement_fold_id: disclosure.statement_seen/1.0.0
36
+ statement_fold_digest: 925e7ccd2e29b3f1984f966e968bdef47d0d22d2b85b8e0205a39d6a5a7ff0be
@@ -0,0 +1,42 @@
1
+ # required_disclosure wicket, version 1.2: version 1.1 with one change. A
2
+ # required class also counts as stated when the sale's listings in force
3
+ # state it: the action's listing_classes, the rule input capsulectl seals on
4
+ # each offer and commit check of a sale's thread (the classes every live
5
+ # listing states, the intersection over the latest post per platform when
6
+ # the check was made; capsule-engine#190). A listing is the agent's words to
7
+ # every buyer, so a class it states is stated to this one too. Under version
8
+ # 1.1 a listing counted for nothing, and an offer after a listing that stated
9
+ # the condition asked for it again. The value is read from the check, never
10
+ # recomputed: a later re-post changes later checks only. With no value (no
11
+ # post) or an empty one, the class needs one of 1.1's two paths. Version 1.1
12
+ # (required_disclosure.v1.1.yaml) stays as it was for the packs that pin it.
13
+ #
14
+ # Both `fold_digest` and `statement_fold_digest` are the real
15
+ # definition_digest() of their folds in folds/catalog_defs; the check refuses
16
+ # to run when a catalog fold does not match.
17
+ #
18
+ # Declared disposition: ask.
19
+ wicket_id: required_disclosure/1.2.0
20
+ check: required_disclosure
21
+ semantics: >-
22
+ For an action in action_classes, each class in required_classes must
23
+ already have been stated to the counterparty: by an accepted prior action
24
+ of the operator addressed to the action's target that carried that
25
+ representation_class, leaving dry runs out; by a claim the operator's
26
+ agent made in the action's deal before the action with source_kind agent
27
+ and that class (a sealed delivery_promise reads as delivery_date); or by
28
+ the sale's listings in force, when the class is in the action's
29
+ listing_classes as its check sealed them. A class with none of the three
30
+ fails, and an approval never clears it. A class is never read from a
31
+ disclosure record's personal-data fields. An action naming neither a
32
+ target nor a deal is not applicable. Any other action class is out of
33
+ scope.
34
+ config:
35
+ action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
36
+ representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
37
+ required_classes: [condition]
38
+ fold_id: disclosure.class_seen/1.0.0
39
+ fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
40
+ statement_fold_id: disclosure.statement_seen/1.0.0
41
+ statement_fold_digest: 925e7ccd2e29b3f1984f966e968bdef47d0d22d2b85b8e0205a39d6a5a7ff0be
42
+ count_listing_classes: true
@@ -0,0 +1,25 @@
1
+ # required_disclosure wicket: classes of statement that must have been made
2
+ # to the counterparty before the action (guards/checks/
3
+ # required_disclosure.py). For each class in `required_classes`, the
4
+ # disclosure.class_seen/1.0.0 fold counts this operator's accepted prior
5
+ # actions addressed to Action.target that carried that
6
+ # representation_class; a zero fails the check. It checks that a statement
7
+ # of the class was recorded, never what it said or whether it was true.
8
+ # A failure means: make the disclosure first, or change the task
9
+ # authority. An approval never waives it.
10
+ #
11
+ # `representation_classes` is the closed set a class must come from;
12
+ # `required_classes` outside it is refused when the check runs.
13
+ # `fold_digest` is the real definition_digest() of
14
+ # folds/catalog_defs/disclosure.class_seen.yaml; the check refuses to run
15
+ # when the catalog fold does not match it.
16
+ #
17
+ # Declared disposition: ask.
18
+ wicket_id: required_disclosure/1.0.0
19
+ check: required_disclosure
20
+ config:
21
+ action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
22
+ representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
23
+ required_classes: [condition]
24
+ fold_id: disclosure.class_seen/1.0.0
25
+ fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
@@ -0,0 +1,14 @@
1
+ # single_commitment wicket, configured for a seller (guards/checks/
2
+ # single_commitment.py). One sale, several buyer threads: once an acceptance
3
+ # is sealed for a task_authority_ref and an item_ref, a commitment for the
4
+ # same pair fails unless it cites that acceptance and is addressed to the
5
+ # counterparty that accepted. A failure refuses; it never asks.
6
+ #
7
+ # `acceptance_classes` are the classes whose accepted, non-dry-run record
8
+ # seals the acceptance. `commit_classes` are the commitments the rule
9
+ # applies to; anything else (a message, a disclosure) is n/a.
10
+ wicket_id: seller.single_commitment/1.0.0
11
+ check: single_commitment
12
+ config:
13
+ acceptance_classes: [agreement.accept]
14
+ commit_classes: [agreement.accept, marketplace.offer, marketplace.sale, external_commitment.other]
@@ -0,0 +1,20 @@
1
+ # task_authority wicket, version 1.1: task_authority/1.0.0's config with
2
+ # marketplace.sale added to action_classes (guards/checks/task_authority.py).
3
+ # The rule itself is the `semantics` field below: it is inside the digested
4
+ # body, so rewording it moves the digest. task_authority/1.0.0
5
+ # (task_authority.yaml) stays as it was for the packs that pin it.
6
+ wicket_id: task_authority/1.1.0
7
+ check: task_authority
8
+ semantics: >-
9
+ For an action in action_classes, Action.task_authority_ref is the SHA-256
10
+ over the JCS bytes of the whole task-authority record the action cites. The
11
+ record supplied with the decision is read only when its digest equals that
12
+ reference, and the plan at its body, in guards/plan.py's shape, decides by
13
+ plan containment: an action inside the plan passes, one outside it fails.
14
+ It is not applicable, naming task_authority_ref, when the action carries
15
+ none, and naming task_authority_record when no record is supplied, the
16
+ supplied record is not the one the reference names or has no digest, or
17
+ the record carries no plan that parses. marketplace.sale is in scope. Any
18
+ other action class is out of scope.
19
+ config:
20
+ action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other, marketplace.sale]
@@ -0,0 +1,8 @@
1
+ # task_authority wicket: the action is inside the task authority it cites
2
+ # (guards/checks/task_authority.py). The whole cited record is read only
3
+ # when the engine's own digest of it equals the action's task_authority_ref;
4
+ # the plan inside it is in guards/plan.py's shape, and plan_containment decides.
5
+ wicket_id: task_authority/1.0.0
6
+ check: task_authority
7
+ config:
8
+ action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other]
@@ -0,0 +1,19 @@
1
+ # upfront_amount wicket, version 1.1: upfront_amount/1.0.0's config with
2
+ # marketplace.sale added to action_classes (guards/checks/upfront_amount.py).
3
+ # The rule itself is the `semantics` field below: it is inside the digested
4
+ # body, so rewording it moves the digest. upfront_amount/1.0.0
5
+ # (upfront_amount.yaml) stays as it was for the packs that pin it.
6
+ wicket_id: upfront_amount/1.1.0
7
+ check: upfront_amount
8
+ semantics: >-
9
+ For an action in action_classes, Action.upfront_amount_minor (the part of
10
+ the total stated as due before delivery) fails when it is above
11
+ upfront_max_minor, or when upfront_amount_minor * 10000 is above
12
+ Action.amount_minor * upfront_max_bps; otherwise it passes. Without an
13
+ amount_minor only upfront_max_minor is compared. It is not applicable,
14
+ naming the field, when the action carries no upfront_amount_minor.
15
+ marketplace.sale is in scope. Any other action class is out of scope.
16
+ config:
17
+ action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other, marketplace.sale]
18
+ upfront_max_minor: 10000
19
+ upfront_max_bps: 2500
@@ -0,0 +1,11 @@
1
+ # upfront_amount wicket: the deposit a payment states, read as
2
+ # Action.upfront_amount_minor
3
+ # (guards/checks/upfront_amount.py). Fails above `upfront_max_minor`, or
4
+ # above `upfront_max_bps` basis points of the total. Default limits:
5
+ # 100.00 and 25%, in the action's own minor units.
6
+ wicket_id: upfront_amount/1.0.0
7
+ check: upfront_amount
8
+ config:
9
+ action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other]
10
+ upfront_max_minor: 10000
11
+ upfront_max_bps: 2500
@@ -0,0 +1,9 @@
1
+ # verify_before_dispatch wicket: the cited mandate capsule must exist in the
2
+ # ledger and independently re-verify (guards/checks/verify_before_dispatch.py).
3
+ # This check takes no per-deployment tuning parameters -- `config` is empty
4
+ # on purpose, not omitted, so the digest still pins "this check, unconfigured"
5
+ # distinctly from "this wicket_id was never declared at all".
6
+ wicket_id: verify_before_dispatch/1.0.0
7
+ check: verify_before_dispatch
8
+ config:
9
+ method: agent_action_capsule.verify
@@ -0,0 +1,164 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Wicket definitions: parsing, validation, and the definition_digest.
3
+
4
+ A "wicket" is this workspace's name for a guard
5
+ constraint -- caps / dedupe / verify_before_dispatch (``guards/checks/*.py``).
6
+ Unlike a fold, a wicket's CHECK LOGIC is Python code, not declarative data --
7
+ there is no YAML that fully describes "how caps computes weekly spend". What
8
+ *is* declarative, and varies per deployment, is the wicket's own
9
+ *configuration*: which check it invokes and the parameters that check runs
10
+ with (caps' per-class minor-unit limits, dedupe's window). A ``WicketDefinition``
11
+ captures exactly that slice -- ``wicket_id``, ``check``, ``config`` -- the same
12
+ way ``folds/definition.py``'s ``FoldDefinition`` captures a fold. Its digest
13
+ (SHA-256 over the JCS-canonical bytes, via the same
14
+ ``agent_action_capsule.canonical.json_digest`` the fold definitions and the
15
+ capsule format itself use) identifies "this configuration of this check", not
16
+ the Python source file the check lives in -- a source-code change with no
17
+ config change is invisible to this digest, and that is intentional: this is a
18
+ policy-configuration digest, not a code-integrity digest.
19
+
20
+ Because the digest cannot see the check's code, a definition may also carry
21
+ ``semantics``: a short normative statement of what passes, what fails and
22
+ what is not applicable. It is data inside the digested body, never a YAML
23
+ comment, so rewording the rule moves the digest. A definition without it
24
+ digests exactly as before (the key is left out of the canonical form). A
25
+ definition whose meaning moved under an unchanged digest is retired
26
+ (``retired.py``) and refused here at parse time.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import re
31
+ from dataclasses import dataclass, field
32
+ from typing import Any
33
+
34
+ from agent_action_capsule.canonical import FloatInDigestError, UnsafeIntegerError, json_digest
35
+
36
+ from .errors import (
37
+ FLOAT_IN_DEFINITION,
38
+ INVALID_WICKET_ID,
39
+ MALFORMED_DEFINITION,
40
+ RETIRED_DEFINITION,
41
+ UNKNOWN_CHECK,
42
+ UNSAFE_INTEGER_IN_DEFINITION,
43
+ WicketDefinitionError,
44
+ )
45
+ from .retired import RETIRED_IDS, retired_entry
46
+
47
+ __all__ = ["WICKET_ID_RE", "KNOWN_CHECKS", "WicketDefinition", "parse_definition"]
48
+
49
+ # wicket_id: same "human name + semver" shape as fold_id (folds/definition.py).
50
+ WICKET_ID_RE = re.compile(r"^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*/\d+\.\d+\.\d+$")
51
+
52
+ # The closed, registered set of checks a wicket may configure -- the three
53
+ # reference checks ``guards/engine.py`` evaluates and ``cli/constraints_cmd.
54
+ # py``'s ``CHECKS`` catalog lists, plus ``hold_reconcile`` (``holds/engine.
55
+ # py``'s tolerance check for planned-vs-executed reconciliation, capsule-emit
56
+ # #53), plus ``plan_containment`` (``guards/checks/plan_containment.py`` --
57
+ # forward-compiled-plan containment), plus the configured checks a
58
+ # pack can add to a decision (``destination_rail``,
59
+ # ``counterparty_identity_change``, ``counterparty_seen_before``,
60
+ # ``credential_pattern``, ``recurring_charge``, ``action_class_gate``,
61
+ # ``counterparty_list``, ``recipient_role``, ``refundability``,
62
+ # ``material_fields_changed``, ``offer_fields_changed``,
63
+ # ``recipient_seen_before``, ``channel_change``, ``upfront_amount``,
64
+ # ``task_authority``, and the seller-side ``price_floor``,
65
+ # ``required_disclosure``, ``promise_requires_approval``, ``promise_never``,
66
+ # ``offer_expiry``, ``single_commitment``, ``release_on_acceptance`` --
67
+ # ``guards/checks/__init__.py``'s ``RUNNABLE_CHECKS``). Closed for
68
+ # the same reason ``folds/definition.py``'s ``KNOWN_REDUCERS`` is: an
69
+ # unregistered name is a typo or a not-yet-built check, never silently
70
+ # accepted as data.
71
+ KNOWN_CHECKS = frozenset(
72
+ {
73
+ "dedupe",
74
+ "caps",
75
+ "verify_before_dispatch",
76
+ "hold_reconcile",
77
+ "plan_containment",
78
+ "destination_rail",
79
+ "counterparty_identity_change",
80
+ "counterparty_seen_before",
81
+ "credential_pattern",
82
+ "recurring_charge",
83
+ "action_class_gate",
84
+ "counterparty_list",
85
+ "recipient_role",
86
+ "refundability",
87
+ "material_fields_changed",
88
+ "offer_fields_changed",
89
+ "recipient_seen_before",
90
+ "channel_change",
91
+ "upfront_amount",
92
+ "task_authority",
93
+ "price_floor",
94
+ "required_disclosure",
95
+ "promise_requires_approval",
96
+ "promise_never",
97
+ "offer_expiry",
98
+ "single_commitment",
99
+ "release_on_acceptance",
100
+ }
101
+ )
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class WicketDefinition:
106
+ wicket_id: str
107
+ check: str
108
+ config: dict[str, Any] = field(default_factory=dict)
109
+ semantics: str | None = None
110
+
111
+ def canonical_dict(self) -> dict:
112
+ """The JCS-canonicalizable form of this definition -- drives definition_digest."""
113
+ # Left out when not stated, so a definition without it keeps its digest.
114
+ semantics = {} if self.semantics is None else {"semantics": self.semantics}
115
+ return {"wicket_id": self.wicket_id, "check": self.check, "config": self.config, **semantics}
116
+
117
+ def definition_digest(self) -> str:
118
+ """SHA-256 over the JCS bytes of the canonical definition."""
119
+ try:
120
+ return json_digest(self.canonical_dict())
121
+ except FloatInDigestError as exc:
122
+ raise WicketDefinitionError(FLOAT_IN_DEFINITION, str(exc)) from exc
123
+ except UnsafeIntegerError as exc:
124
+ raise WicketDefinitionError(UNSAFE_INTEGER_IN_DEFINITION, str(exc)) from exc
125
+
126
+
127
+ def parse_definition(data: Any) -> WicketDefinition:
128
+ """Validate a plain dict (as loaded from YAML) into a ``WicketDefinition``."""
129
+ if not isinstance(data, dict):
130
+ raise WicketDefinitionError(MALFORMED_DEFINITION, "definition must be a mapping")
131
+
132
+ wicket_id = data.get("wicket_id")
133
+ if not isinstance(wicket_id, str) or not WICKET_ID_RE.match(wicket_id):
134
+ raise WicketDefinitionError(
135
+ INVALID_WICKET_ID,
136
+ f"wicket_id {wicket_id!r} must match '<namespace>[.<namespace>...]/<major>.<minor>.<patch>' "
137
+ "(e.g. 'caps/1.0.0')",
138
+ )
139
+
140
+ check = data.get("check")
141
+ if check not in KNOWN_CHECKS:
142
+ raise WicketDefinitionError(
143
+ UNKNOWN_CHECK, f"check {check!r} is not in the closed registered set {sorted(KNOWN_CHECKS)}"
144
+ )
145
+
146
+ config = data.get("config") if "config" in data else {}
147
+ if not isinstance(config, dict):
148
+ raise WicketDefinitionError(MALFORMED_DEFINITION, "config must be a mapping")
149
+
150
+ semantics = data.get("semantics")
151
+ if semantics is not None and (not isinstance(semantics, str) or not semantics.strip()):
152
+ raise WicketDefinitionError(MALFORMED_DEFINITION, "semantics must be a non-empty string")
153
+
154
+ definition = WicketDefinition(wicket_id=wicket_id, check=check, config=config, semantics=semantics)
155
+ # Only a retired id pays for a digest here, so a float or unsafe integer
156
+ # in any other definition still surfaces at definition_digest(), as before.
157
+ if wicket_id in RETIRED_IDS:
158
+ retired = retired_entry(wicket_id, definition.definition_digest())
159
+ if retired is not None:
160
+ raise WicketDefinitionError(
161
+ RETIRED_DEFINITION,
162
+ f"{wicket_id} at digest {retired.digest} is retired: {retired.reason}; use {retired.replaced_by}",
163
+ )
164
+ return definition
@@ -0,0 +1,18 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Named-reason errors for wicket definitions (mirrors ``folds/errors.py``)."""
3
+ from __future__ import annotations
4
+
5
+ INVALID_WICKET_ID = "invalid_wicket_id_namespace"
6
+ UNKNOWN_CHECK = "unknown_check"
7
+ MALFORMED_DEFINITION = "malformed_definition"
8
+ FLOAT_IN_DEFINITION = "float_in_definition"
9
+ UNSAFE_INTEGER_IN_DEFINITION = "unsafe_integer_in_definition"
10
+ RETIRED_DEFINITION = "retired_definition"
11
+
12
+
13
+ class WicketDefinitionError(ValueError):
14
+ """A wicket definition fails to parse or validate. Carries a stable reason code."""
15
+
16
+ def __init__(self, reason: str, message: str) -> None:
17
+ self.reason = reason
18
+ super().__init__(f"{reason}: {message}")
@@ -0,0 +1,26 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """YAML front door for wicket definitions: YAML text/file -> validated WicketDefinition."""
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+ from .definition import WicketDefinition, parse_definition
10
+ from .errors import MALFORMED_DEFINITION, WicketDefinitionError
11
+
12
+ __all__ = ["load_definition_text", "load_definition_file"]
13
+
14
+
15
+ def load_definition_text(text: str) -> WicketDefinition:
16
+ try:
17
+ data = yaml.safe_load(text)
18
+ except yaml.YAMLError as exc:
19
+ raise WicketDefinitionError(MALFORMED_DEFINITION, f"invalid YAML: {exc}") from exc
20
+ if data is None:
21
+ raise WicketDefinitionError(MALFORMED_DEFINITION, "empty definition document")
22
+ return parse_definition(data)
23
+
24
+
25
+ def load_definition_file(path: str | Path) -> WicketDefinition:
26
+ return load_definition_text(Path(path).read_text())
@@ -0,0 +1,62 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Retired wicket definitions: an (id, digest) pair that must not load.
3
+
4
+ A definition is retired when its meaning moved without its digest moving, so
5
+ the pin no longer tells a reader which verdicts it produces. The bytes stay
6
+ recomputable (the digest is listed here verbatim); what changes is that the
7
+ loader, the catalog and a pack citing the pair all refuse it, naming the
8
+ replacement. A retirement is permanent: a row is never removed.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ __all__ = ["RetiredDefinition", "RETIRED", "RETIRED_IDS", "retired_entry"]
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class RetiredDefinition:
19
+ wicket_id: str
20
+ digest: str
21
+ reason: str
22
+ replaced_by: str
23
+
24
+
25
+ RETIRED: tuple[RetiredDefinition, ...] = (
26
+ RetiredDefinition(
27
+ wicket_id="offer_expiry/1.0.0",
28
+ digest="7b1072fc6997b07e7f08941a723e60d53fd3a54dbccfda6fa7391124ec2702ee",
29
+ reason=(
30
+ "its check changed meaning under this digest (an expired or unverifiable proposal went from "
31
+ "not applicable to fail) because the digest covered config only"
32
+ ),
33
+ replaced_by="offer_expiry/1.0.1",
34
+ ),
35
+ RetiredDefinition(
36
+ wicket_id="price_floor/1.0.0",
37
+ digest="a7eb755a71cf9dabaf04fbd740fc9ceaad5f0ba883795adad6fd9038796dffa9",
38
+ reason=(
39
+ "its check changed meaning under this digest (the floor moved from min_total_minor in clear on "
40
+ "the task-authority record to a private commercial-bounds document the record commits to) "
41
+ "because the digest covered config only"
42
+ ),
43
+ replaced_by="price_floor/2.0.1",
44
+ ),
45
+ RetiredDefinition(
46
+ wicket_id="price_floor/2.0.0",
47
+ digest="52fd588ea62ce467ad5ee59cef4347a4a0fbb0836d20ebfe783923805230bc9b",
48
+ reason=(
49
+ "its check changed meaning under this digest (a task-authority record that seals no "
50
+ "bounds_commitment went from in scope and not evaluable to out of scope)"
51
+ ),
52
+ replaced_by="price_floor/2.0.1",
53
+ ),
54
+ )
55
+
56
+ _BY_PAIR = {(r.wicket_id, r.digest): r for r in RETIRED}
57
+ RETIRED_IDS = frozenset(r.wicket_id for r in RETIRED)
58
+
59
+
60
+ def retired_entry(wicket_id: str, digest: str) -> RetiredDefinition | None:
61
+ """The retirement row for this exact (id, digest) pair, if there is one."""
62
+ return _BY_PAIR.get((wicket_id, digest))
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Reservation-as-capsule semantics: ``hold.reserve``/``release``/``expire``/
3
+ ``reconcile`` record types, atomic evaluate-and-reserve, expiry-terminal +
4
+ resume, and planned-vs-executed reconciliation (capsule-emit #51/#52/#53).
5
+
6
+ Ledger-side only: the distributed sequencer across processes/nodes is
7
+ capsule-emit's Dapr-side integration, not this package's.
8
+ """
9
+ from .capsules import (
10
+ build_hold_expire_capsule,
11
+ build_hold_reconcile_capsule,
12
+ build_hold_release_capsule,
13
+ build_hold_reserve_capsule,
14
+ check_integer_amount,
15
+ )
16
+ from .engine import HoldDecision, HoldEngine, HoldStatus
17
+ from .errors import HoldError
18
+ from .policy import HoldPolicy, resolve_hold_policy
19
+ from .scope import ScopeKey, ScopeLocks
20
+
21
+ __all__ = [
22
+ "build_hold_reserve_capsule",
23
+ "build_hold_release_capsule",
24
+ "build_hold_expire_capsule",
25
+ "build_hold_reconcile_capsule",
26
+ "check_integer_amount",
27
+ "HoldDecision",
28
+ "HoldEngine",
29
+ "HoldStatus",
30
+ "HoldError",
31
+ "HoldPolicy",
32
+ "resolve_hold_policy",
33
+ "ScopeKey",
34
+ "ScopeLocks",
35
+ ]