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,286 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """release_on_acceptance check: personal data is released only to the buyer
3
+ who accepted.
4
+
5
+ A seller negotiates one sale with several buyers, one thread each. Personal
6
+ data may reach a buyer only once that buyer's acceptance is sealed. The
7
+ wicket's ``binding`` selects how a disclosure is bound to it.
8
+
9
+ **Cited** (no ``binding``: seller.release_on_acceptance/1.0.0 and 1.1.0).
10
+ ``release_classes`` is the closed set of classes a disclosure may declare;
11
+ a disclosure in ``action_classes`` whose ``representation_class`` is in it
12
+ passes only when ALL hold:
13
+
14
+ - it names its sale (``task_authority_ref`` and ``item_ref``) and its
15
+ recipient (``target``);
16
+ - it cites (``cited_mandate_capsule_id``) the sale's acceptance, found by
17
+ ``single_commitment.first_sealed_acceptance`` so both checks read the same
18
+ record: the sale is the ``item_ref``, under any task authority, and an
19
+ acceptance that is not known (a live history that cannot settle it) is
20
+ none;
21
+ - that acceptance was sealed for the same recipient (its ``target`` equals
22
+ the disclosure's);
23
+ - the cited record re-verifies in the ledger.
24
+
25
+ Anything else fails: no citation, a citation of another sale's or another
26
+ buyer's acceptance, a record that is not a sealed acceptance, a missing
27
+ reference, and a ``representation_class`` that is missing or outside
28
+ ``release_classes``.
29
+
30
+ **Same deal, same buyer** (``binding: same_deal_same_buyer``,
31
+ seller.release_on_acceptance/1.2.0). The disclosure's classes of data
32
+ (``disclosed_classes``) are read, never ``representation_class``. Every
33
+ class must be in ``release_classes``; a disclosure naming none, or any
34
+ other, fails. One whose classes are all in ``pre_acceptance_classes`` (the
35
+ listing's public pickup area) passes to any buyer, before or after any
36
+ acceptance. Anything else passes only when it names its ``item_ref``, its
37
+ ``deal_id`` and the counterparty fingerprint its check sealed
38
+ (``counterparty_ids`` under ``counterparty_fp_alg``), and the sale's
39
+ acceptance (``single_commitment.sale_acceptance``, the same record the cited
40
+ binding reads) is known, is in the same deal (``deal_id`` equal), and was
41
+ sealed for the same buyer: its ``thread_counterparty``, which the history
42
+ carries beside the act and no decision seals
43
+ (``report/live_history.py``), is under the same algorithm, and at least one
44
+ per-person identity kind (``email``, ``phone``, ``profile_id``,
45
+ ``relay_address``) is on both sides and every such kind on both sides is
46
+ equal; ``name``, ``payee`` and ``domain`` are then not compared: a name is
47
+ written many ways, and the thread default seals ``payee`` from it, while a
48
+ marketplace domain is shared by every stranger on it. When neither side
49
+ seals any per-person kind (an opening that named the buyer by name and
50
+ domain only), the buyer is the same when both sides seal a ``name`` and a
51
+ ``domain`` and both are equal; with the deal equal too, that is the thread's
52
+ own buyer. A per-person kind on one side only, a name alone or a domain
53
+ alone is no buyer. The ``payee`` is never compared. The acceptance is not
54
+ cited and not re-verified: the history entries are the producer's own
55
+ sealed records on the user's device, the trust ``single_commitment`` places
56
+ in them. A history that may be missing acts, an act of this sale that could
57
+ not be read, an acceptance-class act with no disposition, or an earlier
58
+ accepted commitment in the history that names no item, leave the
59
+ acceptance unknown, which is none here. The counterparty block must be a
60
+ check's, whole (``history_state.well_formed_counterparty``) on both sides: a
61
+ share whose block holds any member of another kind or shape fails naming
62
+ ``counterparty``, so a member dropped for its shape never reads as absent
63
+ (and never opens the name-and-domain reading), and an acceptance whose
64
+ block is not whole carries none, so it binds no share. No acceptance, an unknown one, and
65
+ one in another deal or by another buyer fail with one reason, so a buyer
66
+ cannot learn whether someone else accepted. A share naming no class, or a
67
+ class outside ``release_classes`` (the credential, verification-code, card
68
+ and ID classes), has its own reason: it says nothing about other buyers.
69
+ The rule applies to every recipient but ``except_recipient_roles`` (a
70
+ buyer, a third party, the user), so those classes go to no one but the
71
+ platform.
72
+
73
+ Under either binding two inputs fail closed: an ``action_class`` that is
74
+ missing or has no row in the action taxonomy (it is resolved there, so a
75
+ legacy alias counts as its canonical class), because the action cannot be
76
+ shown to be out of scope. Only another known action class is ``n/a`` out of
77
+ scope, and, when the wicket configures ``except_recipient_roles`` (1.1.0
78
+ and 1.2.0: ``platform``), a disclosure whose ``recipient_role`` is one of
79
+ them: it goes to no buyer, so there is no acceptance to bind, and
80
+ ``recipient_role`` measures what it may carry. The engine lists this as an
81
+ integrity check, so a failure refuses whatever the pack declares; a
82
+ one-shot approval does not clear it.
83
+
84
+ The outcome carries no value. A checker result is sealed into a record that
85
+ can reach a buyer's copy, so the reason and evidence never hold the
86
+ disclosed data, ``item_ref``, ``task_authority_ref``, a deal, a counterparty
87
+ or a capsule id. The evidence names only the class or classes read (class
88
+ names from a closed set), whether the release was bound to an acceptance,
89
+ and a missing field's name.
90
+ """
91
+ from __future__ import annotations
92
+
93
+ from typing import TypedDict
94
+
95
+ from cll.ledger.api import LedgerAPI
96
+
97
+ from ..action import Action
98
+ from ..capsule import ConstraintOutcome, NotApplicableEvidence, not_applicable_evidence
99
+ from ..classes import resolve
100
+ from ..history_state import well_formed_counterparty
101
+ from .base import CheckOutcome
102
+ from .single_commitment import ThreadCounterparty, first_sealed_acceptance, sale_acceptance
103
+
104
+ __all__ = ["SAME_DEAL_SAME_BUYER", "check_release_on_acceptance", "same_buyer"]
105
+
106
+ _CHECK_ID = "release_on_acceptance"
107
+ _METHOD = "cited_sealed_acceptance_v1"
108
+ _NOT_BOUND = "the disclosure does not cite this sale's sealed acceptance by this recipient"
109
+ # The ``binding`` that binds the acceptance from the history (1.2.0).
110
+ SAME_DEAL_SAME_BUYER = "same_deal_same_buyer"
111
+ _HISTORY_METHOD = "same_deal_same_buyer_v1"
112
+ _NOT_ACCEPTED = "the history given shows no acceptance of this sale by this buyer in this deal"
113
+ # The identity kinds that name one person (capsule-cli deal_counterparty.go
114
+ # perPartyKinds). A name, a payee and a domain are fingerprinted too but are
115
+ # not identities.
116
+ _PER_PERSON_KINDS = ("email", "phone", "profile_id", "relay_address")
117
+ # What names the thread's own buyer when neither side seals a per-person
118
+ # kind: both of these, equal. The payee is never read.
119
+ _NO_PER_PERSON_KINDS = ("name", "domain")
120
+
121
+
122
+ class ReleaseEvidence(TypedDict):
123
+ constraint_id: str
124
+ representation_class: str | None
125
+ bound_to_acceptance: bool
126
+ missing_field: str | None
127
+
128
+
129
+ class ClassesEvidence(TypedDict):
130
+ """1.2.0's evidence: the class names the disclosure gives, never the data."""
131
+
132
+ constraint_id: str
133
+ disclosed_classes: list[str] | None
134
+ bound_to_acceptance: bool
135
+ missing_field: str | None
136
+
137
+
138
+ def _evidence(representation_class: str | None, bound: bool, missing_field: str | None = None) -> ReleaseEvidence:
139
+ return ReleaseEvidence(constraint_id=_CHECK_ID, representation_class=representation_class,
140
+ bound_to_acceptance=bound, missing_field=missing_field)
141
+
142
+
143
+ def _classes_evidence(classes: tuple[str, ...] | None, bound: bool,
144
+ missing_field: str | None = None) -> ClassesEvidence:
145
+ return ClassesEvidence(constraint_id=_CHECK_ID, disclosed_classes=list(classes) if classes else None,
146
+ bound_to_acceptance=bound, missing_field=missing_field)
147
+
148
+
149
+ def _outcome(result: str, reason: str, evidence: ReleaseEvidence | ClassesEvidence | NotApplicableEvidence,
150
+ method: str = _METHOD) -> CheckOutcome:
151
+ return CheckOutcome(
152
+ constraint=ConstraintOutcome(
153
+ id=_CHECK_ID, result=result, reason=reason, evidence=evidence, check_type="policy", method=method
154
+ )
155
+ )
156
+
157
+
158
+ def _missing_shape(action: Action, field: str) -> str:
159
+ # An ignored input is named, never the value it was given.
160
+ return f"the {field} input was not in its agreed shape" if field in action.ignored_inputs else f"no {field}"
161
+
162
+
163
+ def _missing(action: Action, cls: str, field: str) -> CheckOutcome:
164
+ return _outcome("fail", f"the disclosure has {_missing_shape(action, field)}; it cannot be bound to an acceptance",
165
+ _evidence(cls, False, missing_field=field))
166
+
167
+
168
+ def same_buyer(acceptance: ThreadCounterparty | None, fp_alg: str | None, ids: dict[str, str] | None) -> bool:
169
+ """Whether the counterparty an acceptance's check sealed and the one a
170
+ disclosure's check sealed are one buyer: both fingerprinted under the
171
+ same algorithm, and either at least one per-person identity kind on both
172
+ sides with every per-person kind on both sides equal, or, when neither
173
+ side seals any per-person kind, both a ``name`` and a ``domain`` on both
174
+ sides, each equal. A payee is never read (module docstring)."""
175
+ if acceptance is None or not fp_alg or not ids or acceptance["fp_alg"] != fp_alg:
176
+ return False
177
+ theirs = acceptance["ids"]
178
+ if not any(k in theirs or k in ids for k in _PER_PERSON_KINDS):
179
+ # The deal is checked apart: this is the thread's own buyer by the
180
+ # opening's name and domain.
181
+ return all(k in theirs and k in ids and theirs[k] == ids[k] for k in _NO_PER_PERSON_KINDS)
182
+ shared = [k for k in _PER_PERSON_KINDS if k in theirs and k in ids]
183
+ return bool(shared) and all(theirs[k] == ids[k] for k in shared)
184
+
185
+
186
+ def check_release_on_acceptance(
187
+ action: Action,
188
+ ledger: LedgerAPI,
189
+ *,
190
+ release_classes: list[str],
191
+ acceptance_classes: list[str],
192
+ action_classes: list[str],
193
+ except_recipient_roles: list[str] | None = None,
194
+ binding: str | None = None,
195
+ pre_acceptance_classes: list[str] | None = None,
196
+ ) -> CheckOutcome:
197
+ """The check under the wicket's ``binding``: cited when it is unset (the
198
+ 1.0.0 and 1.1.0 configs, decided as they always were), from the history
199
+ under ``same_deal_same_buyer`` (module docstring)."""
200
+ if binding is not None and binding != SAME_DEAL_SAME_BUYER:
201
+ raise ValueError(f"release_on_acceptance has no binding {binding!r}")
202
+ method = _HISTORY_METHOD if binding is not None else _METHOD
203
+ ac = resolve(action.action_class) if action.action_class is not None else None
204
+ if ac is None:
205
+ evidence = (_classes_evidence(action.disclosed_classes, False, missing_field="action_class")
206
+ if binding is not None else _evidence(None, False, missing_field="action_class"))
207
+ return _outcome("fail", "the action class has no taxonomy row; it cannot be shown to be outside the rule",
208
+ evidence, method)
209
+ if ac.name not in action_classes:
210
+ return _outcome("n/a", "the rule is not configured for this action class",
211
+ not_applicable_evidence(_CHECK_ID, in_scope=False), method)
212
+ if action.recipient_role is not None and action.recipient_role in (except_recipient_roles or ()):
213
+ return _outcome("n/a", f"the recipient is a {action.recipient_role}, not a buyer; recipient_role measures it",
214
+ not_applicable_evidence(_CHECK_ID, in_scope=False), method)
215
+ if binding is not None:
216
+ return _release_from_history(action, ledger, release_classes=release_classes,
217
+ acceptance_classes=acceptance_classes,
218
+ pre_acceptance_classes=pre_acceptance_classes or [])
219
+ cls = action.representation_class
220
+ if cls not in release_classes:
221
+ # An undeclared class's name is the producer's free text, never recorded.
222
+ return _outcome("fail", "the disclosure declares no class this rule knows; it is not released",
223
+ _evidence(None, False, missing_field="representation_class"))
224
+ for field, value in (("task_authority_ref", action.task_authority_ref), ("item_ref", action.item_ref),
225
+ ("target", action.target)):
226
+ if value is None:
227
+ return _missing(action, cls, field)
228
+ if action.cited_mandate_capsule_id is None:
229
+ return _outcome("fail", "the disclosure cites no acceptance", _evidence(cls, False))
230
+
231
+ acceptance = first_sealed_acceptance(action.operator, ledger, acceptance_classes, action.item_ref)
232
+ if (
233
+ acceptance is None
234
+ or action.cited_mandate_capsule_id != acceptance["capsule_id"]
235
+ or action.target != acceptance["counterparty"]
236
+ ):
237
+ return _outcome("fail", _NOT_BOUND, _evidence(cls, False))
238
+ verified = ledger.verify(acceptance["capsule_id"])
239
+ if verified is None or not verified.ok:
240
+ return _outcome("fail", _NOT_BOUND, _evidence(cls, False))
241
+ return _outcome("pass", "the disclosure cites this sale's sealed acceptance by this recipient",
242
+ _evidence(cls, True))
243
+
244
+
245
+ def _release_from_history(
246
+ action: Action,
247
+ ledger: LedgerAPI,
248
+ *,
249
+ release_classes: list[str],
250
+ acceptance_classes: list[str],
251
+ pre_acceptance_classes: list[str],
252
+ ) -> CheckOutcome:
253
+ """The same-deal, same-buyer binding (1.2.0) for a disclosure in scope."""
254
+ classes = action.disclosed_classes
255
+ if classes is None:
256
+ return _outcome("fail", "the share names no class of data",
257
+ _classes_evidence(None, False, missing_field="disclosed_classes"), _HISTORY_METHOD)
258
+ if any(c not in release_classes for c in classes):
259
+ return _outcome("fail", "a credential, a verification code, a payment card or an ID document is never shared "
260
+ "with anyone",
261
+ _classes_evidence(classes, False), _HISTORY_METHOD)
262
+ if all(c in pre_acceptance_classes for c in classes):
263
+ return _outcome("pass", "every class the disclosure names may go to a buyer before acceptance",
264
+ _classes_evidence(classes, False), _HISTORY_METHOD)
265
+ for field, present in (("item_ref", action.item_ref is not None), ("deal_id", action.deal_id is not None),
266
+ ("counterparty", bool(action.counterparty_ids) and bool(action.counterparty_fp_alg))):
267
+ if not present:
268
+ return _outcome("fail", f"the disclosure has {_missing_shape(action, field)}; it cannot be bound to an "
269
+ "acceptance", _classes_evidence(classes, False, missing_field=field), _HISTORY_METHOD)
270
+ if action.counterparty_malformed or not well_formed_counterparty(action.counterparty_fp_alg,
271
+ action.counterparty_ids):
272
+ # A member dropped for its shape must not read as absent: the whole
273
+ # block is unusable.
274
+ return _outcome("fail", "the counterparty input was not in its agreed shape; it cannot be bound to an "
275
+ "acceptance", _classes_evidence(classes, False, missing_field="counterparty"),
276
+ _HISTORY_METHOD)
277
+ sale = sale_acceptance(action.operator, ledger, acceptance_classes, action.item_ref)
278
+ acceptance = sale.acceptance if sale.unknown is None else None
279
+ if (
280
+ acceptance is None
281
+ or acceptance["deal_id"] != action.deal_id
282
+ or not same_buyer(acceptance["thread_counterparty"], action.counterparty_fp_alg, action.counterparty_ids)
283
+ ):
284
+ return _outcome("fail", _NOT_ACCEPTED, _classes_evidence(classes, False), _HISTORY_METHOD)
285
+ return _outcome("pass", "the history given shows this buyer accepted this sale in this deal",
286
+ _classes_evidence(classes, True), _HISTORY_METHOD)
@@ -0,0 +1,233 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """required_disclosure check: has each required class of statement been
3
+ made to this counterparty before the action?
4
+
5
+ For each class in ``required_classes``, the count is the
6
+ ``disclosure.class_seen`` fold's own result, keyed by
7
+ ``asg_payload.representation_class``, over this operator's records addressed
8
+ to ``Action.target``. Only accepted actions that were not dry runs count. A
9
+ zero for any required class fails the check; the evidence lists each class's
10
+ count and the classes still missing. It checks that a statement of the class
11
+ was recorded, never what it said or whether it was true. Applies only to
12
+ the configured ``action_classes``.
13
+
14
+ A failure means the action cannot proceed as constructed: make the
15
+ disclosure first, or change the task authority. It is never cleared by an
16
+ approval: this check reads no approval record and ignores
17
+ ``Action.authorized_by``, because a one-shot approval does not waive a
18
+ statement the person already required.
19
+
20
+ With ``statement_definition`` (``required_disclosure/1.1.0``, its fold
21
+ ``disclosure.statement_seen/1.0.0``), a class also counts as stated when the
22
+ action's deal (``Action.deal_id``) already holds a statement of it: a record
23
+ ``report/replay.py``'s ``statement_record`` wrote for a claim the seller's
24
+ agent made in that deal before the action, read from the bundle in a replay
25
+ and from the check input's history live. Both counts are in the evidence,
26
+ the deal's beside the counterparty's. The deal stands for the counterparty
27
+ here: capsulectl opens one thread per buyer. A statement record names no
28
+ counterparty, so a counterparty that changes within a deal is not seen here
29
+ (``counterparty_identity_change`` reads that). An action with neither a
30
+ target nor a deal is not applicable, as one with no target is without it;
31
+ one with a target and no deal counts the counterparty's statements only, and
32
+ one with a deal and no target the deal's only.
33
+
34
+ With ``count_listing_classes`` (``required_disclosure/1.2.0``), a class also
35
+ counts as stated when it is in ``Action.listing_classes``: the classes every
36
+ live listing of the sale stated when the check was made, as capsulectl sealed
37
+ them in the check's body (the intersection over the latest post per platform).
38
+ A listing is the agent's words to every buyer, so a class it states is stated
39
+ to this one too. The value is read from the check, never recomputed: a re-post
40
+ changes later checks only. With no value (no post) or an empty one, a listing
41
+ counts for nothing, and the class needs one of the other two paths. The
42
+ evidence lists the value beside the two counts.
43
+
44
+ ``required_classes`` must come from the closed set ``representation_classes``;
45
+ a class outside it is a configuration error and raises ``ValueError``.
46
+ """
47
+ from __future__ import annotations
48
+
49
+ from typing import TypedDict
50
+
51
+ from cll.ledger.api import LedgerAPI, ScanQuery
52
+
53
+ from ...folds.definition import FoldDefinition
54
+ from ...folds.engine import EvaluationTrace, evaluate_one
55
+ from ..action import Action
56
+ from ..capsule import ConstraintOutcome, NotApplicableEvidence, not_applicable_evidence
57
+ from ..statements import STATED
58
+ from .base import CheckOutcome
59
+
60
+ __all__ = ["check_required_disclosure", "require_known_classes"]
61
+
62
+ _CHECK_ID = "required_disclosure"
63
+
64
+
65
+ class DisclosureScope(TypedDict):
66
+ operator: str
67
+ target: str
68
+
69
+
70
+ class DisclosureEvidence(TypedDict):
71
+ fold: str
72
+ scope: DisclosureScope
73
+ prior_counts: dict[str, int]
74
+ missing_classes: list[str]
75
+
76
+
77
+ class StatementScope(TypedDict):
78
+ operator: str
79
+ target: str | None
80
+ deal_id: str | None
81
+
82
+
83
+ class StatementEvidence(TypedDict):
84
+ """``required_disclosure/1.1.0``'s evidence: each fold's digest and
85
+ counts, and the classes neither counted."""
86
+
87
+ fold: str
88
+ statement_fold: str
89
+ scope: StatementScope
90
+ prior_counts: dict[str, int]
91
+ stated_counts: dict[str, int]
92
+ missing_classes: list[str]
93
+
94
+
95
+ class ListingEvidence(StatementEvidence):
96
+ """``required_disclosure/1.2.0``'s evidence: 1.1.0's, and the listing's
97
+ classes the action carried (``None`` with no post)."""
98
+
99
+ listing_classes: list[str] | None
100
+
101
+
102
+ def require_known_classes(classes: list[str], representation_classes: list[str], *, what: str) -> None:
103
+ unknown = [c for c in classes if c not in representation_classes]
104
+ if unknown:
105
+ raise ValueError(f"{what} names {unknown}, outside the closed set {representation_classes}")
106
+
107
+
108
+ def _outcome(
109
+ result: str, reason: str, evidence: DisclosureEvidence | StatementEvidence | NotApplicableEvidence, method: str,
110
+ traces: tuple[EvaluationTrace, ...] = (),
111
+ ) -> CheckOutcome:
112
+ return CheckOutcome(
113
+ constraint=ConstraintOutcome(
114
+ id=_CHECK_ID, result=result, reason=reason, evidence=evidence, check_type="policy", method=method
115
+ ),
116
+ fold_envelopes=tuple(trace.to_envelope() for trace in traces),
117
+ )
118
+
119
+
120
+ def check_required_disclosure(
121
+ action: Action,
122
+ ledger: LedgerAPI,
123
+ *,
124
+ definition: FoldDefinition,
125
+ representation_classes: list[str],
126
+ required_classes: list[str],
127
+ action_classes: list[str],
128
+ statement_definition: FoldDefinition | None = None,
129
+ count_listing_classes: bool = False,
130
+ ) -> CheckOutcome:
131
+ require_known_classes(required_classes, representation_classes, what="required_classes")
132
+ if count_listing_classes and statement_definition is None:
133
+ raise ValueError("count_listing_classes goes with statement_fold_id: required_disclosure/1.2.0 builds on 1.1.0")
134
+ method = definition.fold_id
135
+ if action.action_class not in action_classes:
136
+ return _outcome("n/a", "the rule is not configured for this action class",
137
+ not_applicable_evidence(_CHECK_ID, in_scope=False), method)
138
+ if statement_definition is not None:
139
+ return _check_with_statements(action, ledger, definition, statement_definition, required_classes,
140
+ listing=count_listing_classes)
141
+ target = action.target
142
+ if target is None:
143
+ return _outcome("n/a", "the action names no target; the counterparty could not be identified",
144
+ not_applicable_evidence(_CHECK_ID, in_scope=True, missing_field="target"), method)
145
+
146
+ traces = _counted(definition, _to_target(action, ledger, target), required_classes)
147
+ counts = _counts(traces, required_classes)
148
+ missing = [cls for cls in required_classes if counts[cls] == 0]
149
+ evidence = DisclosureEvidence(
150
+ fold=definition.definition_digest(),
151
+ scope=DisclosureScope(operator=action.operator, target=target),
152
+ prior_counts=counts,
153
+ missing_classes=missing,
154
+ )
155
+ return _decided(missing, evidence, method, traces)
156
+
157
+
158
+ def _to_target(action: Action, ledger: LedgerAPI, target: str) -> list[dict]:
159
+ """This operator's records addressed to ``target``. ``ScanQuery.
160
+ counterparty`` is the ledger's filter on ``operator``; the ledger has no
161
+ filter on target, so the records are selected here."""
162
+ return [
163
+ r.capsule for r in ledger.scan(ScanQuery(counterparty=action.operator))
164
+ if (r.capsule.get("asg_payload") or {}).get("target") == target
165
+ ]
166
+
167
+
168
+ def _in_deal(action: Action, ledger: LedgerAPI, deal_id: str) -> list[dict]:
169
+ """This operator's statement records in the deal ``deal_id``."""
170
+ return [
171
+ r.capsule for r in ledger.scan(ScanQuery(counterparty=action.operator))
172
+ if (((r.capsule.get("asg_payload") or {}).get(STATED)) or {}).get("deal_id") == deal_id
173
+ ]
174
+
175
+
176
+ def _counted(definition: FoldDefinition, records: list[dict], classes: list[str]) -> tuple[EvaluationTrace, ...]:
177
+ return tuple(evaluate_one(definition, records, key_value=cls) for cls in classes)
178
+
179
+
180
+ def _counts(traces: tuple[EvaluationTrace, ...], classes: list[str]) -> dict[str, int]:
181
+ return {cls: trace.result or 0 for cls, trace in zip(classes, traces, strict=True)}
182
+
183
+
184
+ def _decided(
185
+ missing: list[str], evidence: DisclosureEvidence | StatementEvidence | ListingEvidence, method: str,
186
+ traces: tuple[EvaluationTrace, ...],
187
+ ) -> CheckOutcome:
188
+ if missing:
189
+ return _outcome("fail", f"no accepted prior statement of {missing} to this counterparty: make the disclosure "
190
+ "first or change the task authority; an approval does not waive it", evidence, method,
191
+ traces)
192
+ return _outcome("pass", "every required class was stated to this counterparty", evidence, method, traces)
193
+
194
+
195
+ def _check_with_statements(
196
+ action: Action, ledger: LedgerAPI, definition: FoldDefinition, statement_definition: FoldDefinition,
197
+ required_classes: list[str], *, listing: bool = False,
198
+ ) -> CheckOutcome:
199
+ """``required_disclosure/1.1.0``: a class counts as stated to the
200
+ counterparty's target or in the action's deal; with ``listing`` (1.2.0),
201
+ also when the sale's listings in force state it (module docstring)."""
202
+ method = definition.fold_id
203
+ target, deal_id = action.target, action.deal_id
204
+ if target is None and deal_id is None:
205
+ return _outcome("n/a", "the action names no target and no deal; the counterparty could not be identified",
206
+ not_applicable_evidence(_CHECK_ID, in_scope=True, missing_field="target"), method)
207
+ told = _counted(definition, _to_target(action, ledger, target) if target is not None else [], required_classes)
208
+ stated = _counted(statement_definition, _in_deal(action, ledger, deal_id) if deal_id is not None else [],
209
+ required_classes)
210
+ prior, in_deal = _counts(told, required_classes), _counts(stated, required_classes)
211
+ listed = action.listing_classes if listing and action.listing_classes is not None else ()
212
+ missing = [cls for cls in required_classes if prior[cls] == 0 and in_deal[cls] == 0 and cls not in listed]
213
+ scope = StatementScope(operator=action.operator, target=target, deal_id=deal_id)
214
+ if listing:
215
+ evidence: StatementEvidence = ListingEvidence(
216
+ fold=definition.definition_digest(),
217
+ statement_fold=statement_definition.definition_digest(),
218
+ scope=scope,
219
+ prior_counts=prior,
220
+ stated_counts=in_deal,
221
+ missing_classes=missing,
222
+ listing_classes=None if action.listing_classes is None else list(action.listing_classes),
223
+ )
224
+ else:
225
+ evidence = StatementEvidence(
226
+ fold=definition.definition_digest(),
227
+ statement_fold=statement_definition.definition_digest(),
228
+ scope=scope,
229
+ prior_counts=prior,
230
+ stated_counts=in_deal,
231
+ missing_classes=missing,
232
+ )
233
+ return _decided(missing, evidence, method, told + stated)