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
apsimo/chain/identity.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""Colony Identity — colony_id management and Genesis trust anchor.
|
|
2
|
+
|
|
3
|
+
Colony identity is decoupled from the signing keypair:
|
|
4
|
+
colony_id — permanent UUID, generated once at init, never changes
|
|
5
|
+
keypair — Ed25519, can rotate without changing colony_id
|
|
6
|
+
public_key — derived from keypair, published openly for verification
|
|
7
|
+
|
|
8
|
+
Genesis:
|
|
9
|
+
The first Colony — the Genesis Colony — is the trust anchor for the network.
|
|
10
|
+
The Genesis manifest is self-signed: it contains a signature created with
|
|
11
|
+
the Genesis Colony's private key that verifies against a HARDCODED public key in this
|
|
12
|
+
source file. This makes Genesis unforgeable even locally:
|
|
13
|
+
|
|
14
|
+
- Editing genesis.json → signature fails verification → is_genesis: false
|
|
15
|
+
- Editing the hardcoded key in source → creates a different trust anchor,
|
|
16
|
+
other Colonies running the official release won't recognize it
|
|
17
|
+
- Only the Genesis Colony can produce a valid signature (requires private key)
|
|
18
|
+
|
|
19
|
+
This is the same model as CA root certificates in browsers, or the
|
|
20
|
+
Bitcoin genesis block hash — open, verifiable, unfakeable.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import uuid
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Optional
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Genesis trust anchor — HARDCODED public key (the Genesis Colony)
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# This is the root of trust. The private key never leaves the Genesis owner's machine.
|
|
38
|
+
# This public key is used to VERIFY the Genesis manifest signature.
|
|
39
|
+
# It cannot be used to CREATE signatures — that requires the private key.
|
|
40
|
+
#
|
|
41
|
+
# Even if someone edits their local genesis.json, the signature won't
|
|
42
|
+
# verify against this key. Even if they edit this source file, it only
|
|
43
|
+
# affects their own Colony — other Colonies run the official release.
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
GENESIS_TRUST_PUBLIC_KEY = "341065fd6cd26ca501c5786ed1517eedc448fec60aeaea8d047d07bf1a9cc351"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _verify_ed25519_signature(public_key_hex: str, message: bytes, signature_hex: str) -> bool:
|
|
50
|
+
"""Verify an Ed25519 signature against a public key."""
|
|
51
|
+
try:
|
|
52
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
53
|
+
pub_bytes = bytes.fromhex(public_key_hex)
|
|
54
|
+
pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
|
|
55
|
+
sig_bytes = bytes.fromhex(signature_hex)
|
|
56
|
+
pub_key.verify(sig_bytes, message)
|
|
57
|
+
return True
|
|
58
|
+
except Exception:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _sign_with_key(private_key_pem: str | bytes, message: bytes, passphrase: Optional[bytes] = None) -> str:
|
|
63
|
+
"""Sign a message with an Ed25519 private key, returning hex signature."""
|
|
64
|
+
from cryptography.hazmat.primitives import serialization
|
|
65
|
+
key = serialization.load_pem_private_key(
|
|
66
|
+
private_key_pem.encode() if isinstance(private_key_pem, str) else private_key_pem,
|
|
67
|
+
password=passphrase,
|
|
68
|
+
)
|
|
69
|
+
signature = key.sign(message)
|
|
70
|
+
return signature.hex()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Genesis manifest
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
_GENESIS_MANIFEST: Optional[dict] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def set_genesis_manifest(manifest: dict) -> None:
|
|
81
|
+
"""Set the Genesis manifest (called after verification)."""
|
|
82
|
+
global _GENESIS_MANIFEST
|
|
83
|
+
_GENESIS_MANIFEST = manifest
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_genesis_manifest() -> Optional[dict]:
|
|
87
|
+
"""Get the Genesis manifest, if configured and verified."""
|
|
88
|
+
return _GENESIS_MANIFEST
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
GENESIS_MANIFEST_FILE = "genesis.json"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bundled_genesis_manifest_path() -> Optional[Path]:
|
|
95
|
+
"""Locate the Genesis manifest shipped with the source code.
|
|
96
|
+
|
|
97
|
+
The manifest lives inside the package (``apsimo/genesis.json``)
|
|
98
|
+
so pip installs and source checkouts both carry it. The sidecar root is
|
|
99
|
+
still checked for checkouts that keep the older layout.
|
|
100
|
+
"""
|
|
101
|
+
package_dir = Path(__file__).resolve().parent.parent
|
|
102
|
+
for candidate in (package_dir / GENESIS_MANIFEST_FILE,
|
|
103
|
+
package_dir.parent / GENESIS_MANIFEST_FILE):
|
|
104
|
+
if candidate.is_file():
|
|
105
|
+
return candidate
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def resolve_genesis_manifest_path(state_dir: str | Path) -> Optional[Path]:
|
|
110
|
+
"""Pick the manifest to load: the state directory wins, else the bundled copy.
|
|
111
|
+
|
|
112
|
+
A colony that claimed Genesis has its own signed manifest in its state
|
|
113
|
+
directory. Every other colony verifies the network trust anchor from the
|
|
114
|
+
bundled manifest; without it the trust anchor stays unverified and the
|
|
115
|
+
colony never reaches the REGULAR tier.
|
|
116
|
+
"""
|
|
117
|
+
local = Path(state_dir) / GENESIS_MANIFEST_FILE
|
|
118
|
+
if local.is_file():
|
|
119
|
+
return local
|
|
120
|
+
return bundled_genesis_manifest_path()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def is_genesis(colony_id: str, public_key_hex: str) -> bool:
|
|
124
|
+
"""Check if a colony_id + public_key combination matches the verified Genesis manifest.
|
|
125
|
+
|
|
126
|
+
The manifest must have been loaded with a valid signature against the
|
|
127
|
+
hardcoded GENESIS_TRUST_PUBLIC_KEY. Simply matching fields is not enough —
|
|
128
|
+
the signature proves it was created by the Genesis Colony's private key.
|
|
129
|
+
"""
|
|
130
|
+
if _GENESIS_MANIFEST is None:
|
|
131
|
+
return False
|
|
132
|
+
return (
|
|
133
|
+
colony_id == _GENESIS_MANIFEST.get("colony_id")
|
|
134
|
+
and public_key_hex == _GENESIS_MANIFEST.get("public_key_ed25519")
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _manifest_signing_payload(manifest: dict) -> bytes:
|
|
139
|
+
"""Create the canonical signing payload from a manifest dict.
|
|
140
|
+
|
|
141
|
+
This is the JSON-serialized content excluding the signature field itself,
|
|
142
|
+
with sorted keys for deterministic ordering.
|
|
143
|
+
"""
|
|
144
|
+
payload = {k: v for k, v in manifest.items() if k != "signature"}
|
|
145
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def verify_genesis_manifest(manifest: dict) -> bool:
|
|
149
|
+
"""Verify a Genesis manifest's signature against the hardcoded trust key.
|
|
150
|
+
|
|
151
|
+
Returns True if the signature is valid, False otherwise.
|
|
152
|
+
A manifest without a signature is automatically invalid.
|
|
153
|
+
"""
|
|
154
|
+
signature = manifest.get("signature")
|
|
155
|
+
if not signature:
|
|
156
|
+
logger.warning("Genesis manifest has no signature — cannot verify")
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
payload = _manifest_signing_payload(manifest)
|
|
160
|
+
return _verify_ed25519_signature(GENESIS_TRUST_PUBLIC_KEY, payload, signature)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def load_genesis_manifest(path: str | Path) -> Optional[dict]:
|
|
164
|
+
"""Load and verify a Genesis manifest from a JSON file.
|
|
165
|
+
|
|
166
|
+
Only accepts the manifest if its signature verifies against the
|
|
167
|
+
hardcoded GENESIS_TRUST_PUBLIC_KEY. An unsigned or tampered manifest
|
|
168
|
+
is rejected.
|
|
169
|
+
"""
|
|
170
|
+
p = Path(path)
|
|
171
|
+
if not p.exists():
|
|
172
|
+
return None
|
|
173
|
+
try:
|
|
174
|
+
data = json.loads(p.read_text())
|
|
175
|
+
# Validate required fields
|
|
176
|
+
required = {"colony_id", "public_key_ed25519", "alias", "genesis_version", "signature"}
|
|
177
|
+
if not required.issubset(set(data.keys())):
|
|
178
|
+
missing = required - set(data.keys())
|
|
179
|
+
logger.warning("Genesis manifest missing fields: %s", missing)
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
# Verify signature against hardcoded trust key
|
|
183
|
+
if not verify_genesis_manifest(data):
|
|
184
|
+
logger.warning("Genesis manifest signature INVALID — rejecting (tampered or unsigned)")
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
set_genesis_manifest(data)
|
|
188
|
+
logger.info("Genesis manifest verified and loaded")
|
|
189
|
+
return data
|
|
190
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
191
|
+
logger.warning("Failed to load Genesis manifest: %s", e)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Colony ID management
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
COLONY_ID_FILE = "colony-id"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_or_create_colony_id(state_dir: str | Path) -> str:
|
|
203
|
+
"""Get the existing colony_id or create a new random UUID.
|
|
204
|
+
|
|
205
|
+
The colony_id is stored in {state_dir}/colony-id.
|
|
206
|
+
It is generated once and never changes.
|
|
207
|
+
For disaster recovery, use 'colony backup' / 'colony restore'.
|
|
208
|
+
"""
|
|
209
|
+
id_path = Path(state_dir) / COLONY_ID_FILE
|
|
210
|
+
if id_path.exists():
|
|
211
|
+
colony_id = id_path.read_text().strip()
|
|
212
|
+
if colony_id:
|
|
213
|
+
return colony_id
|
|
214
|
+
|
|
215
|
+
# Generate new
|
|
216
|
+
colony_id = str(uuid.uuid4())
|
|
217
|
+
id_path.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
id_path.write_text(colony_id)
|
|
219
|
+
logger.info("Generated new colony_id: %s", colony_id)
|
|
220
|
+
return colony_id
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def create_genesis_manifest(
|
|
224
|
+
colony_id: str,
|
|
225
|
+
public_key_hex: str,
|
|
226
|
+
output_path: str | Path,
|
|
227
|
+
private_key_pem: Optional[str | bytes] = None,
|
|
228
|
+
passphrase: Optional[bytes] = None,
|
|
229
|
+
) -> dict:
|
|
230
|
+
"""Create a signed Genesis manifest for the Genesis Colony.
|
|
231
|
+
|
|
232
|
+
The manifest is signed with the Genesis Colony's private key so that all Colonies
|
|
233
|
+
can verify it against the hardcoded GENESIS_TRUST_PUBLIC_KEY.
|
|
234
|
+
|
|
235
|
+
This should only be called once — when the Genesis Colony is first initialized.
|
|
236
|
+
The manifest is saved to disk and should be committed to the repo.
|
|
237
|
+
"""
|
|
238
|
+
manifest = {
|
|
239
|
+
"colony_id": colony_id,
|
|
240
|
+
"public_key_ed25519": public_key_hex,
|
|
241
|
+
"alias": "genesis",
|
|
242
|
+
"genesis_version": 1,
|
|
243
|
+
"signed_at": datetime.now(timezone.utc).isoformat(),
|
|
244
|
+
"description": "The original Colony — trust anchor for the network",
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Sign the manifest with the private key
|
|
248
|
+
if private_key_pem:
|
|
249
|
+
payload = _manifest_signing_payload(manifest)
|
|
250
|
+
signature = _sign_with_key(private_key_pem, payload, passphrase=passphrase)
|
|
251
|
+
manifest["signature"] = signature
|
|
252
|
+
logger.info("Genesis manifest signed")
|
|
253
|
+
else:
|
|
254
|
+
raise ValueError("Cannot create Genesis manifest without a private key — signing is required")
|
|
255
|
+
|
|
256
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
Path(output_path).write_text(json.dumps(manifest, indent=2) + "\n")
|
|
258
|
+
logger.info("Genesis manifest created at %s", output_path)
|
|
259
|
+
set_genesis_manifest(manifest)
|
|
260
|
+
return manifest
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_colony_manifest(
|
|
264
|
+
colony_id: str,
|
|
265
|
+
public_key_hex: str,
|
|
266
|
+
output_path: str | Path,
|
|
267
|
+
) -> dict:
|
|
268
|
+
"""Create a standard (non-Genesis) colony manifest.
|
|
269
|
+
|
|
270
|
+
Every Colony can generate a manifest to share its public identity.
|
|
271
|
+
Other Colonies can load this to establish trust.
|
|
272
|
+
"""
|
|
273
|
+
manifest = {
|
|
274
|
+
"colony_id": colony_id,
|
|
275
|
+
"public_key_ed25519": public_key_hex,
|
|
276
|
+
"alias": "", # User-settable nickname
|
|
277
|
+
"genesis_version": 0, # 0 = not Genesis
|
|
278
|
+
"signed_at": datetime.now(timezone.utc).isoformat(),
|
|
279
|
+
}
|
|
280
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
Path(output_path).write_text(json.dumps(manifest, indent=2) + "\n")
|
|
282
|
+
logger.info("Colony manifest created at %s", output_path)
|
|
283
|
+
return manifest
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def backup_colony(state_dir: str | Path, passphrase: Optional[bytes] = None) -> dict:
|
|
287
|
+
"""Export colony identity as a portable backup.
|
|
288
|
+
|
|
289
|
+
Returns a dict containing:
|
|
290
|
+
- colony_id
|
|
291
|
+
- public_key_ed25519
|
|
292
|
+
- private_key_pem (optionally encrypted)
|
|
293
|
+
- genesis_manifest (if this Colony is Genesis)
|
|
294
|
+
- backup_version
|
|
295
|
+
- created_at
|
|
296
|
+
|
|
297
|
+
This is everything needed to restore a Colony on a new machine.
|
|
298
|
+
"""
|
|
299
|
+
state_dir = Path(state_dir)
|
|
300
|
+
|
|
301
|
+
# Colony ID
|
|
302
|
+
id_path = state_dir / COLONY_ID_FILE
|
|
303
|
+
if not id_path.exists():
|
|
304
|
+
raise FileNotFoundError("No colony-id found — nothing to back up")
|
|
305
|
+
colony_id = id_path.read_text().strip()
|
|
306
|
+
|
|
307
|
+
# Keypair
|
|
308
|
+
keys_dir = state_dir / "colony-keys"
|
|
309
|
+
priv_path = keys_dir / "private.pem"
|
|
310
|
+
if not priv_path.exists():
|
|
311
|
+
raise FileNotFoundError("No private key found — nothing to back up")
|
|
312
|
+
|
|
313
|
+
# Read private key PEM
|
|
314
|
+
private_pem = priv_path.read_bytes()
|
|
315
|
+
|
|
316
|
+
# If a backup passphrase is given, encrypt the private key
|
|
317
|
+
if passphrase:
|
|
318
|
+
from cryptography.hazmat.primitives import serialization
|
|
319
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
320
|
+
|
|
321
|
+
existing_pass = None
|
|
322
|
+
key_obj = serialization.load_pem_private_key(private_pem, password=existing_pass)
|
|
323
|
+
private_pem = key_obj.private_bytes(
|
|
324
|
+
encoding=serialization.Encoding.PEM,
|
|
325
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
326
|
+
encryption_algorithm=serialization.BestAvailableEncryption(passphrase),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Public key
|
|
330
|
+
pub_path = keys_dir / "public.pem"
|
|
331
|
+
public_key_hex = ""
|
|
332
|
+
if pub_path.exists():
|
|
333
|
+
from cryptography.hazmat.primitives import serialization
|
|
334
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
335
|
+
pub_pem = pub_path.read_bytes()
|
|
336
|
+
pub_key = serialization.load_pem_public_key(pub_pem)
|
|
337
|
+
public_key_hex = pub_key.public_bytes(
|
|
338
|
+
encoding=serialization.Encoding.Raw,
|
|
339
|
+
format=serialization.PublicFormat.Raw,
|
|
340
|
+
).hex()
|
|
341
|
+
|
|
342
|
+
# Genesis manifest (if this Colony is Genesis)
|
|
343
|
+
genesis_data = None
|
|
344
|
+
genesis_path = state_dir / "genesis.json"
|
|
345
|
+
if genesis_path.exists():
|
|
346
|
+
genesis_data = json.loads(genesis_path.read_text())
|
|
347
|
+
|
|
348
|
+
backup = {
|
|
349
|
+
"backup_version": 1,
|
|
350
|
+
"colony_id": colony_id,
|
|
351
|
+
"public_key_ed25519": public_key_hex,
|
|
352
|
+
"private_key_pem": private_pem.decode() if isinstance(private_pem, bytes) else private_pem,
|
|
353
|
+
"encrypted": passphrase is not None,
|
|
354
|
+
"genesis": genesis_data,
|
|
355
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
356
|
+
}
|
|
357
|
+
return backup
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def restore_colony(
|
|
361
|
+
state_dir: str | Path,
|
|
362
|
+
backup_data: dict,
|
|
363
|
+
passphrase: Optional[bytes] = None,
|
|
364
|
+
) -> str:
|
|
365
|
+
"""Restore a Colony from a backup.
|
|
366
|
+
|
|
367
|
+
Writes colony-id, private key, public key, and optionally genesis manifest.
|
|
368
|
+
Returns the restored colony_id.
|
|
369
|
+
"""
|
|
370
|
+
state_dir = Path(state_dir)
|
|
371
|
+
|
|
372
|
+
if backup_data.get("backup_version") != 1:
|
|
373
|
+
raise ValueError(f"Unsupported backup version: {backup_data.get('backup_version')}")
|
|
374
|
+
|
|
375
|
+
colony_id = backup_data["colony_id"]
|
|
376
|
+
private_pem = backup_data["private_key_pem"]
|
|
377
|
+
is_encrypted = backup_data.get("encrypted", False)
|
|
378
|
+
|
|
379
|
+
load_password = passphrase if is_encrypted else None
|
|
380
|
+
|
|
381
|
+
from cryptography.hazmat.primitives import serialization
|
|
382
|
+
try:
|
|
383
|
+
key_obj = serialization.load_pem_private_key(
|
|
384
|
+
private_pem.encode() if isinstance(private_pem, str) else private_pem,
|
|
385
|
+
password=load_password,
|
|
386
|
+
)
|
|
387
|
+
except (ValueError, TypeError) as e:
|
|
388
|
+
raise ValueError(f"Failed to decrypt private key — wrong passphrase? {e}") from e
|
|
389
|
+
|
|
390
|
+
state_dir.mkdir(parents=True, exist_ok=True)
|
|
391
|
+
(state_dir / COLONY_ID_FILE).write_text(colony_id)
|
|
392
|
+
|
|
393
|
+
keys_dir = state_dir / "colony-keys"
|
|
394
|
+
keys_dir.mkdir(parents=True, exist_ok=True)
|
|
395
|
+
priv_path = keys_dir / "private.pem"
|
|
396
|
+
pub_path = keys_dir / "public.pem"
|
|
397
|
+
|
|
398
|
+
pem_bytes = private_pem.encode() if isinstance(private_pem, str) else private_pem
|
|
399
|
+
priv_path.write_bytes(pem_bytes)
|
|
400
|
+
import os
|
|
401
|
+
os.chmod(priv_path, 0o600)
|
|
402
|
+
|
|
403
|
+
pub_pem = key_obj.public_key().public_bytes(
|
|
404
|
+
encoding=serialization.Encoding.PEM,
|
|
405
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
406
|
+
)
|
|
407
|
+
pub_path.write_bytes(pub_pem)
|
|
408
|
+
|
|
409
|
+
genesis_data = backup_data.get("genesis")
|
|
410
|
+
if genesis_data:
|
|
411
|
+
genesis_path = state_dir / "genesis.json"
|
|
412
|
+
genesis_path.write_text(json.dumps(genesis_data, indent=2) + "\n")
|
|
413
|
+
set_genesis_manifest(genesis_data)
|
|
414
|
+
|
|
415
|
+
logger.info("Colony restored: %s", colony_id)
|
|
416
|
+
return colony_id
|