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/tom/leveled.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Leveled tom2 renderers (L2.2) — level 1 self-reflexive, level 2 silent-prior.
|
|
2
|
+
|
|
3
|
+
Two renderers, one per level above owner-surfacing, both shipped dark
|
|
4
|
+
(nothing live calls them until the wiring tranche):
|
|
5
|
+
|
|
6
|
+
* ``render_level1`` — the READER'S OWN epistemic rows only. ``knows`` rows
|
|
7
|
+
render the reader's own fact text (every ref must resolve to a fact row
|
|
8
|
+
the reader owns — fail closed); ``unaware_of`` rows collapse to ONE
|
|
9
|
+
content-free caution line: no fact text, no counts, no refs, because any
|
|
10
|
+
of those would leak topology about what is being withheld.
|
|
11
|
+
* ``render_level2`` — third-party epistemic topology, consuming ONLY rows
|
|
12
|
+
the L2.1 eligibility pipeline already passed, and rendering each through
|
|
13
|
+
the UNMODIFIED H3.5 gate (``render_inference_for_contact``) — this module
|
|
14
|
+
deliberately owns no visibility logic of its own, so it cannot weaken the
|
|
15
|
+
double gate. Output is framed as a SILENT prior: instructions to never
|
|
16
|
+
volunteer or reference it, only to avoid wrongly assuming shared
|
|
17
|
+
knowledge (M8 is a style, not a safety layer — the safety is upstream).
|
|
18
|
+
|
|
19
|
+
Neither renderer touches the H3.5 functions; both fail closed to None.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
from apsimo.tom.tom2 import (
|
|
28
|
+
_ref_visible_to, render_inference_for_contact)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
#: The one content-free line an unaware_of row about the reader may become.
|
|
33
|
+
UNAWARE_CAUTION = ("Some context has not been shared with this contact yet; "
|
|
34
|
+
"do not assume they know everything you know.")
|
|
35
|
+
|
|
36
|
+
LEVEL2_HEADER = ("Epistemic prior (SILENT — never volunteer, quote, or "
|
|
37
|
+
"reference this; use it only to avoid wrongly assuming "
|
|
38
|
+
"shared knowledge):")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def render_level1(tom2_store: Any, facts_store: Any, reader_contact_id: str,
|
|
42
|
+
*, limit: int = 5) -> Optional[str]:
|
|
43
|
+
"""Self-reflexive prior for the reader: their own knows/unaware rows.
|
|
44
|
+
|
|
45
|
+
Never renders anything about a third party; never renders fact text the
|
|
46
|
+
reader does not own; any error returns None (fail closed)."""
|
|
47
|
+
reader = str(reader_contact_id or "").strip()
|
|
48
|
+
if tom2_store is None or facts_store is None or not reader:
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
rows = tom2_store.list_inferences(contact_id=reader, limit=100)
|
|
52
|
+
except Exception:
|
|
53
|
+
logger.debug("level1 read failed (=> None)", exc_info=True)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
lines: List[str] = []
|
|
57
|
+
saw_unaware = False
|
|
58
|
+
for row in rows:
|
|
59
|
+
if str(row.get("contact_id") or "") != reader:
|
|
60
|
+
continue # never a third party
|
|
61
|
+
kind = row.get("kind")
|
|
62
|
+
if kind == "unaware_of":
|
|
63
|
+
try:
|
|
64
|
+
refs = [row.get('fact_ref')] + list(row.get('evidence_refs') or [])
|
|
65
|
+
if any(facts_store.get_fact(str(ref or '')) is None for ref in refs):
|
|
66
|
+
continue
|
|
67
|
+
except Exception:
|
|
68
|
+
continue
|
|
69
|
+
saw_unaware = True # content-free, collapsed
|
|
70
|
+
continue
|
|
71
|
+
if kind != "knows":
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
refs = [row.get("fact_ref")] + list(row.get("evidence_refs")
|
|
75
|
+
or [])
|
|
76
|
+
if not refs or any(not _ref_visible_to(facts_store, r, reader)
|
|
77
|
+
for r in refs):
|
|
78
|
+
continue # fail closed per row
|
|
79
|
+
fact = facts_store.get_fact(str(row.get("fact_ref")))
|
|
80
|
+
text = str((fact or {}).get("fact") or "").strip()
|
|
81
|
+
except Exception:
|
|
82
|
+
continue
|
|
83
|
+
if not text:
|
|
84
|
+
continue
|
|
85
|
+
lines.append(f"- This contact already knows: {text[:160]}")
|
|
86
|
+
if len(lines) >= max(1, int(limit)):
|
|
87
|
+
break
|
|
88
|
+
if saw_unaware:
|
|
89
|
+
lines.append(f"- {UNAWARE_CAUTION}")
|
|
90
|
+
return "\n".join(lines) if lines else None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def render_level2(eligible_rows: List[Dict[str, Any]], facts_store: Any,
|
|
94
|
+
reader_contact_id: str, *, limit: int = 3) -> Optional[str]:
|
|
95
|
+
"""Silent-prior rendering of ALREADY-ELIGIBLE third-party inferences.
|
|
96
|
+
|
|
97
|
+
Consumes only rows the L2.1 pipeline passed; every line still goes
|
|
98
|
+
through the unmodified H3.5 gate, so even a caller that hands this an
|
|
99
|
+
unvetted row cannot make it render foreign content. None when nothing
|
|
100
|
+
renders (fail closed, no partial hints)."""
|
|
101
|
+
reader = str(reader_contact_id or "").strip()
|
|
102
|
+
if not eligible_rows or facts_store is None or not reader:
|
|
103
|
+
return None
|
|
104
|
+
lines: List[str] = []
|
|
105
|
+
for row in eligible_rows:
|
|
106
|
+
try:
|
|
107
|
+
line = render_inference_for_contact(row, facts_store, reader)
|
|
108
|
+
except Exception:
|
|
109
|
+
logger.debug("level2 render failed for a row (skipped)",
|
|
110
|
+
exc_info=True)
|
|
111
|
+
continue
|
|
112
|
+
if line:
|
|
113
|
+
lines.append(f"- {line}")
|
|
114
|
+
if len(lines) >= max(1, int(limit)):
|
|
115
|
+
break
|
|
116
|
+
if not lines:
|
|
117
|
+
return None
|
|
118
|
+
return "\n".join([LEVEL2_HEADER] + lines)
|
apsimo/tom/levels.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Effective-level resolver for leveled cross-contact tom2 (L1.3).
|
|
2
|
+
|
|
3
|
+
The single place that decides, per conversation and per reader, how much
|
|
4
|
+
second-order theory-of-mind may render THIS TURN:
|
|
5
|
+
|
|
6
|
+
L0 — owner surfacing only (H3.3; today's behavior).
|
|
7
|
+
L1 — self-reflexive prior: the reader's own knows/unaware rows.
|
|
8
|
+
L2 — third-party epistemic topology via the full eligibility pipeline.
|
|
9
|
+
|
|
10
|
+
``resolve_effective_level()`` is a pure min-chain over independent brakes —
|
|
11
|
+
any ONE of them decaying silently drops the level for the turn, no human
|
|
12
|
+
action required:
|
|
13
|
+
|
|
14
|
+
effective = min(COLONY_TOM2_LEVEL [default 0],
|
|
15
|
+
COLONY_TOM2_MAX_LEVEL [default 1],
|
|
16
|
+
cap(environment risk) [COLONY_TOM2_RISK_CAPS],
|
|
17
|
+
2 if live-enforce-evidence else 1,
|
|
18
|
+
2 if COLONY_TOM2_CROSS_CONTEXT else 1)
|
|
19
|
+
... and 0 on ANY error anywhere.
|
|
20
|
+
|
|
21
|
+
The enforce-evidence input is an injected probe (``set_evidence_probe``).
|
|
22
|
+
Candidate-verdict audit rows are deliberately insufficient: a future
|
|
23
|
+
transport-owned mediator must attest the digest of the output it actually
|
|
24
|
+
applied. Until such a receipt-backed probe is wired, the server leaves this
|
|
25
|
+
unset and the min-chain caps the system at L1. Results are cached for <=60s
|
|
26
|
+
per (conversation, reader) to keep the hot path cheap; the cache only ever
|
|
27
|
+
serves a REsolved grade, never a default.
|
|
28
|
+
|
|
29
|
+
Everything here is default-inert: with shipped defaults the resolved level
|
|
30
|
+
is 0 everywhere, so the leveled context wiring renders nothing above the
|
|
31
|
+
existing owner path.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
import os
|
|
38
|
+
import threading
|
|
39
|
+
import time
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
#: cap(env risk) shipped defaults: R0/R1 rooms may reach L2, R2 caps at L1,
|
|
46
|
+
#: R3 (open/hostile, the default grade) renders nothing above L0.
|
|
47
|
+
DEFAULT_RISK_CAPS = "0:2,1:2,2:1,3:0"
|
|
48
|
+
|
|
49
|
+
_FAIL_CLOSED_CAPS = {0: 0, 1: 0, 2: 0, 3: 0}
|
|
50
|
+
|
|
51
|
+
_CACHE_TTL_SECS = 60.0
|
|
52
|
+
_CACHE_MAX = 512
|
|
53
|
+
|
|
54
|
+
_cache: Dict[Any, Any] = {}
|
|
55
|
+
_cache_lock = threading.Lock()
|
|
56
|
+
_evidence_generation = 0
|
|
57
|
+
|
|
58
|
+
#: Injected enforce-evidence probe: Callable[[gateway: str], bool].
|
|
59
|
+
#: None (shipped) => no evidence => the min-chain caps at 1. Set only by a
|
|
60
|
+
#: receipt-backed applied-egress mediator; evaluation-row counts do not qualify.
|
|
61
|
+
_evidence_probe: Optional[Callable[[str], bool]] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_evidence_probe(probe: Optional[Callable[[str], bool]]) -> None:
|
|
65
|
+
"""Wire (or clear) the live enforce-evidence probe. The probe must
|
|
66
|
+
answer 'is the outbound guard PROVABLY enforcing on this gateway?';
|
|
67
|
+
any probe error is treated as False (no proof, no L2)."""
|
|
68
|
+
global _evidence_generation, _evidence_probe
|
|
69
|
+
with _cache_lock:
|
|
70
|
+
_evidence_probe = probe
|
|
71
|
+
_evidence_generation += 1
|
|
72
|
+
# A cached L2 decision from an older probe must not survive a probe
|
|
73
|
+
# downgrade, replacement, shutdown, or startup reconciliation.
|
|
74
|
+
_cache.clear()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def clear_level_cache() -> None:
|
|
78
|
+
with _cache_lock:
|
|
79
|
+
_cache.clear()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _env_int(name: str, default: int, lo: int = 0, hi: int = 2) -> int:
|
|
83
|
+
"""Parse an int env var, clamped; ANY malformation fails closed to 0."""
|
|
84
|
+
raw = os.environ.get(name)
|
|
85
|
+
if raw is None or not str(raw).strip():
|
|
86
|
+
return default
|
|
87
|
+
try:
|
|
88
|
+
v = int(str(raw).strip())
|
|
89
|
+
except (TypeError, ValueError):
|
|
90
|
+
logger.warning("%s=%r is not an integer — failing closed to 0",
|
|
91
|
+
name, raw)
|
|
92
|
+
return 0
|
|
93
|
+
return max(lo, min(hi, v))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def configured_level() -> int:
|
|
97
|
+
"""COLONY_TOM2_LEVEL (default 0): the owner's requested level."""
|
|
98
|
+
return _env_int("COLONY_TOM2_LEVEL", 0)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def configured_max_level() -> int:
|
|
102
|
+
"""COLONY_TOM2_MAX_LEVEL (default 1): the hard ceiling."""
|
|
103
|
+
return _env_int("COLONY_TOM2_MAX_LEVEL", 1)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def risk_caps_valid() -> bool:
|
|
107
|
+
"""True when COLONY_TOM2_RISK_CAPS parses cleanly (doctor surface)."""
|
|
108
|
+
return _parse_risk_caps_strict() is not None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _parse_risk_caps_strict() -> Optional[Dict[int, int]]:
|
|
112
|
+
raw = os.environ.get("COLONY_TOM2_RISK_CAPS", DEFAULT_RISK_CAPS).strip()
|
|
113
|
+
caps: Dict[int, int] = {}
|
|
114
|
+
try:
|
|
115
|
+
for pair in raw.split(","):
|
|
116
|
+
k, v = pair.split(":")
|
|
117
|
+
risk, cap = int(k.strip()), int(v.strip())
|
|
118
|
+
if risk not in (0, 1, 2, 3) or not (0 <= cap <= 2):
|
|
119
|
+
return None
|
|
120
|
+
if risk in caps:
|
|
121
|
+
return None
|
|
122
|
+
caps[risk] = cap
|
|
123
|
+
except (TypeError, ValueError):
|
|
124
|
+
return None
|
|
125
|
+
if set(caps) != {0, 1, 2, 3}:
|
|
126
|
+
return None
|
|
127
|
+
return caps
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def parse_risk_caps() -> Dict[int, int]:
|
|
131
|
+
"""Risk->cap map from COLONY_TOM2_RISK_CAPS (default '0:2,1:2,2:1,3:0').
|
|
132
|
+
|
|
133
|
+
A malformed value fails closed to all-zero caps (every environment
|
|
134
|
+
renders L0 only) — and the doctor WARNs about it, because silently
|
|
135
|
+
running with all-0 caps while the owner believes their custom caps are
|
|
136
|
+
live is a posture mismatch worth surfacing."""
|
|
137
|
+
caps = _parse_risk_caps_strict()
|
|
138
|
+
if caps is None:
|
|
139
|
+
logger.warning("COLONY_TOM2_RISK_CAPS=%r is malformed — failing "
|
|
140
|
+
"closed to all-0 caps",
|
|
141
|
+
os.environ.get("COLONY_TOM2_RISK_CAPS"))
|
|
142
|
+
return dict(_FAIL_CLOSED_CAPS)
|
|
143
|
+
return caps
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class LevelResolution:
|
|
148
|
+
level: int # the effective level, 0..2
|
|
149
|
+
env_risk: Optional[int] = None # 0..3, None on error
|
|
150
|
+
terms: Dict[str, int] = field(default_factory=dict)
|
|
151
|
+
reasons: List[str] = field(default_factory=list)
|
|
152
|
+
|
|
153
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
154
|
+
return {"level": self.level, "env_risk": self.env_risk,
|
|
155
|
+
"terms": dict(self.terms), "reasons": list(self.reasons)}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _probe_evidence(gateway: str) -> bool:
|
|
159
|
+
"""The injected enforce-evidence probe, failed closed on every edge:
|
|
160
|
+
no probe wired => False; probe raises => False."""
|
|
161
|
+
probe = _evidence_probe
|
|
162
|
+
if probe is None:
|
|
163
|
+
return False
|
|
164
|
+
try:
|
|
165
|
+
return bool(probe(gateway))
|
|
166
|
+
except Exception:
|
|
167
|
+
logger.debug("enforce-evidence probe failed (=> no evidence)",
|
|
168
|
+
exc_info=True)
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def resolve_effective_level(
|
|
173
|
+
conversation_key: str,
|
|
174
|
+
reader_contact_id: str,
|
|
175
|
+
*,
|
|
176
|
+
presence_store: Any,
|
|
177
|
+
contacts_store: Any,
|
|
178
|
+
owner_id: Optional[str] = None,
|
|
179
|
+
use_cache: bool = True,
|
|
180
|
+
) -> LevelResolution:
|
|
181
|
+
"""Resolve the effective tom2 level for (conversation, reader).
|
|
182
|
+
|
|
183
|
+
Never raises; ANY internal error resolves level 0. Results are cached
|
|
184
|
+
for <=60s per pair (a decayed brake takes effect within a minute)."""
|
|
185
|
+
key = (str(conversation_key or ""), str(reader_contact_id or ""))
|
|
186
|
+
for _attempt in range(2):
|
|
187
|
+
now = time.monotonic()
|
|
188
|
+
with _cache_lock:
|
|
189
|
+
generation = _evidence_generation
|
|
190
|
+
hit = _cache.get(key) if use_cache else None
|
|
191
|
+
if (hit is not None and hit[1] == generation
|
|
192
|
+
and now - hit[0] <= _CACHE_TTL_SECS):
|
|
193
|
+
return hit[2]
|
|
194
|
+
try:
|
|
195
|
+
resolution = await _resolve(
|
|
196
|
+
conversation_key, reader_contact_id,
|
|
197
|
+
presence_store=presence_store,
|
|
198
|
+
contacts_store=contacts_store,
|
|
199
|
+
owner_id=owner_id,
|
|
200
|
+
)
|
|
201
|
+
except Exception as exc:
|
|
202
|
+
logger.debug("effective-level resolution failed (=> 0): %s", exc,
|
|
203
|
+
exc_info=True)
|
|
204
|
+
resolution = LevelResolution(
|
|
205
|
+
level=0, reasons=[f"resolver-error:{type(exc).__name__}"])
|
|
206
|
+
with _cache_lock:
|
|
207
|
+
if generation != _evidence_generation:
|
|
208
|
+
# Probe authority changed while an async environment lookup was
|
|
209
|
+
# in flight. Re-resolve once instead of returning/caching a
|
|
210
|
+
# grade calculated under the superseded authority source.
|
|
211
|
+
continue
|
|
212
|
+
if len(_cache) >= _CACHE_MAX:
|
|
213
|
+
_cache.clear()
|
|
214
|
+
_cache[key] = (now, generation, resolution)
|
|
215
|
+
return resolution
|
|
216
|
+
|
|
217
|
+
# A probe that churns twice during one resolution is not stable evidence.
|
|
218
|
+
return LevelResolution(level=0, reasons=["evidence-probe-unstable"])
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def _resolve(conversation_key: str, reader_contact_id: str, *,
|
|
222
|
+
presence_store: Any, contacts_store: Any,
|
|
223
|
+
owner_id: Optional[str]) -> LevelResolution:
|
|
224
|
+
from apsimo.gate.env_risk import classify
|
|
225
|
+
from apsimo.tom.tom2 import tom2_cross_context_enabled
|
|
226
|
+
|
|
227
|
+
terms: Dict[str, int] = {}
|
|
228
|
+
reasons: List[str] = []
|
|
229
|
+
|
|
230
|
+
terms["configured"] = configured_level()
|
|
231
|
+
terms["max"] = configured_max_level()
|
|
232
|
+
|
|
233
|
+
risk = await classify(conversation_key, reader_contact_id,
|
|
234
|
+
presence_store=presence_store,
|
|
235
|
+
contacts_store=contacts_store, owner_id=owner_id)
|
|
236
|
+
caps = parse_risk_caps()
|
|
237
|
+
terms["risk_cap"] = caps.get(risk.level, 0)
|
|
238
|
+
reasons.extend(f"env:{r}" for r in risk.reasons[:4])
|
|
239
|
+
|
|
240
|
+
gateway = str(conversation_key or "").split(":", 1)[0]
|
|
241
|
+
terms["enforce_evidence"] = 2 if _probe_evidence(gateway) else 1
|
|
242
|
+
terms["cross_context"] = 2 if tom2_cross_context_enabled() else 1
|
|
243
|
+
|
|
244
|
+
level = min(terms.values())
|
|
245
|
+
level = max(0, min(2, level))
|
|
246
|
+
return LevelResolution(level=level, env_risk=risk.level, terms=terms,
|
|
247
|
+
reasons=reasons)
|