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,451 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Dry-run report viewer: reads the report payload from the URL fragment
4
+ // (never a fetch, never a server round trip -- the fragment is never sent
5
+ // over the wire by the browser) and re-derives every number on this page
6
+ // from it. Also re-verifies each cited capsule's own capsule_id by
7
+ // recomputing it, so a tampered fragment or an altered cited record is
8
+ // caught here, not just trusted.
9
+ //
10
+ // The canonicalization below (normalize/jcsString/jcsValue) is a hand port
11
+ // of agent_action_capsule/canonical.py's JCS subset (RFC 8785): absent-field
12
+ // normalization, then lexicographic-by-UTF-16-code-unit key ordering, then
13
+ // SHA-256 over the UTF-8 bytes. It must stay byte-for-byte in step with
14
+ // that module -- this is the client-side half of the same digest.
15
+ (function () {
16
+ "use strict";
17
+
18
+ // ---- canonicalization (mirrors agent_action_capsule/canonical.py) ----
19
+
20
+ function normalize(v) {
21
+ if (Array.isArray(v)) {
22
+ return v.map(normalize);
23
+ }
24
+ if (v !== null && typeof v === "object") {
25
+ var out = {};
26
+ Object.keys(v).forEach(function (k) {
27
+ var nv = normalize(v[k]);
28
+ if (nv === null || nv === undefined) return;
29
+ if (Array.isArray(nv) && nv.length === 0) return;
30
+ if (!Array.isArray(nv) && typeof nv === "object" && Object.keys(nv).length === 0) return;
31
+ out[k] = nv;
32
+ });
33
+ return out;
34
+ }
35
+ return v === undefined ? null : v;
36
+ }
37
+
38
+ function jcsString(s) {
39
+ var out = ['"'];
40
+ for (var i = 0; i < s.length; i++) {
41
+ var ch = s.charAt(i);
42
+ var code = s.charCodeAt(i);
43
+ if (ch === '"') out.push('\\"');
44
+ else if (ch === "\\") out.push("\\\\");
45
+ else if (code === 0x08) out.push("\\b");
46
+ else if (code === 0x09) out.push("\\t");
47
+ else if (code === 0x0a) out.push("\\n");
48
+ else if (code === 0x0c) out.push("\\f");
49
+ else if (code === 0x0d) out.push("\\r");
50
+ else if (code < 0x20) out.push("\\u" + code.toString(16).padStart(4, "0"));
51
+ else out.push(ch);
52
+ }
53
+ out.push('"');
54
+ return out.join("");
55
+ }
56
+
57
+ function jcsValue(v) {
58
+ if (v === null || v === undefined) return "null";
59
+ if (v === true) return "true";
60
+ if (v === false) return "false";
61
+ if (typeof v === "string") return jcsString(v);
62
+ if (typeof v === "number") {
63
+ if (!Number.isFinite(v) || Math.floor(v) !== v) {
64
+ throw new Error("non-integer number in a digest-bearing field");
65
+ }
66
+ return String(v);
67
+ }
68
+ if (Array.isArray(v)) return "[" + v.map(jcsValue).join(",") + "]";
69
+ if (typeof v === "object") {
70
+ var keys = Object.keys(v).sort(function (a, b) {
71
+ return a < b ? -1 : a > b ? 1 : 0;
72
+ });
73
+ return (
74
+ "{" +
75
+ keys
76
+ .map(function (k) {
77
+ return jcsString(k) + ":" + jcsValue(v[k]);
78
+ })
79
+ .join(",") +
80
+ "}"
81
+ );
82
+ }
83
+ throw new Error("unsupported value in digest-bearing field");
84
+ }
85
+
86
+ function utf8Bytes(s) {
87
+ return new TextEncoder().encode(s);
88
+ }
89
+
90
+ async function sha256Hex(bytes) {
91
+ var digest = await crypto.subtle.digest("SHA-256", bytes);
92
+ return Array.prototype.map
93
+ .call(new Uint8Array(digest), function (b) {
94
+ return b.toString(16).padStart(2, "0");
95
+ })
96
+ .join("");
97
+ }
98
+
99
+ async function jsonDigest(v) {
100
+ return sha256Hex(utf8Bytes(jcsValue(normalize(v))));
101
+ }
102
+
103
+ async function computeCapsuleId(capsule) {
104
+ var canonical = {};
105
+ Object.keys(capsule).forEach(function (k) {
106
+ if (k !== "capsule_id" && k !== "chain") canonical[k] = capsule[k];
107
+ });
108
+ return jsonDigest(canonical);
109
+ }
110
+
111
+ // ---- fragment encode/decode (mirrors report/render.py) ----
112
+
113
+ function decodeFragment(hash) {
114
+ var token = (hash || "").replace(/^#/, "");
115
+ if (!token) return null;
116
+ var b64 = token.replace(/-/g, "+").replace(/_/g, "/");
117
+ while (b64.length % 4) b64 += "=";
118
+ var binary = atob(b64);
119
+ var bytes = new Uint8Array(binary.length);
120
+ for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
121
+ return JSON.parse(new TextDecoder("utf-8").decode(bytes));
122
+ }
123
+
124
+ function encodeFragment(payload) {
125
+ var bytes = utf8Bytes(JSON.stringify(payload));
126
+ var binary = Array.prototype.map
127
+ .call(bytes, function (b) {
128
+ return String.fromCharCode(b);
129
+ })
130
+ .join("");
131
+ var b64 = btoa(binary);
132
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
133
+ }
134
+
135
+ // ---- money formatting (mirrors report/build.py) ----
136
+
137
+ var CURRENCY_SYMBOLS = { USD: "$", EUR: "€", GBP: "£" };
138
+
139
+ function formatCompactMoney(amountMinor, currency) {
140
+ var symbol = CURRENCY_SYMBOLS[currency] || (currency ? currency + " " : "");
141
+ var major = amountMinor / 100;
142
+ if (Math.abs(major) >= 1000) {
143
+ return symbol + (major / 1000).toFixed(1) + "k";
144
+ }
145
+ return symbol + major.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
146
+ }
147
+
148
+ function valueHeldLabel(rows) {
149
+ var totals = {};
150
+ rows.forEach(function (r) {
151
+ if (r.amount_minor !== null && r.amount_minor !== undefined) {
152
+ var cur = r.currency || "";
153
+ totals[cur] = (totals[cur] || 0) + r.amount_minor;
154
+ }
155
+ });
156
+ var keys = Object.keys(totals).sort();
157
+ if (!keys.length) return "no amount carried by these actions";
158
+ return keys.map(function (k) { return formatCompactMoney(totals[k], k); }).join(" + ");
159
+ }
160
+
161
+ function shortId(id) {
162
+ return id ? id.slice(0, 8) + "…" : "";
163
+ }
164
+
165
+ // ---- redaction (display fields only -- cited capsule bodies travel
166
+ // byte-for-byte so their own digest stays checkable after redaction) ----
167
+
168
+ var MONEY_RE = /[$€£]\s?[\d][\d,.]*/g;
169
+ var ID_RE = /\b[A-Z]{2,}-\d{2,}\b|\b[A-Z]{2}\d{2}[A-Z0-9]{6,}\b/g;
170
+
171
+ async function sealToken(value) {
172
+ var hex = await sha256Hex(utf8Bytes(String(value)));
173
+ return "sealed:" + hex.slice(0, 12) + "…";
174
+ }
175
+
176
+ async function redactText(text) {
177
+ var tokens = [];
178
+ (text.match(MONEY_RE) || []).forEach(function (t) { tokens.push(t); });
179
+ (text.match(ID_RE) || []).forEach(function (t) { tokens.push(t); });
180
+ var seen = {};
181
+ var result = text;
182
+ for (var i = 0; i < tokens.length; i++) {
183
+ var token = tokens[i];
184
+ if (seen[token]) continue;
185
+ seen[token] = true;
186
+ var sealed = await sealToken(token);
187
+ result = result.split(token).join("[" + sealed + "]");
188
+ }
189
+ return result;
190
+ }
191
+
192
+ async function redactPayload(payload) {
193
+ var clone = JSON.parse(JSON.stringify(payload));
194
+ var agentSeals = {};
195
+ for (var g = 0; g < clone.guards.length; g++) {
196
+ var rows = clone.guards[g].rows;
197
+ for (var i = 0; i < rows.length; i++) {
198
+ var row = rows[i];
199
+ if (!agentSeals[row.agent]) agentSeals[row.agent] = await sealToken(row.agent);
200
+ row.agent = agentSeals[row.agent];
201
+ row.why = await redactText(row.why);
202
+ }
203
+ }
204
+ clone.operator = await sealToken(clone.operator);
205
+ clone.agents = clone.agents.map(function (_, idx) {
206
+ return "sealed-agent-" + idx;
207
+ });
208
+ clone.redacted = true;
209
+ return clone;
210
+ }
211
+
212
+ // ---- DOM rendering ----
213
+
214
+ function $(sel, root) {
215
+ return (root || document).querySelector(sel);
216
+ }
217
+
218
+ function setText(sel, text, root) {
219
+ var el = $(sel, root);
220
+ if (el) el.textContent = text;
221
+ }
222
+
223
+ function el(tag, className, text) {
224
+ var node = document.createElement(tag);
225
+ if (className) node.className = className;
226
+ if (text !== undefined) node.textContent = text;
227
+ return node;
228
+ }
229
+
230
+ function flattenRows(payload) {
231
+ var out = [];
232
+ payload.guards.forEach(function (section) {
233
+ section.rows.forEach(function (row) {
234
+ out.push({ section: section, row: row });
235
+ });
236
+ });
237
+ return out;
238
+ }
239
+
240
+ function renderHeadline(payload, allRows) {
241
+ setText("[data-headline-replayed]", String(payload.actions_replayed));
242
+ setText(
243
+ "[data-headline-replayed-envelope]",
244
+ "records " + payload.record_range[0] + "–" + payload.record_range[1] + " · ckpt #" + payload.checkpoint
245
+ );
246
+ setText("[data-headline-held]", String(allRows.length));
247
+ var triggered = Array.from(new Set(allRows.map(function (x) { return x.section.id; })));
248
+ setText(
249
+ "[data-headline-held-envelope]",
250
+ String(triggered.length) + (triggered.length === 1 ? " guard" : " guards") + " · named reasons below"
251
+ );
252
+ setText("[data-headline-value]", valueHeldLabel(allRows.map(function (x) { return x.row; })));
253
+ }
254
+
255
+ function renderMeta(payload) {
256
+ setText("[data-operator]", payload.operator || "—");
257
+ setText("[data-agent-count]", String((payload.agents || []).length));
258
+ setText("[data-record-range]", payload.record_range[0] + "–" + payload.record_range[1]);
259
+ setText("[data-checkpoint]", String(payload.checkpoint));
260
+ setText("[data-verify-command]", (payload.replay_command || "").replace("--share", "--verify"));
261
+ setText("[data-enforce-count]", String(flattenRows(payload).length));
262
+ }
263
+
264
+ function renderGuardSections(payload) {
265
+ var container = $("[data-guard-sections]");
266
+ container.innerHTML = "";
267
+ var rowTemplate = $("#row-template");
268
+ var sectionTemplate = $("#section-template");
269
+ payload.guards.forEach(function (section) {
270
+ if (!section.rows.length) return;
271
+ var node = sectionTemplate.content.firstElementChild.cloneNode(true);
272
+ setText("[data-guard-id]", section.id, node);
273
+ setText("[data-guard-what]", section.what, node);
274
+ setText("[data-guard-count]", String(section.rows.length) + " would-have-held", node);
275
+ var table = $("[data-guard-table]", node);
276
+ section.rows.forEach(function (row, idx) {
277
+ var rowNode = rowTemplate.content.firstElementChild.cloneNode(true);
278
+ setText("[data-row-when]", row.when, rowNode);
279
+ setText("[data-row-agent]", row.agent, rowNode);
280
+ setText("[data-row-why]", row.why, rowNode);
281
+ setText("[data-row-fp]", shortId(row.capsule && row.capsule.capsule_id), rowNode);
282
+ if (idx % 2 === 1) rowNode.style.background = "rgba(122,140,110,0.05)";
283
+ table.appendChild(rowNode);
284
+ });
285
+ container.appendChild(node);
286
+ });
287
+ }
288
+
289
+ function renderConsequential(payload) {
290
+ var wrap = $("[data-consequential]");
291
+ if (!payload.consequential) {
292
+ wrap.hidden = true;
293
+ return;
294
+ }
295
+ var section = payload.guards.filter(function (s) { return s.id === payload.consequential.guard_id; })[0];
296
+ var row = section && section.rows[payload.consequential.row_index];
297
+ if (!row) {
298
+ wrap.hidden = true;
299
+ return;
300
+ }
301
+ wrap.hidden = false;
302
+ var body = row.when + " — " + row.agent + " " + row.why + ".";
303
+ if (section.id === "dedupe" && row.cited_capsule_id) {
304
+ body += " Both carry the same order fingerprint — the dedupe guard matches them byte-for-byte.";
305
+ }
306
+ setText("[data-consequential-body]", body);
307
+ var linksWrap = $("[data-consequential-links]");
308
+ linksWrap.innerHTML = "";
309
+ if (row.cited_capsule_id) {
310
+ linksWrap.appendChild(el("span", "fp-link", "first payment " + shortId(row.cited_capsule_id)));
311
+ linksWrap.appendChild(document.createTextNode(" · "));
312
+ linksWrap.appendChild(el("span", "fp-link", "duplicate " + shortId(row.capsule.capsule_id)));
313
+ } else {
314
+ linksWrap.appendChild(el("span", "fp-link", "record " + shortId(row.capsule.capsule_id)));
315
+ }
316
+ }
317
+
318
+ function renderModelNote(payload) {
319
+ var wrap = $("[data-model-note-row]");
320
+ if (!payload.model_note) {
321
+ wrap.hidden = true;
322
+ return;
323
+ }
324
+ wrap.hidden = false;
325
+ setText("[data-model-quote]", '“' + payload.model_note.quote + '”');
326
+ setText(
327
+ "[data-model-byline]",
328
+ "drafted by " + payload.model_note.model_id + ", reading the " + payload.model_note.record_count + " records above · interpretation, not evidence"
329
+ );
330
+ setText("[data-tuning-recheck]", "capsule guard dry-run --since " + (payload.since || "7d") + " --share");
331
+ }
332
+
333
+ function renderPermalink(payload) {
334
+ var href = location.href;
335
+ var shown = href.length > 72 ? href.slice(0, 72) + "…" : href;
336
+ setText("[data-permalink]", shown);
337
+ var disclosure = $("[data-disclosure]");
338
+ if (disclosure) disclosure.hidden = false;
339
+ }
340
+
341
+ async function verifyAll(payload) {
342
+ var capsules = [];
343
+ flattenRows(payload).forEach(function (x) {
344
+ if (x.row.capsule) capsules.push(x.row.capsule);
345
+ if (x.row.cited_capsule) capsules.push(x.row.cited_capsule);
346
+ });
347
+ var mismatches = [];
348
+ for (var i = 0; i < capsules.length; i++) {
349
+ var c = capsules[i];
350
+ if (!c || !c.capsule_id) continue;
351
+ try {
352
+ var recomputed = await computeCapsuleId(c);
353
+ if (recomputed !== c.capsule_id) mismatches.push(c.capsule_id);
354
+ } catch (e) {
355
+ mismatches.push(c.capsule_id || "(unknown)");
356
+ }
357
+ }
358
+ return { ok: mismatches.length === 0, mismatches: mismatches, checked: capsules.length };
359
+ }
360
+
361
+ function setVerifiedBadge(payload, result) {
362
+ var badge = $("[data-verified-badge]");
363
+ if (!badge) return;
364
+ var foldLabel = "fold guard_dry_run@" + payload.checkpoint;
365
+ var status;
366
+ if (result.checked === 0) {
367
+ status = "no cited records to verify";
368
+ } else if (result.ok) {
369
+ status = "verified on open ✓";
370
+ } else {
371
+ status = "recompute differs from " + result.mismatches.length + " cited record(s) — the ledger changed";
372
+ }
373
+ badge.textContent = foldLabel + " · " + status;
374
+ badge.classList.toggle("mismatch", result.checked > 0 && !result.ok);
375
+ }
376
+
377
+ var currentPayload = null;
378
+
379
+ async function renderFromPayload(payload) {
380
+ currentPayload = payload;
381
+ var allRows = flattenRows(payload);
382
+ $("[data-empty-state]").hidden = true;
383
+ $("[data-card]").hidden = false;
384
+ $("[data-below-card]").hidden = false;
385
+ renderHeadline(payload, allRows);
386
+ renderMeta(payload);
387
+ renderGuardSections(payload);
388
+ renderConsequential(payload);
389
+ renderModelNote(payload);
390
+ renderPermalink(payload);
391
+ var result = await verifyAll(payload);
392
+ setVerifiedBadge(payload, result);
393
+ }
394
+
395
+ async function main() {
396
+ var payload = null;
397
+ try {
398
+ payload = decodeFragment(location.hash);
399
+ } catch (e) {
400
+ payload = null;
401
+ }
402
+ if (!payload) {
403
+ $("[data-empty-state]").hidden = false;
404
+ $("[data-card]").hidden = true;
405
+ $("[data-below-card]").hidden = true;
406
+ return;
407
+ }
408
+ await renderFromPayload(payload);
409
+ }
410
+
411
+ document.addEventListener("DOMContentLoaded", function () {
412
+ main();
413
+
414
+ var copyBtn = $("[data-copy-permalink]");
415
+ if (copyBtn) {
416
+ copyBtn.addEventListener("click", function (ev) {
417
+ ev.preventDefault();
418
+ if (navigator.clipboard && navigator.clipboard.writeText) {
419
+ navigator.clipboard.writeText(location.href).then(function () {
420
+ var original = copyBtn.textContent;
421
+ copyBtn.textContent = "copied";
422
+ setTimeout(function () { copyBtn.textContent = original; }, 1200);
423
+ });
424
+ }
425
+ });
426
+ }
427
+
428
+ var redactBtn = $("[data-redact]");
429
+ if (redactBtn) {
430
+ redactBtn.addEventListener("click", function (ev) {
431
+ ev.preventDefault();
432
+ if (!currentPayload) return;
433
+ redactPayload(currentPayload).then(function (redacted) {
434
+ var frag = encodeFragment(redacted);
435
+ history.replaceState(null, "", location.pathname + location.search + "#" + frag);
436
+ return renderFromPayload(redacted);
437
+ });
438
+ });
439
+ }
440
+ });
441
+
442
+ // exposed for tests / --verify self-checks run from a headless JS shell
443
+ window.__dryRunReport = {
444
+ normalize: normalize,
445
+ jcsValue: jcsValue,
446
+ computeCapsuleId: computeCapsuleId,
447
+ decodeFragment: decodeFragment,
448
+ encodeFragment: encodeFragment,
449
+ valueHeldLabel: valueHeldLabel,
450
+ };
451
+ })();