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,44 @@
|
|
|
1
|
+
# seller.release_on_acceptance/1.1.0: 1.0.0 (release_on_acceptance.seller.yaml)
|
|
2
|
+
# not applicable to a disclosure to a platform, the marketplace or relay a
|
|
3
|
+
# sale is listed on: it reaches no buyer, so there is no acceptance to cite,
|
|
4
|
+
# and seller.recipient_role/1.1.0 lets it carry the user's email only. 1.0.0
|
|
5
|
+
# stays as it was for the records and packs that pin it.
|
|
6
|
+
#
|
|
7
|
+
# release_on_acceptance, configured for a seller (guards/checks/
|
|
8
|
+
# release_on_acceptance.py). Personal data reaches a buyer only once that
|
|
9
|
+
# buyer's acceptance of the sale is sealed. The rule itself is the
|
|
10
|
+
# `semantics` field below: it is inside the digested body, so rewording it
|
|
11
|
+
# moves the digest. A failure refuses; it never asks, and an approval does
|
|
12
|
+
# not clear it.
|
|
13
|
+
#
|
|
14
|
+
# `release_classes` is the closed set of statement classes, the same list
|
|
15
|
+
# promise_never/1.0.0 and promise_requires_approval/1.0.0 close over, so
|
|
16
|
+
# every class a disclosure may declare is held back until acceptance and
|
|
17
|
+
# any other class is refused. No class is released before acceptance. `acceptance_classes` are the classes whose accepted,
|
|
18
|
+
# non-dry-run record seals the acceptance (the same reading as
|
|
19
|
+
# seller.single_commitment). `action_classes` are the disclosures the rule
|
|
20
|
+
# applies to.
|
|
21
|
+
wicket_id: seller.release_on_acceptance/1.1.0
|
|
22
|
+
check: release_on_acceptance
|
|
23
|
+
semantics: >-
|
|
24
|
+
A disclosure in action_classes passes only when its representation_class
|
|
25
|
+
is in release_classes and it names its task_authority_ref, item_ref and
|
|
26
|
+
target, cites by cited_mandate_capsule_id the sale's acceptance, and that
|
|
27
|
+
acceptance re-verifies and was sealed for the same target. The sale's
|
|
28
|
+
acceptance is the first record in ledger order, among the operator's,
|
|
29
|
+
whose action_class is in acceptance_classes, whose decision was accept,
|
|
30
|
+
which was not a dry run, and which carries the same task_authority_ref and
|
|
31
|
+
item_ref. Every other such disclosure fails: no citation, another sale's
|
|
32
|
+
or another recipient's acceptance, a record that is not the sale's
|
|
33
|
+
acceptance, a missing reference, or a representation_class missing or
|
|
34
|
+
outside release_classes. An action whose action_class is missing or has no
|
|
35
|
+
row in the action taxonomy fails too, since it cannot be shown to be out
|
|
36
|
+
of scope. Any other known action class is not applicable, and so is a
|
|
37
|
+
disclosure whose recipient_role is in except_recipient_roles: it reaches
|
|
38
|
+
no buyer. A failure is refused and no approval clears it. The reason and
|
|
39
|
+
evidence carry no disclosed value, reference, counterparty or capsule id.
|
|
40
|
+
config:
|
|
41
|
+
release_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
|
|
42
|
+
acceptance_classes: [agreement.accept]
|
|
43
|
+
action_classes: [disclosure.personal]
|
|
44
|
+
except_recipient_roles: [platform]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# seller.release_on_acceptance/1.2.0: seller.release_on_acceptance/1.1.0
|
|
2
|
+
# (release_on_acceptance.seller.v1.1.yaml) reading what a share gives and
|
|
3
|
+
# binding the acceptance from the history (capsule-engine#179, #186).
|
|
4
|
+
#
|
|
5
|
+
# 1.1.0 keyed on representation_class, a statement class no share carries,
|
|
6
|
+
# and needed a cited, re-verified acceptance no producer sends, so no share
|
|
7
|
+
# to a buyer ever passed. 1.2.0 reads the share's classes of data
|
|
8
|
+
# (disclosed_classes). release_classes are the contact classes of
|
|
9
|
+
# DISCLOSED_CLASSES (guards/action.py); a credential, a verification code, a
|
|
10
|
+
# payment card and an id document are left out, so they still refuse. The
|
|
11
|
+
# listing's public pickup area (pre_acceptance_classes) goes to any buyer
|
|
12
|
+
# before acceptance. Anything else needs the sale's acceptance, the same
|
|
13
|
+
# record as before (the first accepted, non-dry-run agreement.accept of the
|
|
14
|
+
# item), in the share's own deal and by the same buyer: the counterparty
|
|
15
|
+
# fingerprint each check seals, compared on the per-person identity kinds,
|
|
16
|
+
# or on the name and the domain together when neither side seals one (an
|
|
17
|
+
# opening that named the buyer by name and domain only). binding selects this reading in the check (guards/checks/
|
|
18
|
+
# release_on_acceptance.py); 1.0.0 and 1.1.0 have none and decide as they
|
|
19
|
+
# always did. The rule itself is the `semantics` field below: it is inside
|
|
20
|
+
# the digested body, so rewording it moves the digest. A failure refuses; it
|
|
21
|
+
# never asks, and an approval does not clear it.
|
|
22
|
+
wicket_id: seller.release_on_acceptance/1.2.0
|
|
23
|
+
check: release_on_acceptance
|
|
24
|
+
semantics: >-
|
|
25
|
+
A disclosure in action_classes passes only when it names at least one
|
|
26
|
+
class of data (disclosed_classes), every class it names is in
|
|
27
|
+
release_classes, and either every class it names is in
|
|
28
|
+
pre_acceptance_classes, or the sale has a known acceptance in the same
|
|
29
|
+
deal by the same buyer. The sale's acceptance is the first record in
|
|
30
|
+
ledger order, among the operator's, whose action_class is in
|
|
31
|
+
acceptance_classes, whose decision was accept, which was not a dry run,
|
|
32
|
+
and which carries the disclosure's item_ref; a history that may be missing
|
|
33
|
+
acts, an act of the sale that could not be read, an earlier act of an
|
|
34
|
+
acceptance class with no disposition, or an earlier accepted commitment in
|
|
35
|
+
the history that names no item leave it unknown, which is none. The
|
|
36
|
+
same deal is an equal deal_id, both set. The same buyer is the
|
|
37
|
+
counterparty the acceptance's check sealed, carried beside it in the
|
|
38
|
+
history, and the one the disclosure's check sealed, under the same
|
|
39
|
+
fingerprint algorithm, with at least one per-person identity (email,
|
|
40
|
+
phone, profile_id, relay_address) on both sides and every one on both
|
|
41
|
+
sides equal; or, when neither side has any per-person identity, with a
|
|
42
|
+
name and a domain on both sides and both equal. A payee is never
|
|
43
|
+
compared, and a name or a domain is compared only when neither side has a
|
|
44
|
+
per-person identity. A counterparty block on either side with any member
|
|
45
|
+
of another kind or shape, or another algorithm, is no buyer. Every other
|
|
46
|
+
such disclosure fails: no class or a class outside release_classes, no
|
|
47
|
+
item_ref, deal_id or counterparty fingerprint, no known acceptance, or one
|
|
48
|
+
in another deal or by another buyer. An action whose action_class is
|
|
49
|
+
missing or has no row in the action taxonomy fails too, since it cannot
|
|
50
|
+
be shown to be out of scope. Any other known action class is not
|
|
51
|
+
applicable, and so is a disclosure whose recipient_role is in
|
|
52
|
+
except_recipient_roles: it reaches no buyer. A failure is refused and no
|
|
53
|
+
approval clears it. The reason and evidence carry no disclosed value,
|
|
54
|
+
reference, deal, counterparty or capsule id: only the class names read,
|
|
55
|
+
whether the disclosure was bound to an acceptance, and a missing field's
|
|
56
|
+
name.
|
|
57
|
+
config:
|
|
58
|
+
binding: same_deal_same_buyer
|
|
59
|
+
release_classes: [name, phone, email, home_address, address, pickup_location, other_contact, pickup_area]
|
|
60
|
+
pre_acceptance_classes: [pickup_area]
|
|
61
|
+
acceptance_classes: [agreement.accept]
|
|
62
|
+
action_classes: [disclosure.personal]
|
|
63
|
+
except_recipient_roles: [platform]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# release_on_acceptance wicket, configured for a seller (guards/checks/
|
|
2
|
+
# release_on_acceptance.py). Personal data reaches a buyer only once that
|
|
3
|
+
# buyer's acceptance of the sale is sealed. The rule itself is the
|
|
4
|
+
# `semantics` field below: it is inside the digested body, so rewording it
|
|
5
|
+
# moves the digest. A failure refuses; it never asks, and an approval does
|
|
6
|
+
# not clear it.
|
|
7
|
+
#
|
|
8
|
+
# `release_classes` is the closed set of statement classes, the same list
|
|
9
|
+
# promise_never/1.0.0 and promise_requires_approval/1.0.0 close over, so
|
|
10
|
+
# every class a disclosure may declare is held back until acceptance and
|
|
11
|
+
# any other class is refused. No class is released before acceptance. `acceptance_classes` are the classes whose accepted,
|
|
12
|
+
# non-dry-run record seals the acceptance (the same reading as
|
|
13
|
+
# seller.single_commitment). `action_classes` are the disclosures the rule
|
|
14
|
+
# applies to.
|
|
15
|
+
wicket_id: seller.release_on_acceptance/1.0.0
|
|
16
|
+
check: release_on_acceptance
|
|
17
|
+
semantics: >-
|
|
18
|
+
A disclosure in action_classes passes only when its representation_class
|
|
19
|
+
is in release_classes and it names its task_authority_ref, item_ref and
|
|
20
|
+
target, cites by cited_mandate_capsule_id the sale's acceptance, and that
|
|
21
|
+
acceptance re-verifies and was sealed for the same target. The sale's
|
|
22
|
+
acceptance is the first record in ledger order, among the operator's,
|
|
23
|
+
whose action_class is in acceptance_classes, whose decision was accept,
|
|
24
|
+
which was not a dry run, and which carries the same task_authority_ref and
|
|
25
|
+
item_ref. Every other such disclosure fails: no citation, another sale's
|
|
26
|
+
or another recipient's acceptance, a record that is not the sale's
|
|
27
|
+
acceptance, a missing reference, or a representation_class missing or
|
|
28
|
+
outside release_classes. An action whose action_class is missing or has no
|
|
29
|
+
row in the action taxonomy fails too, since it cannot be shown to be out
|
|
30
|
+
of scope. Any other known action class is not applicable. A failure is
|
|
31
|
+
refused and no approval clears it. The reason and evidence carry no
|
|
32
|
+
disclosed value, reference, counterparty or capsule id.
|
|
33
|
+
config:
|
|
34
|
+
release_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
|
|
35
|
+
acceptance_classes: [agreement.accept]
|
|
36
|
+
action_classes: [disclosure.personal]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# required_disclosure wicket, version 1.1: version 1.0 with one change. A
|
|
2
|
+
# required class also counts as stated when the action's deal already holds
|
|
3
|
+
# a statement of it: a claim the user's agent made to the counterparty in
|
|
4
|
+
# that deal before the action (a capsulectl claim record sealing
|
|
5
|
+
# source_kind agent and the class), counted by
|
|
6
|
+
# disclosure.statement_seen/1.0.0. Under version 1.0 such a claim stated no
|
|
7
|
+
# act, so no decision carried its class and every offer and commit after it
|
|
8
|
+
# failed as if nothing had been said. Version 1.0
|
|
9
|
+
# (required_disclosure.yaml) stays as it was for the packs that pin it.
|
|
10
|
+
#
|
|
11
|
+
# Both `fold_digest` and `statement_fold_digest` are the real
|
|
12
|
+
# definition_digest() of their folds in folds/catalog_defs; the check refuses
|
|
13
|
+
# to run when a catalog fold does not match.
|
|
14
|
+
#
|
|
15
|
+
# Declared disposition: ask.
|
|
16
|
+
wicket_id: required_disclosure/1.1.0
|
|
17
|
+
check: required_disclosure
|
|
18
|
+
semantics: >-
|
|
19
|
+
For an action in action_classes, each class in required_classes must
|
|
20
|
+
already have been stated to the counterparty: by an accepted prior action
|
|
21
|
+
of the operator addressed to the action's target that carried that
|
|
22
|
+
representation_class, leaving dry runs out, or by a claim the operator's
|
|
23
|
+
agent made in the action's deal before the action with source_kind agent
|
|
24
|
+
and that class (a sealed delivery_promise reads as delivery_date). A class
|
|
25
|
+
with neither fails, and an approval never clears it. A class is never read
|
|
26
|
+
from a disclosure record's personal-data fields. An action naming neither
|
|
27
|
+
a target nor a deal is not applicable. Any other action class is out of
|
|
28
|
+
scope.
|
|
29
|
+
config:
|
|
30
|
+
action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
|
|
31
|
+
representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
|
|
32
|
+
required_classes: [condition]
|
|
33
|
+
fold_id: disclosure.class_seen/1.0.0
|
|
34
|
+
fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
|
|
35
|
+
statement_fold_id: disclosure.statement_seen/1.0.0
|
|
36
|
+
statement_fold_digest: 925e7ccd2e29b3f1984f966e968bdef47d0d22d2b85b8e0205a39d6a5a7ff0be
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# required_disclosure wicket, version 1.2: version 1.1 with one change. A
|
|
2
|
+
# required class also counts as stated when the sale's listings in force
|
|
3
|
+
# state it: the action's listing_classes, the rule input capsulectl seals on
|
|
4
|
+
# each offer and commit check of a sale's thread (the classes every live
|
|
5
|
+
# listing states, the intersection over the latest post per platform when
|
|
6
|
+
# the check was made; capsule-engine#190). A listing is the agent's words to
|
|
7
|
+
# every buyer, so a class it states is stated to this one too. Under version
|
|
8
|
+
# 1.1 a listing counted for nothing, and an offer after a listing that stated
|
|
9
|
+
# the condition asked for it again. The value is read from the check, never
|
|
10
|
+
# recomputed: a later re-post changes later checks only. With no value (no
|
|
11
|
+
# post) or an empty one, the class needs one of 1.1's two paths. Version 1.1
|
|
12
|
+
# (required_disclosure.v1.1.yaml) stays as it was for the packs that pin it.
|
|
13
|
+
#
|
|
14
|
+
# Both `fold_digest` and `statement_fold_digest` are the real
|
|
15
|
+
# definition_digest() of their folds in folds/catalog_defs; the check refuses
|
|
16
|
+
# to run when a catalog fold does not match.
|
|
17
|
+
#
|
|
18
|
+
# Declared disposition: ask.
|
|
19
|
+
wicket_id: required_disclosure/1.2.0
|
|
20
|
+
check: required_disclosure
|
|
21
|
+
semantics: >-
|
|
22
|
+
For an action in action_classes, each class in required_classes must
|
|
23
|
+
already have been stated to the counterparty: by an accepted prior action
|
|
24
|
+
of the operator addressed to the action's target that carried that
|
|
25
|
+
representation_class, leaving dry runs out; by a claim the operator's
|
|
26
|
+
agent made in the action's deal before the action with source_kind agent
|
|
27
|
+
and that class (a sealed delivery_promise reads as delivery_date); or by
|
|
28
|
+
the sale's listings in force, when the class is in the action's
|
|
29
|
+
listing_classes as its check sealed them. A class with none of the three
|
|
30
|
+
fails, and an approval never clears it. A class is never read from a
|
|
31
|
+
disclosure record's personal-data fields. An action naming neither a
|
|
32
|
+
target nor a deal is not applicable. Any other action class is out of
|
|
33
|
+
scope.
|
|
34
|
+
config:
|
|
35
|
+
action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
|
|
36
|
+
representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
|
|
37
|
+
required_classes: [condition]
|
|
38
|
+
fold_id: disclosure.class_seen/1.0.0
|
|
39
|
+
fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
|
|
40
|
+
statement_fold_id: disclosure.statement_seen/1.0.0
|
|
41
|
+
statement_fold_digest: 925e7ccd2e29b3f1984f966e968bdef47d0d22d2b85b8e0205a39d6a5a7ff0be
|
|
42
|
+
count_listing_classes: true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# required_disclosure wicket: classes of statement that must have been made
|
|
2
|
+
# to the counterparty before the action (guards/checks/
|
|
3
|
+
# required_disclosure.py). For each class in `required_classes`, the
|
|
4
|
+
# disclosure.class_seen/1.0.0 fold counts this operator's accepted prior
|
|
5
|
+
# actions addressed to Action.target that carried that
|
|
6
|
+
# representation_class; a zero fails the check. It checks that a statement
|
|
7
|
+
# of the class was recorded, never what it said or whether it was true.
|
|
8
|
+
# A failure means: make the disclosure first, or change the task
|
|
9
|
+
# authority. An approval never waives it.
|
|
10
|
+
#
|
|
11
|
+
# `representation_classes` is the closed set a class must come from;
|
|
12
|
+
# `required_classes` outside it is refused when the check runs.
|
|
13
|
+
# `fold_digest` is the real definition_digest() of
|
|
14
|
+
# folds/catalog_defs/disclosure.class_seen.yaml; the check refuses to run
|
|
15
|
+
# when the catalog fold does not match it.
|
|
16
|
+
#
|
|
17
|
+
# Declared disposition: ask.
|
|
18
|
+
wicket_id: required_disclosure/1.0.0
|
|
19
|
+
check: required_disclosure
|
|
20
|
+
config:
|
|
21
|
+
action_classes: [marketplace.offer, marketplace.sale, agreement.accept]
|
|
22
|
+
representation_classes: [price, condition, features, authenticity, availability, delivery_date, service_scope, warranty, refund_terms, payment_methods, pickup, deadline, address, other]
|
|
23
|
+
required_classes: [condition]
|
|
24
|
+
fold_id: disclosure.class_seen/1.0.0
|
|
25
|
+
fold_digest: 5b4d0280892db43c5209629137be8634be01c32d481ae79e005c4a1c6358c564
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# single_commitment wicket, configured for a seller (guards/checks/
|
|
2
|
+
# single_commitment.py). One sale, several buyer threads: once an acceptance
|
|
3
|
+
# is sealed for a task_authority_ref and an item_ref, a commitment for the
|
|
4
|
+
# same pair fails unless it cites that acceptance and is addressed to the
|
|
5
|
+
# counterparty that accepted. A failure refuses; it never asks.
|
|
6
|
+
#
|
|
7
|
+
# `acceptance_classes` are the classes whose accepted, non-dry-run record
|
|
8
|
+
# seals the acceptance. `commit_classes` are the commitments the rule
|
|
9
|
+
# applies to; anything else (a message, a disclosure) is n/a.
|
|
10
|
+
wicket_id: seller.single_commitment/1.0.0
|
|
11
|
+
check: single_commitment
|
|
12
|
+
config:
|
|
13
|
+
acceptance_classes: [agreement.accept]
|
|
14
|
+
commit_classes: [agreement.accept, marketplace.offer, marketplace.sale, external_commitment.other]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# task_authority wicket, version 1.1: task_authority/1.0.0's config with
|
|
2
|
+
# marketplace.sale added to action_classes (guards/checks/task_authority.py).
|
|
3
|
+
# The rule itself is the `semantics` field below: it is inside the digested
|
|
4
|
+
# body, so rewording it moves the digest. task_authority/1.0.0
|
|
5
|
+
# (task_authority.yaml) stays as it was for the packs that pin it.
|
|
6
|
+
wicket_id: task_authority/1.1.0
|
|
7
|
+
check: task_authority
|
|
8
|
+
semantics: >-
|
|
9
|
+
For an action in action_classes, Action.task_authority_ref is the SHA-256
|
|
10
|
+
over the JCS bytes of the whole task-authority record the action cites. The
|
|
11
|
+
record supplied with the decision is read only when its digest equals that
|
|
12
|
+
reference, and the plan at its body, in guards/plan.py's shape, decides by
|
|
13
|
+
plan containment: an action inside the plan passes, one outside it fails.
|
|
14
|
+
It is not applicable, naming task_authority_ref, when the action carries
|
|
15
|
+
none, and naming task_authority_record when no record is supplied, the
|
|
16
|
+
supplied record is not the one the reference names or has no digest, or
|
|
17
|
+
the record carries no plan that parses. marketplace.sale is in scope. Any
|
|
18
|
+
other action class is out of scope.
|
|
19
|
+
config:
|
|
20
|
+
action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other, marketplace.sale]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# task_authority wicket: the action is inside the task authority it cites
|
|
2
|
+
# (guards/checks/task_authority.py). The whole cited record is read only
|
|
3
|
+
# when the engine's own digest of it equals the action's task_authority_ref;
|
|
4
|
+
# the plan inside it is in guards/plan.py's shape, and plan_containment decides.
|
|
5
|
+
wicket_id: task_authority/1.0.0
|
|
6
|
+
check: task_authority
|
|
7
|
+
config:
|
|
8
|
+
action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# upfront_amount wicket, version 1.1: upfront_amount/1.0.0's config with
|
|
2
|
+
# marketplace.sale added to action_classes (guards/checks/upfront_amount.py).
|
|
3
|
+
# The rule itself is the `semantics` field below: it is inside the digested
|
|
4
|
+
# body, so rewording it moves the digest. upfront_amount/1.0.0
|
|
5
|
+
# (upfront_amount.yaml) stays as it was for the packs that pin it.
|
|
6
|
+
wicket_id: upfront_amount/1.1.0
|
|
7
|
+
check: upfront_amount
|
|
8
|
+
semantics: >-
|
|
9
|
+
For an action in action_classes, Action.upfront_amount_minor (the part of
|
|
10
|
+
the total stated as due before delivery) fails when it is above
|
|
11
|
+
upfront_max_minor, or when upfront_amount_minor * 10000 is above
|
|
12
|
+
Action.amount_minor * upfront_max_bps; otherwise it passes. Without an
|
|
13
|
+
amount_minor only upfront_max_minor is compared. It is not applicable,
|
|
14
|
+
naming the field, when the action carries no upfront_amount_minor.
|
|
15
|
+
marketplace.sale is in scope. Any other action class is out of scope.
|
|
16
|
+
config:
|
|
17
|
+
action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other, marketplace.sale]
|
|
18
|
+
upfront_max_minor: 10000
|
|
19
|
+
upfront_max_bps: 2500
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# upfront_amount wicket: the deposit a payment states, read as
|
|
2
|
+
# Action.upfront_amount_minor
|
|
3
|
+
# (guards/checks/upfront_amount.py). Fails above `upfront_max_minor`, or
|
|
4
|
+
# above `upfront_max_bps` basis points of the total. Default limits:
|
|
5
|
+
# 100.00 and 25%, in the action's own minor units.
|
|
6
|
+
wicket_id: upfront_amount/1.0.0
|
|
7
|
+
check: upfront_amount
|
|
8
|
+
config:
|
|
9
|
+
action_classes: [money.purchase, money.transfer, money.subscription, booking.create, booking.modify, agreement.accept, marketplace.offer, external_commitment.other]
|
|
10
|
+
upfront_max_minor: 10000
|
|
11
|
+
upfront_max_bps: 2500
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# verify_before_dispatch wicket: the cited mandate capsule must exist in the
|
|
2
|
+
# ledger and independently re-verify (guards/checks/verify_before_dispatch.py).
|
|
3
|
+
# This check takes no per-deployment tuning parameters -- `config` is empty
|
|
4
|
+
# on purpose, not omitted, so the digest still pins "this check, unconfigured"
|
|
5
|
+
# distinctly from "this wicket_id was never declared at all".
|
|
6
|
+
wicket_id: verify_before_dispatch/1.0.0
|
|
7
|
+
check: verify_before_dispatch
|
|
8
|
+
config:
|
|
9
|
+
method: agent_action_capsule.verify
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Wicket definitions: parsing, validation, and the definition_digest.
|
|
3
|
+
|
|
4
|
+
A "wicket" is this workspace's name for a guard
|
|
5
|
+
constraint -- caps / dedupe / verify_before_dispatch (``guards/checks/*.py``).
|
|
6
|
+
Unlike a fold, a wicket's CHECK LOGIC is Python code, not declarative data --
|
|
7
|
+
there is no YAML that fully describes "how caps computes weekly spend". What
|
|
8
|
+
*is* declarative, and varies per deployment, is the wicket's own
|
|
9
|
+
*configuration*: which check it invokes and the parameters that check runs
|
|
10
|
+
with (caps' per-class minor-unit limits, dedupe's window). A ``WicketDefinition``
|
|
11
|
+
captures exactly that slice -- ``wicket_id``, ``check``, ``config`` -- the same
|
|
12
|
+
way ``folds/definition.py``'s ``FoldDefinition`` captures a fold. Its digest
|
|
13
|
+
(SHA-256 over the JCS-canonical bytes, via the same
|
|
14
|
+
``agent_action_capsule.canonical.json_digest`` the fold definitions and the
|
|
15
|
+
capsule format itself use) identifies "this configuration of this check", not
|
|
16
|
+
the Python source file the check lives in -- a source-code change with no
|
|
17
|
+
config change is invisible to this digest, and that is intentional: this is a
|
|
18
|
+
policy-configuration digest, not a code-integrity digest.
|
|
19
|
+
|
|
20
|
+
Because the digest cannot see the check's code, a definition may also carry
|
|
21
|
+
``semantics``: a short normative statement of what passes, what fails and
|
|
22
|
+
what is not applicable. It is data inside the digested body, never a YAML
|
|
23
|
+
comment, so rewording the rule moves the digest. A definition without it
|
|
24
|
+
digests exactly as before (the key is left out of the canonical form). A
|
|
25
|
+
definition whose meaning moved under an unchanged digest is retired
|
|
26
|
+
(``retired.py``) and refused here at parse time.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from agent_action_capsule.canonical import FloatInDigestError, UnsafeIntegerError, json_digest
|
|
35
|
+
|
|
36
|
+
from .errors import (
|
|
37
|
+
FLOAT_IN_DEFINITION,
|
|
38
|
+
INVALID_WICKET_ID,
|
|
39
|
+
MALFORMED_DEFINITION,
|
|
40
|
+
RETIRED_DEFINITION,
|
|
41
|
+
UNKNOWN_CHECK,
|
|
42
|
+
UNSAFE_INTEGER_IN_DEFINITION,
|
|
43
|
+
WicketDefinitionError,
|
|
44
|
+
)
|
|
45
|
+
from .retired import RETIRED_IDS, retired_entry
|
|
46
|
+
|
|
47
|
+
__all__ = ["WICKET_ID_RE", "KNOWN_CHECKS", "WicketDefinition", "parse_definition"]
|
|
48
|
+
|
|
49
|
+
# wicket_id: same "human name + semver" shape as fold_id (folds/definition.py).
|
|
50
|
+
WICKET_ID_RE = re.compile(r"^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*/\d+\.\d+\.\d+$")
|
|
51
|
+
|
|
52
|
+
# The closed, registered set of checks a wicket may configure -- the three
|
|
53
|
+
# reference checks ``guards/engine.py`` evaluates and ``cli/constraints_cmd.
|
|
54
|
+
# py``'s ``CHECKS`` catalog lists, plus ``hold_reconcile`` (``holds/engine.
|
|
55
|
+
# py``'s tolerance check for planned-vs-executed reconciliation, capsule-emit
|
|
56
|
+
# #53), plus ``plan_containment`` (``guards/checks/plan_containment.py`` --
|
|
57
|
+
# forward-compiled-plan containment), plus the configured checks a
|
|
58
|
+
# pack can add to a decision (``destination_rail``,
|
|
59
|
+
# ``counterparty_identity_change``, ``counterparty_seen_before``,
|
|
60
|
+
# ``credential_pattern``, ``recurring_charge``, ``action_class_gate``,
|
|
61
|
+
# ``counterparty_list``, ``recipient_role``, ``refundability``,
|
|
62
|
+
# ``material_fields_changed``, ``offer_fields_changed``,
|
|
63
|
+
# ``recipient_seen_before``, ``channel_change``, ``upfront_amount``,
|
|
64
|
+
# ``task_authority``, and the seller-side ``price_floor``,
|
|
65
|
+
# ``required_disclosure``, ``promise_requires_approval``, ``promise_never``,
|
|
66
|
+
# ``offer_expiry``, ``single_commitment``, ``release_on_acceptance`` --
|
|
67
|
+
# ``guards/checks/__init__.py``'s ``RUNNABLE_CHECKS``). Closed for
|
|
68
|
+
# the same reason ``folds/definition.py``'s ``KNOWN_REDUCERS`` is: an
|
|
69
|
+
# unregistered name is a typo or a not-yet-built check, never silently
|
|
70
|
+
# accepted as data.
|
|
71
|
+
KNOWN_CHECKS = frozenset(
|
|
72
|
+
{
|
|
73
|
+
"dedupe",
|
|
74
|
+
"caps",
|
|
75
|
+
"verify_before_dispatch",
|
|
76
|
+
"hold_reconcile",
|
|
77
|
+
"plan_containment",
|
|
78
|
+
"destination_rail",
|
|
79
|
+
"counterparty_identity_change",
|
|
80
|
+
"counterparty_seen_before",
|
|
81
|
+
"credential_pattern",
|
|
82
|
+
"recurring_charge",
|
|
83
|
+
"action_class_gate",
|
|
84
|
+
"counterparty_list",
|
|
85
|
+
"recipient_role",
|
|
86
|
+
"refundability",
|
|
87
|
+
"material_fields_changed",
|
|
88
|
+
"offer_fields_changed",
|
|
89
|
+
"recipient_seen_before",
|
|
90
|
+
"channel_change",
|
|
91
|
+
"upfront_amount",
|
|
92
|
+
"task_authority",
|
|
93
|
+
"price_floor",
|
|
94
|
+
"required_disclosure",
|
|
95
|
+
"promise_requires_approval",
|
|
96
|
+
"promise_never",
|
|
97
|
+
"offer_expiry",
|
|
98
|
+
"single_commitment",
|
|
99
|
+
"release_on_acceptance",
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class WicketDefinition:
|
|
106
|
+
wicket_id: str
|
|
107
|
+
check: str
|
|
108
|
+
config: dict[str, Any] = field(default_factory=dict)
|
|
109
|
+
semantics: str | None = None
|
|
110
|
+
|
|
111
|
+
def canonical_dict(self) -> dict:
|
|
112
|
+
"""The JCS-canonicalizable form of this definition -- drives definition_digest."""
|
|
113
|
+
# Left out when not stated, so a definition without it keeps its digest.
|
|
114
|
+
semantics = {} if self.semantics is None else {"semantics": self.semantics}
|
|
115
|
+
return {"wicket_id": self.wicket_id, "check": self.check, "config": self.config, **semantics}
|
|
116
|
+
|
|
117
|
+
def definition_digest(self) -> str:
|
|
118
|
+
"""SHA-256 over the JCS bytes of the canonical definition."""
|
|
119
|
+
try:
|
|
120
|
+
return json_digest(self.canonical_dict())
|
|
121
|
+
except FloatInDigestError as exc:
|
|
122
|
+
raise WicketDefinitionError(FLOAT_IN_DEFINITION, str(exc)) from exc
|
|
123
|
+
except UnsafeIntegerError as exc:
|
|
124
|
+
raise WicketDefinitionError(UNSAFE_INTEGER_IN_DEFINITION, str(exc)) from exc
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def parse_definition(data: Any) -> WicketDefinition:
|
|
128
|
+
"""Validate a plain dict (as loaded from YAML) into a ``WicketDefinition``."""
|
|
129
|
+
if not isinstance(data, dict):
|
|
130
|
+
raise WicketDefinitionError(MALFORMED_DEFINITION, "definition must be a mapping")
|
|
131
|
+
|
|
132
|
+
wicket_id = data.get("wicket_id")
|
|
133
|
+
if not isinstance(wicket_id, str) or not WICKET_ID_RE.match(wicket_id):
|
|
134
|
+
raise WicketDefinitionError(
|
|
135
|
+
INVALID_WICKET_ID,
|
|
136
|
+
f"wicket_id {wicket_id!r} must match '<namespace>[.<namespace>...]/<major>.<minor>.<patch>' "
|
|
137
|
+
"(e.g. 'caps/1.0.0')",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
check = data.get("check")
|
|
141
|
+
if check not in KNOWN_CHECKS:
|
|
142
|
+
raise WicketDefinitionError(
|
|
143
|
+
UNKNOWN_CHECK, f"check {check!r} is not in the closed registered set {sorted(KNOWN_CHECKS)}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
config = data.get("config") if "config" in data else {}
|
|
147
|
+
if not isinstance(config, dict):
|
|
148
|
+
raise WicketDefinitionError(MALFORMED_DEFINITION, "config must be a mapping")
|
|
149
|
+
|
|
150
|
+
semantics = data.get("semantics")
|
|
151
|
+
if semantics is not None and (not isinstance(semantics, str) or not semantics.strip()):
|
|
152
|
+
raise WicketDefinitionError(MALFORMED_DEFINITION, "semantics must be a non-empty string")
|
|
153
|
+
|
|
154
|
+
definition = WicketDefinition(wicket_id=wicket_id, check=check, config=config, semantics=semantics)
|
|
155
|
+
# Only a retired id pays for a digest here, so a float or unsafe integer
|
|
156
|
+
# in any other definition still surfaces at definition_digest(), as before.
|
|
157
|
+
if wicket_id in RETIRED_IDS:
|
|
158
|
+
retired = retired_entry(wicket_id, definition.definition_digest())
|
|
159
|
+
if retired is not None:
|
|
160
|
+
raise WicketDefinitionError(
|
|
161
|
+
RETIRED_DEFINITION,
|
|
162
|
+
f"{wicket_id} at digest {retired.digest} is retired: {retired.reason}; use {retired.replaced_by}",
|
|
163
|
+
)
|
|
164
|
+
return definition
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Named-reason errors for wicket definitions (mirrors ``folds/errors.py``)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
INVALID_WICKET_ID = "invalid_wicket_id_namespace"
|
|
6
|
+
UNKNOWN_CHECK = "unknown_check"
|
|
7
|
+
MALFORMED_DEFINITION = "malformed_definition"
|
|
8
|
+
FLOAT_IN_DEFINITION = "float_in_definition"
|
|
9
|
+
UNSAFE_INTEGER_IN_DEFINITION = "unsafe_integer_in_definition"
|
|
10
|
+
RETIRED_DEFINITION = "retired_definition"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WicketDefinitionError(ValueError):
|
|
14
|
+
"""A wicket definition fails to parse or validate. Carries a stable reason code."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, reason: str, message: str) -> None:
|
|
17
|
+
self.reason = reason
|
|
18
|
+
super().__init__(f"{reason}: {message}")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""YAML front door for wicket definitions: YAML text/file -> validated WicketDefinition."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from .definition import WicketDefinition, parse_definition
|
|
10
|
+
from .errors import MALFORMED_DEFINITION, WicketDefinitionError
|
|
11
|
+
|
|
12
|
+
__all__ = ["load_definition_text", "load_definition_file"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_definition_text(text: str) -> WicketDefinition:
|
|
16
|
+
try:
|
|
17
|
+
data = yaml.safe_load(text)
|
|
18
|
+
except yaml.YAMLError as exc:
|
|
19
|
+
raise WicketDefinitionError(MALFORMED_DEFINITION, f"invalid YAML: {exc}") from exc
|
|
20
|
+
if data is None:
|
|
21
|
+
raise WicketDefinitionError(MALFORMED_DEFINITION, "empty definition document")
|
|
22
|
+
return parse_definition(data)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_definition_file(path: str | Path) -> WicketDefinition:
|
|
26
|
+
return load_definition_text(Path(path).read_text())
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Retired wicket definitions: an (id, digest) pair that must not load.
|
|
3
|
+
|
|
4
|
+
A definition is retired when its meaning moved without its digest moving, so
|
|
5
|
+
the pin no longer tells a reader which verdicts it produces. The bytes stay
|
|
6
|
+
recomputable (the digest is listed here verbatim); what changes is that the
|
|
7
|
+
loader, the catalog and a pack citing the pair all refuse it, naming the
|
|
8
|
+
replacement. A retirement is permanent: a row is never removed.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
__all__ = ["RetiredDefinition", "RETIRED", "RETIRED_IDS", "retired_entry"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class RetiredDefinition:
|
|
19
|
+
wicket_id: str
|
|
20
|
+
digest: str
|
|
21
|
+
reason: str
|
|
22
|
+
replaced_by: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
RETIRED: tuple[RetiredDefinition, ...] = (
|
|
26
|
+
RetiredDefinition(
|
|
27
|
+
wicket_id="offer_expiry/1.0.0",
|
|
28
|
+
digest="7b1072fc6997b07e7f08941a723e60d53fd3a54dbccfda6fa7391124ec2702ee",
|
|
29
|
+
reason=(
|
|
30
|
+
"its check changed meaning under this digest (an expired or unverifiable proposal went from "
|
|
31
|
+
"not applicable to fail) because the digest covered config only"
|
|
32
|
+
),
|
|
33
|
+
replaced_by="offer_expiry/1.0.1",
|
|
34
|
+
),
|
|
35
|
+
RetiredDefinition(
|
|
36
|
+
wicket_id="price_floor/1.0.0",
|
|
37
|
+
digest="a7eb755a71cf9dabaf04fbd740fc9ceaad5f0ba883795adad6fd9038796dffa9",
|
|
38
|
+
reason=(
|
|
39
|
+
"its check changed meaning under this digest (the floor moved from min_total_minor in clear on "
|
|
40
|
+
"the task-authority record to a private commercial-bounds document the record commits to) "
|
|
41
|
+
"because the digest covered config only"
|
|
42
|
+
),
|
|
43
|
+
replaced_by="price_floor/2.0.1",
|
|
44
|
+
),
|
|
45
|
+
RetiredDefinition(
|
|
46
|
+
wicket_id="price_floor/2.0.0",
|
|
47
|
+
digest="52fd588ea62ce467ad5ee59cef4347a4a0fbb0836d20ebfe783923805230bc9b",
|
|
48
|
+
reason=(
|
|
49
|
+
"its check changed meaning under this digest (a task-authority record that seals no "
|
|
50
|
+
"bounds_commitment went from in scope and not evaluable to out of scope)"
|
|
51
|
+
),
|
|
52
|
+
replaced_by="price_floor/2.0.1",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
_BY_PAIR = {(r.wicket_id, r.digest): r for r in RETIRED}
|
|
57
|
+
RETIRED_IDS = frozenset(r.wicket_id for r in RETIRED)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def retired_entry(wicket_id: str, digest: str) -> RetiredDefinition | None:
|
|
61
|
+
"""The retirement row for this exact (id, digest) pair, if there is one."""
|
|
62
|
+
return _BY_PAIR.get((wicket_id, digest))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Reservation-as-capsule semantics: ``hold.reserve``/``release``/``expire``/
|
|
3
|
+
``reconcile`` record types, atomic evaluate-and-reserve, expiry-terminal +
|
|
4
|
+
resume, and planned-vs-executed reconciliation (capsule-emit #51/#52/#53).
|
|
5
|
+
|
|
6
|
+
Ledger-side only: the distributed sequencer across processes/nodes is
|
|
7
|
+
capsule-emit's Dapr-side integration, not this package's.
|
|
8
|
+
"""
|
|
9
|
+
from .capsules import (
|
|
10
|
+
build_hold_expire_capsule,
|
|
11
|
+
build_hold_reconcile_capsule,
|
|
12
|
+
build_hold_release_capsule,
|
|
13
|
+
build_hold_reserve_capsule,
|
|
14
|
+
check_integer_amount,
|
|
15
|
+
)
|
|
16
|
+
from .engine import HoldDecision, HoldEngine, HoldStatus
|
|
17
|
+
from .errors import HoldError
|
|
18
|
+
from .policy import HoldPolicy, resolve_hold_policy
|
|
19
|
+
from .scope import ScopeKey, ScopeLocks
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"build_hold_reserve_capsule",
|
|
23
|
+
"build_hold_release_capsule",
|
|
24
|
+
"build_hold_expire_capsule",
|
|
25
|
+
"build_hold_reconcile_capsule",
|
|
26
|
+
"check_integer_amount",
|
|
27
|
+
"HoldDecision",
|
|
28
|
+
"HoldEngine",
|
|
29
|
+
"HoldStatus",
|
|
30
|
+
"HoldError",
|
|
31
|
+
"HoldPolicy",
|
|
32
|
+
"resolve_hold_policy",
|
|
33
|
+
"ScopeKey",
|
|
34
|
+
"ScopeLocks",
|
|
35
|
+
]
|