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.
- capsule_engine/__init__.py +17 -0
- capsule_engine/bundle_viewer/__init__.py +21 -0
- capsule_engine/bundle_viewer/static/offline_shell.html +1509 -0
- capsule_engine/bundle_viewer/viewer.py +57 -0
- capsule_engine/cli/__init__.py +5 -0
- capsule_engine/cli/bundle_cmd.py +269 -0
- capsule_engine/cli/console_cmd.py +70 -0
- capsule_engine/cli/constraints_cmd.py +74 -0
- capsule_engine/cli/format.py +173 -0
- capsule_engine/cli/guard_cmds.py +361 -0
- capsule_engine/cli/init_cmds.py +169 -0
- capsule_engine/cli/main.py +120 -0
- capsule_engine/cli/packs_cmds.py +96 -0
- capsule_engine/cli/telemetry_cmd.py +80 -0
- capsule_engine/cli/tenant_cmds.py +199 -0
- capsule_engine/cli/verify_cmd.py +110 -0
- capsule_engine/compiler/__init__.py +13 -0
- capsule_engine/compiler/effect_model.py +79 -0
- capsule_engine/compiler/vocabulary.py +253 -0
- capsule_engine/console/__init__.py +10 -0
- capsule_engine/console/api.py +251 -0
- capsule_engine/console/components.css +406 -0
- capsule_engine/console/console.css +130 -0
- capsule_engine/console/console.html +51 -0
- capsule_engine/console/console.js +330 -0
- capsule_engine/console/gallery.html +274 -0
- capsule_engine/console/server.py +137 -0
- capsule_engine/console/tokens.css +68 -0
- capsule_engine/conversation/__init__.py +88 -0
- capsule_engine/conversation/capsules.py +268 -0
- capsule_engine/conversation/exchange.py +279 -0
- capsule_engine/conversation/merkle.py +80 -0
- capsule_engine/conversation/session.py +101 -0
- capsule_engine/envcompat.py +15 -0
- capsule_engine/events/__init__.py +14 -0
- capsule_engine/events/capsule.py +133 -0
- capsule_engine/examples/__init__.py +9 -0
- capsule_engine/examples/data/tau2_airline/LICENSE-tau2-bench +21 -0
- capsule_engine/examples/data/tau2_airline/PROVENANCE.json +70 -0
- capsule_engine/examples/data/tau2_airline/hand_labels.json +893 -0
- capsule_engine/examples/data/tau2_airline/pilot1_live_gemini-2-5-flash_airline_shift1.jsonl +190 -0
- capsule_engine/examples/data/tau2_airline/tau2_committed_claude-3-7-sonnet_airline_trial0.jsonl +556 -0
- capsule_engine/examples/data/tau2_airline/tau2_committed_gpt-4-1-mini_airline_trial0.jsonl +480 -0
- capsule_engine/examples/data/tau2_airline/tau2_committed_gpt-4-1_airline_trial0.jsonl +496 -0
- capsule_engine/examples/data/tau2_airline/tau2_committed_o4-mini_airline_trial0.jsonl +392 -0
- capsule_engine/examples/data/tau2_airline/tau2_conversations_claude-3-7-sonnet_airline_4trials.jsonl +200 -0
- capsule_engine/examples/tau2_airline_reference.py +460 -0
- capsule_engine/examples/tau2_conversation_exchange.py +92 -0
- capsule_engine/folds/__init__.py +104 -0
- capsule_engine/folds/account_core.py +89 -0
- capsule_engine/folds/approval_latency.py +86 -0
- capsule_engine/folds/catalog.py +79 -0
- capsule_engine/folds/catalog_defs/actions.count_by_developer.yaml +10 -0
- capsule_engine/folds/catalog_defs/actions.executed_count.yaml +16 -0
- capsule_engine/folds/catalog_defs/actions.last_decision.yaml +12 -0
- capsule_engine/folds/catalog_defs/counterparty.seen_before.v2.yaml +34 -0
- capsule_engine/folds/catalog_defs/counterparty.seen_before.v3.yaml +38 -0
- capsule_engine/folds/catalog_defs/counterparty.seen_before.yaml +28 -0
- capsule_engine/folds/catalog_defs/disclosure.class_seen.yaml +29 -0
- capsule_engine/folds/catalog_defs/disclosure.statement_seen.yaml +32 -0
- capsule_engine/folds/catalog_defs/hold.active_exposure.yaml +44 -0
- capsule_engine/folds/catalog_defs/spend.weekly.v2.yaml +38 -0
- capsule_engine/folds/catalog_defs/spend.weekly.v3.1.yaml +71 -0
- capsule_engine/folds/catalog_defs/spend.weekly.v3.yaml +76 -0
- capsule_engine/folds/catalog_defs/spend.weekly.yaml +33 -0
- capsule_engine/folds/counterparty_signals.py +179 -0
- capsule_engine/folds/definition.py +421 -0
- capsule_engine/folds/duration.py +16 -0
- capsule_engine/folds/engine.py +436 -0
- capsule_engine/folds/errors.py +44 -0
- capsule_engine/folds/loader.py +24 -0
- capsule_engine/folds/ordering.py +143 -0
- capsule_engine/folds/paths.py +25 -0
- capsule_engine/folds/record_type_coverage.py +58 -0
- capsule_engine/folds/reducers.py +75 -0
- capsule_engine/folds/retention_continuity.py +117 -0
- capsule_engine/folds/set_membership.py +59 -0
- capsule_engine/folds/taxonomy.py +231 -0
- capsule_engine/guards/__init__.py +59 -0
- capsule_engine/guards/action.py +446 -0
- capsule_engine/guards/action_taxonomy.json +27 -0
- capsule_engine/guards/action_taxonomy_v2.json +27 -0
- capsule_engine/guards/action_taxonomy_v3.json +27 -0
- capsule_engine/guards/action_taxonomy_v4.json +27 -0
- capsule_engine/guards/action_taxonomy_v5.json +27 -0
- capsule_engine/guards/capsule.py +377 -0
- capsule_engine/guards/checks/__init__.py +268 -0
- capsule_engine/guards/checks/action_class_gate.py +199 -0
- capsule_engine/guards/checks/base.py +33 -0
- capsule_engine/guards/checks/caps.py +517 -0
- capsule_engine/guards/checks/channel_change.py +64 -0
- capsule_engine/guards/checks/commitment.py +41 -0
- capsule_engine/guards/checks/counterparty_identity_change.py +95 -0
- capsule_engine/guards/checks/counterparty_list.py +176 -0
- capsule_engine/guards/checks/counterparty_seen_before.py +160 -0
- capsule_engine/guards/checks/credential_pattern.py +73 -0
- capsule_engine/guards/checks/dedupe.py +259 -0
- capsule_engine/guards/checks/destination_rail.py +120 -0
- capsule_engine/guards/checks/field_change_count.py +100 -0
- capsule_engine/guards/checks/offer_expiry.py +134 -0
- capsule_engine/guards/checks/plan_containment.py +180 -0
- capsule_engine/guards/checks/price_floor.py +211 -0
- capsule_engine/guards/checks/promise_never.py +236 -0
- capsule_engine/guards/checks/promise_requires_approval.py +214 -0
- capsule_engine/guards/checks/recipient_role.py +117 -0
- capsule_engine/guards/checks/recurring_charge.py +49 -0
- capsule_engine/guards/checks/refundability.py +46 -0
- capsule_engine/guards/checks/release_on_acceptance.py +286 -0
- capsule_engine/guards/checks/required_disclosure.py +233 -0
- capsule_engine/guards/checks/single_commitment.py +221 -0
- capsule_engine/guards/checks/task_authority.py +182 -0
- capsule_engine/guards/checks/upfront_amount.py +71 -0
- capsule_engine/guards/checks/verify_before_dispatch.py +75 -0
- capsule_engine/guards/classes.py +393 -0
- capsule_engine/guards/engine.py +926 -0
- capsule_engine/guards/history_state.py +127 -0
- capsule_engine/guards/plan.py +237 -0
- capsule_engine/guards/revocation.py +120 -0
- capsule_engine/guards/signing.py +60 -0
- capsule_engine/guards/statements.py +62 -0
- capsule_engine/guards/tool_call.py +83 -0
- capsule_engine/guards/wickets/__init__.py +23 -0
- capsule_engine/guards/wickets/catalog.py +75 -0
- capsule_engine/guards/wickets/catalog_defs/action_class_gate.v1.1.yaml +38 -0
- capsule_engine/guards/wickets/catalog_defs/action_class_gate.v1.2.yaml +52 -0
- capsule_engine/guards/wickets/catalog_defs/action_class_gate.yaml +34 -0
- capsule_engine/guards/wickets/catalog_defs/caps.v2.yaml +30 -0
- capsule_engine/guards/wickets/catalog_defs/caps.v3.yaml +49 -0
- capsule_engine/guards/wickets/catalog_defs/caps.v4.yaml +45 -0
- capsule_engine/guards/wickets/catalog_defs/caps.v5.1.yaml +53 -0
- capsule_engine/guards/wickets/catalog_defs/caps.v5.yaml +43 -0
- capsule_engine/guards/wickets/catalog_defs/caps.yaml +17 -0
- capsule_engine/guards/wickets/catalog_defs/caps_holds.yaml +18 -0
- capsule_engine/guards/wickets/catalog_defs/channel_change.v1.1.yaml +18 -0
- capsule_engine/guards/wickets/catalog_defs/channel_change.yaml +10 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_identity_change.yaml +9 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_list.yaml +20 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v2.yaml +15 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v3.yaml +23 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.v4.yaml +26 -0
- capsule_engine/guards/wickets/catalog_defs/counterparty_seen_before.yaml +14 -0
- capsule_engine/guards/wickets/catalog_defs/credential_pattern.yaml +14 -0
- capsule_engine/guards/wickets/catalog_defs/dedupe.yaml +19 -0
- capsule_engine/guards/wickets/catalog_defs/destination_rail.seller.v1.1.yaml +32 -0
- capsule_engine/guards/wickets/catalog_defs/destination_rail.seller.yaml +10 -0
- capsule_engine/guards/wickets/catalog_defs/destination_rail.yaml +9 -0
- capsule_engine/guards/wickets/catalog_defs/hold_reconcile.yaml +14 -0
- capsule_engine/guards/wickets/catalog_defs/material_fields_changed.v1.1.yaml +22 -0
- capsule_engine/guards/wickets/catalog_defs/material_fields_changed.yaml +14 -0
- capsule_engine/guards/wickets/catalog_defs/offer_expiry.v1.1.yaml +31 -0
- capsule_engine/guards/wickets/catalog_defs/offer_expiry.yaml +24 -0
- capsule_engine/guards/wickets/catalog_defs/offer_fields_changed.v1.1.yaml +22 -0
- capsule_engine/guards/wickets/catalog_defs/offer_fields_changed.yaml +10 -0
- capsule_engine/guards/wickets/catalog_defs/price_floor.yaml +32 -0
- capsule_engine/guards/wickets/catalog_defs/promise_never.v1.1.yaml +64 -0
- capsule_engine/guards/wickets/catalog_defs/promise_never.yaml +16 -0
- capsule_engine/guards/wickets/catalog_defs/promise_requires_approval.v1.1.yaml +26 -0
- capsule_engine/guards/wickets/catalog_defs/promise_requires_approval.yaml +21 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.v1.1.yaml +23 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.v1.2.yaml +24 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_role.seller.yaml +10 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_role.v1.1.yaml +26 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_role.yaml +11 -0
- capsule_engine/guards/wickets/catalog_defs/recipient_seen_before.yaml +16 -0
- capsule_engine/guards/wickets/catalog_defs/recurring_charge.yaml +9 -0
- capsule_engine/guards/wickets/catalog_defs/refundability.v1.1.yaml +15 -0
- capsule_engine/guards/wickets/catalog_defs/refundability.yaml +7 -0
- capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.v1.1.yaml +44 -0
- capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.v1.2.yaml +63 -0
- capsule_engine/guards/wickets/catalog_defs/release_on_acceptance.seller.yaml +36 -0
- capsule_engine/guards/wickets/catalog_defs/required_disclosure.v1.1.yaml +36 -0
- capsule_engine/guards/wickets/catalog_defs/required_disclosure.v1.2.yaml +42 -0
- capsule_engine/guards/wickets/catalog_defs/required_disclosure.yaml +25 -0
- capsule_engine/guards/wickets/catalog_defs/single_commitment.seller.yaml +14 -0
- capsule_engine/guards/wickets/catalog_defs/task_authority.v1.1.yaml +20 -0
- capsule_engine/guards/wickets/catalog_defs/task_authority.yaml +8 -0
- capsule_engine/guards/wickets/catalog_defs/upfront_amount.v1.1.yaml +19 -0
- capsule_engine/guards/wickets/catalog_defs/upfront_amount.yaml +11 -0
- capsule_engine/guards/wickets/catalog_defs/verify_before_dispatch.yaml +9 -0
- capsule_engine/guards/wickets/definition.py +164 -0
- capsule_engine/guards/wickets/errors.py +18 -0
- capsule_engine/guards/wickets/loader.py +26 -0
- capsule_engine/guards/wickets/retired.py +62 -0
- capsule_engine/holds/__init__.py +35 -0
- capsule_engine/holds/capsules.py +266 -0
- capsule_engine/holds/engine.py +476 -0
- capsule_engine/holds/errors.py +29 -0
- capsule_engine/holds/policy.py +62 -0
- capsule_engine/holds/scope.py +47 -0
- capsule_engine/mcp/__init__.py +16 -0
- capsule_engine/mcp/config.py +117 -0
- capsule_engine/mcp/server.py +338 -0
- capsule_engine/mcp/tools.py +424 -0
- capsule_engine/packaging.py +60 -0
- capsule_engine/packs/__init__.py +45 -0
- capsule_engine/packs/backfill_coverage.py +238 -0
- capsule_engine/packs/catalog/airline-engagement/AI-BOOTSTRAP.md +53 -0
- capsule_engine/packs/catalog/airline-engagement/folds/session_close.yaml +8 -0
- capsule_engine/packs/catalog/airline-engagement/pack.yaml +146 -0
- capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/good_execution.json +36 -0
- capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/oversight_declared.json +36 -0
- capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/tampered_execution.json +36 -0
- capsule_engine/packs/catalog/eu-ai-act/fixtures/trace/trusted_issuer.jwk.json +5 -0
- capsule_engine/packs/catalog/eu-ai-act/obligations-pack-note.md +68 -0
- capsule_engine/packs/catalog/eu-ai-act/obligations-register.yaml +761 -0
- capsule_engine/packs/catalog/eu-ai-act/pack.yaml +88 -0
- capsule_engine/packs/catalog/eu-ai-act/register.yaml +96 -0
- capsule_engine/packs/catalog/eu-ai-act-deterministic/fixtures/synthetic_internal_assist_sample.jsonl +6 -0
- capsule_engine/packs/catalog/eu-ai-act-deterministic/pack.yaml +580 -0
- capsule_engine/packs/catalog/everyday/AI-BOOTSTRAP.md +194 -0
- capsule_engine/packs/catalog/everyday/fixtures/mini_ledger.jsonl +45 -0
- capsule_engine/packs/catalog/everyday/pack.yaml +480 -0
- capsule_engine/packs/catalog/payments-safety/AI-BOOTSTRAP.md +83 -0
- capsule_engine/packs/catalog/payments-safety/fixtures/mini_ledger.jsonl +8 -0
- capsule_engine/packs/catalog/payments-safety/folds/spend_weekly.yaml +29 -0
- capsule_engine/packs/catalog/payments-safety/pack.yaml +112 -0
- capsule_engine/packs/catalog/seller/fixtures/mini_ledger.jsonl +83 -0
- capsule_engine/packs/catalog/seller/pack.yaml +503 -0
- capsule_engine/packs/catalog/standard-vendor/AI-BOOTSTRAP.md +70 -0
- capsule_engine/packs/catalog/standard-vendor/folds/session_close.yaml +8 -0
- capsule_engine/packs/catalog/standard-vendor/pack.yaml +466 -0
- capsule_engine/packs/contract_diff.py +809 -0
- capsule_engine/packs/contract_validate.py +185 -0
- capsule_engine/packs/corpus_verify.py +126 -0
- capsule_engine/packs/enforce.py +81 -0
- capsule_engine/packs/errors.py +159 -0
- capsule_engine/packs/eu_ai_act_trace_field_map.py +46 -0
- capsule_engine/packs/install.py +266 -0
- capsule_engine/packs/loader.py +1462 -0
- capsule_engine/packs/measurability_report.py +280 -0
- capsule_engine/packs/obligation_results.py +72 -0
- capsule_engine/packs/pins.py +92 -0
- capsule_engine/packs/schema.py +1053 -0
- capsule_engine/packs/thresholds.py +146 -0
- capsule_engine/packs/trace_attainment.py +188 -0
- capsule_engine/packs/trace_reader.py +104 -0
- capsule_engine/policy/__init__.py +37 -0
- capsule_engine/policy/activation.py +87 -0
- capsule_engine/policy/catalog_defs/default.yaml +23 -0
- capsule_engine/policy/catalog_defs/holds.yaml +27 -0
- capsule_engine/policy/errors.py +46 -0
- capsule_engine/policy/limits.py +287 -0
- capsule_engine/policy/loader.py +26 -0
- capsule_engine/policy/manifest.py +289 -0
- capsule_engine/policy/profile.py +281 -0
- capsule_engine/policy/resolve.py +224 -0
- capsule_engine/register/__init__.py +39 -0
- capsule_engine/register/compiler.py +219 -0
- capsule_engine/register/errors.py +40 -0
- capsule_engine/register/loader.py +247 -0
- capsule_engine/register/schema.py +129 -0
- capsule_engine/registry/__init__.py +19 -0
- capsule_engine/registry/conventions.json +162 -0
- capsule_engine/registry/conventions.py +188 -0
- capsule_engine/registry/cpb_registry.json +78 -0
- capsule_engine/report/__init__.py +62 -0
- capsule_engine/report/build.py +267 -0
- capsule_engine/report/coverage.py +777 -0
- capsule_engine/report/errors.py +35 -0
- capsule_engine/report/live_check.py +126 -0
- capsule_engine/report/live_history.py +420 -0
- capsule_engine/report/model.py +95 -0
- capsule_engine/report/render.py +467 -0
- capsule_engine/report/replay.py +1677 -0
- capsule_engine/report/result.py +504 -0
- capsule_engine/report/result_from_folds.py +222 -0
- capsule_engine/report/result_from_layers.py +670 -0
- capsule_engine/report/sale_bundle.py +864 -0
- capsule_engine/report/static/verify.js +451 -0
- capsule_engine/schemas/evidence-contract-v0.json +383 -0
- capsule_engine/schemas/vendor/epistemic-types.json +21 -0
- capsule_engine/schemas/vendor/evidence-result-v0.json +463 -0
- capsule_engine/telemetry/__init__.py +9 -0
- capsule_engine/telemetry/consent.py +39 -0
- capsule_engine/telemetry/events.py +93 -0
- capsule_engine/telemetry/fixtures/synthetic_events.jsonl +45 -0
- capsule_engine/telemetry/funnel.py +147 -0
- capsule_engine/telemetry/record.py +106 -0
- capsule_engine/telemetry/sink.py +61 -0
- capsule_engine/telemetry/state.py +60 -0
- capsule_engine/tenants.py +327 -0
- capsule_engine-0.1.0.dist-info/METADATA +47 -0
- capsule_engine-0.1.0.dist-info/RECORD +288 -0
- capsule_engine-0.1.0.dist-info/WHEEL +5 -0
- capsule_engine-0.1.0.dist-info/entry_points.txt +2 -0
- capsule_engine-0.1.0.dist-info/licenses/LICENSE +202 -0
- capsule_engine-0.1.0.dist-info/licenses/capsule_engine/examples/data/tau2_airline/LICENSE-tau2-bench +21 -0
- 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
|
+
})();
|