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,593 @@
1
+ """Deterministic, advisory recipient simulation for P8 social boundaries.
2
+
3
+ The simulator is deliberately less powerful than the delivery plane. It can
4
+ project facts and arcs through a server-attested recipient scope and report
5
+ risks, but it cannot send, authorize, approve, mutate an arc, or grant a tool
6
+ capability. ``shadow`` is observation-only. Even ``live`` is an advisory
7
+ result for a future caller to interpret; real-time voice surfaces are always
8
+ asynchronous observation and never enter their synchronous turn path.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from datetime import datetime, timezone
15
+ import hashlib
16
+ import json
17
+ import os
18
+ import re
19
+ from types import MappingProxyType
20
+ from typing import Any, Mapping, Optional, Sequence
21
+
22
+ from apsimo.tom.visibility import (
23
+ FactCandidateV1,
24
+ ViewerContextV1,
25
+ content_digest,
26
+ project_facts,
27
+ )
28
+
29
+
30
+ SCHEMA_VERSION = 1
31
+ SIMULATOR_MODES = frozenset({"off", "shadow", "live"})
32
+ RISK_CLASSES = frozenset({"low", "medium", "high", "critical"})
33
+ REALTIME_VOICE_SURFACES = frozenset({
34
+ "voice",
35
+ "phone",
36
+ "intercom",
37
+ "meet",
38
+ "google_meet",
39
+ "google-meet",
40
+ "googlemeet",
41
+ "gmeet",
42
+ "pstn",
43
+ "sip",
44
+ "webrtc",
45
+ "telephone",
46
+ "telephony",
47
+ "voice_call",
48
+ "phone_call",
49
+ "facetime",
50
+ })
51
+ _REALTIME_VOICE_TOKENS = frozenset({
52
+ "voice", "phone", "intercom", "meet", "call", "pstn", "sip",
53
+ "webrtc", "telephone", "telephony", "gmeet", "facetime",
54
+ })
55
+
56
+ MAX_DRAFT_CHARS = 12_000
57
+ MAX_DRAFT_FACT_REFS = 64
58
+ MAX_RISKS = 64
59
+ MAX_REPAIRS = 32
60
+
61
+ _REF_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]{0,191}$")
62
+ _PRESSURE_RE = re.compile(
63
+ r"\b(urgent|must|immediately|right\s+now|now|hurry|no\s+excuses|"
64
+ r"do\s+it|finish\s+it)\b",
65
+ re.IGNORECASE,
66
+ )
67
+ _TOKEN_RE = re.compile(r"[a-z0-9]+")
68
+
69
+ FAIL_BEHAVIOR_BY_RISK: Mapping[str, str] = MappingProxyType({
70
+ "low": "observe",
71
+ "medium": "review",
72
+ "high": "hold",
73
+ "critical": "hold",
74
+ })
75
+
76
+ _SEVERITY_RANK = {"low": 1, "medium": 2, "high": 3, "critical": 4}
77
+
78
+
79
+ def _canonical(value: Any) -> str:
80
+ return json.dumps(
81
+ value,
82
+ ensure_ascii=True,
83
+ separators=(",", ":"),
84
+ sort_keys=True,
85
+ default=str,
86
+ )
87
+
88
+
89
+ def _digest(value: Any) -> str:
90
+ return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
91
+
92
+
93
+ def _ref(value: Any, *, field: str, allow_empty: bool = False) -> str:
94
+ normalized = str(value or "").strip()
95
+ if allow_empty and not normalized:
96
+ return ""
97
+ if not _REF_RE.fullmatch(normalized):
98
+ raise ValueError(f"{field} is not a bounded opaque reference")
99
+ return normalized
100
+
101
+
102
+ def _refs(values: Sequence[str], *, field: str,
103
+ maximum: int) -> tuple[str, ...]:
104
+ normalized = tuple(sorted(dict.fromkeys(
105
+ _ref(value, field=field) for value in values
106
+ )))
107
+ if len(normalized) > maximum:
108
+ raise ValueError(f"{field} exceeds bounded reference limit")
109
+ return normalized
110
+
111
+
112
+ def _iso(value: datetime | str, *, field: str) -> str:
113
+ if isinstance(value, datetime):
114
+ parsed = value
115
+ else:
116
+ try:
117
+ parsed = datetime.fromisoformat(
118
+ str(value).replace("Z", "+00:00"))
119
+ except (TypeError, ValueError) as exc:
120
+ raise ValueError(f"{field} must be an ISO-8601 timestamp") from exc
121
+ if parsed.tzinfo is None:
122
+ raise ValueError(f"{field} must include a timezone")
123
+ return parsed.astimezone(timezone.utc).isoformat()
124
+
125
+
126
+ def recipient_simulator_mode(environ: Optional[Mapping[str, str]] = None) -> str:
127
+ """Return an explicit safe mode; unknown and unset values are ``off``."""
128
+
129
+ source = os.environ if environ is None else environ
130
+ mode = str(source.get("COLONY_RECIPIENT_SIMULATOR_MODE", "off"))\
131
+ .strip().lower()
132
+ return mode if mode in SIMULATOR_MODES else "off"
133
+
134
+
135
+ def is_realtime_voice_surface(surface: str) -> bool:
136
+ """Classify stable names and compound deployment aliases conservatively."""
137
+
138
+ normalized = str(surface or "").strip().lower()
139
+ if normalized in REALTIME_VOICE_SURFACES:
140
+ return True
141
+ tokens = {
142
+ token for token in re.split(r"[_:./+\-]+", normalized) if token
143
+ }
144
+ return bool(tokens.intersection(_REALTIME_VOICE_TOKENS))
145
+
146
+
147
+ @dataclass(frozen=True, slots=True)
148
+ class RecipientSimulationRequestV1:
149
+ simulation_id: str
150
+ draft_text: str
151
+ draft_fact_refs: tuple[str, ...]
152
+ recipient: ViewerContextV1
153
+ risk_class: str
154
+ surface: str
155
+ high_salience: bool
156
+ created_at: str
157
+ schema_version: int = SCHEMA_VERSION
158
+
159
+ def __post_init__(self) -> None:
160
+ if self.schema_version != SCHEMA_VERSION:
161
+ raise ValueError("unsupported recipient simulation version")
162
+ simulation_id = _ref(self.simulation_id, field="simulation_id")
163
+ if not isinstance(self.draft_text, str) or not self.draft_text.strip():
164
+ raise ValueError("simulation draft text is required")
165
+ if len(self.draft_text) > MAX_DRAFT_CHARS:
166
+ raise ValueError("simulation draft exceeds bounded length")
167
+ refs = _refs(
168
+ self.draft_fact_refs,
169
+ field="draft_fact_ref",
170
+ maximum=MAX_DRAFT_FACT_REFS,
171
+ )
172
+ if not isinstance(self.recipient, ViewerContextV1):
173
+ raise ValueError("recipient must be a ViewerContextV1")
174
+ risk_class = str(self.risk_class or "").strip().lower()
175
+ if risk_class not in RISK_CLASSES:
176
+ raise ValueError("unknown recipient simulation risk class")
177
+ surface = _ref(self.surface, field="surface").lower()
178
+ created_at = _iso(self.created_at, field="created_at")
179
+ object.__setattr__(self, "simulation_id", simulation_id)
180
+ object.__setattr__(self, "draft_fact_refs", refs)
181
+ object.__setattr__(self, "risk_class", risk_class)
182
+ object.__setattr__(self, "surface", surface)
183
+ object.__setattr__(self, "high_salience", bool(self.high_salience))
184
+ object.__setattr__(self, "created_at", created_at)
185
+
186
+ @property
187
+ def audit_digest(self) -> str:
188
+ return _digest({
189
+ "schema_version": self.schema_version,
190
+ "simulation_id": self.simulation_id,
191
+ "draft_digest": content_digest(self.draft_text),
192
+ "draft_fact_refs": self.draft_fact_refs,
193
+ "recipient_digest": self.recipient.audit_digest,
194
+ "risk_class": self.risk_class,
195
+ "surface": self.surface,
196
+ "high_salience": self.high_salience,
197
+ "created_at": self.created_at,
198
+ })
199
+
200
+
201
+ @dataclass(frozen=True, slots=True)
202
+ class SimulationRiskV1:
203
+ code: str
204
+ severity: str
205
+ fact_ref: str = ""
206
+ arc_ref: str = ""
207
+
208
+ def __post_init__(self) -> None:
209
+ code = _ref(self.code, field="risk_code")
210
+ severity = str(self.severity or "").strip().lower()
211
+ if severity not in _SEVERITY_RANK:
212
+ raise ValueError("unknown simulation risk severity")
213
+ fact_ref = _ref(
214
+ self.fact_ref, field="risk_fact_ref", allow_empty=True)
215
+ arc_ref = _ref(
216
+ self.arc_ref, field="risk_arc_ref", allow_empty=True)
217
+ object.__setattr__(self, "code", code)
218
+ object.__setattr__(self, "severity", severity)
219
+ object.__setattr__(self, "fact_ref", fact_ref)
220
+ object.__setattr__(self, "arc_ref", arc_ref)
221
+
222
+ def public(self) -> dict[str, Any]:
223
+ value: dict[str, Any] = {
224
+ "code": self.code,
225
+ "severity": self.severity,
226
+ }
227
+ if self.fact_ref:
228
+ value["fact_ref"] = self.fact_ref
229
+ if self.arc_ref:
230
+ value["arc_ref"] = self.arc_ref
231
+ return value
232
+
233
+
234
+ @dataclass(frozen=True, slots=True)
235
+ class RepairSuggestionV1:
236
+ code: str
237
+ priority: str
238
+ related_ref: str = ""
239
+
240
+ def __post_init__(self) -> None:
241
+ code = _ref(self.code, field="repair_code")
242
+ priority = str(self.priority or "").strip().lower()
243
+ if priority not in _SEVERITY_RANK:
244
+ raise ValueError("unknown repair priority")
245
+ related_ref = _ref(
246
+ self.related_ref, field="repair_related_ref", allow_empty=True)
247
+ object.__setattr__(self, "code", code)
248
+ object.__setattr__(self, "priority", priority)
249
+ object.__setattr__(self, "related_ref", related_ref)
250
+
251
+ def public(self) -> dict[str, Any]:
252
+ value: dict[str, Any] = {
253
+ "code": self.code,
254
+ "priority": self.priority,
255
+ }
256
+ if self.related_ref:
257
+ value["related_ref"] = self.related_ref
258
+ return value
259
+
260
+
261
+ @dataclass(frozen=True, slots=True)
262
+ class RecipientSimulationResultV1:
263
+ simulation_id: str
264
+ mode: str
265
+ evaluated: bool
266
+ risk_class: str
267
+ fail_behavior: str
268
+ recommended_action: str
269
+ would_recommend: str
270
+ risks: tuple[SimulationRiskV1, ...]
271
+ repairs: tuple[RepairSuggestionV1, ...]
272
+ authorized_fact_refs: tuple[str, ...]
273
+ active_arc_refs: tuple[str, ...]
274
+ fact_projection_digest: str
275
+ arc_projection_digest: str
276
+ evaluation_path: str
277
+ external_effect: bool
278
+ authority_granted: bool
279
+ synchronous_gate: bool
280
+ request_digest: str
281
+ audit_digest: str
282
+ schema_version: int = SCHEMA_VERSION
283
+
284
+ def public(self) -> dict[str, Any]:
285
+ """Return reference-only output; draft and fact content are omitted."""
286
+
287
+ return {
288
+ "schema_version": self.schema_version,
289
+ "simulation_id": self.simulation_id,
290
+ "mode": self.mode,
291
+ "evaluated": self.evaluated,
292
+ "risk_class": self.risk_class,
293
+ "fail_behavior": self.fail_behavior,
294
+ "recommended_action": self.recommended_action,
295
+ "would_recommend": self.would_recommend,
296
+ "risks": [risk.public() for risk in self.risks],
297
+ "repairs": [repair.public() for repair in self.repairs],
298
+ "authorized_fact_refs": list(self.authorized_fact_refs),
299
+ "active_arc_refs": list(self.active_arc_refs),
300
+ "fact_projection_digest": self.fact_projection_digest,
301
+ "arc_projection_digest": self.arc_projection_digest,
302
+ "evaluation_path": self.evaluation_path,
303
+ "external_effect": self.external_effect,
304
+ "authority_granted": self.authority_granted,
305
+ "synchronous_gate": self.synchronous_gate,
306
+ "request_digest": self.request_digest,
307
+ "audit_digest": self.audit_digest,
308
+ }
309
+
310
+
311
+ def _risk_sort(risk: SimulationRiskV1) -> tuple[Any, ...]:
312
+ return (
313
+ -_SEVERITY_RANK[risk.severity],
314
+ risk.code,
315
+ risk.fact_ref,
316
+ risk.arc_ref,
317
+ )
318
+
319
+
320
+ def _repair_sort(repair: RepairSuggestionV1) -> tuple[Any, ...]:
321
+ return (
322
+ -_SEVERITY_RANK[repair.priority],
323
+ repair.code,
324
+ repair.related_ref,
325
+ )
326
+
327
+
328
+ def _dedupe_risks(values: Sequence[SimulationRiskV1])\
329
+ -> tuple[SimulationRiskV1, ...]:
330
+ unique = {
331
+ (item.code, item.severity, item.fact_ref, item.arc_ref): item
332
+ for item in values
333
+ }
334
+ return tuple(sorted(unique.values(), key=_risk_sort)[:MAX_RISKS])
335
+
336
+
337
+ def _dedupe_repairs(values: Sequence[RepairSuggestionV1])\
338
+ -> tuple[RepairSuggestionV1, ...]:
339
+ unique = {
340
+ (item.code, item.priority, item.related_ref): item
341
+ for item in values
342
+ }
343
+ return tuple(sorted(unique.values(), key=_repair_sort)[:MAX_REPAIRS])
344
+
345
+
346
+ def _would_recommend(
347
+ risks: Sequence[SimulationRiskV1],
348
+ repairs: Sequence[RepairSuggestionV1],
349
+ ) -> str:
350
+ rank = max((_SEVERITY_RANK[risk.severity] for risk in risks), default=0)
351
+ if rank >= _SEVERITY_RANK["high"]:
352
+ return "hold"
353
+ if rank >= _SEVERITY_RANK["medium"]:
354
+ return "repair" if repairs else "review"
355
+ if rank:
356
+ return "observe"
357
+ return "send"
358
+
359
+
360
+ def _topic_overlap(draft: str, topic: str) -> bool:
361
+ draft_tokens = set(_TOKEN_RE.findall(draft.lower()))
362
+ topic_tokens = {
363
+ token for token in _TOKEN_RE.findall(topic.lower()) if len(token) >= 4
364
+ }
365
+ return bool(topic_tokens and draft_tokens.intersection(topic_tokens))
366
+
367
+
368
+ class RecipientSimulator:
369
+ """Pure advisory evaluator over recipient-authorized projections."""
370
+
371
+ def __init__(self, *, arc_store: Any = None) -> None:
372
+ self._arc_store = arc_store
373
+
374
+ def _result(
375
+ self,
376
+ request: RecipientSimulationRequestV1,
377
+ *,
378
+ mode: str,
379
+ evaluated: bool,
380
+ risks: Sequence[SimulationRiskV1] = (),
381
+ repairs: Sequence[RepairSuggestionV1] = (),
382
+ authorized_fact_refs: Sequence[str] = (),
383
+ active_arc_refs: Sequence[str] = (),
384
+ fact_projection_digest: str = "",
385
+ arc_projection_digest: str = "",
386
+ dependency_failed: bool = False,
387
+ ) -> RecipientSimulationResultV1:
388
+ normalized_risks = _dedupe_risks(tuple(risks))
389
+ normalized_repairs = _dedupe_repairs(tuple(repairs))
390
+ fail_behavior = FAIL_BEHAVIOR_BY_RISK[request.risk_class]
391
+ would = (
392
+ fail_behavior
393
+ if dependency_failed
394
+ else _would_recommend(normalized_risks, normalized_repairs)
395
+ )
396
+ if not evaluated:
397
+ recommended = "no_effect"
398
+ would = "no_effect"
399
+ evaluation_path = "disabled"
400
+ elif is_realtime_voice_surface(request.surface):
401
+ recommended = "observe_async"
402
+ evaluation_path = "async_observation"
403
+ elif mode == "shadow":
404
+ recommended = "observe_only"
405
+ evaluation_path = "shadow_observation"
406
+ else:
407
+ recommended = would
408
+ evaluation_path = "pre_send_advisory"
409
+
410
+ payload = {
411
+ "schema_version": SCHEMA_VERSION,
412
+ "simulation_id": request.simulation_id,
413
+ "mode": mode,
414
+ "evaluated": evaluated,
415
+ "risk_class": request.risk_class,
416
+ "fail_behavior": fail_behavior,
417
+ "recommended_action": recommended,
418
+ "would_recommend": would,
419
+ "risks": [item.public() for item in normalized_risks],
420
+ "repairs": [item.public() for item in normalized_repairs],
421
+ "authorized_fact_refs": tuple(sorted(authorized_fact_refs)),
422
+ "active_arc_refs": tuple(sorted(active_arc_refs)),
423
+ "fact_projection_digest": fact_projection_digest,
424
+ "arc_projection_digest": arc_projection_digest,
425
+ "evaluation_path": evaluation_path,
426
+ "external_effect": False,
427
+ "authority_granted": False,
428
+ "synchronous_gate": False,
429
+ "request_digest": request.audit_digest,
430
+ }
431
+ return RecipientSimulationResultV1(
432
+ simulation_id=request.simulation_id,
433
+ mode=mode,
434
+ evaluated=evaluated,
435
+ risk_class=request.risk_class,
436
+ fail_behavior=fail_behavior,
437
+ recommended_action=recommended,
438
+ would_recommend=would,
439
+ risks=normalized_risks,
440
+ repairs=normalized_repairs,
441
+ authorized_fact_refs=tuple(sorted(authorized_fact_refs)),
442
+ active_arc_refs=tuple(sorted(active_arc_refs)),
443
+ fact_projection_digest=fact_projection_digest,
444
+ arc_projection_digest=arc_projection_digest,
445
+ evaluation_path=evaluation_path,
446
+ external_effect=False,
447
+ authority_granted=False,
448
+ synchronous_gate=False,
449
+ request_digest=request.audit_digest,
450
+ audit_digest=_digest(payload),
451
+ )
452
+
453
+ def simulate(
454
+ self,
455
+ request: RecipientSimulationRequestV1,
456
+ *,
457
+ fact_candidates: Sequence[FactCandidateV1],
458
+ now: datetime | str,
459
+ min_confidence: float = 0.0,
460
+ ) -> RecipientSimulationResultV1:
461
+ if not isinstance(request, RecipientSimulationRequestV1):
462
+ raise ValueError("request must be RecipientSimulationRequestV1")
463
+ mode = recipient_simulator_mode()
464
+ if mode == "off":
465
+ return self._result(request, mode=mode, evaluated=False)
466
+
467
+ risks: list[SimulationRiskV1] = []
468
+ repairs: list[RepairSuggestionV1] = []
469
+ authorized_fact_refs: tuple[str, ...] = ()
470
+ active_arc_refs: tuple[str, ...] = ()
471
+ fact_projection_digest = ""
472
+ arc_projection_digest = ""
473
+
474
+ # An unattested identity is a boundary result, not permission to query
475
+ # broader stores. In particular, no global or owner fallback occurs.
476
+ if not request.recipient.attested\
477
+ or not request.recipient.viewer_person_id:
478
+ risks.append(SimulationRiskV1(
479
+ code="recipient_identity_unattested", severity="critical"))
480
+ return self._result(
481
+ request,
482
+ mode=mode,
483
+ evaluated=True,
484
+ risks=risks,
485
+ authorized_fact_refs=(),
486
+ active_arc_refs=(),
487
+ )
488
+
489
+ try:
490
+ fact_projection = project_facts(
491
+ tuple(fact_candidates),
492
+ request.recipient,
493
+ now=now,
494
+ min_confidence=min_confidence,
495
+ max_facts=64,
496
+ max_total_chars=24_000,
497
+ )
498
+ authorized_fact_refs = tuple(
499
+ fact.fact_ref for fact in fact_projection.facts)
500
+ fact_projection_digest = fact_projection.audit_digest
501
+
502
+ arcs = ()
503
+ if self._arc_store is not None:
504
+ arc_projection = self._arc_store.project_active(
505
+ request.recipient,
506
+ now=now,
507
+ max_arcs=24,
508
+ max_topic_chars=8_000,
509
+ )
510
+ arcs = arc_projection.arcs
511
+ active_arc_refs = tuple(arc.arc_id for arc in arcs)
512
+ arc_projection_digest = arc_projection.audit_digest
513
+ if arc_projection.corrupt_count or arc_projection.truncated:
514
+ risks.append(SimulationRiskV1(
515
+ code="arc_projection_incomplete", severity="medium"))
516
+ repairs.append(RepairSuggestionV1(
517
+ code="review_arc_context", priority="medium"))
518
+
519
+ authorized = set(authorized_fact_refs)
520
+ for fact_ref in request.draft_fact_refs:
521
+ if fact_ref not in authorized:
522
+ risks.append(SimulationRiskV1(
523
+ code="fact_ref_not_recipient_authorized",
524
+ severity="critical",
525
+ fact_ref=fact_ref,
526
+ ))
527
+
528
+ if request.high_salience and not request.draft_fact_refs:
529
+ risks.append(SimulationRiskV1(
530
+ code="high_salience_provenance_unknown",
531
+ severity="medium",
532
+ ))
533
+ repairs.append(RepairSuggestionV1(
534
+ code="add_fact_provenance", priority="medium"))
535
+
536
+ if _PRESSURE_RE.search(request.draft_text):
537
+ for arc in arcs:
538
+ if arc.arc_type == "stress_topic"\
539
+ and _topic_overlap(request.draft_text, arc.topic):
540
+ risks.append(SimulationRiskV1(
541
+ code="stress_topic_pressure",
542
+ severity="medium",
543
+ arc_ref=arc.arc_id,
544
+ ))
545
+ repairs.append(RepairSuggestionV1(
546
+ code="soften_pressure_language",
547
+ priority="medium",
548
+ related_ref=arc.arc_id,
549
+ ))
550
+
551
+ except Exception:
552
+ # Dependency details can contain private content or host topology;
553
+ # only the stable class is emitted from this boundary.
554
+ risks.append(SimulationRiskV1(
555
+ code="simulation_dependency_error",
556
+ severity=request.risk_class,
557
+ ))
558
+ return self._result(
559
+ request,
560
+ mode=mode,
561
+ evaluated=True,
562
+ risks=risks,
563
+ repairs=repairs,
564
+ authorized_fact_refs=(),
565
+ active_arc_refs=(),
566
+ dependency_failed=True,
567
+ )
568
+
569
+ return self._result(
570
+ request,
571
+ mode=mode,
572
+ evaluated=True,
573
+ risks=risks,
574
+ repairs=repairs,
575
+ authorized_fact_refs=authorized_fact_refs,
576
+ active_arc_refs=active_arc_refs,
577
+ fact_projection_digest=fact_projection_digest,
578
+ arc_projection_digest=arc_projection_digest,
579
+ )
580
+
581
+
582
+ __all__ = [
583
+ "FAIL_BEHAVIOR_BY_RISK",
584
+ "REALTIME_VOICE_SURFACES",
585
+ "RecipientSimulationRequestV1",
586
+ "RecipientSimulationResultV1",
587
+ "RecipientSimulator",
588
+ "RepairSuggestionV1",
589
+ "SIMULATOR_MODES",
590
+ "SimulationRiskV1",
591
+ "is_realtime_voice_surface",
592
+ "recipient_simulator_mode",
593
+ ]
@@ -0,0 +1,93 @@
1
+ """Canonical source checks shared by the existing ToM projections.
2
+
3
+ Unknown historical origins remain explicitly unlinked. New conversation-derived
4
+ records retain the exact canonical turn, session and message hashes.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+
10
+
11
+ class SourceLinkedStore:
12
+ """A source reader only; each store owns its own projection and cleanup."""
13
+
14
+ def _ledger(self):
15
+ if self._source_ledger is None:
16
+ from apsimo import get_state_dir
17
+ from apsimo.turns import get_turn_idempotency_ledger
18
+ self._source_ledger = get_turn_idempotency_ledger(get_state_dir())
19
+ return self._source_ledger
20
+
21
+ def source_input(self, turn_id: str, contact_id: str) -> tuple[dict, str]:
22
+ """Canonical input and exact support hashes, never a generated summary."""
23
+ from apsimo.turns.idempotency import source_message_hash, SourceErased
24
+ ledger = self._ledger()
25
+ if ledger.is_projection_erased(turn_id):
26
+ raise SourceErased('source_erased')
27
+ conn = ledger._connect()
28
+ try:
29
+ row = conn.execute(
30
+ 'SELECT * FROM turn_sources WHERE turn_id=? AND contact_id=?',
31
+ (turn_id, contact_id),
32
+ ).fetchone()
33
+ finally:
34
+ conn.close()
35
+ if row is None or row['scope'] != 'person':
36
+ raise SourceErased('canonical_person_source_unavailable')
37
+ messages = json.loads(row['messages_json'])
38
+ lineage = {
39
+ 'turn_id': turn_id, 'session_id': row['session_id'],
40
+ 'message_hashes': [source_message_hash(row['session_id'], m) for m in messages],
41
+ 'occurred_at': row['occurred_at'], 'ingested_at': row['ingested_at'],
42
+ }
43
+ texts = []
44
+ for message in messages:
45
+ content = message.get('content')
46
+ if isinstance(content, list):
47
+ content = '\n'.join(
48
+ b['text'] for b in content
49
+ if isinstance(b, dict) and isinstance(b.get('text'), str)
50
+ )
51
+ if isinstance(content, str) and content:
52
+ texts.append(message['role'] + ': ' + content)
53
+ return lineage, '\n'.join(texts)
54
+
55
+ def _source_visible(self, contact_id: str, raw) -> bool:
56
+ if raw is None:
57
+ return True # No invented provenance or blanket removal of legacy facts.
58
+ from apsimo.turns.idempotency import SourceErased
59
+ lineage = json.loads(raw) if isinstance(raw, str) else raw
60
+ try:
61
+ current, _ = self.source_input(lineage['turn_id'], contact_id)
62
+ except SourceErased:
63
+ return False
64
+ return (current['session_id'] == lineage['session_id']
65
+ and current['message_hashes'] == lineage['message_hashes'])
66
+
67
+ def _invalid_sources(self, rows, turn_ids=None):
68
+ """Read one canonical snapshot, not a new connection per observation."""
69
+ from contextlib import closing
70
+ from apsimo.turns.idempotency import source_message_hash
71
+ selected = set(turn_ids) if turn_ids is not None else None
72
+ candidates = []
73
+ for row in rows:
74
+ lineage = json.loads(row['source_lineage_json'])
75
+ if selected is None or lineage['turn_id'] in selected:
76
+ candidates.append((row, lineage))
77
+ if not candidates:
78
+ return []
79
+ ids = list(dict.fromkeys(lineage['turn_id'] for _, lineage in candidates))
80
+ current = {}
81
+ with closing(self._ledger()._connect()) as conn:
82
+ conn.execute('BEGIN')
83
+ for start in range(0, len(ids), 400):
84
+ batch = ids[start:start + 400]
85
+ sql = ("SELECT turn_id,contact_id,session_id,messages_json FROM turn_sources s "
86
+ "WHERE scope='person' AND turn_id IN (" + ','.join('?' for _ in batch) + ") "
87
+ "AND NOT EXISTS (SELECT 1 FROM source_projection_erasures e WHERE e.turn_id=s.turn_id)")
88
+ for source in conn.execute(sql, batch):
89
+ current[source['turn_id']] = (source['contact_id'], source['session_id'],
90
+ [source_message_hash(source['session_id'], m) for m in json.loads(source['messages_json'])])
91
+ return [row for row, lineage in candidates
92
+ if current.get(lineage['turn_id']) !=
93
+ (row['contact_id'], lineage['session_id'], lineage['message_hashes'])]