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,253 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Closed vocabulary for the outcome compiler, and its display strings.
3
+
4
+ Five closed sets, each shipping its plain-language rendering in this same
5
+ module (design §3.6: "an enum without a display string is developer
6
+ vocabulary leaking onto the auditor's desk"):
7
+
8
+ - ``FORWARD_VERDICTS`` / ``BACKWARD_VERDICTS`` -- the verdict PAIR every
9
+ compiled statement carries (design §2.2). This is a schema *shape*
10
+ decision: two enums per statement, not one annotation. The judge is never
11
+ in the enforcement path, so ``MODEL-ASSISTED`` is a backward-only value --
12
+ it has no forward counterpart.
13
+ - ``RESPONSE_CLASSES`` -- the offer/response denominator primitive (design
14
+ §4b gap 2): an offer was made, a response occurred, and the response is
15
+ recorded whether or not it was the one wanted. Named ``response_class``,
16
+ never ``consent`` -- that word imports one partner's domain vocabulary
17
+ into a shared format.
18
+ - ``REFUSAL_REASON_CODES`` -- v0, closed, extended deliberately (same
19
+ "unregistered is a typo" doctrine as ``packs/schema.py``'s other closed
20
+ sets). Seeded with exactly the two refusals this wave's design work
21
+ demonstrated: an effect claim that assumes the agent caused a resolution
22
+ it can only be shown to have recommended, and a goal too unbounded to
23
+ decompose into windowed proxies.
24
+ - ``RE_DERIVABILITY_GRADES`` -- design §2.3: whether a constraint's inputs
25
+ are sealed and its verdict independently re-derivable from a compiled
26
+ plan alone (``pure_replay`` -- e.g. containment), or whether re-deriving
27
+ it means possessing the ledger and adopting this repo's own fold
28
+ semantics over ledger state no capsule carries (``ledger_state_dependent``
29
+ -- e.g. caps). Labelled rather than hidden: "an asymmetry named is an
30
+ honesty feature; an asymmetry discovered is an embarrassment."
31
+
32
+ **The reserved-verdict-word deny-list.** Per licensing law and design §3.6:
33
+ publisher vocabulary may name things; only the verifier states verdicts.
34
+ Seven words are reserved outright (they assert soundness properties this
35
+ format does not make): *verified, certified, compliant, guaranteed, pass,
36
+ approved, tamper-proof*. On top of that fixed list, ``check_display_string`` also refuses any
37
+ display string that contains the raw enum token it is describing
38
+ (case-insensitively) -- e.g. ``MODEL-ASSISTED``'s own rendering may not
39
+ contain the substring "model-assisted". That second rule is what "the
40
+ mappability labels" (build plan Phase 1 item 6) cashes out to
41
+ structurally: self-referential, so it covers the verdict-pair and
42
+ refusal/grade jargon without a second, driftable list to keep in sync.
43
+ It does not apply to ``response_class`` -- ``accepted``/``declined``/
44
+ ``deferred``/``no_response`` are already plain English, and restating one
45
+ is not the "developer vocabulary leaking onto the auditor's desk" failure
46
+ this rule exists to catch.
47
+ """
48
+ from __future__ import annotations
49
+
50
+ import re
51
+ from dataclasses import dataclass
52
+
53
+ __all__ = [
54
+ "FORWARD_VERDICTS",
55
+ "BACKWARD_VERDICTS",
56
+ "RESPONSE_CLASSES",
57
+ "REFUSAL_REASON_CODES",
58
+ "RE_DERIVABILITY_GRADES",
59
+ "RESERVED_VERDICT_WORDS",
60
+ "VerdictPair",
61
+ "DisplayViolation",
62
+ "display_string",
63
+ "check_display_string",
64
+ "check_all_display_strings",
65
+ "assert_every_value_has_a_display_string",
66
+ ]
67
+
68
+ # --- Verdict pair (design §2.2) --------------------------------------------
69
+ FORWARD_VERDICTS = frozenset(
70
+ {"DETERMINISTIC", "UNAVAILABLE-MODEL-REQUIRED", "UNAVAILABLE-STATE-REQUIRED", "REFUSED"}
71
+ )
72
+ BACKWARD_VERDICTS = frozenset({"DETERMINISTIC", "MODEL-ASSISTED", "MANUAL", "WITH-INSTRUMENTATION", "REFUSED"})
73
+
74
+ # --- offer/response denominator primitive (design §4b gap 2) ---------------
75
+ RESPONSE_CLASSES = frozenset({"accepted", "declined", "deferred", "no_response"})
76
+
77
+ # --- refusal reason codes, v0 -----------------------------------------------
78
+ REFUSAL_REASON_CODES = frozenset(
79
+ {
80
+ # design §4b gap 1: the format is incoherent without this refusal --
81
+ # a causal claim about an advisory-only agent has no evidence that
82
+ # could confirm it, only correlation.
83
+ "agent_caused_resolution_undecomposable",
84
+ # design §3.4 / build plan Phase 1: an "eventually X" goal with no
85
+ # window cannot be compiled into a windowed proxy and must refuse
86
+ # rather than ship as an opaque score.
87
+ "unbounded_goal_unmonitorable",
88
+ # airline-engagement A8 ("the customer was satisfied"): a
89
+ # DIFFERENT refusal from unbounded_goal_unmonitorable above -- that
90
+ # one is about a missing time window (a windowed proxy would fix
91
+ # it); this one is not fixed by any window, because a person's
92
+ # felt state is never witnessed by a record, only their own
93
+ # statement about it, which is testimony, not detection. "We do
94
+ # not measure feelings" (agent-human-engagement thesis): a claim
95
+ # about satisfaction/trust/comfort inferred from conduct or words
96
+ # alone is the exact overclaim this format refuses to make.
97
+ "subjective_state_unattestable",
98
+ }
99
+ )
100
+
101
+ # --- re-derivability grade (design §2.3) ------------------------------------
102
+ RE_DERIVABILITY_GRADES = frozenset({"pure_replay", "ledger_state_dependent"})
103
+
104
+ RESERVED_VERDICT_WORDS = frozenset(
105
+ {"verified", "certified", "compliant", "guaranteed", "pass", "approved", "tamper-proof"}
106
+ )
107
+
108
+ _ALL_CLOSED_SETS: dict[str, frozenset[str]] = {
109
+ "forward_verdict": FORWARD_VERDICTS,
110
+ "backward_verdict": BACKWARD_VERDICTS,
111
+ "response_class": RESPONSE_CLASSES,
112
+ "refusal_reason_code": REFUSAL_REASON_CODES,
113
+ "re_derivability_grade": RE_DERIVABILITY_GRADES,
114
+ }
115
+
116
+ # ``response_class`` values (accepted/declined/deferred/no_response) are
117
+ # already plain English -- restating one in its own display string is not
118
+ # "developer vocabulary leaking onto the auditor's desk", the failure mode
119
+ # the self-referential check exists to catch. That failure mode is real for
120
+ # the SCREAMING-KEBAB and snake_case categories below (a verifier stating
121
+ # raw "MODEL-ASSISTED" instead of a sentence), so the self-check applies
122
+ # only to those.
123
+ _SELF_TOKEN_CHECK_CATEGORIES = frozenset(
124
+ {"forward_verdict", "backward_verdict", "refusal_reason_code", "re_derivability_grade"}
125
+ )
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class VerdictPair:
130
+ """A compiled statement's two verdicts. Both must come from their own
131
+ closed set -- ``MODEL-ASSISTED`` forward or ``UNAVAILABLE-MODEL-REQUIRED``
132
+ backward are both typos, not values, because the judge is never in the
133
+ enforcement path (design §2.2)."""
134
+
135
+ forward: str
136
+ backward: str
137
+
138
+ def __post_init__(self) -> None:
139
+ if self.forward not in FORWARD_VERDICTS:
140
+ raise ValueError(f"forward verdict must be one of {sorted(FORWARD_VERDICTS)}; got {self.forward!r}")
141
+ if self.backward not in BACKWARD_VERDICTS:
142
+ raise ValueError(f"backward verdict must be one of {sorted(BACKWARD_VERDICTS)}; got {self.backward!r}")
143
+
144
+
145
+ # category -> {value -> display string}. Every value in every closed set
146
+ # above MUST have an entry here -- enforced by
147
+ # ``assert_every_value_has_a_display_string`` (run at import time, below,
148
+ # and exercised directly by tests/test_compiler_vocabulary.py).
149
+ DISPLAY_STRINGS: dict[str, dict[str, str]] = {
150
+ "forward_verdict": {
151
+ "DETERMINISTIC": "checked automatically before the action ran",
152
+ "UNAVAILABLE-MODEL-REQUIRED": "not checked before the action ran -- would need a live judgment call",
153
+ "UNAVAILABLE-STATE-REQUIRED": "not checked before the action ran -- the needed record did not exist yet",
154
+ "REFUSED": "no automatic check exists for this claim",
155
+ },
156
+ "backward_verdict": {
157
+ "DETERMINISTIC": "provable from the record alone",
158
+ "MODEL-ASSISTED": "provable with a reviewer's judgment over the record",
159
+ "MANUAL": "provable only by a person attesting to it directly",
160
+ "WITH-INSTRUMENTATION": "provable once a missing record is captured; not claimed today",
161
+ "REFUSED": "this claim cannot be decomposed into evidence that would prove it",
162
+ },
163
+ "response_class": {
164
+ "accepted": "the offer was accepted",
165
+ "declined": "the offer was declined",
166
+ "deferred": "a decision on the offer was put off",
167
+ "no_response": "no response to the offer was recorded",
168
+ },
169
+ "refusal_reason_code": {
170
+ "agent_caused_resolution_undecomposable": (
171
+ "the record can show a recommendation was made and a person acted, "
172
+ "but not that the recommendation is what made them act"
173
+ ),
174
+ "unbounded_goal_unmonitorable": "this goal has no time window, so no record could ever settle it",
175
+ "subjective_state_unattestable": (
176
+ "a person's own felt state cannot be read off a record -- only what they said or did"
177
+ ),
178
+ },
179
+ "re_derivability_grade": {
180
+ "pure_replay": "a stranger can recompute this verdict from the compiled plan alone",
181
+ "ledger_state_dependent": "recomputing this verdict requires the ledger, not just the compiled plan",
182
+ },
183
+ }
184
+
185
+
186
+ @dataclass(frozen=True)
187
+ class DisplayViolation:
188
+ category: str
189
+ value: str
190
+ display_string: str
191
+ reserved_words: tuple[str, ...]
192
+
193
+
194
+ def display_string(category: str, value: str) -> str:
195
+ """The plain-language rendering for ``value`` in the named closed set.
196
+ Raises ``KeyError`` for anything outside the closed set or missing a
197
+ rendering -- these are curated, closed vocabularies (unlike the open,
198
+ registry-resolved ``action_class`` conventions), so an unregistered
199
+ value here is a typo, not a legitimate degraded state."""
200
+ table = DISPLAY_STRINGS[category]
201
+ return table[value]
202
+
203
+
204
+ def check_display_string(category: str, value: str, text: str) -> DisplayViolation | None:
205
+ """Return a ``DisplayViolation`` if ``text`` (the display string for
206
+ ``category``/``value``) carries a reserved verdict word, or the raw
207
+ token it is itself describing. ``None`` means clean."""
208
+ lowered = text.lower()
209
+ hits: list[str] = []
210
+ for word in RESERVED_VERDICT_WORDS:
211
+ if re.search(rf"\b{re.escape(word)}\b", lowered):
212
+ hits.append(word)
213
+ if category in _SELF_TOKEN_CHECK_CATEGORIES and value.lower() in lowered:
214
+ hits.append(value)
215
+ if hits:
216
+ return DisplayViolation(category=category, value=value, display_string=text, reserved_words=tuple(hits))
217
+ return None
218
+
219
+
220
+ def check_all_display_strings(
221
+ table: dict[str, dict[str, str]] | None = None,
222
+ ) -> list[DisplayViolation]:
223
+ """Scan every entry in ``table`` (defaults to the real, shipped
224
+ ``DISPLAY_STRINGS``) and return every violation found. Used both as the
225
+ CI gate (``tests/test_compiler_vocabulary.py`` fails the build if this
226
+ is non-empty against the real table) and, with a deliberately-corrupted
227
+ ``table`` argument, as the RED-before-green mutant proof that the check
228
+ can fail at all."""
229
+ violations: list[DisplayViolation] = []
230
+ for category, entries in (table or DISPLAY_STRINGS).items():
231
+ for value, text in entries.items():
232
+ v = check_display_string(category, value, text)
233
+ if v is not None:
234
+ violations.append(v)
235
+ return violations
236
+
237
+
238
+ def assert_every_value_has_a_display_string() -> None:
239
+ """Every value in every closed set above must render -- the P1
240
+ acceptance line ("every enum renders"). Raises ``AssertionError`` naming
241
+ exactly what's missing; run at import time so a schema addition that
242
+ forgets its display string fails immediately, not at first render."""
243
+ for category, values in _ALL_CLOSED_SETS.items():
244
+ table = DISPLAY_STRINGS.get(category, {})
245
+ missing = sorted(v for v in values if v not in table)
246
+ if missing:
247
+ raise AssertionError(f"{category}: missing display string for {missing}")
248
+ extra = sorted(v for v in table if v not in values)
249
+ if extra:
250
+ raise AssertionError(f"{category}: display string table carries unregistered value(s) {extra}")
251
+
252
+
253
+ assert_every_value_has_a_display_string()
@@ -0,0 +1,10 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The local console: `capsule console`'s data layer (`api.py`), HTTP wiring
3
+ (`server.py`), and the static UI it serves -- `tokens.css`/`components.css`
4
+ (the shared component library), plus `console.css`/`console.js`/`console.html`
5
+ (this screen's own layout and client logic). `gallery.html` next to these
6
+ files is the component-library review page, not part of the served UI.
7
+
8
+ LOCAL ONLY: this package serves localhost, reads a real ledger on disk, and
9
+ makes no outbound network calls anywhere in what it renders.
10
+ """
@@ -0,0 +1,251 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Data-shaping functions for the local console server (`capsule console`).
3
+
4
+ Pure functions over a real :class:`~cll.ledger.api.LedgerAPI` -- no
5
+ HTTP, no server state -- so they're exercisable directly in tests without a
6
+ socket. ``server.py`` is the thin HTTP wiring on top of this module. Every
7
+ field returned here comes straight off a real capsule, a real
8
+ ``store.verify()`` result, or a real fold replay -- nothing is invented for
9
+ display.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from cll.ledger.api import LedgerAPI, ScanQuery
17
+ from cll.ledger.records import LedgerRecord
18
+
19
+ from ..cli.format import (
20
+ assurance_grade_parts,
21
+ build_echo,
22
+ format_coverage_footnote,
23
+ format_envelope_line,
24
+ format_staleness,
25
+ summarize_action,
26
+ )
27
+ from ..envcompat import env_get
28
+ from ..folds import Catalog, FoldDeterminismError, evaluate_one
29
+ from ..registry import describe_action_class
30
+
31
+ __all__ = [
32
+ "checkpoint_status",
33
+ "list_records",
34
+ "record_detail",
35
+ "record_summary",
36
+ ]
37
+
38
+ DEFAULT_CATALOG_DIR = Path(__file__).resolve().parent.parent / "folds" / "catalog_defs"
39
+
40
+
41
+ def _fold_catalog_dir() -> Path:
42
+ env = env_get("CAPSULE_FOLD_DIR")
43
+ return Path(env) if env else DEFAULT_CATALOG_DIR
44
+
45
+
46
+ def _fingerprint(capsule_id: str) -> str:
47
+ return capsule_id[:8] + "…" if capsule_id else ""
48
+
49
+
50
+ def _withheld_payloads(capsule: dict) -> list[dict[str, Any]]:
51
+ """Every payload the capsule commits to by digest: the disposition reason
52
+ and each check's evidence. A capsule never carries the preimages, so each
53
+ one renders as withheld with its digest, never as a blank field."""
54
+ payloads = []
55
+ reason_digest = (capsule.get("disposition") or {}).get("reason_digest")
56
+ if reason_digest:
57
+ payloads.append({"label": "reason", "check_id": None, "digest": reason_digest, "state": "withheld"})
58
+ for c in capsule.get("constraints") or []:
59
+ if c.get("evidence_digest"):
60
+ payloads.append(
61
+ {"label": "evidence", "check_id": c.get("id"), "digest": c["evidence_digest"], "state": "withheld"}
62
+ )
63
+ return payloads
64
+
65
+
66
+ def _action_class_info(capsule: dict) -> dict[str, Any] | None:
67
+ action_class = (capsule.get("asg_payload") or {}).get("action_class")
68
+ if not action_class:
69
+ return None
70
+ convention = describe_action_class(action_class)
71
+ return {"value": action_class, "label": convention.label, "registered": convention.registered}
72
+
73
+
74
+ def _assurance_grade_info(assurance: dict) -> dict[str, Any]:
75
+ grade, badged = assurance_grade_parts(assurance)
76
+ return {"grade": grade, "badged": badged}
77
+
78
+
79
+ def checkpoint_status(store: LedgerAPI) -> dict[str, Any]:
80
+ """The console's freshness status line: "checkpoint #N · as of <age> ·
81
+ verifies offline" -- always computed from a fresh re-scan, never cached,
82
+ so its own age is always ~0 (the same convention `capsule log`'s own
83
+ trailing status line already uses -- see `cli/log_cmd.py`)."""
84
+ total = sum(1 for _ in store.scan(ScanQuery()))
85
+ staleness_label = format_staleness(0)
86
+ return {
87
+ "checkpoint": total,
88
+ "staleness_label": staleness_label,
89
+ "line": f"checkpoint #{total} · as of {staleness_label} · verifies offline",
90
+ }
91
+
92
+
93
+ def record_summary(record: LedgerRecord) -> dict[str, Any]:
94
+ capsule = record.capsule
95
+ assurance = capsule.get("assurance") or {}
96
+ return {
97
+ "capsule_id": record.capsule_id,
98
+ "fingerprint": _fingerprint(record.capsule_id),
99
+ "seq": record.seq,
100
+ "agent": capsule.get("developer", ""),
101
+ "operator": capsule.get("operator", ""),
102
+ "action": summarize_action(capsule),
103
+ "action_type": capsule.get("action_type", ""),
104
+ "action_class": _action_class_info(capsule),
105
+ "timestamp": capsule.get("timestamp", ""),
106
+ "disposition": capsule.get("disposition") or {},
107
+ "assurance": assurance,
108
+ "assurance_grade": _assurance_grade_info(assurance),
109
+ }
110
+
111
+
112
+ def _echo_parts(filters: dict[str, Any]) -> list[tuple[str, object]]:
113
+ return [
114
+ ("--agent", filters.get("agent")),
115
+ ("--since", filters.get("since")),
116
+ ("--until", filters.get("until")),
117
+ ("--counterparty", filters.get("counterparty")),
118
+ ("--verdict", filters.get("verdict")),
119
+ ("--action-type", filters.get("action_type")),
120
+ ("--limit", filters.get("limit")),
121
+ ]
122
+
123
+
124
+ def list_records(store: LedgerAPI, filters: dict[str, Any]) -> dict[str, Any]:
125
+ """The filtered record stream, plus the exact `capsule log` invocation
126
+ that reproduces this same filtered view -- the CLI-echo standing rule
127
+ applied to the console's own record stream/filters panel."""
128
+ query = ScanQuery(
129
+ agent=filters.get("agent"),
130
+ since=filters.get("since"),
131
+ until=filters.get("until"),
132
+ counterparty=filters.get("counterparty"),
133
+ verdict=filters.get("verdict"),
134
+ action_type=filters.get("action_type"),
135
+ limit=filters.get("limit"),
136
+ )
137
+ total = sum(1 for _ in store.scan(ScanQuery()))
138
+ records = [record_summary(r) for r in store.scan(query)]
139
+ gaps = store.find_gaps()
140
+ return {
141
+ "records": records,
142
+ "total": total,
143
+ "shown": len(records),
144
+ "gap_count": len(gaps),
145
+ "cli_echo": build_echo("log", flags=_echo_parts(filters)),
146
+ }
147
+
148
+
149
+ def _fold_strip(store: LedgerAPI, developer: str) -> list[dict[str, Any]]:
150
+ """Live fold values for this record's agent -- the catalog's own
151
+ definitions, replayed over the real ledger. A rolling-window fold's
152
+ anchor is the ledger's own latest record timestamp (spec §3 rule 1:
153
+ never a wall-clock read), matching how `report/build.py` anchors its
154
+ own replay."""
155
+ records = [r.capsule for r in store.scan(ScanQuery())]
156
+ if not records:
157
+ return []
158
+ as_of = records[-1].get("timestamp")
159
+ catalog = Catalog(_fold_catalog_dir())
160
+ strip: list[dict[str, Any]] = []
161
+ for entry in catalog.list_entries():
162
+ definition = entry.definition
163
+ key_value = developer if definition.key else None
164
+ try:
165
+ trace = evaluate_one(definition, records, key_value=key_value, as_of=as_of)
166
+ except FoldDeterminismError:
167
+ continue
168
+ strip.append(
169
+ {
170
+ "fold_id": definition.fold_id,
171
+ "result": trace.result,
172
+ "envelope_line": format_envelope_line(trace.to_envelope()),
173
+ # T2R rev9(a): "a report cannot render a number without its
174
+ # coverage statement" -- this is the one place today that
175
+ # renders a fold's `result` for a human, so it never does so
176
+ # without the footnote beside it.
177
+ "coverage_footnote": format_coverage_footnote(trace.coverage_statement()),
178
+ }
179
+ )
180
+ return strip
181
+
182
+
183
+ def record_detail(store: LedgerAPI, capsule_id: str) -> dict[str, Any] | None:
184
+ """Everything the inspector panel needs for one selected record:
185
+ identity fingerprint, sealed fields, verdict, which checks ran, the
186
+ chain in both directions (what this record cites, what cites it), a
187
+ real cryptographic verify() result, and its agent's live fold strip."""
188
+ record = store.fetch(capsule_id)
189
+ if record is None:
190
+ return None
191
+
192
+ capsule = record.capsule
193
+ chain = capsule.get("chain") or {}
194
+ constraints = capsule.get("constraints") or []
195
+ disposition = capsule.get("disposition") or {}
196
+
197
+ parent_id = chain.get("parent_capsule_id")
198
+ cites = None
199
+ if parent_id:
200
+ parent = store.fetch(parent_id)
201
+ cites = {
202
+ "capsule_id": parent_id,
203
+ "fingerprint": _fingerprint(parent_id),
204
+ "relation": chain.get("relation"),
205
+ "found": parent is not None,
206
+ }
207
+
208
+ cited_by = [
209
+ {
210
+ "capsule_id": other.capsule_id,
211
+ "fingerprint": _fingerprint(other.capsule_id),
212
+ "relation": (other.capsule.get("chain") or {}).get("relation"),
213
+ }
214
+ for other in store.scan(ScanQuery())
215
+ if (other.capsule.get("chain") or {}).get("parent_capsule_id") == record.capsule_id
216
+ ]
217
+
218
+ verify_result = store.verify(record.capsule_id)
219
+
220
+ return {
221
+ "capsule_id": record.capsule_id,
222
+ "fingerprint": _fingerprint(record.capsule_id),
223
+ "seq": record.seq,
224
+ "sealed": capsule,
225
+ "disposition": disposition,
226
+ "action_class": _action_class_info(capsule),
227
+ "assurance_grade": _assurance_grade_info(capsule.get("assurance") or {}),
228
+ "checks": [
229
+ {
230
+ "id": c.get("id"),
231
+ "result": c.get("result"),
232
+ "severity": c.get("severity"),
233
+ "check_type": c.get("check_type"),
234
+ "method": c.get("method"),
235
+ "evidence_digest": c.get("evidence_digest"),
236
+ }
237
+ for c in constraints
238
+ ],
239
+ "payloads": _withheld_payloads(capsule),
240
+ "chain": {"cites": cites, "cited_by": cited_by},
241
+ "verify": {
242
+ "ok": verify_result.ok if verify_result is not None else None,
243
+ "findings": (
244
+ [{"code": f.code, "detail": f.detail, "severity": f.severity} for f in verify_result.findings]
245
+ if verify_result is not None
246
+ else []
247
+ ),
248
+ },
249
+ "fold_strip": _fold_strip(store, capsule.get("developer", "")),
250
+ "cli_echo": build_echo("show", positional=record.capsule_id),
251
+ }