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,183 @@
|
|
|
1
|
+
"""Offline, rollback-first competence reconciliation CLI.
|
|
2
|
+
|
|
3
|
+
Dry-run is the default and operates on a temporary SQLite backup, so merely
|
|
4
|
+
validating a legacy database cannot run schema migrations against it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import sqlite3
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from apsimo.self_model.store import CompetenceStore, event_fingerprint
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_ts(value: str) -> float:
|
|
23
|
+
try:
|
|
24
|
+
return float(value)
|
|
25
|
+
except ValueError:
|
|
26
|
+
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
27
|
+
if dt.tzinfo is None:
|
|
28
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
29
|
+
return dt.timestamp()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sqlite_backup(source: Path, destination: Path) -> str:
|
|
33
|
+
"""Create one consistent SQLite backup; never overwrite a prior backup."""
|
|
34
|
+
if not source.is_file():
|
|
35
|
+
raise ValueError(f"database does not exist: {source}")
|
|
36
|
+
if destination.exists():
|
|
37
|
+
raise ValueError(f"backup already exists: {destination}")
|
|
38
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
src = sqlite3.connect(f"file:{source}?mode=ro", uri=True)
|
|
40
|
+
dst = sqlite3.connect(str(destination))
|
|
41
|
+
try:
|
|
42
|
+
src.backup(dst)
|
|
43
|
+
finally:
|
|
44
|
+
dst.close()
|
|
45
|
+
src.close()
|
|
46
|
+
digest = hashlib.sha256()
|
|
47
|
+
with destination.open("rb") as handle:
|
|
48
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
49
|
+
digest.update(chunk)
|
|
50
|
+
return digest.hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def inspect_database(db_path: Path, domain: str, since: float,
|
|
54
|
+
until: float) -> Dict[str, Any]:
|
|
55
|
+
"""Read legacy or current raw events without migrating the source DB."""
|
|
56
|
+
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
57
|
+
conn.row_factory = sqlite3.Row
|
|
58
|
+
try:
|
|
59
|
+
rows = [dict(r) for r in conn.execute(
|
|
60
|
+
"SELECT * FROM competence_events WHERE domain=? "
|
|
61
|
+
"AND ts>=? AND ts<? ORDER BY ts ASC, id ASC",
|
|
62
|
+
((domain or "").strip().lower(), since, until)).fetchall()]
|
|
63
|
+
try:
|
|
64
|
+
ledger_count = int(conn.execute(
|
|
65
|
+
"SELECT COUNT(*) FROM competence_reconciliations"
|
|
66
|
+
).fetchone()[0])
|
|
67
|
+
except sqlite3.OperationalError:
|
|
68
|
+
ledger_count = 0
|
|
69
|
+
finally:
|
|
70
|
+
conn.close()
|
|
71
|
+
candidates: List[Dict[str, Any]] = []
|
|
72
|
+
for row in rows:
|
|
73
|
+
candidates.append({
|
|
74
|
+
"event_id": int(row["id"]),
|
|
75
|
+
"target_fingerprint": event_fingerprint(row),
|
|
76
|
+
"domain": row["domain"],
|
|
77
|
+
"recorded_outcome": row["outcome"],
|
|
78
|
+
"shadow": bool(row.get("shadow")),
|
|
79
|
+
"violation": bool(row.get("violation")),
|
|
80
|
+
"stated_confidence": row.get("stated_confidence"),
|
|
81
|
+
"source": row.get("source") or "legacy_unattributed",
|
|
82
|
+
"source_ref": row.get("source_ref"),
|
|
83
|
+
"evidence_status": (
|
|
84
|
+
row.get("evidence_status") or "legacy_unattributed"),
|
|
85
|
+
"outcome_contract": (
|
|
86
|
+
row.get("outcome_contract") or "legacy.unversioned"),
|
|
87
|
+
"ts": float(row["ts"]),
|
|
88
|
+
})
|
|
89
|
+
return {
|
|
90
|
+
"database": str(db_path), "domain": domain,
|
|
91
|
+
"since_ts": since, "until_ts": until,
|
|
92
|
+
"candidate_count": len(candidates), "candidates": candidates,
|
|
93
|
+
"ledger_entries": ledger_count, "mutated": False,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _load_manifest(path: Path) -> Dict[str, Any]:
|
|
98
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
99
|
+
if not isinstance(value, dict):
|
|
100
|
+
raise ValueError("manifest root must be an object")
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def dry_run(db_path: Path, manifest: Dict[str, Any]) -> Dict[str, Any]:
|
|
105
|
+
"""Validate against a consistent disposable copy of the source DB."""
|
|
106
|
+
with tempfile.TemporaryDirectory(prefix="colony-competence-dryrun-") as td:
|
|
107
|
+
copy_path = Path(td) / "self-model.db"
|
|
108
|
+
sqlite_backup(db_path, copy_path)
|
|
109
|
+
store = CompetenceStore(str(copy_path))
|
|
110
|
+
try:
|
|
111
|
+
result = store.plan_reconciliation(manifest)
|
|
112
|
+
finally:
|
|
113
|
+
store.close()
|
|
114
|
+
return {**result, "mode": "dry-run", "database_mutated": False}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def commit(db_path: Path, manifest: Dict[str, Any],
|
|
118
|
+
backup_path: Path) -> Dict[str, Any]:
|
|
119
|
+
"""Back up first, then atomically append the manifest."""
|
|
120
|
+
# Validate on a disposable consistent copy before opening (and therefore
|
|
121
|
+
# migrating) the source database.
|
|
122
|
+
dry_run(db_path, manifest)
|
|
123
|
+
backup_sha256 = sqlite_backup(db_path, backup_path)
|
|
124
|
+
store = CompetenceStore(str(db_path))
|
|
125
|
+
try:
|
|
126
|
+
result = store.apply_reconciliation(manifest)
|
|
127
|
+
finally:
|
|
128
|
+
store.close()
|
|
129
|
+
return {**result, "mode": "commit", "database_mutated": True,
|
|
130
|
+
"backup": str(backup_path), "backup_sha256": backup_sha256}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
134
|
+
parser = argparse.ArgumentParser(
|
|
135
|
+
description="Inspect or reconcile Colony competence evidence")
|
|
136
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
137
|
+
inspect = sub.add_parser("inspect", help="list exact raw event targets")
|
|
138
|
+
inspect.add_argument("--db", required=True, type=Path)
|
|
139
|
+
inspect.add_argument("--domain", required=True)
|
|
140
|
+
inspect.add_argument("--since", required=True, type=_parse_ts)
|
|
141
|
+
inspect.add_argument("--until", required=True, type=_parse_ts)
|
|
142
|
+
apply = sub.add_parser("apply", help="validate/apply a v1 manifest")
|
|
143
|
+
apply.add_argument("--db", required=True, type=Path)
|
|
144
|
+
apply.add_argument("--manifest", required=True, type=Path)
|
|
145
|
+
apply.add_argument(
|
|
146
|
+
"--commit", action="store_true",
|
|
147
|
+
help="append after a backup (otherwise uses a disposable dry-run)")
|
|
148
|
+
apply.add_argument(
|
|
149
|
+
"--backup", type=Path,
|
|
150
|
+
help="new backup path; required with --commit and never overwritten")
|
|
151
|
+
return parser
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
155
|
+
parser = build_parser()
|
|
156
|
+
args = parser.parse_args(argv)
|
|
157
|
+
try:
|
|
158
|
+
if args.command == "inspect":
|
|
159
|
+
if args.since >= args.until:
|
|
160
|
+
raise ValueError("--since must be earlier than --until")
|
|
161
|
+
result = inspect_database(
|
|
162
|
+
args.db, args.domain, args.since, args.until)
|
|
163
|
+
else:
|
|
164
|
+
manifest = _load_manifest(args.manifest)
|
|
165
|
+
if args.commit:
|
|
166
|
+
if args.backup is None:
|
|
167
|
+
raise ValueError("--backup is required with --commit")
|
|
168
|
+
result = commit(args.db, manifest, args.backup)
|
|
169
|
+
else:
|
|
170
|
+
if args.backup is not None:
|
|
171
|
+
raise ValueError("--backup is only valid with --commit")
|
|
172
|
+
result = dry_run(args.db, manifest)
|
|
173
|
+
except (OSError, TypeError, ValueError, sqlite3.Error,
|
|
174
|
+
json.JSONDecodeError) as exc:
|
|
175
|
+
print(json.dumps({"ok": False, "error": str(exc)}, sort_keys=True),
|
|
176
|
+
file=sys.stderr)
|
|
177
|
+
return 2
|
|
178
|
+
print(json.dumps({"ok": True, **result}, sort_keys=True, indent=2))
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__": # pragma: no cover
|
|
183
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""Observe exact replies by a declared horizon, without scheduling or sending.
|
|
2
|
+
|
|
3
|
+
An accepted dispatch is not delivery/read evidence. The fixed 0.5 probability
|
|
4
|
+
is an explicitly uncalibrated baseline, not a judgment about the recipient.
|
|
5
|
+
Only existing trusted callbacks can issue a forecast; reads only reconcile it.
|
|
6
|
+
"""
|
|
7
|
+
from contextlib import closing
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sqlite3
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
from apsimo import get_state_dir
|
|
16
|
+
from apsimo.contacts.transport_ingress import TransportIngress
|
|
17
|
+
from apsimo.turns import get_turn_idempotency_ledger
|
|
18
|
+
from .expectations import expectations_enabled
|
|
19
|
+
from .runtime_forecasts import _current, _digest
|
|
20
|
+
|
|
21
|
+
METHOD = 'accepted-dispatch-reply-by-declared-horizon-v1'
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _parts():
|
|
26
|
+
from apsimo.api.routers import host
|
|
27
|
+
owner = (os.environ.get('COLONY_OWNER_PERSON_ID') or os.environ.get('COLONY_OWNER_CONTACT_ID', '')).strip()
|
|
28
|
+
if (not owner or not expectations_enabled() or host._expectations is None
|
|
29
|
+
or host._commitment_store is None or host._comms_log is None):
|
|
30
|
+
return None
|
|
31
|
+
return (host._expectations.store, get_turn_idempotency_ledger(get_state_dir()),
|
|
32
|
+
host._commitment_store, host._comms_log, owner)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fid(wait_id):
|
|
36
|
+
return 'expected-reply:'+_digest(wait_id)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _metadata(ledger, source):
|
|
40
|
+
with closing(ledger._connect()) as db:
|
|
41
|
+
row = db.execute('SELECT messages_json FROM turn_sources WHERE turn_id=?', (source,)).fetchone()
|
|
42
|
+
return json.loads(row[0])[0].get('_expected_reply_facts') if row else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _retain(ledger, source, person, facts, stamp, dependencies=None, session_id=''):
|
|
46
|
+
previous = _metadata(ledger, source)
|
|
47
|
+
if previous is None:
|
|
48
|
+
ledger.record_source(source, contact_id=person, session_id=session_id or source,
|
|
49
|
+
occurred_at=datetime.fromtimestamp(stamp, timezone.utc).isoformat(), derive_claims=False,
|
|
50
|
+
messages=[{'role':'assistant', 'content':'', '_expected_reply_facts':facts,
|
|
51
|
+
'_supplied_sources':[{'source_id':sid, 'source_version':version}
|
|
52
|
+
for sid, version in (dependencies or {}).items()]}])
|
|
53
|
+
previous = _metadata(ledger, source)
|
|
54
|
+
refs = ledger.source_references([source], contact_id=person, session_id='')
|
|
55
|
+
if not refs or ledger.is_projection_erased(source) or previous.get('method') != METHOD:
|
|
56
|
+
raise ValueError('reply_forecast_evidence_unavailable')
|
|
57
|
+
return {'receipt:'+source:refs[0]['source_version']}, previous
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parent_current(ledger, commitments, conditions, owner):
|
|
61
|
+
from apsimo.api.routers.temporal_followups import source_bindings
|
|
62
|
+
parent = commitments.get(conditions['commitment_id'])
|
|
63
|
+
with closing(commitments._connect()) as db:
|
|
64
|
+
row = db.execute('SELECT material_digest FROM temporal_followups WHERE wait_id=?',
|
|
65
|
+
(conditions['wait_id'],)).fetchone()
|
|
66
|
+
return bool(parent and parent['person_id'] == owner and row
|
|
67
|
+
and row[0] == conditions['wait_material_digest']
|
|
68
|
+
and not source_bindings(conditions['parent_sources'], conditions['parent_sources'],
|
|
69
|
+
person=owner, session_id=conditions['parent_session_id']))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _reply(ingress, ledger, conditions, item):
|
|
73
|
+
row = ingress.get(item['receipt_ref'])
|
|
74
|
+
if not row or row['state'] != 'completed' or row['outcome'] != 'captured':
|
|
75
|
+
return None
|
|
76
|
+
metadata = json.loads(row['metadata_json'])
|
|
77
|
+
channel = conditions['channel']
|
|
78
|
+
if (row['producer'] != conditions['producer'] or row['account_id'] != conditions['account_id']
|
|
79
|
+
or row['contact_id'] != conditions['recipient_id'] or not row['media_available']
|
|
80
|
+
or metadata.get('channel') != channel or metadata.get('from_owner') or metadata.get('is_group')
|
|
81
|
+
or channel+':'+metadata.get('reply_to_ref', '') != conditions['provider_external_ref']
|
|
82
|
+
or channel+':'+row['event_id'] != item['external_ref']):
|
|
83
|
+
return None
|
|
84
|
+
native = json.loads(row['native_turn_json'] or '{}')
|
|
85
|
+
versions = json.loads(row['source_versions_json'] or '{}')
|
|
86
|
+
if set(versions) != {native.get('turn_id')}:
|
|
87
|
+
return None
|
|
88
|
+
from apsimo.api.routers.temporal_followups import source_bindings
|
|
89
|
+
if source_bindings(versions, versions, person=row['contact_id'], session_id=native['session_id']):
|
|
90
|
+
return None
|
|
91
|
+
return row
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def evidence_current(prediction, observation=None):
|
|
95
|
+
"""Narrow read predicate; raw forecast_history remains historical evidence."""
|
|
96
|
+
if prediction.detail.get('method') != METHOD:
|
|
97
|
+
return True
|
|
98
|
+
try:
|
|
99
|
+
parts = _parts()
|
|
100
|
+
if parts is None:
|
|
101
|
+
return False
|
|
102
|
+
store, ledger, commitments, comms, owner = parts
|
|
103
|
+
c = prediction.detail['conditions']
|
|
104
|
+
if (prediction.subject_person_id != owner or prediction.viewer_scope != owner
|
|
105
|
+
or prediction.shareability != 'owner_private'
|
|
106
|
+
or not _parent_current(ledger, commitments, c, owner)
|
|
107
|
+
or not _current(ledger, prediction.evidence_refs, prediction.detail['source_versions'], owner)):
|
|
108
|
+
return False
|
|
109
|
+
if observation is None:
|
|
110
|
+
outcomes = store.forecast_history(prediction.detail['forecast_id'])['outcomes']
|
|
111
|
+
observation = outcomes[-1] if outcomes else None
|
|
112
|
+
if observation is None:
|
|
113
|
+
return True
|
|
114
|
+
facts = _metadata(ledger, observation['receipt_ref'].removeprefix('receipt:'))
|
|
115
|
+
if not facts or facts.get('method') != METHOD:
|
|
116
|
+
return False
|
|
117
|
+
person = c['recipient_id'] if facts['kind'] == 'reply' else owner
|
|
118
|
+
if not _current(ledger, observation['evidence_refs'], observation['source_versions'], person):
|
|
119
|
+
return False
|
|
120
|
+
if facts['kind'] == 'wait_stopped':
|
|
121
|
+
return observation['status'] == 'censored'
|
|
122
|
+
with closing(comms.read_connection()) as db:
|
|
123
|
+
ingress = TransportIngress(db)
|
|
124
|
+
if facts['kind'] == 'reply':
|
|
125
|
+
row = _reply(ingress, ledger, c, facts['reply'])
|
|
126
|
+
return bool(row and json.loads(row['source_versions_json']) == facts['reply_sources'])
|
|
127
|
+
coverage = ingress.coverage(producer=c['producer'], account_id=c['account_id'],
|
|
128
|
+
contact_id=c['recipient_id'], since=prediction.detail['origin_at'], until=prediction.horizon,
|
|
129
|
+
now=facts['coverage']['observed_at'], observation=facts['coverage'])
|
|
130
|
+
return coverage['observed'] and coverage['observed_through'] >= prediction.horizon
|
|
131
|
+
except (KeyError, TypeError, ValueError, OSError, sqlite3.Error):
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def observe_dispatch(waits, row, *, producer, receipt, created):
|
|
136
|
+
parts = _parts()
|
|
137
|
+
if parts is None or receipt.direction != 'out':
|
|
138
|
+
return
|
|
139
|
+
store, ledger, commitments, comms, owner = parts
|
|
140
|
+
fid = _fid(row['wait_id'])
|
|
141
|
+
if store.forecast_history(fid)['forecasts']:
|
|
142
|
+
return
|
|
143
|
+
source = fid+':dispatch'
|
|
144
|
+
retained = _metadata(ledger, source)
|
|
145
|
+
# A later ledger scan cannot invent a forecast. An interrupted first
|
|
146
|
+
# callback can replay its already frozen declaration, never a new horizon.
|
|
147
|
+
if not created and retained is None:
|
|
148
|
+
return
|
|
149
|
+
if retained is None:
|
|
150
|
+
now = time.time()
|
|
151
|
+
parent = commitments.get(row['commitment_id'])
|
|
152
|
+
if (not parent or parent['person_id'] != owner or row['reply'] or row['state'] not in {'open','deferred'}
|
|
153
|
+
or row['dispatch_receipt_ref'] != receipt.receipt_ref
|
|
154
|
+
or row['outbound_ref'] not in {receipt.outbound_ref, receipt.channel+':'+receipt.external_ref}
|
|
155
|
+
or not row['expected_at'] or row['expected_at'] <= now or row['expires_at'] <= now):
|
|
156
|
+
return
|
|
157
|
+
with closing(comms.read_connection()) as db:
|
|
158
|
+
accounts = db.execute('SELECT account_id FROM transport_ingress_coverage WHERE producer=?',
|
|
159
|
+
(producer,)).fetchall()
|
|
160
|
+
# This first method uses the existing one-account WhatsApp contract.
|
|
161
|
+
if receipt.channel != 'whatsapp' or len(accounts) != 1:
|
|
162
|
+
return
|
|
163
|
+
with closing(commitments._connect()) as db:
|
|
164
|
+
material = db.execute('SELECT material_digest FROM temporal_followups WHERE wait_id=?',
|
|
165
|
+
(row['wait_id'],)).fetchone()[0]
|
|
166
|
+
c = {'wait_id':row['wait_id'], 'commitment_id':row['commitment_id'], 'work_id':row['work_id'],
|
|
167
|
+
'wait_material_digest':material, 'parent_sources':row['source_versions'],
|
|
168
|
+
'parent_origin':parent['made_at'],
|
|
169
|
+
'parent_session_id':row['source_session_id'], 'recipient_id':row['contact_id'],
|
|
170
|
+
'producer':producer, 'account_id':accounts[0]['account_id'], 'channel':receipt.channel,
|
|
171
|
+
'provider_external_ref':receipt.channel+':'+receipt.external_ref,
|
|
172
|
+
'dispatch_receipt_ref':receipt.receipt_ref, 'dispatch_status':receipt.status,
|
|
173
|
+
'outbound_ref':row['outbound_ref'], 'probability_basis':'fixed uncalibrated 0.5 baseline'}
|
|
174
|
+
if not _parent_current(ledger, commitments, c, owner):
|
|
175
|
+
return
|
|
176
|
+
retained = {'method':METHOD, 'kind':'dispatch', 'conditions':c,
|
|
177
|
+
'origin_at':row['dispatch_occurred_at'], 'horizon':row['expected_at'], 'issued_at':now}
|
|
178
|
+
c = retained['conditions']
|
|
179
|
+
if not _parent_current(ledger, commitments, c, owner):
|
|
180
|
+
return
|
|
181
|
+
versions, retained = _retain(ledger, source, owner, retained, retained['issued_at'],
|
|
182
|
+
c['parent_sources'], c['parent_session_id'])
|
|
183
|
+
store.issue_forecast(forecast_id=fid, subject=fid, domain='expected_reply',
|
|
184
|
+
expectation='An exact reply is observed by the declared horizon after the recorded dispatch acknowledgment.',
|
|
185
|
+
confidence=.5, horizon=retained['horizon'], origin_at=retained['origin_at'], issued_at=retained['issued_at'],
|
|
186
|
+
evidence_refs=versions, source_versions=versions, source_kind='transport_receipt',
|
|
187
|
+
cohort='declared-reply:whatsapp:'+c['dispatch_status'], method=METHOD,
|
|
188
|
+
model_provenance={'served_model':None}, subject_person_id=owner, viewer_scope=owner,
|
|
189
|
+
shareability='owner_private', conditions=c)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _stop_facts(ledger, commitments, prediction, now):
|
|
193
|
+
"""Freeze the earliest known stop, without guessing a legacy event time."""
|
|
194
|
+
from apsimo.commitments.store import OPEN_STATUSES
|
|
195
|
+
c = prediction.detail['conditions']
|
|
196
|
+
retained = _metadata(ledger, _fid(c['wait_id'])+':stopped')
|
|
197
|
+
if retained:
|
|
198
|
+
return retained
|
|
199
|
+
with closing(commitments._connect()) as db:
|
|
200
|
+
row = db.execute('SELECT state,revision,payload FROM temporal_followups WHERE wait_id=?',
|
|
201
|
+
(c['wait_id'],)).fetchone()
|
|
202
|
+
if row is None:
|
|
203
|
+
return None # _parent_current already rejects a missing wait.
|
|
204
|
+
wait = json.loads(row['payload'])
|
|
205
|
+
parent = commitments.get(c['commitment_id'])
|
|
206
|
+
stops = []
|
|
207
|
+
if not parent or parent['status'] not in OPEN_STATUSES:
|
|
208
|
+
stamp = ((parent or {}).get('metadata') or {}).get('resolution', {}).get('at')
|
|
209
|
+
stamp = stamp or (parent or {}).get('fulfilled_at')
|
|
210
|
+
stops.append(('cancelled', 'cancelled', datetime.fromisoformat(stamp).timestamp() if stamp else None))
|
|
211
|
+
if row['state'] == 'cancelled' and (not stops or wait.get('cancelled_at') is not None):
|
|
212
|
+
stops.append(('cancelled', 'cancelled', wait.get('cancelled_at')))
|
|
213
|
+
if wait.get('followup_receipt_ref'):
|
|
214
|
+
stops.append(('intervened', 'intervened', wait.get('followup_observed_at')))
|
|
215
|
+
if row['state'] == 'expired' or wait['expires_at'] <= now:
|
|
216
|
+
stops.append(('expired', 'unavailable', wait['expires_at']))
|
|
217
|
+
if not stops:
|
|
218
|
+
return None
|
|
219
|
+
state, reason, cutoff = min(stops, key=lambda s: s[2] if s[2] is not None else float('-inf'))
|
|
220
|
+
return {'method':METHOD, 'kind':'wait_stopped', 'state':state, 'reason':reason,
|
|
221
|
+
'stopped_at':cutoff,
|
|
222
|
+
'wait_revision':row['revision'], 'observed_at':now,
|
|
223
|
+
'resolution_ref':wait.get('resolution_ref'),
|
|
224
|
+
'followup_receipt_ref':wait.get('followup_receipt_ref')}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _record_stop(store, ledger, prediction, latest, owner, facts, now):
|
|
228
|
+
c = prediction.detail['conditions']
|
|
229
|
+
versions, facts = _retain(ledger, _fid(c['wait_id'])+':stopped', owner, facts, now,
|
|
230
|
+
c['parent_sources'], c['parent_session_id'])
|
|
231
|
+
store.record_forecast_outcome(forecast_id=prediction.detail['forecast_id'],
|
|
232
|
+
receipt_ref=next(iter(versions)), evidence_refs=versions, source_versions=versions,
|
|
233
|
+
source_kind='work_receipt', observed_at=facts['observed_at'], recorded_at=now,
|
|
234
|
+
subject_person_id=owner, viewer_scope=owner, shareability='owner_private',
|
|
235
|
+
status='censored', value=None, reason=facts['reason'],
|
|
236
|
+
previous_revision=latest['revision'] if latest else 0)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def reconcile(wait_id):
|
|
240
|
+
parts = _parts()
|
|
241
|
+
if parts is None:
|
|
242
|
+
return
|
|
243
|
+
store, ledger, commitments, comms, owner = parts
|
|
244
|
+
history = store.forecast_history(_fid(wait_id))
|
|
245
|
+
if not history['forecasts']:
|
|
246
|
+
return
|
|
247
|
+
raw = history['forecasts'][0]
|
|
248
|
+
prediction = store.get(raw['prediction_id'])
|
|
249
|
+
if not evidence_current(prediction):
|
|
250
|
+
return
|
|
251
|
+
latest = history['outcomes'][-1] if history['outcomes'] else None
|
|
252
|
+
if latest and latest['status'] == 'observed':
|
|
253
|
+
return
|
|
254
|
+
c = prediction.detail['conditions']
|
|
255
|
+
now = time.time()
|
|
256
|
+
stop = _stop_facts(ledger, commitments, prediction, now)
|
|
257
|
+
recorded_receipts = {o['receipt_ref'] for o in history['outcomes']}
|
|
258
|
+
with closing(comms.read_connection()) as db:
|
|
259
|
+
ingress = TransportIngress(db)
|
|
260
|
+
matches = comms.match_reply(contact_id=c['recipient_id'], outbound_ref=c['provider_external_ref'],
|
|
261
|
+
since_iso=c['parent_origin'],
|
|
262
|
+
until_iso=datetime.fromtimestamp(now, timezone.utc).isoformat(), connection=db)
|
|
263
|
+
facts = None
|
|
264
|
+
for item in matches['matches']:
|
|
265
|
+
row = _reply(ingress, ledger, c, item)
|
|
266
|
+
if row is None:
|
|
267
|
+
continue
|
|
268
|
+
observed = row['occurred_at']
|
|
269
|
+
# Canonical admission can lag the actual reply or an already
|
|
270
|
+
# recorded censor. Only a reply known to precede the stop can
|
|
271
|
+
# replace that censor. Legacy missing timestamps stay unscored.
|
|
272
|
+
if stop and (stop.get('stopped_at') is None or observed >= stop['stopped_at']
|
|
273
|
+
or not prediction.created_at <= observed <= prediction.horizon):
|
|
274
|
+
continue
|
|
275
|
+
state = ('retrospective_unscored' if observed < prediction.created_at else
|
|
276
|
+
'reply_observed_in_time' if observed <= prediction.horizon else 'deadline_elapsed_unobserved')
|
|
277
|
+
facts = {'method':METHOD, 'kind':'reply', 'state':state, 'reply':item,
|
|
278
|
+
'reply_sources':json.loads(row['source_versions_json']), 'observed_at':observed}
|
|
279
|
+
source = _fid(wait_id)+':reply:'+_digest(row['receipt_id'])
|
|
280
|
+
if 'receipt:'+source in recorded_receipts:
|
|
281
|
+
continue
|
|
282
|
+
versions, facts = _retain(ledger, source, c['recipient_id'], facts, now,
|
|
283
|
+
facts['reply_sources'], json.loads(row['native_turn_json'])['session_id'])
|
|
284
|
+
store.record_forecast_outcome(forecast_id=_fid(wait_id), receipt_ref=next(iter(versions)),
|
|
285
|
+
evidence_refs=versions, source_versions=versions, source_kind='transport_receipt',
|
|
286
|
+
observed_at=facts['observed_at'], recorded_at=now, subject_person_id=owner,
|
|
287
|
+
viewer_scope=owner, shareability='owner_private',
|
|
288
|
+
status='observed' if state == 'reply_observed_in_time' else 'unresolved',
|
|
289
|
+
value=True if state == 'reply_observed_in_time' else None,
|
|
290
|
+
reason='' if state == 'reply_observed_in_time' else 'unknown',
|
|
291
|
+
previous_revision=latest['revision'] if latest else 0)
|
|
292
|
+
if state == 'reply_observed_in_time':
|
|
293
|
+
return
|
|
294
|
+
latest = store.forecast_history(_fid(wait_id))['outcomes'][-1]
|
|
295
|
+
if stop:
|
|
296
|
+
if not latest or latest['status'] != 'censored':
|
|
297
|
+
_record_stop(store, ledger, prediction, latest, owner, stop, now)
|
|
298
|
+
return
|
|
299
|
+
if now < prediction.horizon:
|
|
300
|
+
return
|
|
301
|
+
accounts = db.execute('SELECT * FROM transport_ingress_coverage WHERE producer=?',
|
|
302
|
+
(c['producer'],)).fetchall()
|
|
303
|
+
if len(accounts) != 1 or accounts[0]['account_id'] != c['account_id']:
|
|
304
|
+
return
|
|
305
|
+
coverage = dict(accounts[0])
|
|
306
|
+
checked = ingress.coverage(producer=c['producer'], account_id=c['account_id'],
|
|
307
|
+
contact_id=c['recipient_id'], since=prediction.detail['origin_at'], until=prediction.horizon, now=now)
|
|
308
|
+
if not checked['observed'] or checked['observed_through'] < prediction.horizon:
|
|
309
|
+
return
|
|
310
|
+
facts = {'method':METHOD, 'kind':'coverage', 'state':'no_reply_with_coverage', 'coverage':coverage}
|
|
311
|
+
versions, facts = _retain(ledger, _fid(wait_id)+':coverage', owner, facts, now,
|
|
312
|
+
c['parent_sources'], c['parent_session_id'])
|
|
313
|
+
store.record_forecast_outcome(forecast_id=_fid(wait_id), receipt_ref=next(iter(versions)),
|
|
314
|
+
evidence_refs=versions, source_versions=versions, source_kind='transport_receipt',
|
|
315
|
+
observed_at=prediction.horizon, coverage_until=facts['coverage']['observed_at'], recorded_at=now,
|
|
316
|
+
subject_person_id=owner, viewer_scope=owner, shareability='owner_private', value=False,
|
|
317
|
+
previous_revision=latest['revision'] if latest else 0)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def safe(call, *args, **kwargs):
|
|
321
|
+
try:
|
|
322
|
+
return call(*args, **kwargs)
|
|
323
|
+
except Exception as error:
|
|
324
|
+
logger.warning('Declared reply forecast observation unavailable: %s', type(error).__name__)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def reconcile_existing(*, receipt=None, producer=None, coverage=None):
|
|
328
|
+
"""At most 100 newest relevant waits, never a FIFO of historical unknowns.
|
|
329
|
+
|
|
330
|
+
Exact incoming lineage bypasses unrelated waits. Coverage considers only
|
|
331
|
+
this account's observed connected interval. Older broad-coverage history
|
|
332
|
+
is not exhaustively drained; an owner wait read reconciles that exact row.
|
|
333
|
+
"""
|
|
334
|
+
parts = _parts()
|
|
335
|
+
if parts is None:
|
|
336
|
+
return
|
|
337
|
+
store = parts[0]
|
|
338
|
+
query = "SELECT detail FROM predictions WHERE domain='expected_reply' AND source=? AND outcome IN ('pending','unresolved') "
|
|
339
|
+
parameters = [METHOD]
|
|
340
|
+
if receipt is not None:
|
|
341
|
+
metadata = json.loads(receipt['metadata_json'])
|
|
342
|
+
if not metadata.get('reply_to_ref') or not receipt['contact_id']:
|
|
343
|
+
return
|
|
344
|
+
for name, value in {'recipient_id':receipt['contact_id'], 'producer':receipt['producer'],
|
|
345
|
+
'account_id':receipt['account_id'],
|
|
346
|
+
'provider_external_ref':metadata['channel']+':'+metadata['reply_to_ref']}.items():
|
|
347
|
+
query += "AND json_extract(detail,'$.conditions."+name+"')=? "
|
|
348
|
+
parameters.append(value)
|
|
349
|
+
elif coverage is not None and coverage['connected'] and coverage['connected_since'] is not None:
|
|
350
|
+
query += ("AND json_extract(detail,'$.conditions.producer')=? "
|
|
351
|
+
"AND json_extract(detail,'$.conditions.account_id')=? "
|
|
352
|
+
"AND json_extract(detail,'$.origin_at')>=? AND horizon<=? ")
|
|
353
|
+
parameters += [producer, coverage['account_id'], coverage['connected_since'], coverage['observed_at']]
|
|
354
|
+
else:
|
|
355
|
+
return
|
|
356
|
+
with store._lock:
|
|
357
|
+
rows = store._conn.execute(query+"ORDER BY created_at DESC,prediction_id LIMIT 100", parameters).fetchall()
|
|
358
|
+
for row in rows:
|
|
359
|
+
c = json.loads(row[0])['conditions']
|
|
360
|
+
safe(reconcile, c['wait_id'])
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def project(wait_id):
|
|
364
|
+
result = {'status':'no_prospective_forecast', 'suggestion_enabled':False}
|
|
365
|
+
parts = _parts()
|
|
366
|
+
if parts is None:
|
|
367
|
+
return result
|
|
368
|
+
safe(reconcile, wait_id)
|
|
369
|
+
store = parts[0]
|
|
370
|
+
history = store.forecast_history(_fid(wait_id))
|
|
371
|
+
if not history['forecasts']:
|
|
372
|
+
return result
|
|
373
|
+
p = store.get(history['forecasts'][0]['prediction_id'])
|
|
374
|
+
result.update(status='pending' if time.time() < p.horizon else 'deadline_elapsed_unobserved',
|
|
375
|
+
forecast_id=_fid(wait_id), horizon=p.horizon, probability=.5, probability_calibrated=False)
|
|
376
|
+
if not evidence_current(p):
|
|
377
|
+
return {**result, 'status':'source_unavailable'}
|
|
378
|
+
if history['outcomes']:
|
|
379
|
+
facts = _metadata(parts[1], history['outcomes'][-1]['receipt_ref'].removeprefix('receipt:'))
|
|
380
|
+
result['status'] = facts['state']
|
|
381
|
+
return result
|