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/events/stream.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Bounded buffering primitives for the host event WebSocket stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_OVERFLOW = object()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EventSubscriberBuffer:
|
|
15
|
+
"""A bounded per-subscriber queue with explicit overflow state.
|
|
16
|
+
|
|
17
|
+
Once a subscriber falls behind, queued frames and subsequent frames are no
|
|
18
|
+
longer offered piecemeal. A single overflow sentinel wakes the socket
|
|
19
|
+
writer, which reports the exact dropped sequence range and closes the
|
|
20
|
+
connection. The client can then replay from its last processed sequence.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
maxsize: int,
|
|
26
|
+
*,
|
|
27
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
if maxsize < 1:
|
|
30
|
+
raise ValueError("event subscriber queue size must be positive")
|
|
31
|
+
self.queue: asyncio.Queue[Any] = asyncio.Queue(maxsize=maxsize)
|
|
32
|
+
self.loop = loop
|
|
33
|
+
self.active = True
|
|
34
|
+
self.overflowed = False
|
|
35
|
+
self.dropped_count = 0
|
|
36
|
+
self.first_dropped_seq: Optional[int] = None
|
|
37
|
+
self.last_dropped_seq: Optional[int] = None
|
|
38
|
+
self.last_delivered_seq = 0
|
|
39
|
+
|
|
40
|
+
def publish(self, event: dict[str, Any]) -> None:
|
|
41
|
+
"""Offer an event on the subscriber's owning event loop."""
|
|
42
|
+
if not self.active:
|
|
43
|
+
return
|
|
44
|
+
if self.loop is None:
|
|
45
|
+
self._publish_on_loop(event)
|
|
46
|
+
return
|
|
47
|
+
if not self.loop.is_running():
|
|
48
|
+
self.close()
|
|
49
|
+
logger.warning("Discarding stale event subscriber with stopped loop")
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
# Schedule every publication, including same-loop calls. This
|
|
53
|
+
# preserves the host's sequence order when a worker thread has
|
|
54
|
+
# already scheduled an earlier frame on this loop.
|
|
55
|
+
self.loop.call_soon_threadsafe(self._publish_on_loop, event)
|
|
56
|
+
except RuntimeError:
|
|
57
|
+
self.close()
|
|
58
|
+
logger.warning("Discarding stale event subscriber with closed loop")
|
|
59
|
+
|
|
60
|
+
def _note_dropped(self, event: dict[str, Any]) -> None:
|
|
61
|
+
self.dropped_count += 1
|
|
62
|
+
try:
|
|
63
|
+
seq = int(event.get("seq", 0))
|
|
64
|
+
except (TypeError, ValueError):
|
|
65
|
+
seq = 0
|
|
66
|
+
if seq > 0:
|
|
67
|
+
if self.first_dropped_seq is None:
|
|
68
|
+
self.first_dropped_seq = seq
|
|
69
|
+
else:
|
|
70
|
+
self.first_dropped_seq = min(self.first_dropped_seq, seq)
|
|
71
|
+
if self.last_dropped_seq is None:
|
|
72
|
+
self.last_dropped_seq = seq
|
|
73
|
+
else:
|
|
74
|
+
self.last_dropped_seq = max(self.last_dropped_seq, seq)
|
|
75
|
+
|
|
76
|
+
def _publish_on_loop(self, event: dict[str, Any]) -> None:
|
|
77
|
+
if not self.active:
|
|
78
|
+
return
|
|
79
|
+
if self.overflowed:
|
|
80
|
+
self._note_dropped(event)
|
|
81
|
+
return
|
|
82
|
+
try:
|
|
83
|
+
self.queue.put_nowait(event)
|
|
84
|
+
return
|
|
85
|
+
except asyncio.QueueFull:
|
|
86
|
+
self.overflowed = True
|
|
87
|
+
|
|
88
|
+
# Everything not yet delivered must be replayed, including the frame
|
|
89
|
+
# which first exceeded the bound.
|
|
90
|
+
while True:
|
|
91
|
+
try:
|
|
92
|
+
queued = self.queue.get_nowait()
|
|
93
|
+
except asyncio.QueueEmpty:
|
|
94
|
+
break
|
|
95
|
+
if isinstance(queued, dict):
|
|
96
|
+
self._note_dropped(queued)
|
|
97
|
+
self._note_dropped(event)
|
|
98
|
+
self.queue.put_nowait(_OVERFLOW)
|
|
99
|
+
logger.warning(
|
|
100
|
+
"Event subscriber overflowed: dropped=%d first_seq=%s last_seq=%s",
|
|
101
|
+
self.dropped_count,
|
|
102
|
+
self.first_dropped_seq,
|
|
103
|
+
self.last_dropped_seq,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def get(self) -> Any:
|
|
107
|
+
return await self.queue.get()
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def is_overflow(item: Any) -> bool:
|
|
111
|
+
return item is _OVERFLOW
|
|
112
|
+
|
|
113
|
+
def mark_delivered(self, event: dict[str, Any]) -> None:
|
|
114
|
+
try:
|
|
115
|
+
seq = int(event.get("seq", 0))
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
return
|
|
118
|
+
if seq > self.last_delivered_seq:
|
|
119
|
+
self.last_delivered_seq = seq
|
|
120
|
+
|
|
121
|
+
def overflow_frame(self) -> dict[str, Any]:
|
|
122
|
+
return {
|
|
123
|
+
"type": "stream_overflow",
|
|
124
|
+
"droppedCount": self.dropped_count,
|
|
125
|
+
"firstDroppedSeq": self.first_dropped_seq,
|
|
126
|
+
"lastDroppedSeq": self.last_dropped_seq,
|
|
127
|
+
"resumeAfterSeq": self.last_delivered_seq,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def close(self) -> None:
|
|
131
|
+
self.active = False
|
apsimo/events/types.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Event type definitions for Colony's event bus.
|
|
2
|
+
|
|
3
|
+
Typed events carry structured payloads across Colony subsystems.
|
|
4
|
+
Each event has an id, timestamp, and source component. Specialized
|
|
5
|
+
events add domain-specific fields for people, signals, memories,
|
|
6
|
+
mesh nodes, cognition, and external integrations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from apsimo.models.mesh import NodeRole
|
|
14
|
+
from apsimo.models.memory import Memory
|
|
15
|
+
from apsimo.models.signal import Signal
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Event:
|
|
20
|
+
"""Base event class.
|
|
21
|
+
|
|
22
|
+
All Colony events inherit from this. Provides identity, timing,
|
|
23
|
+
and source tracking.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
id: Unique event identifier
|
|
27
|
+
timestamp: When the event occurred
|
|
28
|
+
source: Which component emitted the event
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
id: str
|
|
32
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
33
|
+
source: str = "colony"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class PersonEvent(Event):
|
|
38
|
+
"""Person-related events.
|
|
39
|
+
|
|
40
|
+
Emitted when a person's profile, tier, score, or interaction
|
|
41
|
+
state changes.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
person_id: The affected person's identifier
|
|
45
|
+
event_type: What happened ("created", "tier_changed", "score_updated", "interaction")
|
|
46
|
+
old_value: Previous value before the change
|
|
47
|
+
new_value: New value after the change
|
|
48
|
+
context: Additional event-specific metadata
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
person_id: str = ""
|
|
52
|
+
event_type: str = ""
|
|
53
|
+
old_value: Optional[Any] = None
|
|
54
|
+
new_value: Optional[Any] = None
|
|
55
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class SignalEvent(Event):
|
|
60
|
+
"""Signal collection events.
|
|
61
|
+
|
|
62
|
+
Emitted when a new behavioral signal is captured.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
signal: The captured signal instance
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
signal: Optional[Signal] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class MemoryEvent(Event):
|
|
73
|
+
"""Memory storage events.
|
|
74
|
+
|
|
75
|
+
Emitted when memories are created, accessed, strengthened, or decayed.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
memory: The affected memory instance
|
|
79
|
+
event_type: What happened ("created", "accessed", "strengthened", "decayed")
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
memory: Optional[Memory] = None
|
|
83
|
+
event_type: str = ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class MeshEvent(Event):
|
|
88
|
+
"""Mesh networking events.
|
|
89
|
+
|
|
90
|
+
Emitted when mesh nodes register, come online/offline, or change roles.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
node_id: The affected node's identifier
|
|
94
|
+
event_type: What happened ("registered", "online", "offline", "role_changed", "election")
|
|
95
|
+
old_role: Previous node role (for role changes)
|
|
96
|
+
new_role: New node role (for role changes)
|
|
97
|
+
metadata: Additional event-specific data
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
node_id: str = ""
|
|
101
|
+
event_type: str = ""
|
|
102
|
+
old_role: Optional[NodeRole] = None
|
|
103
|
+
new_role: Optional[NodeRole] = None
|
|
104
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class CognitionEvent(Event):
|
|
109
|
+
"""Cognitive system events.
|
|
110
|
+
|
|
111
|
+
Emitted by Colony's cognitive subsystems (metalearner, synthesis,
|
|
112
|
+
predictor, etc.) when they detect gaps, adjust strategies, or
|
|
113
|
+
compute metrics.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
component: Which cognitive component emitted ("metalearner", "synthesis", "predictor")
|
|
117
|
+
event_type: What happened ("gap_detected", "strategy_adjusted", "cpi_computed")
|
|
118
|
+
details: Event-specific structured data
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
component: str = ""
|
|
122
|
+
event_type: str = ""
|
|
123
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class IntegrationEvent(Event):
|
|
128
|
+
"""External integration events.
|
|
129
|
+
|
|
130
|
+
Emitted when external data sources sync, receive data, or error.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
integration: Which integration ("health", "meetings", "calendar")
|
|
134
|
+
event_type: What happened ("data_received", "sync_complete", "error")
|
|
135
|
+
data: Integration-specific payload
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
integration: str = ""
|
|
139
|
+
event_type: str = ""
|
|
140
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class InsightPushEvent(Event):
|
|
145
|
+
"""High-urgency insight ready for push delivery."""
|
|
146
|
+
|
|
147
|
+
insight_id: str = ""
|
|
148
|
+
description: str = ""
|
|
149
|
+
urgency: float = 1.0
|
|
150
|
+
delivery_channel: str = "push"
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Versioned execution results for canonical Colony WorkOrders.
|
|
2
|
+
|
|
3
|
+
The task queue is only a transport. A terminal queue state is converted into
|
|
4
|
+
an :class:`ExecutionResultV1`, bound to the exact WorkOrder authority digest,
|
|
5
|
+
before a project step can use it. Mutating or disclosing effects remain
|
|
6
|
+
unverified unless a separately wired receipt verifier attests concrete receipt
|
|
7
|
+
references.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import asdict, dataclass, replace
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from typing import Any, Dict, Iterable, Mapping, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
EXECUTION_RESULT_VERSION = 1
|
|
19
|
+
TERMINAL_OUTCOMES = frozenset({"succeeded", "failed", "skipped", "cancelled"})
|
|
20
|
+
EFFECT_CLASSES = frozenset({"none", "read_only", "mutation", "disclosure"})
|
|
21
|
+
VERIFICATION_RESULTS = frozenset({"verified", "unverified", "not_applicable"})
|
|
22
|
+
EXTERNAL_EFFECT_CLASSES = frozenset({"mutation", "disclosure"})
|
|
23
|
+
|
|
24
|
+
_REF_SCHEME = re.compile(r"^[a-z][a-z0-9+.-]{1,31}:[^\s]{1,480}$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExecutionResultError(ValueError):
|
|
28
|
+
"""Raised when an execution claim is stale, malformed, or unbound."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _bounded(value: object, name: str, maximum: int, *, required: bool = True) -> str:
|
|
32
|
+
text = str(value or "").strip()
|
|
33
|
+
if (required and not text) or len(text) > maximum:
|
|
34
|
+
qualifier = f"1..{maximum}" if required else f"0..{maximum}"
|
|
35
|
+
raise ExecutionResultError(f"{name} must be {qualifier} characters")
|
|
36
|
+
return text
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_time(value: object, name: str) -> datetime:
|
|
40
|
+
text = _bounded(value, name, 64)
|
|
41
|
+
try:
|
|
42
|
+
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
|
|
43
|
+
except ValueError as exc:
|
|
44
|
+
raise ExecutionResultError(f"{name} must be an ISO-8601 timestamp") from exc
|
|
45
|
+
if parsed.tzinfo is None:
|
|
46
|
+
raise ExecutionResultError(f"{name} must include a timezone")
|
|
47
|
+
return parsed.astimezone(timezone.utc)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _iso(value: datetime) -> str:
|
|
51
|
+
if value.tzinfo is None:
|
|
52
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
53
|
+
return value.astimezone(timezone.utc).isoformat()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def bounded_refs(values: object, *, name: str = "receipt_refs") -> Tuple[str, ...]:
|
|
57
|
+
"""Normalize bounded reference-only evidence without copying artifacts."""
|
|
58
|
+
|
|
59
|
+
if values is None:
|
|
60
|
+
return ()
|
|
61
|
+
if not isinstance(values, (list, tuple)):
|
|
62
|
+
raise ExecutionResultError(f"{name} must be a list")
|
|
63
|
+
if len(values) > 32:
|
|
64
|
+
raise ExecutionResultError(f"{name} may contain at most 32 references")
|
|
65
|
+
refs = []
|
|
66
|
+
for item in values:
|
|
67
|
+
if isinstance(item, Mapping):
|
|
68
|
+
ref = _bounded(item.get("ref"), f"{name}.ref", 512)
|
|
69
|
+
digest = str(item.get("sha256") or "").strip().lower()
|
|
70
|
+
if digest:
|
|
71
|
+
if not re.fullmatch(r"[0-9a-f]{64}", digest):
|
|
72
|
+
raise ExecutionResultError(f"{name}.sha256 must be 64 hex characters")
|
|
73
|
+
ref = f"{ref}#sha256={digest}"
|
|
74
|
+
else:
|
|
75
|
+
ref = _bounded(item, name, 512)
|
|
76
|
+
if not _REF_SCHEME.fullmatch(ref):
|
|
77
|
+
raise ExecutionResultError(
|
|
78
|
+
f"{name} entries must be bounded scheme-qualified references"
|
|
79
|
+
)
|
|
80
|
+
refs.append(ref)
|
|
81
|
+
return tuple(sorted(set(refs)))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class ReceiptVerificationV1:
|
|
86
|
+
"""Decision returned by an independently wired effect-receipt verifier."""
|
|
87
|
+
|
|
88
|
+
verified: bool
|
|
89
|
+
receipt_refs: Tuple[str, ...] = ()
|
|
90
|
+
verifier_identity: str = ""
|
|
91
|
+
detail: str = ""
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_value(cls, value: object) -> "ReceiptVerificationV1":
|
|
95
|
+
if isinstance(value, cls):
|
|
96
|
+
result = value
|
|
97
|
+
elif isinstance(value, Mapping):
|
|
98
|
+
verified = value.get("verified")
|
|
99
|
+
if not isinstance(verified, bool):
|
|
100
|
+
raise ExecutionResultError("receipt verifier decision must use a boolean")
|
|
101
|
+
result = cls(
|
|
102
|
+
verified=verified,
|
|
103
|
+
receipt_refs=bounded_refs(value.get("receipt_refs")),
|
|
104
|
+
verifier_identity=_bounded(
|
|
105
|
+
value.get("verifier_identity"),
|
|
106
|
+
"verifier_identity",
|
|
107
|
+
256,
|
|
108
|
+
required=False,
|
|
109
|
+
),
|
|
110
|
+
detail=_bounded(value.get("detail"), "detail", 1000, required=False),
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
raise ExecutionResultError("receipt verifier returned an invalid decision")
|
|
114
|
+
if not isinstance(result.verified, bool):
|
|
115
|
+
raise ExecutionResultError("receipt verifier decision must use a boolean")
|
|
116
|
+
if result.verified:
|
|
117
|
+
refs = bounded_refs(result.receipt_refs)
|
|
118
|
+
identity = _bounded(result.verifier_identity, "verifier_identity", 256)
|
|
119
|
+
if not refs:
|
|
120
|
+
raise ExecutionResultError(
|
|
121
|
+
"a verified external effect requires at least one receipt reference"
|
|
122
|
+
)
|
|
123
|
+
return replace(result, receipt_refs=refs, verifier_identity=identity)
|
|
124
|
+
return replace(
|
|
125
|
+
result,
|
|
126
|
+
receipt_refs=bounded_refs(result.receipt_refs),
|
|
127
|
+
verifier_identity=_bounded(
|
|
128
|
+
result.verifier_identity,
|
|
129
|
+
"verifier_identity",
|
|
130
|
+
256,
|
|
131
|
+
required=False,
|
|
132
|
+
),
|
|
133
|
+
detail=_bounded(result.detail, "detail", 1000, required=False),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True)
|
|
138
|
+
class ExecutionResultV1:
|
|
139
|
+
"""One logical, receipt-aware terminal result for a WorkOrder.
|
|
140
|
+
|
|
141
|
+
A WorkOrder may have several run attempts. ``result_ref`` remains stable
|
|
142
|
+
across them; ``run_id`` and ``attempt_number`` identify the current
|
|
143
|
+
terminal attempt stored in the logical result row.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
result_ref: str
|
|
147
|
+
work_order_id: str
|
|
148
|
+
work_order_digest: str
|
|
149
|
+
work_order_version: int
|
|
150
|
+
run_id: str
|
|
151
|
+
attempt_number: int
|
|
152
|
+
terminal_outcome: str
|
|
153
|
+
started_at: str
|
|
154
|
+
ended_at: str
|
|
155
|
+
executor_identity: str
|
|
156
|
+
effect_class: str
|
|
157
|
+
receipt_refs: Tuple[str, ...]
|
|
158
|
+
verification_result: str
|
|
159
|
+
verifier_identity: str = ""
|
|
160
|
+
summary: str = ""
|
|
161
|
+
error: str = ""
|
|
162
|
+
schema: str = "ExecutionResultV1"
|
|
163
|
+
version: int = EXECUTION_RESULT_VERSION
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def ref_for(work_order_id: str) -> str:
|
|
167
|
+
return f"execution-result:{work_order_id}"
|
|
168
|
+
|
|
169
|
+
def payload(self) -> Dict[str, Any]:
|
|
170
|
+
payload = asdict(self)
|
|
171
|
+
payload["receipt_refs"] = list(self.receipt_refs)
|
|
172
|
+
return payload
|
|
173
|
+
|
|
174
|
+
def with_verification(
|
|
175
|
+
self,
|
|
176
|
+
verification_result: str,
|
|
177
|
+
*,
|
|
178
|
+
receipt_refs: Optional[Iterable[str]] = None,
|
|
179
|
+
verifier_identity: str = "",
|
|
180
|
+
error: Optional[str] = None,
|
|
181
|
+
) -> "ExecutionResultV1":
|
|
182
|
+
if verification_result not in VERIFICATION_RESULTS:
|
|
183
|
+
raise ExecutionResultError("invalid verification_result")
|
|
184
|
+
refs = self.receipt_refs if receipt_refs is None else bounded_refs(tuple(receipt_refs))
|
|
185
|
+
return replace(
|
|
186
|
+
self,
|
|
187
|
+
verification_result=verification_result,
|
|
188
|
+
receipt_refs=refs,
|
|
189
|
+
verifier_identity=_bounded(
|
|
190
|
+
verifier_identity, "verifier_identity", 256, required=False
|
|
191
|
+
),
|
|
192
|
+
error=self.error if error is None else _bounded(
|
|
193
|
+
error, "error", 2000, required=False
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def from_job(cls, order: Any, job: Any) -> "ExecutionResultV1":
|
|
199
|
+
"""Bind a terminal queue job to its exact WorkOrder contract.
|
|
200
|
+
|
|
201
|
+
Completed jobs should carry a nested ``execution_result`` object. A
|
|
202
|
+
legacy completed payload is retained as an *unverified* logical result
|
|
203
|
+
so operators can see the claim without treating it as evidence.
|
|
204
|
+
Transport failures/cancellations are converted deterministically.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
result = getattr(job, "result", None)
|
|
208
|
+
output = dict(getattr(result, "output", None) or {})
|
|
209
|
+
raw = output.get("execution_result")
|
|
210
|
+
typed = isinstance(raw, Mapping)
|
|
211
|
+
raw = dict(raw) if typed else {}
|
|
212
|
+
|
|
213
|
+
transport_status = str(getattr(getattr(job, "status", None), "value", "") or "")
|
|
214
|
+
action_plane = output.get("action_plane")
|
|
215
|
+
action_plane = dict(action_plane) if isinstance(action_plane, Mapping) else {}
|
|
216
|
+
|
|
217
|
+
if typed:
|
|
218
|
+
if raw.get("schema") != "ExecutionResultV1":
|
|
219
|
+
raise ExecutionResultError("execution result schema is stale or unsupported")
|
|
220
|
+
if type(raw.get("version")) is not int or raw["version"] != EXECUTION_RESULT_VERSION:
|
|
221
|
+
raise ExecutionResultError("execution result version is stale or unsupported")
|
|
222
|
+
if str(raw.get("work_order_id") or "") != order.work_order_id:
|
|
223
|
+
raise ExecutionResultError("execution result WorkOrder ID mismatch")
|
|
224
|
+
if str(raw.get("work_order_digest") or "") != order.work_order_digest:
|
|
225
|
+
raise ExecutionResultError("execution result WorkOrder digest mismatch")
|
|
226
|
+
if (
|
|
227
|
+
type(raw.get("work_order_version")) is not int
|
|
228
|
+
or raw["work_order_version"] != order.version
|
|
229
|
+
):
|
|
230
|
+
raise ExecutionResultError("execution result WorkOrder version is stale")
|
|
231
|
+
|
|
232
|
+
semantic = str(raw.get("terminal_outcome") or output.get("status") or "").lower()
|
|
233
|
+
action_state = str(action_plane.get("state") or "").lower()
|
|
234
|
+
if transport_status == "failed":
|
|
235
|
+
outcome = "failed"
|
|
236
|
+
elif transport_status == "cancelled":
|
|
237
|
+
outcome = "cancelled"
|
|
238
|
+
elif semantic in ("skipped", "cancelled") or action_state == "skipped":
|
|
239
|
+
outcome = "skipped" if semantic != "cancelled" else "cancelled"
|
|
240
|
+
elif semantic in ("failed", "error") or action_state == "failed":
|
|
241
|
+
outcome = "failed"
|
|
242
|
+
elif semantic in ("succeeded", "completed", "verified") or action_state == "completed":
|
|
243
|
+
outcome = "succeeded"
|
|
244
|
+
else:
|
|
245
|
+
outcome = "failed"
|
|
246
|
+
|
|
247
|
+
claimed_outcome = str(raw.get("terminal_outcome") or "").lower()
|
|
248
|
+
if typed and claimed_outcome not in TERMINAL_OUTCOMES:
|
|
249
|
+
raise ExecutionResultError("invalid terminal_outcome")
|
|
250
|
+
if typed and claimed_outcome != outcome:
|
|
251
|
+
raise ExecutionResultError("execution result conflicts with transport outcome")
|
|
252
|
+
|
|
253
|
+
expected_effect = str(order.effect_class)
|
|
254
|
+
claimed_effect = str(raw.get("effect_class") or expected_effect)
|
|
255
|
+
if expected_effect not in EFFECT_CLASSES or claimed_effect != expected_effect:
|
|
256
|
+
raise ExecutionResultError("execution result effect class mismatch")
|
|
257
|
+
|
|
258
|
+
worker_identity = str(
|
|
259
|
+
getattr(result, "worker_node_id", None)
|
|
260
|
+
or getattr(job, "claimed_by", None)
|
|
261
|
+
or "unknown-queue-worker"
|
|
262
|
+
)
|
|
263
|
+
claimed_identity = str(raw.get("executor_identity") or worker_identity)
|
|
264
|
+
if typed and worker_identity != "unknown-queue-worker" and claimed_identity != worker_identity:
|
|
265
|
+
raise ExecutionResultError("execution result executor identity mismatch")
|
|
266
|
+
|
|
267
|
+
if typed and type(raw.get("attempt_number")) is not int:
|
|
268
|
+
raise ExecutionResultError("attempt_number must be an integer")
|
|
269
|
+
attempt_number = int(
|
|
270
|
+
raw.get("attempt_number") or (getattr(job, "retry_count", 0) + 1)
|
|
271
|
+
)
|
|
272
|
+
if attempt_number < 1 or attempt_number > int(order.max_attempts):
|
|
273
|
+
raise ExecutionResultError("execution result attempt exceeds WorkOrder budget")
|
|
274
|
+
|
|
275
|
+
run_id = str(
|
|
276
|
+
raw.get("run_id")
|
|
277
|
+
or output.get("run_id")
|
|
278
|
+
or action_plane.get("run_id")
|
|
279
|
+
or action_plane.get("action_id")
|
|
280
|
+
or f"queue:{job.job_id}:attempt:{attempt_number}"
|
|
281
|
+
)
|
|
282
|
+
run_id = _bounded(run_id, "run_id", 256)
|
|
283
|
+
|
|
284
|
+
server_started = (
|
|
285
|
+
getattr(result, "started_at", None)
|
|
286
|
+
or getattr(job, "claimed_at", None)
|
|
287
|
+
or getattr(job, "posted_at", None)
|
|
288
|
+
)
|
|
289
|
+
server_ended = (
|
|
290
|
+
getattr(result, "completed_at", None)
|
|
291
|
+
or getattr(job, "last_heartbeat", None)
|
|
292
|
+
or getattr(job, "claimed_at", None)
|
|
293
|
+
or getattr(job, "posted_at", None)
|
|
294
|
+
or datetime.now(timezone.utc)
|
|
295
|
+
)
|
|
296
|
+
if typed:
|
|
297
|
+
# Worker timestamps remain schema-validated diagnostic claims,
|
|
298
|
+
# but they cannot define learning/expectation time. The queue
|
|
299
|
+
# manager's persisted JobResult timestamps are server-owned.
|
|
300
|
+
worker_started = _parse_time(raw.get("started_at"), "started_at")
|
|
301
|
+
worker_ended = _parse_time(raw.get("ended_at"), "ended_at")
|
|
302
|
+
if worker_ended < worker_started:
|
|
303
|
+
raise ExecutionResultError("worker ended_at precedes started_at")
|
|
304
|
+
if server_started is None:
|
|
305
|
+
server_started = server_ended
|
|
306
|
+
if not isinstance(server_started, datetime) \
|
|
307
|
+
or not isinstance(server_ended, datetime):
|
|
308
|
+
raise ExecutionResultError(
|
|
309
|
+
"queue result requires server-owned terminal timestamps"
|
|
310
|
+
)
|
|
311
|
+
started = server_started
|
|
312
|
+
ended = server_ended
|
|
313
|
+
if started.tzinfo is None:
|
|
314
|
+
started = started.replace(tzinfo=timezone.utc)
|
|
315
|
+
if ended.tzinfo is None:
|
|
316
|
+
ended = ended.replace(tzinfo=timezone.utc)
|
|
317
|
+
started = started.astimezone(timezone.utc)
|
|
318
|
+
ended = ended.astimezone(timezone.utc)
|
|
319
|
+
if ended < started:
|
|
320
|
+
raise ExecutionResultError("ended_at precedes started_at")
|
|
321
|
+
|
|
322
|
+
refs = bounded_refs(raw.get("receipt_refs") if typed else output.get("evidence_refs"))
|
|
323
|
+
summary = _bounded(
|
|
324
|
+
raw.get("summary") if typed else (output.get("summary") or output.get("result")),
|
|
325
|
+
"summary",
|
|
326
|
+
4000,
|
|
327
|
+
required=False,
|
|
328
|
+
)
|
|
329
|
+
raw_error = (
|
|
330
|
+
raw.get("error") if typed else (
|
|
331
|
+
getattr(result, "error", None)
|
|
332
|
+
or output.get("reason")
|
|
333
|
+
or action_plane.get("last_error")
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
error = _bounded(raw_error, "error", 2000, required=False)
|
|
337
|
+
if not typed and outcome == "succeeded":
|
|
338
|
+
error = error or "legacy completion lacks ExecutionResultV1 attestation"
|
|
339
|
+
|
|
340
|
+
verification = "not_applicable" if outcome != "succeeded" else "unverified"
|
|
341
|
+
return cls(
|
|
342
|
+
result_ref=cls.ref_for(order.work_order_id),
|
|
343
|
+
work_order_id=order.work_order_id,
|
|
344
|
+
work_order_digest=order.work_order_digest,
|
|
345
|
+
work_order_version=order.version,
|
|
346
|
+
run_id=run_id,
|
|
347
|
+
attempt_number=attempt_number,
|
|
348
|
+
terminal_outcome=outcome,
|
|
349
|
+
started_at=_iso(started),
|
|
350
|
+
ended_at=_iso(ended),
|
|
351
|
+
executor_identity=_bounded(claimed_identity, "executor_identity", 256),
|
|
352
|
+
effect_class=expected_effect,
|
|
353
|
+
receipt_refs=refs,
|
|
354
|
+
verification_result=verification,
|
|
355
|
+
summary=summary,
|
|
356
|
+
error=error,
|
|
357
|
+
)
|