apsimo 1.3.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.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Transaction types and chain state dataclasses for ColonyChain.
|
|
2
|
+
|
|
3
|
+
Each transaction type is fully typed. The ChainState dataclass holds the
|
|
4
|
+
complete materialized state derived from replaying all committed blocks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import uuid
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Callable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Transaction type enum
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
class TxType(str, Enum):
|
|
23
|
+
COLONY_REGISTER = "colony_register"
|
|
24
|
+
COLONY_ROTATE_KEY = "colony_rotate_key"
|
|
25
|
+
COLONY_REVOKE_KEY = "colony_revoke_key"
|
|
26
|
+
COLONY_RELEASE_NAME = "colony_release_name"
|
|
27
|
+
TRUST_ATTEST = "trust_attest"
|
|
28
|
+
UNTRUST_ATTEST = "untrust_attest"
|
|
29
|
+
SENTINEL_REGISTER = "sentinel_register"
|
|
30
|
+
SENTINEL_DEREGISTER = "sentinel_deregister"
|
|
31
|
+
COLONY_SUSPEND = "colony_suspend"
|
|
32
|
+
COLONY_REINSTATE = "colony_reinstate"
|
|
33
|
+
PROTOCOL_UPGRADE = "protocol_upgrade"
|
|
34
|
+
|
|
35
|
+
# Plugin security extensions
|
|
36
|
+
PLUGIN_PUBLISH = "plugin_publish"
|
|
37
|
+
PLUGIN_ATTESTATION = "plugin_attestation"
|
|
38
|
+
PLUGIN_FLAG = "plugin_flag"
|
|
39
|
+
PLUGIN_QUARANTINE = "plugin_quarantine" # system-generated only
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Transaction
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Transaction:
|
|
48
|
+
"""A single chain transaction with Ed25519 signature."""
|
|
49
|
+
|
|
50
|
+
tx_id: str
|
|
51
|
+
type: TxType
|
|
52
|
+
from_colony_id: str
|
|
53
|
+
timestamp: str
|
|
54
|
+
nonce: int
|
|
55
|
+
payload: dict[str, Any]
|
|
56
|
+
signature: str # Ed25519 hex
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def create(
|
|
60
|
+
cls,
|
|
61
|
+
tx_type: TxType,
|
|
62
|
+
from_colony_id: str,
|
|
63
|
+
nonce: int,
|
|
64
|
+
payload: dict[str, Any],
|
|
65
|
+
sign_fn: Callable[[bytes], str],
|
|
66
|
+
) -> "Transaction":
|
|
67
|
+
"""Create and sign a transaction."""
|
|
68
|
+
tx_id = str(uuid.uuid4())
|
|
69
|
+
ts = datetime.now(timezone.utc).isoformat()
|
|
70
|
+
signing_payload = {
|
|
71
|
+
"tx_id": tx_id,
|
|
72
|
+
"type": tx_type.value,
|
|
73
|
+
"from_colony_id": from_colony_id,
|
|
74
|
+
"timestamp": ts,
|
|
75
|
+
"nonce": nonce,
|
|
76
|
+
"payload": payload,
|
|
77
|
+
}
|
|
78
|
+
canonical = json.dumps(signing_payload, sort_keys=True, separators=(",", ":"))
|
|
79
|
+
sig = sign_fn(canonical.encode())
|
|
80
|
+
return cls(
|
|
81
|
+
tx_id=tx_id,
|
|
82
|
+
type=tx_type,
|
|
83
|
+
from_colony_id=from_colony_id,
|
|
84
|
+
timestamp=ts,
|
|
85
|
+
nonce=nonce,
|
|
86
|
+
payload=payload,
|
|
87
|
+
signature=sig,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def signing_bytes(self) -> bytes:
|
|
91
|
+
"""Return the canonical bytes that were/should be signed."""
|
|
92
|
+
payload = {
|
|
93
|
+
"tx_id": self.tx_id,
|
|
94
|
+
"type": self.type.value if isinstance(self.type, TxType) else self.type,
|
|
95
|
+
"from_colony_id": self.from_colony_id,
|
|
96
|
+
"timestamp": self.timestamp,
|
|
97
|
+
"nonce": self.nonce,
|
|
98
|
+
"payload": self.payload,
|
|
99
|
+
}
|
|
100
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
|
|
101
|
+
|
|
102
|
+
def to_dict(self) -> dict[str, Any]:
|
|
103
|
+
return {
|
|
104
|
+
"tx_id": self.tx_id,
|
|
105
|
+
"type": self.type.value if isinstance(self.type, TxType) else self.type,
|
|
106
|
+
"from_colony_id": self.from_colony_id,
|
|
107
|
+
"timestamp": self.timestamp,
|
|
108
|
+
"nonce": self.nonce,
|
|
109
|
+
"payload": self.payload,
|
|
110
|
+
"signature": self.signature,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_dict(cls, d: dict[str, Any]) -> "Transaction":
|
|
115
|
+
try:
|
|
116
|
+
tx_type: Any = TxType(d["type"])
|
|
117
|
+
except (ValueError, KeyError):
|
|
118
|
+
tx_type = d.get("type", "unknown")
|
|
119
|
+
return cls(
|
|
120
|
+
tx_id=d["tx_id"],
|
|
121
|
+
type=tx_type,
|
|
122
|
+
from_colony_id=d["from_colony_id"],
|
|
123
|
+
timestamp=d["timestamp"],
|
|
124
|
+
nonce=d["nonce"],
|
|
125
|
+
payload=d["payload"],
|
|
126
|
+
signature=d.get("signature", ""),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Chain state dataclasses
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class KeyHistoryEntry:
|
|
136
|
+
public_key_hex: str
|
|
137
|
+
active_from_height: int
|
|
138
|
+
rotated_at_height: int | None = None # None = still active
|
|
139
|
+
revoked_at_height: int | None = None # None = not revoked
|
|
140
|
+
revocation_tx: str | None = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class ColonyRecord:
|
|
145
|
+
colony_id: str
|
|
146
|
+
name: str
|
|
147
|
+
active_public_key_hex: str
|
|
148
|
+
endpoint: str
|
|
149
|
+
description: str
|
|
150
|
+
capabilities: list[str]
|
|
151
|
+
protocol_version: str
|
|
152
|
+
registered_at_height: int
|
|
153
|
+
registered_at_tx: str
|
|
154
|
+
is_genesis_admin: bool
|
|
155
|
+
status: str # "active" | "suspended" | "deregistered"
|
|
156
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class TrustEdge:
|
|
161
|
+
from_colony_id: str
|
|
162
|
+
to_colony_id: str
|
|
163
|
+
trust_level: int # 0-4
|
|
164
|
+
attested_at_height: int
|
|
165
|
+
attested_at_tx: str
|
|
166
|
+
valid_until: str | None = None # ISO-8601 or None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class UntrustEvent:
|
|
171
|
+
from_colony_id: str
|
|
172
|
+
target_colony_id: str
|
|
173
|
+
at_height: int
|
|
174
|
+
tx_id: str
|
|
175
|
+
report_abuse: bool
|
|
176
|
+
timestamp: str
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class SentinelRecord:
|
|
181
|
+
sentinel_id: str
|
|
182
|
+
colony_id: str
|
|
183
|
+
host: str
|
|
184
|
+
port: int
|
|
185
|
+
public_key_hex: str
|
|
186
|
+
registered_at_height: int
|
|
187
|
+
status: str # "active" | "demoted" | "deregistered"
|
|
188
|
+
uptime_percent: float
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class ProtocolConfig:
|
|
193
|
+
block_interval_secs: int = 30
|
|
194
|
+
untrust_threshold: int = 3
|
|
195
|
+
untrust_window_days: int = 30
|
|
196
|
+
uptime_requirement_percent: float = 99.0
|
|
197
|
+
min_sentinels: int = 1
|
|
198
|
+
min_protocol_version: str = "1.0.0"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass
|
|
202
|
+
class ProtocolUpgradeRecord:
|
|
203
|
+
upgrade_id: str
|
|
204
|
+
title: str
|
|
205
|
+
proposed_at_height: int
|
|
206
|
+
activation_height: int
|
|
207
|
+
ratified: bool
|
|
208
|
+
changes: dict[str, Any]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class ChainState:
|
|
213
|
+
"""Complete materialized state of the chain at a given height."""
|
|
214
|
+
|
|
215
|
+
height: int = 0
|
|
216
|
+
last_block_hash: str = "0" * 64
|
|
217
|
+
colony_registry: dict[str, ColonyRecord] = field(default_factory=dict)
|
|
218
|
+
name_registry: dict[str, str] = field(default_factory=dict) # name -> colony_id
|
|
219
|
+
key_history: dict[str, list[KeyHistoryEntry]] = field(default_factory=dict)
|
|
220
|
+
trust_graph: dict[tuple[str, str], TrustEdge] = field(default_factory=dict)
|
|
221
|
+
untrust_counters: dict[str, list[UntrustEvent]] = field(default_factory=dict)
|
|
222
|
+
sentinel_roster: dict[str, SentinelRecord] = field(default_factory=dict)
|
|
223
|
+
suspended_colonies: set[str] = field(default_factory=set)
|
|
224
|
+
protocol_config: ProtocolConfig = field(default_factory=ProtocolConfig)
|
|
225
|
+
upgrade_history: list[ProtocolUpgradeRecord] = field(default_factory=list)
|
|
226
|
+
genesis_admin_id: str = ""
|
|
227
|
+
network_id: str = ""
|
|
228
|
+
# Plugin security registry
|
|
229
|
+
plugin_registry: dict[str, Any] = field(default_factory=dict) # plugin_hash -> PluginChainRecord
|
|
230
|
+
# Track publish counts per colony for rate limiting
|
|
231
|
+
plugin_publish_counts: dict[str, list[str]] = field(default_factory=dict) # colony_id -> list of timestamps
|
|
232
|
+
|
|
233
|
+
def colony_id_for_name(self, name: str) -> str | None:
|
|
234
|
+
return self.name_registry.get(name.lower())
|
|
235
|
+
|
|
236
|
+
def active_key_for_colony(self, colony_id: str) -> str | None:
|
|
237
|
+
record = self.colony_registry.get(colony_id)
|
|
238
|
+
if record:
|
|
239
|
+
return record.active_public_key_hex
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
def is_active(self, colony_id: str) -> bool:
|
|
243
|
+
record = self.colony_registry.get(colony_id)
|
|
244
|
+
return record is not None and record.status == "active"
|
|
245
|
+
|
|
246
|
+
def is_suspended(self, colony_id: str) -> bool:
|
|
247
|
+
return colony_id in self.suspended_colonies
|
|
248
|
+
|
|
249
|
+
def active_sentinels(self) -> list[SentinelRecord]:
|
|
250
|
+
return [s for s in self.sentinel_roster.values() if s.status == "active"]
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""Chain validation: block integrity, transaction signatures, state transitions.
|
|
2
|
+
|
|
3
|
+
All validation is deterministic — same state + same input = same result.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
17
|
+
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
|
18
|
+
from cryptography.exceptions import InvalidSignature
|
|
19
|
+
|
|
20
|
+
from .block import Block, build_merkle_root
|
|
21
|
+
from .transactions import (
|
|
22
|
+
ChainState,
|
|
23
|
+
TxType,
|
|
24
|
+
Transaction,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9\-]{0,62}[a-z0-9]?$")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ValidationResult:
|
|
34
|
+
ok: bool
|
|
35
|
+
reason: str = ""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def valid(self) -> bool:
|
|
39
|
+
"""Alias for ok — used by nonce validator tests and external callers."""
|
|
40
|
+
return self.ok
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def success(cls) -> "ValidationResult":
|
|
44
|
+
return cls(ok=True)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def fail(cls, reason: str) -> "ValidationResult":
|
|
48
|
+
return cls(ok=False, reason=reason)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _verify_ed25519(public_key_hex: str, data: bytes, signature_hex: str) -> bool:
|
|
52
|
+
"""Verify an Ed25519 signature. Returns False on any error."""
|
|
53
|
+
try:
|
|
54
|
+
pub_bytes = bytes.fromhex(public_key_hex)
|
|
55
|
+
sig_bytes = bytes.fromhex(signature_hex)
|
|
56
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
57
|
+
pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
|
|
58
|
+
pub_key.verify(sig_bytes, data)
|
|
59
|
+
return True
|
|
60
|
+
except (InvalidSignature, ValueError, Exception):
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TransactionValidator:
|
|
65
|
+
"""Validates transactions against current chain state."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, state: ChainState = None, chain_store=None) -> None:
|
|
68
|
+
self.state = state
|
|
69
|
+
self._chain_store = chain_store
|
|
70
|
+
|
|
71
|
+
def validate(self, tx: Transaction) -> ValidationResult:
|
|
72
|
+
"""Full validation: signature, nonce, type-specific preconditions."""
|
|
73
|
+
# Basic structure
|
|
74
|
+
if not tx.tx_id:
|
|
75
|
+
return ValidationResult.fail("missing tx_id")
|
|
76
|
+
if not tx.from_colony_id:
|
|
77
|
+
return ValidationResult.fail("missing from_colony_id")
|
|
78
|
+
|
|
79
|
+
# System transactions (auto-generated) skip signature check
|
|
80
|
+
is_system = tx.from_colony_id == "system"
|
|
81
|
+
|
|
82
|
+
if not is_system:
|
|
83
|
+
nonce_result = self._check_nonce(tx)
|
|
84
|
+
if not nonce_result.ok:
|
|
85
|
+
return nonce_result
|
|
86
|
+
|
|
87
|
+
sig_result = self._verify_signature(tx)
|
|
88
|
+
if not sig_result.ok:
|
|
89
|
+
return sig_result
|
|
90
|
+
|
|
91
|
+
# Type-specific validation
|
|
92
|
+
return self._validate_by_type(tx)
|
|
93
|
+
|
|
94
|
+
def _verify_signature(self, tx: Transaction) -> ValidationResult:
|
|
95
|
+
"""Verify Ed25519 signature against the colony's active key."""
|
|
96
|
+
colony = self.state.colony_registry.get(tx.from_colony_id)
|
|
97
|
+
|
|
98
|
+
# For colony_register, use the public key from the payload itself
|
|
99
|
+
if tx.type == TxType.COLONY_REGISTER:
|
|
100
|
+
pub_key_hex = tx.payload.get("public_key_hex", "")
|
|
101
|
+
elif colony is not None:
|
|
102
|
+
pub_key_hex = colony.active_public_key_hex
|
|
103
|
+
else:
|
|
104
|
+
return ValidationResult.fail(
|
|
105
|
+
f"colony {tx.from_colony_id!r} not registered"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if not pub_key_hex:
|
|
109
|
+
return ValidationResult.fail("no public key for colony")
|
|
110
|
+
|
|
111
|
+
data = tx.signing_bytes()
|
|
112
|
+
if not _verify_ed25519(pub_key_hex, data, tx.signature):
|
|
113
|
+
return ValidationResult.fail("invalid signature")
|
|
114
|
+
return ValidationResult.success()
|
|
115
|
+
|
|
116
|
+
def _check_nonce(self, tx: Transaction) -> ValidationResult:
|
|
117
|
+
"""Verify nonce is strictly greater than colony's last accepted nonce.
|
|
118
|
+
|
|
119
|
+
The authoritative nonce check is enforced by ChainManager.submit_transaction()
|
|
120
|
+
via ChainStore.get_nonce() + DB UNIQUE(colony_id, nonce). This method is a
|
|
121
|
+
defence-in-depth layer for callers that use TransactionValidator directly
|
|
122
|
+
(e.g. federation sync, offline verification tools).
|
|
123
|
+
|
|
124
|
+
Fails closed when no ChainStore is injected: a missing store means replay
|
|
125
|
+
protection cannot be enforced, and validating without it would let
|
|
126
|
+
offline/federation callers accept replayed transactions.
|
|
127
|
+
"""
|
|
128
|
+
if tx.type == TxType.COLONY_REGISTER:
|
|
129
|
+
# Genesis registrations have no prior nonce to compare against.
|
|
130
|
+
return ValidationResult.success()
|
|
131
|
+
|
|
132
|
+
if self._chain_store is None:
|
|
133
|
+
return ValidationResult.fail(
|
|
134
|
+
"TransactionValidator: chain_store not injected; cannot verify "
|
|
135
|
+
"nonce replay protection. Inject chain_store= when constructing "
|
|
136
|
+
"the validator."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
last_nonce = self._chain_store.get_nonce(tx.from_colony_id)
|
|
140
|
+
if tx.nonce <= last_nonce:
|
|
141
|
+
return ValidationResult.fail(
|
|
142
|
+
f"Nonce replay: tx.nonce={tx.nonce} <= last_accepted={last_nonce}"
|
|
143
|
+
)
|
|
144
|
+
return ValidationResult.success()
|
|
145
|
+
|
|
146
|
+
def _validate_by_type(self, tx: Transaction) -> ValidationResult:
|
|
147
|
+
dispatch = {
|
|
148
|
+
TxType.COLONY_REGISTER: self._validate_colony_register,
|
|
149
|
+
TxType.COLONY_ROTATE_KEY: self._validate_colony_rotate_key,
|
|
150
|
+
TxType.COLONY_REVOKE_KEY: self._validate_colony_revoke_key,
|
|
151
|
+
TxType.COLONY_RELEASE_NAME: self._validate_colony_release_name,
|
|
152
|
+
TxType.TRUST_ATTEST: self._validate_trust_attest,
|
|
153
|
+
TxType.UNTRUST_ATTEST: self._validate_untrust_attest,
|
|
154
|
+
TxType.SENTINEL_REGISTER: self._validate_sentinel_register,
|
|
155
|
+
TxType.SENTINEL_DEREGISTER: self._validate_sentinel_deregister,
|
|
156
|
+
TxType.COLONY_SUSPEND: self._validate_colony_suspend,
|
|
157
|
+
TxType.COLONY_REINSTATE: self._validate_colony_reinstate,
|
|
158
|
+
TxType.PROTOCOL_UPGRADE: self._validate_protocol_upgrade,
|
|
159
|
+
}
|
|
160
|
+
handler = dispatch.get(tx.type)
|
|
161
|
+
if handler is None:
|
|
162
|
+
return ValidationResult.fail(f"unknown transaction type: {tx.type}")
|
|
163
|
+
return handler(tx)
|
|
164
|
+
|
|
165
|
+
def _validate_colony_register(self, tx: Transaction) -> ValidationResult:
|
|
166
|
+
p = tx.payload
|
|
167
|
+
name = p.get("name", "").lower()
|
|
168
|
+
colony_id = p.get("colony_id", "")
|
|
169
|
+
public_key_hex = p.get("public_key_hex", "")
|
|
170
|
+
|
|
171
|
+
if not name:
|
|
172
|
+
return ValidationResult.fail("colony_register: missing name")
|
|
173
|
+
if not _NAME_PATTERN.match(name):
|
|
174
|
+
return ValidationResult.fail(
|
|
175
|
+
f"colony_register: name {name!r} does not match required pattern"
|
|
176
|
+
)
|
|
177
|
+
if name in self.state.name_registry:
|
|
178
|
+
return ValidationResult.fail(
|
|
179
|
+
f"colony_register: name {name!r} already registered"
|
|
180
|
+
)
|
|
181
|
+
if colony_id in self.state.colony_registry:
|
|
182
|
+
return ValidationResult.fail(
|
|
183
|
+
f"colony_register: colony_id {colony_id!r} already registered"
|
|
184
|
+
)
|
|
185
|
+
# Verify colony_id = sha256(pubkey)
|
|
186
|
+
try:
|
|
187
|
+
expected_id = hashlib.sha256(bytes.fromhex(public_key_hex)).hexdigest()
|
|
188
|
+
except ValueError:
|
|
189
|
+
return ValidationResult.fail("colony_register: invalid public_key_hex")
|
|
190
|
+
if expected_id != colony_id:
|
|
191
|
+
return ValidationResult.fail(
|
|
192
|
+
"colony_register: colony_id does not match sha256(public_key)"
|
|
193
|
+
)
|
|
194
|
+
# Genesis admin can only appear once (in genesis block)
|
|
195
|
+
if p.get("genesis_admin", False) and self.state.genesis_admin_id:
|
|
196
|
+
return ValidationResult.fail(
|
|
197
|
+
"colony_register: genesis_admin=true only allowed in genesis block"
|
|
198
|
+
)
|
|
199
|
+
return ValidationResult.success()
|
|
200
|
+
|
|
201
|
+
def _validate_colony_rotate_key(self, tx: Transaction) -> ValidationResult:
|
|
202
|
+
colony = self.state.colony_registry.get(tx.from_colony_id)
|
|
203
|
+
if colony is None:
|
|
204
|
+
return ValidationResult.fail("colony_rotate_key: colony not registered")
|
|
205
|
+
if colony.status == "suspended":
|
|
206
|
+
return ValidationResult.fail("colony_rotate_key: colony is suspended")
|
|
207
|
+
p = tx.payload
|
|
208
|
+
new_key = p.get("new_public_key_hex", "")
|
|
209
|
+
if not new_key:
|
|
210
|
+
return ValidationResult.fail("colony_rotate_key: missing new_public_key_hex")
|
|
211
|
+
# New key must not be registered to another colony
|
|
212
|
+
for cid, record in self.state.colony_registry.items():
|
|
213
|
+
if cid != tx.from_colony_id and record.active_public_key_hex == new_key:
|
|
214
|
+
return ValidationResult.fail(
|
|
215
|
+
"colony_rotate_key: new key already registered to another colony"
|
|
216
|
+
)
|
|
217
|
+
return ValidationResult.success()
|
|
218
|
+
|
|
219
|
+
def _validate_colony_revoke_key(self, tx: Transaction) -> ValidationResult:
|
|
220
|
+
p = tx.payload
|
|
221
|
+
revoked_key = p.get("revoked_key_hex", "")
|
|
222
|
+
if not revoked_key:
|
|
223
|
+
return ValidationResult.fail("colony_revoke_key: missing revoked_key_hex")
|
|
224
|
+
# Key must be in history of the target colony
|
|
225
|
+
# from_colony_id is either the colony itself, or "system"/"genesis"
|
|
226
|
+
target_id = tx.from_colony_id
|
|
227
|
+
history = self.state.key_history.get(target_id, [])
|
|
228
|
+
if not any(e.public_key_hex == revoked_key for e in history):
|
|
229
|
+
return ValidationResult.fail(
|
|
230
|
+
"colony_revoke_key: key not found in colony key history"
|
|
231
|
+
)
|
|
232
|
+
return ValidationResult.success()
|
|
233
|
+
|
|
234
|
+
def _validate_colony_release_name(self, tx: Transaction) -> ValidationResult:
|
|
235
|
+
p = tx.payload
|
|
236
|
+
name = p.get("name", "").lower()
|
|
237
|
+
owner_id = self.state.name_registry.get(name)
|
|
238
|
+
if owner_id is None:
|
|
239
|
+
return ValidationResult.fail(
|
|
240
|
+
f"colony_release_name: name {name!r} not registered"
|
|
241
|
+
)
|
|
242
|
+
is_genesis = tx.from_colony_id == self.state.genesis_admin_id
|
|
243
|
+
if owner_id != tx.from_colony_id and not is_genesis:
|
|
244
|
+
return ValidationResult.fail(
|
|
245
|
+
"colony_release_name: not authorized (not owner or genesis admin)"
|
|
246
|
+
)
|
|
247
|
+
return ValidationResult.success()
|
|
248
|
+
|
|
249
|
+
def _validate_trust_attest(self, tx: Transaction) -> ValidationResult:
|
|
250
|
+
p = tx.payload
|
|
251
|
+
target_id = p.get("target_colony_id", "")
|
|
252
|
+
if not self.state.is_active(tx.from_colony_id):
|
|
253
|
+
return ValidationResult.fail("trust_attest: attesting colony not active")
|
|
254
|
+
if not self.state.is_active(target_id):
|
|
255
|
+
return ValidationResult.fail("trust_attest: target colony not active")
|
|
256
|
+
trust_level = p.get("trust_level", -1)
|
|
257
|
+
if trust_level not in range(5):
|
|
258
|
+
return ValidationResult.fail("trust_attest: trust_level must be 0-4")
|
|
259
|
+
return ValidationResult.success()
|
|
260
|
+
|
|
261
|
+
def _validate_untrust_attest(self, tx: Transaction) -> ValidationResult:
|
|
262
|
+
p = tx.payload
|
|
263
|
+
target_id = p.get("target_colony_id", "")
|
|
264
|
+
if not self.state.colony_registry.get(tx.from_colony_id):
|
|
265
|
+
return ValidationResult.fail("untrust_attest: attesting colony not registered")
|
|
266
|
+
if not self.state.colony_registry.get(target_id):
|
|
267
|
+
return ValidationResult.fail("untrust_attest: target colony not registered")
|
|
268
|
+
return ValidationResult.success()
|
|
269
|
+
|
|
270
|
+
def _validate_sentinel_register(self, tx: Transaction) -> ValidationResult:
|
|
271
|
+
colony = self.state.colony_registry.get(tx.from_colony_id)
|
|
272
|
+
if colony is None:
|
|
273
|
+
return ValidationResult.fail("sentinel_register: colony not registered")
|
|
274
|
+
if colony.status == "suspended":
|
|
275
|
+
return ValidationResult.fail("sentinel_register: colony is suspended")
|
|
276
|
+
p = tx.payload
|
|
277
|
+
sentinel_id = p.get("sentinel_id", "")
|
|
278
|
+
if not sentinel_id:
|
|
279
|
+
return ValidationResult.fail("sentinel_register: missing sentinel_id")
|
|
280
|
+
if sentinel_id in self.state.sentinel_roster:
|
|
281
|
+
return ValidationResult.fail(
|
|
282
|
+
f"sentinel_register: sentinel_id {sentinel_id!r} already registered"
|
|
283
|
+
)
|
|
284
|
+
return ValidationResult.success()
|
|
285
|
+
|
|
286
|
+
def _validate_sentinel_deregister(self, tx: Transaction) -> ValidationResult:
|
|
287
|
+
p = tx.payload
|
|
288
|
+
sentinel_id = p.get("sentinel_id", "")
|
|
289
|
+
record = self.state.sentinel_roster.get(sentinel_id)
|
|
290
|
+
if record is None:
|
|
291
|
+
return ValidationResult.fail(
|
|
292
|
+
f"sentinel_deregister: sentinel {sentinel_id!r} not in roster"
|
|
293
|
+
)
|
|
294
|
+
is_genesis = tx.from_colony_id == self.state.genesis_admin_id
|
|
295
|
+
is_system = tx.from_colony_id == "system"
|
|
296
|
+
if record.colony_id != tx.from_colony_id and not is_genesis and not is_system:
|
|
297
|
+
return ValidationResult.fail(
|
|
298
|
+
"sentinel_deregister: not authorized (not owner or genesis admin)"
|
|
299
|
+
)
|
|
300
|
+
return ValidationResult.success()
|
|
301
|
+
|
|
302
|
+
def _validate_colony_suspend(self, tx: Transaction) -> ValidationResult:
|
|
303
|
+
p = tx.payload
|
|
304
|
+
target_id = p.get("target_colony_id", "")
|
|
305
|
+
if not target_id:
|
|
306
|
+
return ValidationResult.fail("colony_suspend: missing target_colony_id")
|
|
307
|
+
if target_id not in self.state.colony_registry:
|
|
308
|
+
return ValidationResult.fail("colony_suspend: target colony not registered")
|
|
309
|
+
if self.state.is_suspended(target_id):
|
|
310
|
+
return ValidationResult.fail("colony_suspend: colony already suspended")
|
|
311
|
+
is_genesis = tx.from_colony_id == self.state.genesis_admin_id
|
|
312
|
+
is_system = tx.from_colony_id == "system"
|
|
313
|
+
if not is_genesis and not is_system:
|
|
314
|
+
return ValidationResult.fail(
|
|
315
|
+
"colony_suspend: only genesis admin or system may suspend"
|
|
316
|
+
)
|
|
317
|
+
return ValidationResult.success()
|
|
318
|
+
|
|
319
|
+
def _validate_colony_reinstate(self, tx: Transaction) -> ValidationResult:
|
|
320
|
+
p = tx.payload
|
|
321
|
+
target_id = p.get("target_colony_id", "")
|
|
322
|
+
if not target_id:
|
|
323
|
+
return ValidationResult.fail("colony_reinstate: missing target_colony_id")
|
|
324
|
+
if not self.state.is_suspended(target_id):
|
|
325
|
+
return ValidationResult.fail("colony_reinstate: colony is not suspended")
|
|
326
|
+
is_genesis = tx.from_colony_id == self.state.genesis_admin_id
|
|
327
|
+
is_system = tx.from_colony_id == "system"
|
|
328
|
+
if not is_genesis and not is_system:
|
|
329
|
+
return ValidationResult.fail(
|
|
330
|
+
"colony_reinstate: only genesis admin or system may reinstate"
|
|
331
|
+
)
|
|
332
|
+
return ValidationResult.success()
|
|
333
|
+
|
|
334
|
+
def _validate_protocol_upgrade(self, tx: Transaction) -> ValidationResult:
|
|
335
|
+
is_genesis = tx.from_colony_id == self.state.genesis_admin_id
|
|
336
|
+
if not is_genesis:
|
|
337
|
+
return ValidationResult.fail(
|
|
338
|
+
"protocol_upgrade: only genesis admin may propose upgrades"
|
|
339
|
+
)
|
|
340
|
+
p = tx.payload
|
|
341
|
+
if not p.get("upgrade_id"):
|
|
342
|
+
return ValidationResult.fail("protocol_upgrade: missing upgrade_id")
|
|
343
|
+
if not p.get("title"):
|
|
344
|
+
return ValidationResult.fail("protocol_upgrade: missing title")
|
|
345
|
+
activation_height = p.get("activation_height", 0)
|
|
346
|
+
if activation_height <= self.state.height:
|
|
347
|
+
return ValidationResult.fail(
|
|
348
|
+
"protocol_upgrade: activation_height must be in the future"
|
|
349
|
+
)
|
|
350
|
+
return ValidationResult.success()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class BlockValidator:
|
|
354
|
+
"""Validates block structure, hash chain integrity, and merkle root."""
|
|
355
|
+
|
|
356
|
+
def validate_block(
|
|
357
|
+
self, block: Block, previous_block: Block | None
|
|
358
|
+
) -> ValidationResult:
|
|
359
|
+
# Hash chain
|
|
360
|
+
if previous_block is None:
|
|
361
|
+
if block.index != 0:
|
|
362
|
+
return ValidationResult.fail("first block must have index 0")
|
|
363
|
+
if block.previous_hash != "0" * 64:
|
|
364
|
+
return ValidationResult.fail(
|
|
365
|
+
"genesis block previous_hash must be 64 zeros"
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
if block.index != previous_block.index + 1:
|
|
369
|
+
return ValidationResult.fail(
|
|
370
|
+
f"expected index {previous_block.index + 1}, got {block.index}"
|
|
371
|
+
)
|
|
372
|
+
if block.previous_hash != previous_block.block_hash:
|
|
373
|
+
return ValidationResult.fail("previous_hash mismatch")
|
|
374
|
+
|
|
375
|
+
# Merkle root
|
|
376
|
+
tx_ids = [tx.tx_id for tx in block.transactions]
|
|
377
|
+
expected_merkle = build_merkle_root(tx_ids)
|
|
378
|
+
if block.merkle_root != expected_merkle:
|
|
379
|
+
return ValidationResult.fail(
|
|
380
|
+
f"merkle_root mismatch: {block.merkle_root!r} != {expected_merkle!r}"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return ValidationResult.success()
|
|
384
|
+
|
|
385
|
+
def validate_sentinel_signature(
|
|
386
|
+
self,
|
|
387
|
+
block: Block,
|
|
388
|
+
sentinel_public_key_hex: str,
|
|
389
|
+
) -> ValidationResult:
|
|
390
|
+
"""Verify the block's Ed25519 signature from the producing Sentinel."""
|
|
391
|
+
block_dict = block.to_dict()
|
|
392
|
+
block_dict.pop("signature", None)
|
|
393
|
+
canonical = json.dumps(block_dict, sort_keys=True, separators=(",", ":"))
|
|
394
|
+
data = canonical.encode()
|
|
395
|
+
if not _verify_ed25519(sentinel_public_key_hex, data, block.signature):
|
|
396
|
+
return ValidationResult.fail("block signature invalid")
|
|
397
|
+
return ValidationResult.success()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Channel registration subsystem."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Channel manifest -- what a channel declares about itself on registration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ChannelManifest(BaseModel):
|
|
9
|
+
"""Describes a channel's capabilities, identity policies, and delivery config."""
|
|
10
|
+
|
|
11
|
+
channel_key: str = Field(..., min_length=1, max_length=128)
|
|
12
|
+
display_name: str = Field(..., min_length=1, max_length=256)
|
|
13
|
+
gateway_family: str = Field(..., min_length=1, max_length=64)
|
|
14
|
+
|
|
15
|
+
supports_media: bool = False
|
|
16
|
+
supports_reactions: bool = False
|
|
17
|
+
supports_voice: bool = False
|
|
18
|
+
supports_rich_text: bool = False
|
|
19
|
+
max_message_length: int | None = None
|
|
20
|
+
|
|
21
|
+
phone_identity_unification: bool = False
|
|
22
|
+
session_isolation: bool = False
|
|
23
|
+
provides_channel_id: bool = False
|
|
24
|
+
|
|
25
|
+
delivery_webhook: str | None = None
|
|
26
|
+
delivery_protocol: str = "hermes"
|
|
27
|
+
delivery_aliases: list[str] = Field(default_factory=list)
|
|
28
|
+
home_chat_id: str | None = None
|
|
29
|
+
|
|
30
|
+
platform_hint: str = ""
|
|
31
|
+
pii_safe: bool = True
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Channel registration store: any channel can self-register with Colony.
|
|
2
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
3
|
+
channel_key TEXT PRIMARY KEY,
|
|
4
|
+
display_name TEXT NOT NULL,
|
|
5
|
+
gateway_family TEXT NOT NULL,
|
|
6
|
+
manifest_json TEXT NOT NULL,
|
|
7
|
+
channel_token TEXT NOT NULL,
|
|
8
|
+
registered_at TEXT NOT NULL,
|
|
9
|
+
last_seen_at TEXT,
|
|
10
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
11
|
+
CHECK(status IN ('active', 'inactive', 'revoked'))
|
|
12
|
+
);
|