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,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
|
+
}
|