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.
Files changed (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
@@ -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
+ )
@@ -0,0 +1,5 @@
1
+ """Outcome-driven per-type priority feedback (item 3b)."""
2
+
3
+ from apsimo.feedback.store import TypeFeedbackStore
4
+
5
+ __all__ = ["TypeFeedbackStore"]