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,676 @@
|
|
|
1
|
+
"""Deterministic chain state machine.
|
|
2
|
+
|
|
3
|
+
Applies blocks and transactions to produce the canonical ChainState.
|
|
4
|
+
Full replay from genesis is always possible; checkpoints speed up recovery.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .block import Block
|
|
15
|
+
from .storage import ChainStore
|
|
16
|
+
from .transactions import (
|
|
17
|
+
ChainState,
|
|
18
|
+
ColonyRecord,
|
|
19
|
+
KeyHistoryEntry,
|
|
20
|
+
ProtocolConfig,
|
|
21
|
+
ProtocolUpgradeRecord,
|
|
22
|
+
SentinelRecord,
|
|
23
|
+
Transaction,
|
|
24
|
+
TrustEdge,
|
|
25
|
+
TxType,
|
|
26
|
+
UntrustEvent,
|
|
27
|
+
)
|
|
28
|
+
from .plugin_transactions import (
|
|
29
|
+
FindingSeverity,
|
|
30
|
+
PluginChainRecord,
|
|
31
|
+
PluginChainStatus,
|
|
32
|
+
ScanResult,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
_CHECKPOINT_INTERVAL = 100
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _iso_to_ts(iso: str) -> float:
|
|
41
|
+
"""Convert ISO-8601 string to UTC unix timestamp; returns 0.0 on failure."""
|
|
42
|
+
if not iso:
|
|
43
|
+
return 0.0
|
|
44
|
+
try:
|
|
45
|
+
dt = datetime.fromisoformat(iso)
|
|
46
|
+
if dt.tzinfo is None:
|
|
47
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
48
|
+
return dt.timestamp()
|
|
49
|
+
except (ValueError, TypeError):
|
|
50
|
+
return 0.0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ChainStateMachine:
|
|
54
|
+
"""Deterministic state computation from the block sequence."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, store: ChainStore) -> None:
|
|
57
|
+
self.store = store
|
|
58
|
+
|
|
59
|
+
def apply_block(self, state: ChainState, block: Block) -> ChainState:
|
|
60
|
+
"""Apply all transactions in block to state. Returns new state."""
|
|
61
|
+
new_state = copy.deepcopy(state)
|
|
62
|
+
new_state.height = block.index
|
|
63
|
+
new_state.last_block_hash = block.block_hash
|
|
64
|
+
|
|
65
|
+
for tx in block.transactions:
|
|
66
|
+
new_state = self._apply_tx(new_state, tx, block.index)
|
|
67
|
+
|
|
68
|
+
# Check for pending protocol upgrades that activate at this height
|
|
69
|
+
new_state = self._apply_pending_upgrades(new_state, block.index)
|
|
70
|
+
|
|
71
|
+
return new_state
|
|
72
|
+
|
|
73
|
+
def compute_state_at(self, height: int) -> ChainState:
|
|
74
|
+
"""Compute state at height using checkpoints to minimize replay."""
|
|
75
|
+
checkpoint = self.store.get_latest_checkpoint(height)
|
|
76
|
+
if checkpoint is not None:
|
|
77
|
+
state, start_height = checkpoint
|
|
78
|
+
start_index = start_height + 1
|
|
79
|
+
else:
|
|
80
|
+
state = ChainState()
|
|
81
|
+
start_index = 0
|
|
82
|
+
|
|
83
|
+
for idx in range(start_index, height + 1):
|
|
84
|
+
block = self.store.get_block(idx)
|
|
85
|
+
if block is None:
|
|
86
|
+
break
|
|
87
|
+
state = self.apply_block(state, block)
|
|
88
|
+
|
|
89
|
+
return state
|
|
90
|
+
|
|
91
|
+
def get_current_state(self) -> ChainState:
|
|
92
|
+
"""Return state at the latest committed height."""
|
|
93
|
+
height = self.store.get_height()
|
|
94
|
+
if height < 0:
|
|
95
|
+
return ChainState()
|
|
96
|
+
return self.compute_state_at(height)
|
|
97
|
+
|
|
98
|
+
def rebuild_and_checkpoint(self) -> ChainState:
|
|
99
|
+
"""Full replay from genesis, saving checkpoints every 100 blocks."""
|
|
100
|
+
state = ChainState()
|
|
101
|
+
height = self.store.get_height()
|
|
102
|
+
for idx in range(height + 1):
|
|
103
|
+
block = self.store.get_block(idx)
|
|
104
|
+
if block is None:
|
|
105
|
+
break
|
|
106
|
+
state = self.apply_block(state, block)
|
|
107
|
+
if idx > 0 and idx % _CHECKPOINT_INTERVAL == 0:
|
|
108
|
+
self.store.save_checkpoint(state, idx)
|
|
109
|
+
return state
|
|
110
|
+
|
|
111
|
+
# ── Transaction dispatch ────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
def _apply_tx(
|
|
114
|
+
self, state: ChainState, tx: Transaction, block_index: int
|
|
115
|
+
) -> ChainState:
|
|
116
|
+
dispatch = {
|
|
117
|
+
TxType.COLONY_REGISTER: self._apply_colony_register,
|
|
118
|
+
TxType.COLONY_ROTATE_KEY: self._apply_colony_rotate_key,
|
|
119
|
+
TxType.COLONY_REVOKE_KEY: self._apply_colony_revoke_key,
|
|
120
|
+
TxType.COLONY_RELEASE_NAME: self._apply_colony_release_name,
|
|
121
|
+
TxType.TRUST_ATTEST: self._apply_trust_attest,
|
|
122
|
+
TxType.UNTRUST_ATTEST: self._apply_untrust_attest,
|
|
123
|
+
TxType.SENTINEL_REGISTER: self._apply_sentinel_register,
|
|
124
|
+
TxType.SENTINEL_DEREGISTER: self._apply_sentinel_deregister,
|
|
125
|
+
TxType.COLONY_SUSPEND: self._apply_colony_suspend,
|
|
126
|
+
TxType.COLONY_REINSTATE: self._apply_colony_reinstate,
|
|
127
|
+
TxType.PROTOCOL_UPGRADE: self._apply_protocol_upgrade,
|
|
128
|
+
TxType.PLUGIN_PUBLISH: self._apply_plugin_publish,
|
|
129
|
+
TxType.PLUGIN_ATTESTATION: self._apply_plugin_attestation,
|
|
130
|
+
TxType.PLUGIN_FLAG: self._apply_plugin_flag,
|
|
131
|
+
TxType.PLUGIN_QUARANTINE: self._apply_plugin_quarantine,
|
|
132
|
+
}
|
|
133
|
+
handler = dispatch.get(tx.type)
|
|
134
|
+
if handler is None:
|
|
135
|
+
logger.warning("Unknown tx type %s — skipping", tx.type)
|
|
136
|
+
return state
|
|
137
|
+
# Handler exceptions must propagate: silently returning the unmodified
|
|
138
|
+
# state on a handler bug would mean different replicas could disagree
|
|
139
|
+
# on which transactions actually applied, breaking consensus
|
|
140
|
+
# invariants. If a handler raises, fail the whole apply_block so the
|
|
141
|
+
# operator sees the corruption and can stop the node.
|
|
142
|
+
return handler(state, tx, block_index)
|
|
143
|
+
|
|
144
|
+
def _apply_colony_register(
|
|
145
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
146
|
+
) -> ChainState:
|
|
147
|
+
p = tx.payload
|
|
148
|
+
colony_id = p["colony_id"]
|
|
149
|
+
name = p["name"].lower()
|
|
150
|
+
is_genesis_admin = p.get("genesis_admin", False)
|
|
151
|
+
|
|
152
|
+
record = ColonyRecord(
|
|
153
|
+
colony_id=colony_id,
|
|
154
|
+
name=name,
|
|
155
|
+
active_public_key_hex=p["public_key_hex"],
|
|
156
|
+
endpoint=p.get("endpoint", ""),
|
|
157
|
+
description=p.get("description", ""),
|
|
158
|
+
capabilities=p.get("capabilities", []),
|
|
159
|
+
protocol_version=p.get("protocol_version", "1.0.0"),
|
|
160
|
+
registered_at_height=height,
|
|
161
|
+
registered_at_tx=tx.tx_id,
|
|
162
|
+
is_genesis_admin=is_genesis_admin,
|
|
163
|
+
status="active",
|
|
164
|
+
metadata=p.get("metadata", {}),
|
|
165
|
+
)
|
|
166
|
+
state.colony_registry[colony_id] = record
|
|
167
|
+
state.name_registry[name] = colony_id
|
|
168
|
+
state.key_history[colony_id] = [
|
|
169
|
+
KeyHistoryEntry(
|
|
170
|
+
public_key_hex=p["public_key_hex"],
|
|
171
|
+
active_from_height=height,
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
if is_genesis_admin and not state.genesis_admin_id:
|
|
175
|
+
state.genesis_admin_id = colony_id
|
|
176
|
+
return state
|
|
177
|
+
|
|
178
|
+
def _apply_colony_rotate_key(
|
|
179
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
180
|
+
) -> ChainState:
|
|
181
|
+
p = tx.payload
|
|
182
|
+
colony_id = tx.from_colony_id
|
|
183
|
+
new_key = p["new_public_key_hex"]
|
|
184
|
+
|
|
185
|
+
record = state.colony_registry.get(colony_id)
|
|
186
|
+
if record is None:
|
|
187
|
+
return state
|
|
188
|
+
|
|
189
|
+
# Mark old key as rotated
|
|
190
|
+
history = state.key_history.get(colony_id, [])
|
|
191
|
+
for entry in history:
|
|
192
|
+
if entry.rotated_at_height is None and entry.revoked_at_height is None:
|
|
193
|
+
entry.rotated_at_height = height
|
|
194
|
+
|
|
195
|
+
# Add new key
|
|
196
|
+
history.append(KeyHistoryEntry(public_key_hex=new_key, active_from_height=height))
|
|
197
|
+
state.key_history[colony_id] = history
|
|
198
|
+
record.active_public_key_hex = new_key
|
|
199
|
+
return state
|
|
200
|
+
|
|
201
|
+
def _apply_colony_revoke_key(
|
|
202
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
203
|
+
) -> ChainState:
|
|
204
|
+
p = tx.payload
|
|
205
|
+
revoked_key = p["revoked_key_hex"]
|
|
206
|
+
cascade = p.get("cascade", False)
|
|
207
|
+
colony_id = tx.from_colony_id
|
|
208
|
+
|
|
209
|
+
history = state.key_history.get(colony_id, [])
|
|
210
|
+
for entry in history:
|
|
211
|
+
if entry.public_key_hex == revoked_key:
|
|
212
|
+
entry.revoked_at_height = height
|
|
213
|
+
entry.revocation_tx = tx.tx_id
|
|
214
|
+
|
|
215
|
+
if cascade:
|
|
216
|
+
# Downgrade trust attestations signed by revoked key to DISCOVERY
|
|
217
|
+
# (We don't track which key signed each attestation, so we downgrade
|
|
218
|
+
# all attestations FROM this colony)
|
|
219
|
+
for edge_key, edge in list(state.trust_graph.items()):
|
|
220
|
+
if edge.from_colony_id == colony_id:
|
|
221
|
+
edge.trust_level = 0 # downgrade to DISCOVERY
|
|
222
|
+
|
|
223
|
+
return state
|
|
224
|
+
|
|
225
|
+
def _apply_colony_release_name(
|
|
226
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
227
|
+
) -> ChainState:
|
|
228
|
+
p = tx.payload
|
|
229
|
+
name = p["name"].lower()
|
|
230
|
+
state.name_registry.pop(name, None)
|
|
231
|
+
return state
|
|
232
|
+
|
|
233
|
+
def _apply_trust_attest(
|
|
234
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
235
|
+
) -> ChainState:
|
|
236
|
+
p = tx.payload
|
|
237
|
+
target_id = p["target_colony_id"]
|
|
238
|
+
edge_key = (tx.from_colony_id, target_id)
|
|
239
|
+
state.trust_graph[edge_key] = TrustEdge(
|
|
240
|
+
from_colony_id=tx.from_colony_id,
|
|
241
|
+
to_colony_id=target_id,
|
|
242
|
+
trust_level=p["trust_level"],
|
|
243
|
+
attested_at_height=height,
|
|
244
|
+
attested_at_tx=tx.tx_id,
|
|
245
|
+
valid_until=p.get("valid_until"),
|
|
246
|
+
)
|
|
247
|
+
return state
|
|
248
|
+
|
|
249
|
+
def _apply_untrust_attest(
|
|
250
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
251
|
+
) -> ChainState:
|
|
252
|
+
p = tx.payload
|
|
253
|
+
target_id = p["target_colony_id"]
|
|
254
|
+
edge_key = (tx.from_colony_id, target_id)
|
|
255
|
+
|
|
256
|
+
# Remove trust edge
|
|
257
|
+
state.trust_graph.pop(edge_key, None)
|
|
258
|
+
|
|
259
|
+
# Track untrust event
|
|
260
|
+
if target_id not in state.untrust_counters:
|
|
261
|
+
state.untrust_counters[target_id] = []
|
|
262
|
+
|
|
263
|
+
# Check for duplicate from same colony
|
|
264
|
+
existing = [
|
|
265
|
+
e for e in state.untrust_counters[target_id]
|
|
266
|
+
if e.from_colony_id == tx.from_colony_id
|
|
267
|
+
]
|
|
268
|
+
if not existing:
|
|
269
|
+
event = UntrustEvent(
|
|
270
|
+
from_colony_id=tx.from_colony_id,
|
|
271
|
+
target_colony_id=target_id,
|
|
272
|
+
at_height=height,
|
|
273
|
+
tx_id=tx.tx_id,
|
|
274
|
+
report_abuse=p.get("report_abuse", False),
|
|
275
|
+
timestamp=tx.timestamp,
|
|
276
|
+
)
|
|
277
|
+
state.untrust_counters[target_id].append(event)
|
|
278
|
+
|
|
279
|
+
# Auto-suspend if threshold reached
|
|
280
|
+
threshold = state.protocol_config.untrust_threshold
|
|
281
|
+
abuse_count = sum(
|
|
282
|
+
1 for e in state.untrust_counters[target_id] if e.report_abuse
|
|
283
|
+
)
|
|
284
|
+
total_count = len(state.untrust_counters[target_id])
|
|
285
|
+
|
|
286
|
+
if total_count >= threshold and not state.is_suspended(target_id):
|
|
287
|
+
# Emit implicit suspend
|
|
288
|
+
record = state.colony_registry.get(target_id)
|
|
289
|
+
if record and record.status == "active":
|
|
290
|
+
record.status = "suspended"
|
|
291
|
+
state.suspended_colonies.add(target_id)
|
|
292
|
+
logger.info(
|
|
293
|
+
"Auto-suspended colony %s: untrust threshold %d reached",
|
|
294
|
+
target_id,
|
|
295
|
+
threshold,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return state
|
|
299
|
+
|
|
300
|
+
def _apply_sentinel_register(
|
|
301
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
302
|
+
) -> ChainState:
|
|
303
|
+
p = tx.payload
|
|
304
|
+
sentinel_id = p["sentinel_id"]
|
|
305
|
+
state.sentinel_roster[sentinel_id] = SentinelRecord(
|
|
306
|
+
sentinel_id=sentinel_id,
|
|
307
|
+
colony_id=p.get("colony_id", tx.from_colony_id),
|
|
308
|
+
host=p.get("host", ""),
|
|
309
|
+
port=p.get("port", 7744),
|
|
310
|
+
public_key_hex=p.get("public_key_hex", ""),
|
|
311
|
+
registered_at_height=height,
|
|
312
|
+
status="active",
|
|
313
|
+
uptime_percent=p.get("uptime_proof", {}).get("uptime_percent", 100.0),
|
|
314
|
+
)
|
|
315
|
+
return state
|
|
316
|
+
|
|
317
|
+
def _apply_sentinel_deregister(
|
|
318
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
319
|
+
) -> ChainState:
|
|
320
|
+
p = tx.payload
|
|
321
|
+
sentinel_id = p["sentinel_id"]
|
|
322
|
+
record = state.sentinel_roster.get(sentinel_id)
|
|
323
|
+
if record:
|
|
324
|
+
record.status = "deregistered"
|
|
325
|
+
return state
|
|
326
|
+
|
|
327
|
+
def _apply_colony_suspend(
|
|
328
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
329
|
+
) -> ChainState:
|
|
330
|
+
p = tx.payload
|
|
331
|
+
target_id = p["target_colony_id"]
|
|
332
|
+
record = state.colony_registry.get(target_id)
|
|
333
|
+
if record:
|
|
334
|
+
record.status = "suspended"
|
|
335
|
+
state.suspended_colonies.add(target_id)
|
|
336
|
+
return state
|
|
337
|
+
|
|
338
|
+
def _apply_colony_reinstate(
|
|
339
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
340
|
+
) -> ChainState:
|
|
341
|
+
p = tx.payload
|
|
342
|
+
target_id = p["target_colony_id"]
|
|
343
|
+
record = state.colony_registry.get(target_id)
|
|
344
|
+
if record:
|
|
345
|
+
record.status = "active"
|
|
346
|
+
state.suspended_colonies.discard(target_id)
|
|
347
|
+
# Reset untrust counter
|
|
348
|
+
state.untrust_counters.pop(target_id, None)
|
|
349
|
+
return state
|
|
350
|
+
|
|
351
|
+
def _apply_protocol_upgrade(
|
|
352
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
353
|
+
) -> ChainState:
|
|
354
|
+
p = tx.payload
|
|
355
|
+
title = p.get("title", "")
|
|
356
|
+
changes = p.get("changes", {})
|
|
357
|
+
|
|
358
|
+
# ── Special admin actions embedded as protocol_upgrade titles ────────
|
|
359
|
+
|
|
360
|
+
# Genesis admin transfer: title == "transfer_genesis_admin"
|
|
361
|
+
if title == "transfer_genesis_admin":
|
|
362
|
+
new_admin_id = changes.get("new_genesis_admin_id", "")
|
|
363
|
+
if new_admin_id and tx.from_colony_id == state.genesis_admin_id:
|
|
364
|
+
old_admin_id = state.genesis_admin_id
|
|
365
|
+
state.genesis_admin_id = new_admin_id
|
|
366
|
+
# Update ColonyRecord flags
|
|
367
|
+
if old_admin_id in state.colony_registry:
|
|
368
|
+
state.colony_registry[old_admin_id].is_genesis_admin = False
|
|
369
|
+
if new_admin_id in state.colony_registry:
|
|
370
|
+
state.colony_registry[new_admin_id].is_genesis_admin = True
|
|
371
|
+
logger.info(
|
|
372
|
+
"Genesis admin transferred: %s → %s",
|
|
373
|
+
old_admin_id[:16], new_admin_id[:16],
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
logger.warning(
|
|
377
|
+
"transfer_genesis_admin rejected: sender %s is not genesis admin %s",
|
|
378
|
+
tx.from_colony_id[:16], state.genesis_admin_id[:16] if state.genesis_admin_id else "(none)",
|
|
379
|
+
)
|
|
380
|
+
# Record but do not queue for future activation
|
|
381
|
+
state.upgrade_history.append(ProtocolUpgradeRecord(
|
|
382
|
+
upgrade_id=p.get("upgrade_id", ""),
|
|
383
|
+
title=title,
|
|
384
|
+
proposed_at_height=height,
|
|
385
|
+
activation_height=height,
|
|
386
|
+
ratified=True,
|
|
387
|
+
changes=changes,
|
|
388
|
+
))
|
|
389
|
+
return state
|
|
390
|
+
|
|
391
|
+
# Unanimous Sentinel strip of Genesis admin:
|
|
392
|
+
# title == "strip_genesis_admin" with co-signatures from ALL active Sentinels
|
|
393
|
+
if title == "strip_genesis_admin":
|
|
394
|
+
active_sentinels = state.active_sentinels()
|
|
395
|
+
n_sentinels = len(active_sentinels)
|
|
396
|
+
votes = p.get("votes", [])
|
|
397
|
+
sentinel_ids = {s.sentinel_id for s in active_sentinels}
|
|
398
|
+
yes_sentinel_ids = {
|
|
399
|
+
v.get("sentinel_id")
|
|
400
|
+
for v in votes
|
|
401
|
+
if v.get("vote") == "yes" and v.get("sentinel_id") in sentinel_ids
|
|
402
|
+
}
|
|
403
|
+
# Unanimous: ALL active Sentinels must have voted yes
|
|
404
|
+
unanimous = (n_sentinels > 0 and yes_sentinel_ids == sentinel_ids)
|
|
405
|
+
if unanimous:
|
|
406
|
+
old_admin = state.genesis_admin_id
|
|
407
|
+
if old_admin and old_admin in state.colony_registry:
|
|
408
|
+
state.colony_registry[old_admin].is_genesis_admin = False
|
|
409
|
+
state.genesis_admin_id = ""
|
|
410
|
+
logger.warning(
|
|
411
|
+
"Genesis admin STRIPPED by unanimous Sentinel vote (was %s). "
|
|
412
|
+
"Network enters no-admin mode.",
|
|
413
|
+
old_admin[:16] if old_admin else "(none)",
|
|
414
|
+
)
|
|
415
|
+
else:
|
|
416
|
+
logger.warning(
|
|
417
|
+
"strip_genesis_admin rejected: only %d/%d Sentinels voted yes",
|
|
418
|
+
len(yes_sentinel_ids), n_sentinels,
|
|
419
|
+
)
|
|
420
|
+
state.upgrade_history.append(ProtocolUpgradeRecord(
|
|
421
|
+
upgrade_id=p.get("upgrade_id", ""),
|
|
422
|
+
title=title,
|
|
423
|
+
proposed_at_height=height,
|
|
424
|
+
activation_height=height,
|
|
425
|
+
ratified=unanimous,
|
|
426
|
+
changes=changes,
|
|
427
|
+
))
|
|
428
|
+
return state
|
|
429
|
+
|
|
430
|
+
# ── Standard protocol upgrade ────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
votes = p.get("votes", [])
|
|
433
|
+
active_sentinels = state.active_sentinels()
|
|
434
|
+
n_sentinels = len(active_sentinels)
|
|
435
|
+
required = max(1, (n_sentinels * 2 // 3) + 1)
|
|
436
|
+
yes_votes = [v for v in votes if v.get("vote") == "yes"]
|
|
437
|
+
ratified = len(yes_votes) >= required or n_sentinels == 0
|
|
438
|
+
|
|
439
|
+
upgrade = ProtocolUpgradeRecord(
|
|
440
|
+
upgrade_id=p["upgrade_id"],
|
|
441
|
+
title=title,
|
|
442
|
+
proposed_at_height=height,
|
|
443
|
+
activation_height=p.get("activation_height", height + 1),
|
|
444
|
+
ratified=ratified,
|
|
445
|
+
changes=changes,
|
|
446
|
+
)
|
|
447
|
+
state.upgrade_history.append(upgrade)
|
|
448
|
+
return state
|
|
449
|
+
|
|
450
|
+
# ── Plugin transaction handlers ─────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
# Default rate limits (may be overridden by protocol_upgrade)
|
|
453
|
+
_MAX_PUBLISHES_PER_DAY = 20
|
|
454
|
+
_MAX_PUBLISHES_PER_HOUR = 5
|
|
455
|
+
_AUTO_QUARANTINE_THRESHOLD = 3
|
|
456
|
+
_AUTO_QUARANTINE_WINDOW_HOURS = 72
|
|
457
|
+
_SAFE_CONSENSUS_THRESHOLD = 0.8
|
|
458
|
+
_FLAG_SUSPENSION_THRESHOLD = 3
|
|
459
|
+
|
|
460
|
+
def _apply_plugin_publish(
|
|
461
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
462
|
+
) -> ChainState:
|
|
463
|
+
p = tx.payload
|
|
464
|
+
plugin_hash = p.get("plugin_hash", "")
|
|
465
|
+
name = p.get("name", "")
|
|
466
|
+
version = p.get("version", "")
|
|
467
|
+
publisher_id = tx.from_colony_id
|
|
468
|
+
|
|
469
|
+
# Reject if publisher is suspended
|
|
470
|
+
if publisher_id in state.suspended_colonies:
|
|
471
|
+
logger.warning("plugin_publish rejected: publisher %s is suspended", publisher_id)
|
|
472
|
+
return state
|
|
473
|
+
|
|
474
|
+
# Check idempotency: same (name, version, hash) → no-op
|
|
475
|
+
for existing in state.plugin_registry.values():
|
|
476
|
+
if existing.name == name and existing.version == version:
|
|
477
|
+
if existing.plugin_hash == plugin_hash:
|
|
478
|
+
# Idempotent re-publish — no-op
|
|
479
|
+
return state
|
|
480
|
+
else:
|
|
481
|
+
# Different hash for same (name, version) → reject
|
|
482
|
+
logger.warning(
|
|
483
|
+
"plugin_publish rejected: version %s@%s already exists with different hash",
|
|
484
|
+
name, version,
|
|
485
|
+
)
|
|
486
|
+
return state
|
|
487
|
+
|
|
488
|
+
# Rate limiting: track publish timestamps per colony
|
|
489
|
+
now_iso = datetime.now(timezone.utc).isoformat()
|
|
490
|
+
timestamps = state.plugin_publish_counts.get(publisher_id, [])
|
|
491
|
+
# Keep only timestamps in the last 24 hours
|
|
492
|
+
cutoff_24h = datetime.now(timezone.utc).timestamp() - 86400
|
|
493
|
+
cutoff_1h = datetime.now(timezone.utc).timestamp() - 3600
|
|
494
|
+
recent_24h = [t for t in timestamps if _iso_to_ts(t) > cutoff_24h]
|
|
495
|
+
recent_1h = [t for t in timestamps if _iso_to_ts(t) > cutoff_1h]
|
|
496
|
+
|
|
497
|
+
if len(recent_24h) >= self._MAX_PUBLISHES_PER_DAY:
|
|
498
|
+
logger.warning("plugin_publish rejected: %s exceeds daily rate limit", publisher_id)
|
|
499
|
+
return state
|
|
500
|
+
if len(recent_1h) >= self._MAX_PUBLISHES_PER_HOUR:
|
|
501
|
+
logger.warning("plugin_publish rejected: %s exceeds hourly rate limit", publisher_id)
|
|
502
|
+
return state
|
|
503
|
+
|
|
504
|
+
# Add to registry
|
|
505
|
+
record = PluginChainRecord(
|
|
506
|
+
plugin_hash=plugin_hash,
|
|
507
|
+
name=name,
|
|
508
|
+
version=version,
|
|
509
|
+
publisher_id=publisher_id,
|
|
510
|
+
published_at_height=height,
|
|
511
|
+
status=PluginChainStatus.PENDING_SCAN,
|
|
512
|
+
source_url=p.get("source_url"),
|
|
513
|
+
capabilities=p.get("capabilities", []),
|
|
514
|
+
)
|
|
515
|
+
state.plugin_registry[plugin_hash] = record
|
|
516
|
+
|
|
517
|
+
# Update rate limit counters
|
|
518
|
+
recent_24h.append(now_iso)
|
|
519
|
+
state.plugin_publish_counts[publisher_id] = recent_24h
|
|
520
|
+
|
|
521
|
+
logger.info("plugin_publish: registered %s@%s hash=%s", name, version, plugin_hash[:16])
|
|
522
|
+
return state
|
|
523
|
+
|
|
524
|
+
def _apply_plugin_attestation(
|
|
525
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
526
|
+
) -> ChainState:
|
|
527
|
+
p = tx.payload
|
|
528
|
+
plugin_hash = p.get("plugin_hash", "")
|
|
529
|
+
scanner_id = tx.from_colony_id
|
|
530
|
+
scan_result = p.get("scan_result", ScanResult.ERROR.value)
|
|
531
|
+
|
|
532
|
+
record = state.plugin_registry.get(plugin_hash)
|
|
533
|
+
if record is None:
|
|
534
|
+
logger.warning("plugin_attestation for unknown hash %s", plugin_hash[:16])
|
|
535
|
+
return state
|
|
536
|
+
|
|
537
|
+
# Deduplicate: one attestation per (plugin_hash, scanner_colony_id) — supersede old
|
|
538
|
+
record.attestations = [
|
|
539
|
+
a for a in record.attestations
|
|
540
|
+
if a.get("scanner_colony_id") != scanner_id
|
|
541
|
+
]
|
|
542
|
+
record.attestations.append({
|
|
543
|
+
"scanner_colony_id": scanner_id,
|
|
544
|
+
"scan_result": scan_result,
|
|
545
|
+
"scanned_at": p.get("scanned_at", ""),
|
|
546
|
+
"stages_completed": p.get("stages_completed", []),
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
# Recompute consensus using sentinel weights
|
|
550
|
+
sentinel_ids = {s.colony_id for s in state.sentinel_roster.values() if s.status == "active"}
|
|
551
|
+
scanner_weights = {sid: 2.0 for sid in sentinel_ids}
|
|
552
|
+
# Trusted non-sentinels (trust_level >= 3) get weight 1.0, others 0.25 (default)
|
|
553
|
+
record.recompute_consensus(scanner_weights)
|
|
554
|
+
|
|
555
|
+
# State transitions
|
|
556
|
+
if scan_result == ScanResult.FLAGGED.value:
|
|
557
|
+
# Any FLAGGED from trusted scanner (sentinel) → FLAGGED status
|
|
558
|
+
if scanner_id in sentinel_ids:
|
|
559
|
+
record.status = PluginChainStatus.FLAGGED
|
|
560
|
+
logger.info("plugin %s flagged by sentinel %s", plugin_hash[:16], scanner_id)
|
|
561
|
+
elif record.status == PluginChainStatus.PENDING_SCAN:
|
|
562
|
+
record.status = PluginChainStatus.FLAGGED
|
|
563
|
+
elif scan_result == ScanResult.SAFE.value:
|
|
564
|
+
# Check consensus threshold for SAFE transition
|
|
565
|
+
if (
|
|
566
|
+
record.status == PluginChainStatus.PENDING_SCAN
|
|
567
|
+
and record.safe_consensus >= self._SAFE_CONSENSUS_THRESHOLD
|
|
568
|
+
and record.has_sentinel_safe(frozenset(sentinel_ids))
|
|
569
|
+
):
|
|
570
|
+
record.status = PluginChainStatus.SAFE
|
|
571
|
+
logger.info(
|
|
572
|
+
"plugin %s reached SAFE consensus (%.2f)",
|
|
573
|
+
plugin_hash[:16], record.safe_consensus,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return state
|
|
577
|
+
|
|
578
|
+
def _apply_plugin_flag(
|
|
579
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
580
|
+
) -> ChainState:
|
|
581
|
+
p = tx.payload
|
|
582
|
+
plugin_hash = p.get("plugin_hash", "")
|
|
583
|
+
reporter_id = tx.from_colony_id
|
|
584
|
+
severity = p.get("severity", FindingSeverity.INFO.value)
|
|
585
|
+
|
|
586
|
+
record = state.plugin_registry.get(plugin_hash)
|
|
587
|
+
if record is None:
|
|
588
|
+
logger.warning("plugin_flag for unknown hash %s", plugin_hash[:16])
|
|
589
|
+
return state
|
|
590
|
+
|
|
591
|
+
# Deduplicate: same (reporter, vuln_description) → idempotent
|
|
592
|
+
vuln_desc = p.get("vulnerability_description", "")
|
|
593
|
+
for existing_flag in record.flags:
|
|
594
|
+
if (
|
|
595
|
+
existing_flag.get("reporter_colony_id") == reporter_id
|
|
596
|
+
and existing_flag.get("vulnerability_description") == vuln_desc
|
|
597
|
+
):
|
|
598
|
+
return state # duplicate
|
|
599
|
+
|
|
600
|
+
record.flags.append({
|
|
601
|
+
"reporter_colony_id": reporter_id,
|
|
602
|
+
"severity": severity,
|
|
603
|
+
"vulnerability_description": vuln_desc,
|
|
604
|
+
"flagged_at": datetime.now(timezone.utc).isoformat(),
|
|
605
|
+
"tx_id": tx.tx_id,
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
# Count trusted flags for auto-quarantine
|
|
609
|
+
sentinel_ids = {s.colony_id for s in state.sentinel_roster.values() if s.status == "active"}
|
|
610
|
+
high_severity = {FindingSeverity.HIGH.value, FindingSeverity.CRITICAL.value}
|
|
611
|
+
mid_and_above = {FindingSeverity.MEDIUM.value, FindingSeverity.HIGH.value, FindingSeverity.CRITICAL.value}
|
|
612
|
+
|
|
613
|
+
# Single Sentinel flag at medium+ → immediate quarantine
|
|
614
|
+
if reporter_id in sentinel_ids and severity in mid_and_above:
|
|
615
|
+
record.status = PluginChainStatus.QUARANTINED
|
|
616
|
+
logger.info("plugin %s quarantined by sentinel flag from %s", plugin_hash[:16], reporter_id)
|
|
617
|
+
return state
|
|
618
|
+
|
|
619
|
+
# Count distinct trusted reporters with high/critical severity in last 72h
|
|
620
|
+
window_ts = datetime.now(timezone.utc).timestamp() - self._AUTO_QUARANTINE_WINDOW_HOURS * 3600
|
|
621
|
+
trusted_high_reporters = set()
|
|
622
|
+
for flag in record.flags:
|
|
623
|
+
if (
|
|
624
|
+
flag["severity"] in high_severity
|
|
625
|
+
and _iso_to_ts(flag.get("flagged_at", "")) > window_ts
|
|
626
|
+
):
|
|
627
|
+
trusted_high_reporters.add(flag["reporter_colony_id"])
|
|
628
|
+
|
|
629
|
+
record.flag_count_trusted = len(trusted_high_reporters)
|
|
630
|
+
|
|
631
|
+
if len(trusted_high_reporters) >= self._AUTO_QUARANTINE_THRESHOLD:
|
|
632
|
+
record.status = PluginChainStatus.QUARANTINED
|
|
633
|
+
logger.info(
|
|
634
|
+
"plugin %s auto-quarantined: %d trusted flags",
|
|
635
|
+
plugin_hash[:16], len(trusted_high_reporters),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
return state
|
|
639
|
+
|
|
640
|
+
def _apply_plugin_quarantine(
|
|
641
|
+
self, state: ChainState, tx: Transaction, height: int
|
|
642
|
+
) -> ChainState:
|
|
643
|
+
"""Apply a system-generated plugin_quarantine transaction."""
|
|
644
|
+
p = tx.payload
|
|
645
|
+
plugin_hash = p.get("plugin_hash", "")
|
|
646
|
+
|
|
647
|
+
record = state.plugin_registry.get(plugin_hash)
|
|
648
|
+
if record is None:
|
|
649
|
+
logger.warning("plugin_quarantine for unknown hash %s", plugin_hash[:16])
|
|
650
|
+
return state
|
|
651
|
+
|
|
652
|
+
record.status = PluginChainStatus.QUARANTINED
|
|
653
|
+
logger.info(
|
|
654
|
+
"plugin %s quarantined via system tx (reason: %s)",
|
|
655
|
+
plugin_hash[:16], p.get("quarantine_reason", "unknown"),
|
|
656
|
+
)
|
|
657
|
+
return state
|
|
658
|
+
|
|
659
|
+
def _apply_pending_upgrades(self, state: ChainState, height: int) -> ChainState:
|
|
660
|
+
"""Apply any ratified protocol upgrades whose activation height has arrived."""
|
|
661
|
+
for upgrade in state.upgrade_history:
|
|
662
|
+
if upgrade.ratified and upgrade.activation_height == height:
|
|
663
|
+
changes = upgrade.changes
|
|
664
|
+
pc = state.protocol_config
|
|
665
|
+
if "block_interval_secs" in changes:
|
|
666
|
+
pc.block_interval_secs = changes["block_interval_secs"]
|
|
667
|
+
if "untrust_threshold" in changes:
|
|
668
|
+
pc.untrust_threshold = changes["untrust_threshold"]
|
|
669
|
+
if "untrust_window_days" in changes:
|
|
670
|
+
pc.untrust_window_days = changes["untrust_window_days"]
|
|
671
|
+
if "uptime_requirement_percent" in changes:
|
|
672
|
+
pc.uptime_requirement_percent = changes["uptime_requirement_percent"]
|
|
673
|
+
if "min_protocol_version" in changes:
|
|
674
|
+
pc.min_protocol_version = changes["min_protocol_version"]
|
|
675
|
+
logger.info("Applied protocol upgrade %s at height %d", upgrade.upgrade_id, height)
|
|
676
|
+
return state
|