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,700 @@
1
+ """ResponseGuard — the rebuilt outbound response gate for the messaging path.
2
+
3
+ A focused replacement for the dormant 7-layer ``ResponseGate`` (which had a dead
4
+ cross-context layer, a recipient layer that blocks anything without a pre-registered
5
+ session, and an LLM review + send delay that don't belong in a live chat hot path).
6
+
7
+ ResponseGuard runs a small set of FAST, deterministic checks plus a provenance-based
8
+ cross-context leak check, in one of two modes on an explicit outbound surface:
9
+
10
+ * ``shadow`` — evaluate and report findings, but never change the outcome (ALLOW).
11
+ Used to observe real traffic and tune before enforcing.
12
+ * ``enforce`` — a blocking finding yields REVISE (caller regenerates once, then
13
+ suppresses on repeat).
14
+
15
+ Contract guarantees:
16
+ * shadow evaluation remains fail-open and observational;
17
+ * enforce evaluation on a guarded text/artifact surface fails closed when a
18
+ configured check is unavailable;
19
+ * real-time speech is excluded by exact surface type, never by a caller-chosen
20
+ gateway label; and
21
+ * a request may strengthen shadow to enforce but cannot weaken configured enforce.
22
+
23
+ The deterministic checks reuse the existing layer implementations (PII, trust tier,
24
+ injection); the cross-context check is an injected, provenance-backed dependency
25
+ (``cross_context``) so this module stays decoupled from the memory/provenance layer.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import logging
31
+ import os
32
+ import time
33
+ from collections import deque
34
+ from dataclasses import dataclass, field
35
+ from datetime import datetime, timezone
36
+ from enum import Enum
37
+ from hashlib import sha256
38
+ from typing import Any, Dict, Iterable, List, Optional, Sequence
39
+
40
+ from apsimo.gate.config import GateConfig
41
+ from apsimo.gate.communication_policy import CommunicationPolicyContextV1
42
+ from apsimo.gate.layers.l2_pii import PIIScanner
43
+ from apsimo.gate.layers.l4_trust_tier import TrustTierChecker
44
+ from apsimo.gate.layers.l5_injection import InjectionDetector
45
+ from apsimo.gate.models import GatePayload
46
+ from apsimo.gate.surface_policy import (
47
+ POLICY_DIGEST,
48
+ ResponseGuardSurfacePolicyV1,
49
+ SurfacePolicyDecisionV1,
50
+ )
51
+ from apsimo.intelligence.relationships.trust_tiers import TrustTier
52
+
53
+ logger = logging.getLogger(__name__)
54
+ _guard_audit = logging.getLogger("colony.gate.guard")
55
+
56
+ # Contact-layer tiers that the 5-value gate enum does not carry map to the nearest
57
+ # gate tier so disclosure gating still applies (acquaintance/unknown -> peripheral).
58
+ _CONTACT_TO_GATE_TIER = {
59
+ "acquaintance": TrustTier.PERIPHERAL,
60
+ "unknown": TrustTier.PERIPHERAL,
61
+ }
62
+
63
+
64
+ def to_gate_tier(value: Any) -> TrustTier:
65
+ """Coerce a tier (TrustTier or contact-layer string) to a gate TrustTier."""
66
+ if isinstance(value, TrustTier):
67
+ return value
68
+ try:
69
+ return TrustTier(value)
70
+ except (ValueError, TypeError):
71
+ return _CONTACT_TO_GATE_TIER.get(str(value), TrustTier.REGULAR)
72
+
73
+
74
+ class GuardMode(str, Enum):
75
+ SHADOW = "shadow"
76
+ ENFORCE = "enforce"
77
+
78
+
79
+ def enforce_allowlist() -> Optional[frozenset]:
80
+ """Checks allowed to BLOCK in enforce mode (per-check enforce ramp).
81
+
82
+ COLONY_GUARD_ENFORCE_CHECKS, default ``secret_leak,tom2_epistemic`` —
83
+ the lowest false-positive checks go first; everything else keeps shadow
84
+ semantics (observed, audited, never suppressing) until explicitly
85
+ added. ``tom2_epistemic`` ships allowlisted because it is INERT until a
86
+ level-2 injection registers a taint (zero findings, zero false
87
+ positives, on all other traffic). It is one level-2 prerequisite, but is
88
+ not sufficient without a receipt-backed applied-output evidence probe.
89
+ ``all`` / ``*`` restores full enforcement across every check (legacy).
90
+ Returns None for "all checks", else a frozenset of check names.
91
+ """
92
+ raw = os.environ.get("COLONY_GUARD_ENFORCE_CHECKS",
93
+ "secret_leak,tom2_epistemic").strip()
94
+ if raw.lower() in ("all", "*"):
95
+ return None
96
+ return frozenset(c.strip() for c in raw.split(",") if c.strip())
97
+
98
+
99
+ def breaker_enabled() -> bool:
100
+ """COLONY_GUARD_BREAKER, default on. off/0/false disables the breaker."""
101
+ return os.environ.get("COLONY_GUARD_BREAKER", "on").strip().lower() not in (
102
+ "off", "0", "false", "no")
103
+
104
+
105
+ def breaker_trip_blocks() -> int:
106
+ """Enforce blocks within 24h that trip the breaker (default 10).
107
+ <=0 disables tripping."""
108
+ try:
109
+ return int(os.environ.get("COLONY_GUARD_TRIP_BLOCKS", "10"))
110
+ except (TypeError, ValueError):
111
+ return 10
112
+
113
+
114
+ class GuardDecision(str, Enum):
115
+ ALLOW = "allow"
116
+ REVISE = "revise"
117
+ BLOCK = "block"
118
+
119
+
120
+ @dataclass
121
+ class GuardFinding:
122
+ check: str # secret_leak | disclosure_tier | injection | cross_context
123
+ severity: str # "block" (would suppress in enforce) | "warn" (advisory)
124
+ reason: str
125
+ excerpt: Optional[str] = None
126
+
127
+
128
+ @dataclass
129
+ class GuardResult:
130
+ decision: str
131
+ mode: str
132
+ findings: List[GuardFinding] = field(default_factory=list)
133
+ evaluated_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
134
+ surface: str = "text_chat"
135
+ surface_family: str = "text"
136
+ applicability: str = "guarded"
137
+ guard_status: str = "evaluated"
138
+ policy_id: str = "response-guard-surface-policy-v1"
139
+ policy_digest: str = POLICY_DIGEST
140
+ candidate_digest: str = ""
141
+ communication_policy_digest: Optional[str] = None
142
+ communication_context_digest: Optional[str] = None
143
+
144
+ @property
145
+ def blocked(self) -> bool:
146
+ return self.decision in (GuardDecision.REVISE.value, GuardDecision.BLOCK.value)
147
+
148
+ def to_dict(self) -> dict:
149
+ result = {
150
+ "decision": self.decision,
151
+ "mode": self.mode,
152
+ "surface": self.surface,
153
+ "surface_family": self.surface_family,
154
+ "applicability": self.applicability,
155
+ "guard_status": self.guard_status,
156
+ "policy_id": self.policy_id,
157
+ "policy_digest": self.policy_digest,
158
+ "candidate_digest": self.candidate_digest,
159
+ "findings": [
160
+ {"check": f.check, "severity": f.severity, "reason": f.reason,
161
+ "excerpt": f.excerpt}
162
+ for f in self.findings
163
+ ],
164
+ }
165
+ # Preserve the legacy exact result shape when no communication policy
166
+ # was supplied. New clients opt into exactly two binding fields.
167
+ if self.communication_policy_digest is not None:
168
+ result["communication_policy_digest"] = self.communication_policy_digest
169
+ result["communication_context_digest"] = self.communication_context_digest
170
+ return result
171
+
172
+
173
+ def _result_for_surface(
174
+ surface_decision: SurfacePolicyDecisionV1,
175
+ *,
176
+ decision: str,
177
+ findings: List[GuardFinding],
178
+ guard_status: str,
179
+ candidate_digest: str,
180
+ communication_policy: Optional[CommunicationPolicyContextV1] = None,
181
+ ) -> GuardResult:
182
+ if decision not in {item.value for item in GuardDecision}:
183
+ raise ValueError("invalid guard result decision")
184
+ return GuardResult(
185
+ decision=decision,
186
+ mode=surface_decision.effective_mode,
187
+ findings=findings,
188
+ surface=surface_decision.surface,
189
+ surface_family=surface_decision.family,
190
+ applicability=surface_decision.disposition,
191
+ guard_status=guard_status,
192
+ policy_id=surface_decision.policy_id,
193
+ policy_digest=surface_decision.policy_digest,
194
+ candidate_digest=candidate_digest,
195
+ communication_policy_digest=(
196
+ communication_policy.policy_digest
197
+ if communication_policy is not None
198
+ else None
199
+ ),
200
+ communication_context_digest=(
201
+ communication_policy.context_digest
202
+ if communication_policy is not None
203
+ else None
204
+ ),
205
+ )
206
+
207
+
208
+ def response_text_digest(response_text: Any) -> str:
209
+ """Stable identity of the exact Unicode candidate evaluated/released."""
210
+
211
+ return sha256(str(response_text or "").encode("utf-8")).hexdigest()
212
+
213
+
214
+ def unavailable_guard_result(
215
+ *,
216
+ surface: str,
217
+ configured_mode: Any,
218
+ requested_mode: Optional[Any] = None,
219
+ response_text: str = "",
220
+ surface_policy: Optional[ResponseGuardSurfacePolicyV1] = None,
221
+ communication_policy: Optional[CommunicationPolicyContextV1] = None,
222
+ ) -> GuardResult:
223
+ """Return the deterministic result for a configured guard outage.
224
+
225
+ Shadow text/artifact checks remain observational and allow. Enforce
226
+ text/artifact checks block. Speech remains excluded and is never routed
227
+ through guard evaluation.
228
+ """
229
+ policy = surface_policy or ResponseGuardSurfacePolicyV1()
230
+ surface_decision = policy.resolve(
231
+ surface,
232
+ configured_mode=configured_mode,
233
+ requested_mode=requested_mode,
234
+ )
235
+ if not surface_decision.guarded:
236
+ return _result_for_surface(
237
+ surface_decision,
238
+ decision=GuardDecision.ALLOW.value,
239
+ findings=[],
240
+ guard_status="bypassed",
241
+ candidate_digest=response_text_digest(response_text),
242
+ communication_policy=communication_policy,
243
+ )
244
+ enforce = surface_decision.effective_mode == GuardMode.ENFORCE.value
245
+ return _result_for_surface(
246
+ surface_decision,
247
+ decision=(
248
+ GuardDecision.BLOCK.value
249
+ if enforce else GuardDecision.ALLOW.value
250
+ ),
251
+ findings=[GuardFinding(
252
+ check="guard_unavailable",
253
+ severity="block" if enforce else "warn",
254
+ reason="response guard unavailable",
255
+ )],
256
+ guard_status="degraded",
257
+ candidate_digest=response_text_digest(response_text),
258
+ communication_policy=communication_policy,
259
+ )
260
+
261
+
262
+ class CrossContextGuard:
263
+ """Interface for the provenance-based cross-context leak check (filled in by the
264
+ memory/provenance layer). A no-op default so ResponseGuard works before it lands."""
265
+
266
+ async def check(self, *, response_text: str, conversation_key: Optional[str],
267
+ mentioned_entities: Sequence[str],
268
+ communication_policy: Optional[
269
+ CommunicationPolicyContextV1
270
+ ] = None) -> List[GuardFinding]:
271
+ return []
272
+
273
+
274
+ class ResponseGuard:
275
+ def __init__(self, config: Optional[GateConfig] = None,
276
+ cross_context: Optional[CrossContextGuard] = None,
277
+ default_mode: GuardMode = GuardMode.SHADOW,
278
+ excluded_gateways: Optional[Iterable[str]] = None,
279
+ audit_store: Optional[Any] = None,
280
+ tom2_epistemic: Optional[Any] = None,
281
+ surface_policy: Optional[ResponseGuardSurfacePolicyV1] = None) -> None:
282
+ self._config = config or GateConfig()
283
+ self._audit = audit_store
284
+ # Retained only as migration telemetry. Gateway names are not authority
285
+ # to bypass a content guard; exact SurfacePolicyV1 classification is.
286
+ self._legacy_excluded_gateways = frozenset(
287
+ (g or "").lower() for g in (excluded_gateways or ())
288
+ )
289
+ if self._legacy_excluded_gateways:
290
+ logger.warning(
291
+ "ResponseGuard ignored legacy excluded_gateways=%s; "
292
+ "use an exact outbound surface",
293
+ sorted(self._legacy_excluded_gateways),
294
+ )
295
+ self._surface_policy = surface_policy or ResponseGuardSurfacePolicyV1()
296
+ self._pii = PIIScanner(self._config)
297
+ self._tier = TrustTierChecker(self._config)
298
+ try:
299
+ self._injection: Optional[InjectionDetector] = InjectionDetector(self._config)
300
+ except Exception as exc: # ruleset load failure must not break the guard
301
+ logger.warning("ResponseGuard: injection detector unavailable: %s", exc)
302
+ self._injection = None
303
+ self._cross = cross_context
304
+ # tom2 epistemic egress net (L3.2): injected like cross_context so
305
+ # this module stays decoupled from the taint/tom layer. Inert (zero
306
+ # findings) whenever no level-2 injection taint is live.
307
+ self._tom2_epistemic = tom2_epistemic
308
+ self._default_mode = default_mode
309
+ # Circuit breaker state: timestamps of enforce-mode blocks (24h
310
+ # rolling window). The breaker only ever WEAKENS enforcement (open =
311
+ # fall back to shadow semantics); it can never latch INTO enforce.
312
+ self._block_times: deque = deque()
313
+
314
+ async def evaluate(
315
+ self,
316
+ *,
317
+ surface: str,
318
+ response_text: str,
319
+ incoming_message_text: str = "",
320
+ trust_tier: Any = TrustTier.REGULAR,
321
+ target_contact_id: str = "",
322
+ target_gateway: str = "",
323
+ session_id: str = "",
324
+ turn_id: str = "",
325
+ mentioned_entities: Optional[Sequence[str]] = None,
326
+ conversation_key: Optional[str] = None,
327
+ mode: Optional[GuardMode] = None,
328
+ authorized: bool = False,
329
+ communication_policy: Optional[CommunicationPolicyContextV1] = None,
330
+ ) -> GuardResult:
331
+ if communication_policy is not None:
332
+ if not isinstance(communication_policy, CommunicationPolicyContextV1):
333
+ raise TypeError(
334
+ "communication_policy must be CommunicationPolicyContextV1"
335
+ )
336
+ # Pydantic's trusted ``model_construct``/``model_copy(update=...)``
337
+ # APIs can create an instance without field validation. Re-parse
338
+ # the canonical wire mapping at the effect boundary rather than
339
+ # treating isinstance() as sufficient attestation.
340
+ communication_policy = CommunicationPolicyContextV1.model_validate(
341
+ communication_policy.canonical_dict()
342
+ )
343
+ if target_contact_id != communication_policy.target_contact_id:
344
+ raise ValueError(
345
+ "communication policy target must match target_contact_id"
346
+ )
347
+ surface_decision = self._surface_policy.resolve(
348
+ surface,
349
+ configured_mode=self._default_mode,
350
+ requested_mode=mode,
351
+ )
352
+ if not surface_decision.guarded:
353
+ result = self._surface_result(
354
+ surface_decision,
355
+ decision=GuardDecision.ALLOW.value,
356
+ findings=[],
357
+ guard_status="bypassed",
358
+ response_text=response_text,
359
+ communication_policy=communication_policy,
360
+ )
361
+ self._audit_communication_policy(
362
+ result,
363
+ communication_policy=communication_policy,
364
+ conversation_key=conversation_key,
365
+ target_contact_id=target_contact_id,
366
+ target_gateway=target_gateway,
367
+ authorized=authorized,
368
+ response_text=response_text,
369
+ )
370
+ return result
371
+ effective_mode = GuardMode(surface_decision.effective_mode)
372
+ try:
373
+ tier = to_gate_tier(trust_tier)
374
+ payload = GatePayload(
375
+ response_text=response_text or "",
376
+ target_contact_id=target_contact_id,
377
+ target_gateway=target_gateway,
378
+ session_id=session_id,
379
+ trust_tier=tier,
380
+ mentioned_entities=frozenset(mentioned_entities or []),
381
+ turn_id=turn_id,
382
+ incoming_message_text=incoming_message_text or "",
383
+ communication_policy=communication_policy,
384
+ )
385
+ findings: List[GuardFinding] = []
386
+ unavailable: List[str] = []
387
+
388
+ observed, failed = await self._run_check(
389
+ "secret_leak", "block", self._pii, payload,
390
+ )
391
+ findings += observed
392
+ if failed:
393
+ unavailable.append("secret_leak")
394
+ observed, failed = await self._run_check(
395
+ "disclosure_tier", "block", self._tier, payload,
396
+ )
397
+ findings += observed
398
+ if failed:
399
+ unavailable.append("disclosure_tier")
400
+ if self._injection is not None:
401
+ observed, failed = await self._run_check(
402
+ "injection", "warn", self._injection, payload,
403
+ )
404
+ findings += observed
405
+ if failed:
406
+ unavailable.append("injection")
407
+ else:
408
+ # Configured-but-absent: the guard always configures the
409
+ # injection detector in __init__; None means its ruleset
410
+ # failed to load. Enforce must fail closed on that gap,
411
+ # shadow observes it (warn finding), never a silent skip.
412
+ unavailable.append("injection")
413
+ if self._cross is not None:
414
+ try:
415
+ cross_kwargs = {
416
+ "response_text": response_text or "",
417
+ "conversation_key": conversation_key,
418
+ "mentioned_entities": list(mentioned_entities or []),
419
+ }
420
+ # Legacy check implementations retain their exact call
421
+ # shape for legacy evaluations. A policy-bound evaluation
422
+ # requires a checker to accept the immutable policy input;
423
+ # refusal becomes configured-check unavailability.
424
+ if communication_policy is not None:
425
+ cross_kwargs["communication_policy"] = communication_policy
426
+ findings += list(await self._cross.check(**cross_kwargs))
427
+ except Exception as exc:
428
+ logger.warning("ResponseGuard: cross_context check failed: %s", exc)
429
+ unavailable.append("cross_context")
430
+ if self._tom2_epistemic is not None:
431
+ try:
432
+ findings += list(await self._tom2_epistemic.check(
433
+ response_text=response_text or "",
434
+ conversation_key=conversation_key))
435
+ except Exception as exc:
436
+ logger.warning("ResponseGuard: tom2_epistemic check failed: %s", exc)
437
+ unavailable.append("tom2_epistemic")
438
+
439
+ if unavailable:
440
+ findings.append(GuardFinding(
441
+ check="guard_unavailable",
442
+ severity=(
443
+ "block"
444
+ if effective_mode == GuardMode.ENFORCE
445
+ else "warn"
446
+ ),
447
+ reason="configured checks unavailable: " + ",".join(unavailable),
448
+ ))
449
+
450
+ # Owner-directed (authorized) cross-context transfers are legitimate, not leaks:
451
+ # downgrade them so enforce never blocks them, while still keeping the audit row.
452
+ cross = [f for f in findings if f.check == "cross_context"]
453
+ if authorized:
454
+ for f in cross:
455
+ f.severity = "info"
456
+ f.reason = "authorized owner-directed transfer: " + f.reason
457
+
458
+ fatal_unavailable = bool(
459
+ unavailable and effective_mode == GuardMode.ENFORCE
460
+ )
461
+ decision = (
462
+ GuardDecision.BLOCK.value
463
+ if fatal_unavailable
464
+ else self._decide(findings, effective_mode)
465
+ )
466
+ # Circuit breaker (fails open, never INTO enforce): repeated
467
+ # enforce blocks in a 24h window suspend suppression — the guard
468
+ # keeps evaluating and auditing, but allows, until the window
469
+ # slides. A breaker fault leaves the decision unchanged.
470
+ if decision != GuardDecision.ALLOW.value and not fatal_unavailable:
471
+ try:
472
+ if self._breaker_open():
473
+ logger.warning(
474
+ "ResponseGuard breaker OPEN (%d blocks/24h >= %d)"
475
+ " — enforce suspended, allowing (would be %s)",
476
+ len(self._block_times), breaker_trip_blocks(),
477
+ decision)
478
+ decision = GuardDecision.ALLOW.value
479
+ else:
480
+ self._block_times.append(time.time())
481
+ except Exception:
482
+ logger.debug("guard breaker check failed (decision "
483
+ "unchanged)", exc_info=True)
484
+ result = self._surface_result(
485
+ surface_decision,
486
+ decision=decision,
487
+ findings=findings,
488
+ guard_status="degraded" if unavailable else "evaluated",
489
+ response_text=response_text,
490
+ communication_policy=communication_policy,
491
+ )
492
+ # Audit trail: count EVERY evaluation (the rate denominator), and record a
493
+ # row for any evaluation with findings or a non-allow decision — all checks,
494
+ # not just cross_context — so per-check rates and the would-block rate can
495
+ # be measured in shadow before enforcement is turned on.
496
+ if self._audit is not None:
497
+ try:
498
+ self._audit.count_evaluation()
499
+ if findings or decision != GuardDecision.ALLOW.value:
500
+ self._audit.record(
501
+ conversation_key=conversation_key, mode=result.mode, decision=decision,
502
+ authorized=authorized, checks=[f.check for f in findings],
503
+ entities=[f.excerpt or "" for f in findings],
504
+ response_text=response_text or "",
505
+ would_block=any(f.severity == "block" for f in findings),
506
+ gateway=target_gateway or None,
507
+ surface=surface_decision.surface,
508
+ policy_id=surface_decision.policy_id,
509
+ policy_digest=surface_decision.policy_digest,
510
+ guard_status=result.guard_status)
511
+ except Exception:
512
+ logger.debug("guard audit record failed", exc_info=True)
513
+ self._audit_communication_policy(
514
+ result,
515
+ communication_policy=communication_policy,
516
+ conversation_key=conversation_key,
517
+ target_contact_id=target_contact_id,
518
+ target_gateway=target_gateway,
519
+ authorized=authorized,
520
+ response_text=response_text,
521
+ )
522
+ if findings:
523
+ _guard_audit.info(
524
+ "guard mode=%s decision=%s contact=%s turn=%s findings=%s",
525
+ result.mode, decision, target_contact_id, turn_id,
526
+ [f"{f.check}:{f.severity}" for f in findings],
527
+ )
528
+ return result
529
+ except Exception as exc:
530
+ logger.warning("ResponseGuard evaluation error: %s", exc)
531
+ enforce = effective_mode == GuardMode.ENFORCE
532
+ result = self._surface_result(
533
+ surface_decision,
534
+ decision=(
535
+ GuardDecision.BLOCK.value
536
+ if enforce else GuardDecision.ALLOW.value
537
+ ),
538
+ findings=[GuardFinding(
539
+ check="guard_unavailable",
540
+ severity="block" if enforce else "warn",
541
+ reason="response guard evaluation unavailable",
542
+ )],
543
+ guard_status="degraded",
544
+ response_text=response_text,
545
+ communication_policy=communication_policy,
546
+ )
547
+ self._audit_communication_policy(
548
+ result,
549
+ communication_policy=communication_policy,
550
+ conversation_key=conversation_key,
551
+ target_contact_id=target_contact_id,
552
+ target_gateway=target_gateway,
553
+ authorized=authorized,
554
+ response_text=response_text,
555
+ )
556
+ return result
557
+
558
+ @staticmethod
559
+ def _surface_result(
560
+ surface_decision: SurfacePolicyDecisionV1,
561
+ *,
562
+ decision: str,
563
+ findings: List[GuardFinding],
564
+ guard_status: str,
565
+ response_text: str,
566
+ communication_policy: Optional[CommunicationPolicyContextV1] = None,
567
+ ) -> GuardResult:
568
+ return _result_for_surface(
569
+ surface_decision,
570
+ decision=decision,
571
+ findings=findings,
572
+ guard_status=guard_status,
573
+ candidate_digest=response_text_digest(response_text),
574
+ communication_policy=communication_policy,
575
+ )
576
+
577
+ def _audit_communication_policy(
578
+ self,
579
+ result: GuardResult,
580
+ *,
581
+ communication_policy: Optional[CommunicationPolicyContextV1],
582
+ conversation_key: Optional[str],
583
+ target_contact_id: str,
584
+ target_gateway: str,
585
+ authorized: bool,
586
+ response_text: str,
587
+ ) -> None:
588
+ """Persist a digest-bound row for every policy-bound evaluation.
589
+
590
+ This is separate from finding rows so clean constrained messages do
591
+ not inflate ResponseGuard's historical false-positive denominator.
592
+ """
593
+
594
+ if communication_policy is None or self._audit is None:
595
+ return
596
+ recorder = getattr(self._audit, "record_communication_policy", None)
597
+ if not callable(recorder):
598
+ return
599
+ try:
600
+ recorder(
601
+ conversation_key=conversation_key,
602
+ mode=result.mode,
603
+ decision=result.decision,
604
+ authorized=authorized,
605
+ gateway=target_gateway or None,
606
+ surface=result.surface,
607
+ surface_policy_id=result.policy_id,
608
+ surface_policy_digest=result.policy_digest,
609
+ response_text=response_text or "",
610
+ guard_status=result.guard_status,
611
+ target_contact_id=target_contact_id,
612
+ communication_policy=communication_policy,
613
+ )
614
+ except Exception:
615
+ logger.debug(
616
+ "communication policy audit record failed", exc_info=True
617
+ )
618
+
619
+ async def _run_check(self, name: str, severity: str, checker: Any,
620
+ payload: GatePayload) -> tuple[List[GuardFinding], bool]:
621
+ try:
622
+ r = await checker.check(payload)
623
+ except Exception as exc:
624
+ logger.warning("ResponseGuard: check %s unavailable: %s", name, exc)
625
+ return [], True
626
+ if getattr(r, "blocked", False):
627
+ return ([GuardFinding(check=name, severity=severity,
628
+ reason=getattr(r, "reason", name),
629
+ excerpt=getattr(r, "flagged_excerpt", None))], False)
630
+ return [], False
631
+
632
+ @staticmethod
633
+ def _decide(findings: List[GuardFinding], mode: GuardMode) -> str:
634
+ if getattr(mode, "value", mode) == GuardMode.SHADOW.value:
635
+ return GuardDecision.ALLOW.value # shadow never changes the outcome
636
+ # Per-check enforce allowlist: only allowlisted checks may suppress;
637
+ # everything else keeps shadow semantics inside enforce mode.
638
+ allowed = enforce_allowlist()
639
+ for f in findings:
640
+ if f.severity != "block":
641
+ continue
642
+ if allowed is None or f.check in allowed:
643
+ return GuardDecision.REVISE.value # caller regenerates once, then suppresses
644
+ return GuardDecision.ALLOW.value
645
+
646
+ def _breaker_open(self) -> bool:
647
+ """True when the enforce circuit breaker is tripped (24h window)."""
648
+ if not breaker_enabled():
649
+ return False
650
+ threshold = breaker_trip_blocks()
651
+ if threshold <= 0:
652
+ return False
653
+ cutoff = time.time() - 86400.0
654
+ while self._block_times and self._block_times[0] < cutoff:
655
+ self._block_times.popleft()
656
+ return len(self._block_times) >= threshold
657
+
658
+ @property
659
+ def configured_mode(self) -> GuardMode:
660
+ """The server-owned default mode used by direct in-process callers."""
661
+
662
+ return self._default_mode
663
+
664
+ def evidence_probe(self, audit_store: Optional[Any] = None,
665
+ check: str = "tom2_epistemic",
666
+ hours: float = 24.0,
667
+ surface: str = "text_chat"):
668
+ """Return a conservative probe until applied egress receipts exist.
669
+
670
+ A guard *evaluation* is not proof that a caller actually withheld or
671
+ revised the evaluated bytes. The current audit store records verdicts,
672
+ not digest-bound application receipts, so it must never unlock Tom2
673
+ level 2. The compatibility arguments remain accepted for callers that
674
+ construct this probe, but every result is False until a real egress
675
+ mediator can attest the applied output.
676
+ """
677
+
678
+ del audit_store, check, hours, surface
679
+
680
+ def probe(gateway: str) -> bool:
681
+ del gateway
682
+ return False
683
+
684
+ return probe
685
+
686
+ def breaker_status(self) -> Dict[str, Any]:
687
+ """Observability surface for the enforce breaker + allowlist."""
688
+ allowed = enforce_allowlist()
689
+ cutoff = time.time() - 86400.0
690
+ while self._block_times and self._block_times[0] < cutoff:
691
+ self._block_times.popleft()
692
+ return {
693
+ "enabled": breaker_enabled(),
694
+ "trip_blocks": breaker_trip_blocks(),
695
+ "blocks_24h": len(self._block_times),
696
+ "tripped": self._breaker_open(),
697
+ "enforce_checks": (sorted(allowed) if allowed is not None
698
+ else "all"),
699
+ "surface_policy": self._surface_policy.public(),
700
+ }