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,353 @@
|
|
|
1
|
+
"""GuardAuditStore — durable record of response-guard evaluations.
|
|
2
|
+
|
|
3
|
+
Two kinds of rows:
|
|
4
|
+
|
|
5
|
+
* ``guard_events`` — one row per evaluation that produced ANY finding or a
|
|
6
|
+
non-allow decision (not just cross_context). ``would_block`` marks whether
|
|
7
|
+
the findings would have suppressed the reply under enforce, regardless of
|
|
8
|
+
the mode actually running — this is what a false-positive budget is
|
|
9
|
+
measured against while the guard is still in shadow.
|
|
10
|
+
* ``guard_eval_days`` — a per-UTC-day counter of TOTAL evaluations (clean or
|
|
11
|
+
not), the denominator that turns finding counts into rates.
|
|
12
|
+
* ``guard_communication_policy_evaluations`` — one digest-bound row for every
|
|
13
|
+
evaluation carrying a versioned communication policy, including clean
|
|
14
|
+
allows. It is separate so those rows do not alter false-positive metrics.
|
|
15
|
+
|
|
16
|
+
``summary()`` reports the historical authorized/unauthorized split plus, for
|
|
17
|
+
24h/7d/14d windows: evaluations, flagged events, per-check counts and the
|
|
18
|
+
``would_block_rate`` (would-block events / evaluations). Evaluation counts
|
|
19
|
+
are day-granular, so the "24h" window is really "today + yesterday" (UTC) —
|
|
20
|
+
close enough for budget tracking, cheap enough to keep forever.
|
|
21
|
+
|
|
22
|
+
Generic: ``conversation_key`` is an opaque host-supplied id; the store
|
|
23
|
+
attaches no meaning.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import sqlite3
|
|
30
|
+
from datetime import datetime, timedelta, timezone
|
|
31
|
+
from hashlib import sha256
|
|
32
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
33
|
+
|
|
34
|
+
from apsimo.gate.communication_policy import CommunicationPolicyContextV1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def evidence_min() -> int:
|
|
38
|
+
"""Parse the legacy verdict-row threshold configuration.
|
|
39
|
+
|
|
40
|
+
Retained for configuration compatibility and diagnostics. No value makes
|
|
41
|
+
candidate-verdict rows applied-enforcement evidence; ``enforce_evidence``
|
|
42
|
+
remains False until a receipt-backed egress mediator exists.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
return max(1, int(os.environ.get("COLONY_GUARD_EVIDENCE_MIN", "3")))
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
return 3
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _now() -> str:
|
|
51
|
+
return datetime.now(tz=timezone.utc).isoformat()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _today() -> str:
|
|
55
|
+
return datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GuardAuditStore:
|
|
59
|
+
def __init__(self, db_path: str = ":memory:") -> None:
|
|
60
|
+
self._conn = sqlite3.connect(db_path)
|
|
61
|
+
self._conn.row_factory = sqlite3.Row
|
|
62
|
+
self._conn.execute("PRAGMA journal_mode=WAL")
|
|
63
|
+
self._create_tables()
|
|
64
|
+
|
|
65
|
+
def _create_tables(self) -> None:
|
|
66
|
+
self._conn.executescript(
|
|
67
|
+
"""
|
|
68
|
+
CREATE TABLE IF NOT EXISTS guard_events (
|
|
69
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
ts TEXT NOT NULL,
|
|
71
|
+
conversation_key TEXT,
|
|
72
|
+
mode TEXT,
|
|
73
|
+
decision TEXT,
|
|
74
|
+
authorized INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
checks TEXT,
|
|
76
|
+
entities TEXT,
|
|
77
|
+
response_excerpt TEXT
|
|
78
|
+
);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_guard_events_ts ON guard_events(ts);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_guard_events_auth ON guard_events(authorized);
|
|
81
|
+
CREATE TABLE IF NOT EXISTS guard_eval_days (
|
|
82
|
+
day TEXT PRIMARY KEY,
|
|
83
|
+
evaluations INTEGER NOT NULL DEFAULT 0
|
|
84
|
+
);
|
|
85
|
+
CREATE TABLE IF NOT EXISTS guard_communication_policy_evaluations (
|
|
86
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
|
+
ts TEXT NOT NULL,
|
|
88
|
+
conversation_key TEXT,
|
|
89
|
+
mode TEXT NOT NULL,
|
|
90
|
+
decision TEXT NOT NULL,
|
|
91
|
+
authorized INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
gateway TEXT,
|
|
93
|
+
surface TEXT NOT NULL,
|
|
94
|
+
surface_policy_id TEXT NOT NULL,
|
|
95
|
+
surface_policy_digest TEXT NOT NULL,
|
|
96
|
+
candidate_digest TEXT NOT NULL,
|
|
97
|
+
guard_status TEXT NOT NULL,
|
|
98
|
+
target_contact_id TEXT NOT NULL,
|
|
99
|
+
communication_schema TEXT NOT NULL,
|
|
100
|
+
communication_version INTEGER NOT NULL,
|
|
101
|
+
communication_context_digest TEXT NOT NULL,
|
|
102
|
+
route_id TEXT NOT NULL,
|
|
103
|
+
grant_id TEXT NOT NULL,
|
|
104
|
+
grant_digest TEXT NOT NULL,
|
|
105
|
+
communication_policy_id TEXT NOT NULL,
|
|
106
|
+
communication_policy_digest TEXT NOT NULL,
|
|
107
|
+
disclosure_class TEXT NOT NULL,
|
|
108
|
+
grants_execution_authority INTEGER NOT NULL
|
|
109
|
+
CHECK(grants_execution_authority = 0),
|
|
110
|
+
grants_control_authority INTEGER NOT NULL
|
|
111
|
+
CHECK(grants_control_authority = 0),
|
|
112
|
+
selects_owner_private_context INTEGER NOT NULL
|
|
113
|
+
CHECK(selects_owner_private_context = 0)
|
|
114
|
+
);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_guard_comm_policy_ts
|
|
116
|
+
ON guard_communication_policy_evaluations(ts);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_guard_comm_policy_context
|
|
118
|
+
ON guard_communication_policy_evaluations(
|
|
119
|
+
communication_context_digest
|
|
120
|
+
);
|
|
121
|
+
"""
|
|
122
|
+
)
|
|
123
|
+
# Migrations (additive): pre-existing DBs lack these columns.
|
|
124
|
+
cols = {r["name"] for r in self._conn.execute(
|
|
125
|
+
"PRAGMA table_info(guard_events)").fetchall()}
|
|
126
|
+
if "would_block" not in cols:
|
|
127
|
+
self._conn.execute(
|
|
128
|
+
"ALTER TABLE guard_events ADD COLUMN would_block INTEGER NOT NULL DEFAULT 0")
|
|
129
|
+
if "gateway" not in cols:
|
|
130
|
+
# Nullable: rows recorded before the gateway was threaded stay
|
|
131
|
+
# NULL and can never satisfy a per-gateway evidence query.
|
|
132
|
+
self._conn.execute(
|
|
133
|
+
"ALTER TABLE guard_events ADD COLUMN gateway TEXT")
|
|
134
|
+
if "surface" not in cols:
|
|
135
|
+
# Legacy rows remain NULL: an unclassified row cannot prove that
|
|
136
|
+
# the current exact-surface policy was enforced.
|
|
137
|
+
self._conn.execute(
|
|
138
|
+
"ALTER TABLE guard_events ADD COLUMN surface TEXT")
|
|
139
|
+
if "policy_id" not in cols:
|
|
140
|
+
self._conn.execute(
|
|
141
|
+
"ALTER TABLE guard_events ADD COLUMN policy_id TEXT")
|
|
142
|
+
if "policy_digest" not in cols:
|
|
143
|
+
# A policy name alone is not an immutable policy identity. Legacy
|
|
144
|
+
# rows remain NULL and therefore cannot be mistaken for a verdict
|
|
145
|
+
# produced by the current exact policy document.
|
|
146
|
+
self._conn.execute(
|
|
147
|
+
"ALTER TABLE guard_events ADD COLUMN policy_digest TEXT")
|
|
148
|
+
if "candidate_digest" not in cols:
|
|
149
|
+
# Exact identity of the candidate bytes that were evaluated. This
|
|
150
|
+
# still does not prove a transport applied the verdict.
|
|
151
|
+
self._conn.execute(
|
|
152
|
+
"ALTER TABLE guard_events ADD COLUMN candidate_digest TEXT")
|
|
153
|
+
if "guard_status" not in cols:
|
|
154
|
+
self._conn.execute(
|
|
155
|
+
"ALTER TABLE guard_events ADD COLUMN guard_status TEXT")
|
|
156
|
+
self._conn.commit()
|
|
157
|
+
|
|
158
|
+
def count_evaluation(self) -> None:
|
|
159
|
+
"""One evaluation happened (finding or not) — bump today's counter."""
|
|
160
|
+
self._conn.execute(
|
|
161
|
+
"INSERT INTO guard_eval_days (day, evaluations) VALUES (?, 1) "
|
|
162
|
+
"ON CONFLICT(day) DO UPDATE SET evaluations = evaluations + 1",
|
|
163
|
+
(_today(),),
|
|
164
|
+
)
|
|
165
|
+
self._conn.commit()
|
|
166
|
+
|
|
167
|
+
def record(self, *, conversation_key: Optional[str], mode: str, decision: str,
|
|
168
|
+
authorized: bool, checks: Sequence[str], entities: Sequence[str],
|
|
169
|
+
response_text: str = "", would_block: bool = False,
|
|
170
|
+
gateway: Optional[str] = None, surface: Optional[str] = None,
|
|
171
|
+
policy_id: Optional[str] = None,
|
|
172
|
+
policy_digest: Optional[str] = None,
|
|
173
|
+
guard_status: str = "evaluated") -> None:
|
|
174
|
+
candidate_digest = sha256(
|
|
175
|
+
str(response_text or "").encode("utf-8")
|
|
176
|
+
).hexdigest()
|
|
177
|
+
self._conn.execute(
|
|
178
|
+
"INSERT INTO guard_events (ts, conversation_key, mode, decision, authorized, "
|
|
179
|
+
"checks, entities, response_excerpt, would_block, gateway, surface, "
|
|
180
|
+
"policy_id, policy_digest, candidate_digest, guard_status) "
|
|
181
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
182
|
+
(_now(), conversation_key, mode, decision, 1 if authorized else 0,
|
|
183
|
+
",".join(checks), ",".join(entities), (response_text or "")[:240],
|
|
184
|
+
1 if would_block else 0, (gateway or "").strip().lower() or None,
|
|
185
|
+
surface, policy_id, policy_digest, candidate_digest, guard_status),
|
|
186
|
+
)
|
|
187
|
+
self._conn.commit()
|
|
188
|
+
|
|
189
|
+
def recent(self, *, limit: int = 50, authorized: Optional[bool] = None,
|
|
190
|
+
check: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
191
|
+
sql = "SELECT * FROM guard_events"
|
|
192
|
+
where: list = []
|
|
193
|
+
params: list = []
|
|
194
|
+
if authorized is not None:
|
|
195
|
+
where.append("authorized = ?")
|
|
196
|
+
params.append(1 if authorized else 0)
|
|
197
|
+
if check:
|
|
198
|
+
# checks is a comma-joined list; match the whole token.
|
|
199
|
+
where.append("(',' || checks || ',') LIKE ?")
|
|
200
|
+
params.append(f"%,{check},%")
|
|
201
|
+
if where:
|
|
202
|
+
sql += " WHERE " + " AND ".join(where)
|
|
203
|
+
sql += " ORDER BY id DESC LIMIT ?"
|
|
204
|
+
params.append(int(limit))
|
|
205
|
+
return [dict(r) for r in self._conn.execute(sql, params).fetchall()]
|
|
206
|
+
|
|
207
|
+
def record_communication_policy(
|
|
208
|
+
self,
|
|
209
|
+
*,
|
|
210
|
+
conversation_key: Optional[str],
|
|
211
|
+
mode: str,
|
|
212
|
+
decision: str,
|
|
213
|
+
authorized: bool,
|
|
214
|
+
gateway: Optional[str],
|
|
215
|
+
surface: str,
|
|
216
|
+
surface_policy_id: str,
|
|
217
|
+
surface_policy_digest: str,
|
|
218
|
+
response_text: str,
|
|
219
|
+
guard_status: str,
|
|
220
|
+
target_contact_id: str,
|
|
221
|
+
communication_policy: CommunicationPolicyContextV1,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Record one exact communication-policy/result binding.
|
|
224
|
+
|
|
225
|
+
Purpose and disclosure-statement content are deliberately not copied
|
|
226
|
+
into the durable audit DB. Their complete canonical identity is
|
|
227
|
+
retained by ``communication_context_digest`` while the minimum useful
|
|
228
|
+
route/policy identifiers remain queryable.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
if not isinstance(communication_policy, CommunicationPolicyContextV1):
|
|
232
|
+
raise TypeError(
|
|
233
|
+
"communication_policy must be CommunicationPolicyContextV1"
|
|
234
|
+
)
|
|
235
|
+
communication_policy = CommunicationPolicyContextV1.model_validate(
|
|
236
|
+
communication_policy.canonical_dict()
|
|
237
|
+
)
|
|
238
|
+
if target_contact_id != communication_policy.target_contact_id:
|
|
239
|
+
raise ValueError("communication policy audit target mismatch")
|
|
240
|
+
candidate_digest = sha256(
|
|
241
|
+
str(response_text or "").encode("utf-8")
|
|
242
|
+
).hexdigest()
|
|
243
|
+
self._conn.execute(
|
|
244
|
+
"INSERT INTO guard_communication_policy_evaluations ("
|
|
245
|
+
"ts,conversation_key,mode,decision,authorized,gateway,surface,"
|
|
246
|
+
"surface_policy_id,surface_policy_digest,candidate_digest,"
|
|
247
|
+
"guard_status,target_contact_id,communication_schema,"
|
|
248
|
+
"communication_version,communication_context_digest,route_id,"
|
|
249
|
+
"grant_id,grant_digest,"
|
|
250
|
+
"communication_policy_id,communication_policy_digest,"
|
|
251
|
+
"disclosure_class,grants_execution_authority,"
|
|
252
|
+
"grants_control_authority,selects_owner_private_context) "
|
|
253
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
254
|
+
(
|
|
255
|
+
_now(),
|
|
256
|
+
conversation_key,
|
|
257
|
+
mode,
|
|
258
|
+
decision,
|
|
259
|
+
1 if authorized else 0,
|
|
260
|
+
(gateway or "").strip().lower() or None,
|
|
261
|
+
surface,
|
|
262
|
+
surface_policy_id,
|
|
263
|
+
surface_policy_digest,
|
|
264
|
+
candidate_digest,
|
|
265
|
+
guard_status,
|
|
266
|
+
target_contact_id,
|
|
267
|
+
communication_policy.schema_name,
|
|
268
|
+
communication_policy.version,
|
|
269
|
+
communication_policy.context_digest,
|
|
270
|
+
communication_policy.route_id,
|
|
271
|
+
communication_policy.grant_id,
|
|
272
|
+
communication_policy.grant_digest,
|
|
273
|
+
communication_policy.policy_id,
|
|
274
|
+
communication_policy.policy_digest,
|
|
275
|
+
communication_policy.disclosure_class,
|
|
276
|
+
0,
|
|
277
|
+
0,
|
|
278
|
+
0,
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
self._conn.commit()
|
|
282
|
+
|
|
283
|
+
def recent_communication_policy(self, *, limit: int = 50) -> List[Dict[str, Any]]:
|
|
284
|
+
"""Return newest digest-bound communication-policy evaluations."""
|
|
285
|
+
|
|
286
|
+
rows = self._conn.execute(
|
|
287
|
+
"SELECT * FROM guard_communication_policy_evaluations "
|
|
288
|
+
"ORDER BY id DESC LIMIT ?",
|
|
289
|
+
(max(0, int(limit)),),
|
|
290
|
+
).fetchall()
|
|
291
|
+
return [dict(row) for row in rows]
|
|
292
|
+
|
|
293
|
+
def _window(self, days: int) -> Dict[str, Any]:
|
|
294
|
+
now = datetime.now(tz=timezone.utc)
|
|
295
|
+
ts_cutoff = (now - timedelta(days=days)).isoformat()
|
|
296
|
+
day_cutoff = (now - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
297
|
+
evals = self._conn.execute(
|
|
298
|
+
"SELECT COALESCE(SUM(evaluations), 0) n FROM guard_eval_days WHERE day >= ?",
|
|
299
|
+
(day_cutoff,)).fetchone()["n"]
|
|
300
|
+
rows = self._conn.execute(
|
|
301
|
+
"SELECT checks, would_block FROM guard_events WHERE ts >= ?",
|
|
302
|
+
(ts_cutoff,)).fetchall()
|
|
303
|
+
by_check: Dict[str, int] = {}
|
|
304
|
+
would_block = 0
|
|
305
|
+
for r in rows:
|
|
306
|
+
if r["would_block"]:
|
|
307
|
+
would_block += 1
|
|
308
|
+
for c in (r["checks"] or "").split(","):
|
|
309
|
+
c = c.strip()
|
|
310
|
+
if c:
|
|
311
|
+
by_check[c] = by_check.get(c, 0) + 1
|
|
312
|
+
return {
|
|
313
|
+
"evaluations": int(evals),
|
|
314
|
+
"flagged_events": len(rows),
|
|
315
|
+
"would_block": would_block,
|
|
316
|
+
"would_block_rate": round(would_block / evals, 4) if evals else None,
|
|
317
|
+
"by_check": by_check,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
def enforce_evidence(self, gateway: str, hours: float = 24.0,
|
|
321
|
+
surface: str = "text_chat") -> bool:
|
|
322
|
+
"""Return False until an applied-output receipt store is wired.
|
|
323
|
+
|
|
324
|
+
``guard_events`` proves only that Colony evaluated candidate bytes. It
|
|
325
|
+
cannot prove that a transport actually withheld those bytes or emitted
|
|
326
|
+
a checked revision. Counting verdict rows as applied enforcement would
|
|
327
|
+
let a caller unlock higher-trust cognition while ignoring every verdict,
|
|
328
|
+
so this compatibility method deliberately remains fail-closed.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
del gateway, hours, surface
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def summary(self) -> Dict[str, Any]:
|
|
335
|
+
"""All-time authorized/unauthorized split + windowed rates for the
|
|
336
|
+
false-positive budget (see module docstring for granularity)."""
|
|
337
|
+
rows = self._conn.execute(
|
|
338
|
+
"SELECT authorized, COUNT(*) n FROM guard_events GROUP BY authorized"
|
|
339
|
+
).fetchall()
|
|
340
|
+
by_auth = {("authorized" if r["authorized"] else "unauthorized"): r["n"] for r in rows}
|
|
341
|
+
total = sum(by_auth.values())
|
|
342
|
+
return {"total": total,
|
|
343
|
+
"authorized_transfers": by_auth.get("authorized", 0),
|
|
344
|
+
"unauthorized_flags": by_auth.get("unauthorized", 0),
|
|
345
|
+
"windows": {"24h": self._window(1),
|
|
346
|
+
"7d": self._window(7),
|
|
347
|
+
"14d": self._window(14)}}
|
|
348
|
+
|
|
349
|
+
def close(self) -> None:
|
|
350
|
+
try:
|
|
351
|
+
self._conn.close()
|
|
352
|
+
except Exception:
|
|
353
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Gate layer implementations."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Base LayerResult dataclass shared by all gate layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class LayerResult:
|
|
11
|
+
blocked: bool
|
|
12
|
+
code: str
|
|
13
|
+
reason: Optional[str] = None
|
|
14
|
+
flagged_excerpt: Optional[str] = None
|
|
15
|
+
suspicious: bool = False
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Layer 1 — Recipient verification. Fully deterministic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from apsimo.gate.layers.base import LayerResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RecipientVerifier:
|
|
9
|
+
"""Layer 1 — Recipient verification. Fully deterministic."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, session_store) -> None:
|
|
12
|
+
self._sessions = session_store
|
|
13
|
+
|
|
14
|
+
async def check(self, payload) -> LayerResult:
|
|
15
|
+
# Pass through when no session store configured or empty session_id
|
|
16
|
+
# (direct API calls without an established session)
|
|
17
|
+
if self._sessions is None or not payload.session_id:
|
|
18
|
+
return LayerResult(blocked=False, code="pass", reason="no session context")
|
|
19
|
+
|
|
20
|
+
session = await self._sessions.get(payload.session_id)
|
|
21
|
+
|
|
22
|
+
if session is None:
|
|
23
|
+
return LayerResult(
|
|
24
|
+
blocked=True,
|
|
25
|
+
code="block_recipient",
|
|
26
|
+
reason=f"session {payload.session_id!r} not found",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if not payload.target_contact_id:
|
|
30
|
+
return LayerResult(
|
|
31
|
+
blocked=True,
|
|
32
|
+
code="block_recipient",
|
|
33
|
+
reason="target_contact_id is empty or None",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not payload.target_gateway:
|
|
37
|
+
return LayerResult(
|
|
38
|
+
blocked=True,
|
|
39
|
+
code="block_recipient",
|
|
40
|
+
reason="target_gateway is empty or None",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if payload.target_contact_id != session.contact_id:
|
|
44
|
+
return LayerResult(
|
|
45
|
+
blocked=True,
|
|
46
|
+
code="block_recipient",
|
|
47
|
+
reason=(
|
|
48
|
+
f"payload.target_contact_id={payload.target_contact_id!r} "
|
|
49
|
+
f"!= session.contact_id={session.contact_id!r}"
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
contact_gateways = await self._sessions.get_contact_gateways(
|
|
54
|
+
payload.target_contact_id
|
|
55
|
+
)
|
|
56
|
+
if payload.target_gateway not in contact_gateways:
|
|
57
|
+
return LayerResult(
|
|
58
|
+
blocked=True,
|
|
59
|
+
code="block_recipient",
|
|
60
|
+
reason=(
|
|
61
|
+
f"gateway {payload.target_gateway!r} not in known gateways "
|
|
62
|
+
f"for contact {payload.target_contact_id!r}"
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return LayerResult(blocked=False, code="pass")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Layer 2 — PII and credential scan. Fully deterministic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from apsimo.gate.layers.base import LayerResult
|
|
10
|
+
|
|
11
|
+
# Reuse patterns from agent/redact.py
|
|
12
|
+
from apsimo.redact import (
|
|
13
|
+
_PREFIX_RE,
|
|
14
|
+
_ENV_ASSIGN_RE,
|
|
15
|
+
_JSON_FIELD_RE,
|
|
16
|
+
_AUTH_HEADER_RE,
|
|
17
|
+
_PRIVATE_KEY_RE,
|
|
18
|
+
_SIGNAL_PHONE_RE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Additional outbound-message patterns
|
|
22
|
+
_SSN_RE = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")
|
|
23
|
+
_CARD_RE = re.compile(r"\b(?:\d[ -]?){13,19}\b")
|
|
24
|
+
_CARD_CONTEXT_RE = re.compile(
|
|
25
|
+
r"\b(card|cc|credit|debit|visa|mastercard|amex|payment|pan|cvv|cvc)\b",
|
|
26
|
+
re.IGNORECASE,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _has_card_context(text: str, match_start: int, match_end: int, window: int = 50) -> bool:
|
|
31
|
+
"""Return True if a card-context keyword appears within `window` chars of the match."""
|
|
32
|
+
lo = max(0, match_start - window)
|
|
33
|
+
hi = min(len(text), match_end + window)
|
|
34
|
+
return bool(_CARD_CONTEXT_RE.search(text[lo:hi]))
|
|
35
|
+
_EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
|
|
36
|
+
_IBAN_RE = re.compile(r"\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}(?:[A-Z0-9]?){0,16}\b")
|
|
37
|
+
|
|
38
|
+
_OUTBOUND_PATTERNS = [
|
|
39
|
+
("ssn", _SSN_RE),
|
|
40
|
+
("credit_card", _CARD_RE),
|
|
41
|
+
("email", _EMAIL_RE),
|
|
42
|
+
("iban", _IBAN_RE),
|
|
43
|
+
("api_key", _PREFIX_RE),
|
|
44
|
+
("env_secret", _ENV_ASSIGN_RE),
|
|
45
|
+
("json_secret", _JSON_FIELD_RE),
|
|
46
|
+
("auth_header", _AUTH_HEADER_RE),
|
|
47
|
+
("private_key", _PRIVATE_KEY_RE),
|
|
48
|
+
("phone", _SIGNAL_PHONE_RE),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _luhn_valid(number_str: str) -> bool:
|
|
53
|
+
digits = [int(d) for d in number_str if d.isdigit()]
|
|
54
|
+
if len(digits) < 13:
|
|
55
|
+
return False
|
|
56
|
+
odd_digits = digits[-1::-2]
|
|
57
|
+
even_digits = digits[-2::-2]
|
|
58
|
+
total = sum(odd_digits)
|
|
59
|
+
for d in even_digits:
|
|
60
|
+
total += sum(divmod(d * 2, 10))
|
|
61
|
+
return total % 10 == 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PIIScanner:
|
|
65
|
+
"""Layer 2 — PII and credential scan. Fully deterministic."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, config) -> None:
|
|
68
|
+
self._config = config
|
|
69
|
+
# Compile custom PII patterns from config
|
|
70
|
+
self._custom_patterns = []
|
|
71
|
+
for cp in getattr(config, "custom_pii_patterns", []):
|
|
72
|
+
try:
|
|
73
|
+
compiled = re.compile(cp["pattern"])
|
|
74
|
+
self._custom_patterns.append((cp["name"], compiled))
|
|
75
|
+
except (KeyError, re.error):
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
async def check(self, payload) -> LayerResult:
|
|
79
|
+
text = payload.response_text
|
|
80
|
+
all_patterns = list(_OUTBOUND_PATTERNS) + self._custom_patterns
|
|
81
|
+
|
|
82
|
+
for pattern_name, pattern in all_patterns:
|
|
83
|
+
match = pattern.search(text)
|
|
84
|
+
if match:
|
|
85
|
+
# Credit card: only block if Luhn-valid and in card context (reduces false positives)
|
|
86
|
+
if pattern_name == "credit_card":
|
|
87
|
+
digits = re.sub(r"[ -]", "", match.group())
|
|
88
|
+
if not _luhn_valid(digits):
|
|
89
|
+
continue
|
|
90
|
+
if not _has_card_context(text, match.start(), match.end()):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Owner-shareable exception
|
|
94
|
+
if self._config.is_owner_shareable(match.group(), pattern_name):
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Code-context entropy check in RELAXED mode
|
|
98
|
+
if (
|
|
99
|
+
self._config.sensitivity == "relaxed"
|
|
100
|
+
and self._is_dummy_in_code_block(text, match)
|
|
101
|
+
):
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
excerpt = self._redact_excerpt(match.group())
|
|
105
|
+
return LayerResult(
|
|
106
|
+
blocked=True,
|
|
107
|
+
code="block_pii",
|
|
108
|
+
reason=f"pattern match: {pattern_name}",
|
|
109
|
+
flagged_excerpt=excerpt,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return LayerResult(blocked=False, code="pass")
|
|
113
|
+
|
|
114
|
+
def _redact_excerpt(self, raw: str) -> str:
|
|
115
|
+
if len(raw) <= 4:
|
|
116
|
+
return "***"
|
|
117
|
+
return raw[:2] + "***" + raw[-2:]
|
|
118
|
+
|
|
119
|
+
def _is_dummy_in_code_block(self, text: str, match: re.Match) -> bool:
|
|
120
|
+
"""Heuristic: is this match inside a code fence with low entropy?"""
|
|
121
|
+
start = match.start()
|
|
122
|
+
preceding = text[max(0, start - 200):start]
|
|
123
|
+
if "```" in preceding or "`" in preceding:
|
|
124
|
+
token = match.group()
|
|
125
|
+
if not token:
|
|
126
|
+
return False
|
|
127
|
+
freq: dict = {}
|
|
128
|
+
for c in token:
|
|
129
|
+
freq[c] = freq.get(c, 0) + 1
|
|
130
|
+
entropy = -sum(
|
|
131
|
+
(v / len(token)) * math.log2(v / len(token)) for v in freq.values()
|
|
132
|
+
)
|
|
133
|
+
return entropy < 2.5 # low-entropy dummy tokens
|
|
134
|
+
return False
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Layer 3 — Cross-session entity leak detection. Fully deterministic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import unicodedata
|
|
6
|
+
|
|
7
|
+
from apsimo.gate.layers.base import LayerResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _normalize_entity(name: str) -> str:
|
|
11
|
+
return unicodedata.normalize("NFKC", name).lower().strip()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CrossContextDetector:
|
|
15
|
+
"""Layer 3 — Cross-session entity leak detection. Fully deterministic."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, session_store, config) -> None:
|
|
18
|
+
self._sessions = session_store
|
|
19
|
+
self._config = config
|
|
20
|
+
|
|
21
|
+
async def check(self, payload) -> LayerResult:
|
|
22
|
+
# Allow briefing mode to skip cross-context check (controlled cross-session read)
|
|
23
|
+
if getattr(payload, "is_briefing", False):
|
|
24
|
+
return LayerResult(blocked=False, code="pass")
|
|
25
|
+
|
|
26
|
+
response_lower = _normalize_entity(payload.response_text)
|
|
27
|
+
current_entities = {_normalize_entity(e) for e in payload.mentioned_entities}
|
|
28
|
+
|
|
29
|
+
# Load entities from other recent sessions
|
|
30
|
+
other_sessions = await self._sessions.get_recent_other_sessions(
|
|
31
|
+
exclude_session_id=payload.session_id,
|
|
32
|
+
lookback_hours=self._config.cross_context_lookback_hours,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
for other_session_id, other_entities in other_sessions.items():
|
|
36
|
+
for entity in other_entities:
|
|
37
|
+
normalized = _normalize_entity(entity)
|
|
38
|
+
if normalized in current_entities:
|
|
39
|
+
continue # Entity is in this session's context — OK
|
|
40
|
+
if len(normalized) < 3:
|
|
41
|
+
continue # Skip very short tokens — too many false positives
|
|
42
|
+
if normalized in response_lower:
|
|
43
|
+
return LayerResult(
|
|
44
|
+
blocked=True,
|
|
45
|
+
code="block_cross_context",
|
|
46
|
+
reason=f"entity from session {other_session_id!r} appeared in response",
|
|
47
|
+
flagged_excerpt="[entity from another session]",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return LayerResult(blocked=False, code="pass")
|