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,932 @@
1
+ """Strict Phase C intake for external text and system cognition evidence.
2
+
3
+ External producers may report bounded observations. They cannot assert person,
4
+ audience, boundary, or transport authority, and this module never accepts a
5
+ realtime audio/voice surface. The server-attested envelope is stored before it
6
+ is projected into Colony's existing durable host event journal.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import asdict, dataclass
12
+ from datetime import datetime, timedelta, timezone
13
+ import hashlib
14
+ import json
15
+ import math
16
+ import os
17
+ import re
18
+ import sqlite3
19
+ import threading
20
+ import time
21
+ from typing import Any, Callable, Dict, Mapping, Optional
22
+
23
+ from apsimo.scope_bounds import (
24
+ SUBJECT_PERSON_ID_MAX_CHARS,
25
+ VIEWER_SCOPE_MAX_CHARS,
26
+ )
27
+
28
+
29
+ _KINDS = frozenset({
30
+ "action_outcome", "delivery_outcome", "service_state", "approval_state",
31
+ "operator_reaction", "text_turn_observation",
32
+ })
33
+ EXTERNAL_JOURNAL_EVENT_ID_MAX_CHARS = 128
34
+ _SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]{7,191}$")
35
+ _SAFE_REF = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]{0,191}$")
36
+ _SAFE_JOURNAL_EVENT_ID = re.compile(
37
+ rf"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]"
38
+ rf"{{0,{EXTERNAL_JOURNAL_EVENT_ID_MAX_CHARS - 1}}}$"
39
+ )
40
+ _SAFE_ATTRIBUTE = re.compile(r"^[a-z][a-z0-9_.-]{0,63}$")
41
+ _SECRET_KEY = re.compile(
42
+ r"(?i)(?:^|[_.-])(api[_-]?key|authorization|bearer|cookie|credential|"
43
+ r"password|private[_-]?key|secret|session|token)(?:$|[_.-])"
44
+ )
45
+ _SECRET_VALUE = re.compile(
46
+ r"(?ix)(?:"
47
+ r"\bbearer\s+[A-Za-z0-9._~+/=-]{8,}|"
48
+ r"\bsk-[A-Za-z0-9_-]{8,}|"
49
+ r"\b(?:api[\s_-]?(?:key|token)|authorization|cookie|credential|password|"
50
+ r"private[\s_-]?key|secret|session|token)\s*[:=]\s*"
51
+ r"(?:\"[^\"\r\n]{8,}\"|'[^'\r\n]{8,}'|[A-Za-z0-9._~+/=-]{8,})"
52
+ r")"
53
+ )
54
+ _RESERVED_ATTRIBUTES = frozenset({
55
+ "approval", "audience", "audiences", "authority", "boundary_attested",
56
+ "credential_id", "evidence_status", "grant", "outcome_contract",
57
+ "person_id", "principal_id", "producer_revision", "receipt_ref", "scope",
58
+ "scope_digest", "shareability", "subject_person_id", "verified",
59
+ "viewer_person_id", "viewer_scope",
60
+ })
61
+ _AUTHORITY_ATTRIBUTE_ALIAS = re.compile(
62
+ r"(?:^|[_.-])(?:approved?|approval|authority|authorized|evidence[_-]?status|"
63
+ r"granted?|outcome[_-]?contract|receipt[_-]?ref|verification|verified)"
64
+ r"(?:$|[_.-])"
65
+ )
66
+ _KIND_FIELDS = {
67
+ "action_outcome": {
68
+ "required": {"action_id", "outcome"},
69
+ "allowed": {
70
+ "action_id", "outcome", "action_digest", "error_code",
71
+ "duration_ms",
72
+ },
73
+ "enum": {
74
+ "outcome": {
75
+ "succeeded", "failed", "blocked", "cancelled", "partial",
76
+ },
77
+ },
78
+ },
79
+ "delivery_outcome": {
80
+ "required": {"outcome"},
81
+ "allowed": {
82
+ "delivery_ref", "message_ref", "outcome", "channel", "error_code",
83
+ },
84
+ "enum": {
85
+ "outcome": {
86
+ "delivered", "failed", "bounced", "read", "acknowledged",
87
+ },
88
+ "channel": {
89
+ "text", "sms", "rcs", "whatsapp", "chat", "email",
90
+ "webhook", "system",
91
+ },
92
+ },
93
+ },
94
+ "service_state": {
95
+ "required": {"service", "state"},
96
+ "allowed": {
97
+ "service", "state", "detail", "latency_ms", "observed_samples",
98
+ },
99
+ "enum": {
100
+ "state": {"healthy", "degraded", "offline", "recovering"},
101
+ },
102
+ },
103
+ "approval_state": {
104
+ "required": {"request_id", "state"},
105
+ "allowed": {"request_id", "state", "decision_ref", "reason_code"},
106
+ "enum": {
107
+ "state": {"pending", "approved", "rejected", "expired", "cancelled"},
108
+ },
109
+ },
110
+ "operator_reaction": {
111
+ "required": {"target_ref", "reaction"},
112
+ "allowed": {"target_ref", "reaction", "intensity", "signal"},
113
+ "enum": {
114
+ "reaction": {
115
+ "positive", "negative", "neutral", "correction", "dismissed",
116
+ },
117
+ },
118
+ },
119
+ "text_turn_observation": {
120
+ "required": {"turn_id", "channel", "observation"},
121
+ "allowed": {"turn_id", "channel", "observation"},
122
+ "enum": {
123
+ "channel": {"text", "sms", "rcs", "whatsapp", "chat", "email"},
124
+ },
125
+ },
126
+ }
127
+
128
+ _JOURNAL_PROJECTION_FIELDS = frozenset({
129
+ "schema", "version", "external_event_id", "external_event_digest",
130
+ "external_occurred_at", "kind", "summary", "attributes", "producer_principal_id",
131
+ "producer_revision", "subject_person_id", "viewer_person_id",
132
+ "viewer_scope", "shareability", "audience_scope", "scope_digest",
133
+ "boundary_attested", "evidence_status",
134
+ })
135
+
136
+
137
+ class ExternalEventValidationError(ValueError):
138
+ """The external event is outside the strict Phase C evidence schema."""
139
+
140
+
141
+ class ExternalEventConflict(ValueError):
142
+ """An event ID was replayed with changed immutable content."""
143
+
144
+
145
+ class ExternalEventProjectionError(RuntimeError):
146
+ """The receipt is durable but journal projection is currently unavailable."""
147
+
148
+
149
+ def validate_external_journal_event_id(value: Any) -> str:
150
+ """Return one exact, bounded host-journal identity for a V2 projection."""
151
+
152
+ if type(value) is not str or not _SAFE_JOURNAL_EVENT_ID.fullmatch(value):
153
+ raise ExternalEventValidationError(
154
+ "external cognition journal ID is not canonical"
155
+ )
156
+ return value
157
+
158
+
159
+ def _exact_projection_text(
160
+ value: Any,
161
+ *,
162
+ field: str,
163
+ maximum: int,
164
+ ) -> str:
165
+ """Accept only the exact JSON string form emitted by the server."""
166
+
167
+ if type(value) is not str:
168
+ raise ExternalEventValidationError(
169
+ f"external cognition journal {field} must be an exact string"
170
+ )
171
+ if not value or value != value.strip() or len(value) > maximum:
172
+ raise ExternalEventValidationError(
173
+ f"external cognition journal {field} is not canonical"
174
+ )
175
+ return value
176
+
177
+
178
+ def validate_external_journal_projection(
179
+ event_type: Any,
180
+ value: Any,
181
+ ) -> Dict[str, Any]:
182
+ """Validate one server-produced host-journal projection exactly.
183
+
184
+ This is the shared trust boundary for downstream durable consumers. It
185
+ deliberately revalidates the typed attributes and recomputes the scope
186
+ digest instead of trusting fields copied from the journal payload.
187
+ """
188
+
189
+ if not isinstance(value, Mapping):
190
+ raise ExternalEventValidationError(
191
+ "external cognition journal projection must be one object"
192
+ )
193
+ if set(value) != _JOURNAL_PROJECTION_FIELDS:
194
+ raise ExternalEventValidationError(
195
+ "external cognition journal projection fields are not exact"
196
+ )
197
+ if (
198
+ type(value.get("schema")) is not str
199
+ or value.get("schema") != "ExternalCognitionJournalProjectionV2"
200
+ or (
201
+ type(value.get("version")) is not int
202
+ or value.get("version") != 2
203
+ )
204
+ ):
205
+ raise ExternalEventValidationError(
206
+ "external cognition journal projection schema is invalid"
207
+ )
208
+ kind = _exact_projection_text(
209
+ value.get("kind"), field="kind", maximum=64,
210
+ )
211
+ if kind not in _KINDS:
212
+ raise ExternalEventValidationError(
213
+ "external cognition journal projection kind is unsupported"
214
+ )
215
+ if (
216
+ type(event_type) is not str
217
+ or event_type != f"cognition.external.{kind}"
218
+ ):
219
+ raise ExternalEventValidationError(
220
+ "external cognition journal event type differs from its kind"
221
+ )
222
+ external_id = _exact_projection_text(
223
+ value.get("external_event_id"), field="event ID", maximum=192,
224
+ )
225
+ if not _SAFE_ID.fullmatch(external_id):
226
+ raise ExternalEventValidationError(
227
+ "external cognition journal event ID is invalid"
228
+ )
229
+ external_digest = _exact_projection_text(
230
+ value.get("external_event_digest"), field="event digest", maximum=64,
231
+ )
232
+ scope_digest = _exact_projection_text(
233
+ value.get("scope_digest"), field="scope digest", maximum=64,
234
+ )
235
+ if not re.fullmatch(r"[a-f0-9]{64}", external_digest):
236
+ raise ExternalEventValidationError(
237
+ "external cognition journal event digest is invalid"
238
+ )
239
+ if not re.fullmatch(r"[a-f0-9]{64}", scope_digest):
240
+ raise ExternalEventValidationError(
241
+ "external cognition journal scope digest is invalid"
242
+ )
243
+ external_occurred_at = _exact_projection_text(
244
+ value.get("external_occurred_at"),
245
+ field="occurred time", maximum=64,
246
+ )
247
+ if _iso(_parse_time(
248
+ external_occurred_at, field="external_occurred_at",
249
+ )) != external_occurred_at:
250
+ raise ExternalEventValidationError(
251
+ "external cognition journal occurred time is not canonical"
252
+ )
253
+ producer = _exact_projection_text(
254
+ value.get("producer_principal_id"),
255
+ field="producer principal ID", maximum=128,
256
+ )
257
+ revision = _exact_projection_text(
258
+ value.get("producer_revision"), field="producer revision", maximum=64,
259
+ )
260
+ subject = _exact_projection_text(
261
+ value.get("subject_person_id"), field="subject person ID",
262
+ maximum=SUBJECT_PERSON_ID_MAX_CHARS,
263
+ )
264
+ viewer = _exact_projection_text(
265
+ value.get("viewer_person_id"), field="viewer person ID",
266
+ maximum=SUBJECT_PERSON_ID_MAX_CHARS,
267
+ )
268
+ if not _SAFE_REF.fullmatch(producer) or not re.fullmatch(
269
+ r"external-principal:[a-f0-9]{24}", revision,
270
+ ):
271
+ raise ExternalEventValidationError(
272
+ "external cognition journal producer identity is invalid"
273
+ )
274
+ if (
275
+ not _SAFE_REF.fullmatch(subject)
276
+ or not _SAFE_REF.fullmatch(viewer)
277
+ or subject != viewer
278
+ ):
279
+ raise ExternalEventValidationError(
280
+ "external cognition journal subject binding is invalid"
281
+ )
282
+ sharing = _exact_projection_text(
283
+ value.get("shareability"), field="shareability", maximum=32,
284
+ )
285
+ viewer_scope = _exact_projection_text(
286
+ value.get("viewer_scope"), field="viewer scope",
287
+ maximum=VIEWER_SCOPE_MAX_CHARS,
288
+ )
289
+ audience = value.get("audience_scope")
290
+ if (
291
+ type(audience) is not list
292
+ or any(
293
+ type(item) is not str or not item or item != item.strip()
294
+ for item in audience
295
+ )
296
+ ):
297
+ raise ExternalEventValidationError(
298
+ "external cognition journal audience scope is not canonical"
299
+ )
300
+ configured_owner = (
301
+ os.environ.get("COLONY_OWNER_PERSON_ID", "")
302
+ or os.environ.get("COLONY_OWNER_CONTACT_ID", "")
303
+ or "owner"
304
+ )
305
+ owner = _authority_id(
306
+ configured_owner, field="configured owner_person_id", maximum=128,
307
+ )
308
+ if sharing == "owner_private":
309
+ if subject != owner:
310
+ raise ExternalEventValidationError(
311
+ "external cognition owner lane subject is not the owner"
312
+ )
313
+ expected_viewer_scope = "owner"
314
+ expected_audience = ["owner"]
315
+ elif sharing == "subject_private":
316
+ if subject == owner:
317
+ raise ExternalEventValidationError(
318
+ "external cognition owner cannot assert a subject-private lane"
319
+ )
320
+ expected_viewer_scope = f"person:{subject}"
321
+ expected_audience = []
322
+ else:
323
+ raise ExternalEventValidationError(
324
+ "external cognition journal sharing lane is invalid"
325
+ )
326
+ if viewer_scope != expected_viewer_scope or audience != expected_audience:
327
+ raise ExternalEventValidationError(
328
+ "external cognition journal viewer or audience scope is invalid"
329
+ )
330
+ scope = {
331
+ "schema": "ExternalCognitionScopeV1",
332
+ "version": 1,
333
+ "subject_person_id": subject,
334
+ "viewer_person_id": viewer,
335
+ "viewer_scope": viewer_scope,
336
+ "shareability": sharing,
337
+ "audience_scope": expected_audience,
338
+ }
339
+ if scope_digest != _digest(scope):
340
+ raise ExternalEventValidationError(
341
+ "external cognition journal scope digest mismatch"
342
+ )
343
+ evidence_status = _exact_projection_text(
344
+ value.get("evidence_status"), field="evidence status", maximum=32,
345
+ )
346
+ if value.get("boundary_attested") is not False or (
347
+ evidence_status != "reported/unverified"
348
+ ):
349
+ raise ExternalEventValidationError(
350
+ "external cognition journal projection asserted authority"
351
+ )
352
+ if type(value.get("summary")) is not str:
353
+ raise ExternalEventValidationError(
354
+ "external cognition journal summary must be an exact string"
355
+ )
356
+ summary = _text(value.get("summary"), field="summary", maximum=1000)
357
+ attributes = _typed_attributes(kind, value.get("attributes"))
358
+ if summary != value.get("summary") or _canonical(attributes) != _canonical(
359
+ value.get("attributes")
360
+ ):
361
+ raise ExternalEventValidationError(
362
+ "external cognition journal content is not canonical"
363
+ )
364
+ return dict(value)
365
+
366
+
367
+ def _canonical(value: Any) -> str:
368
+ return json.dumps(
369
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":"),
370
+ )
371
+
372
+
373
+ def _digest(value: Any) -> str:
374
+ return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
375
+
376
+
377
+ def _parse_time(value: Any, *, field: str) -> datetime:
378
+ text = str(value or "").strip()
379
+ if text.endswith("Z"):
380
+ text = text[:-1] + "+00:00"
381
+ try:
382
+ parsed = datetime.fromisoformat(text)
383
+ except ValueError as exc:
384
+ raise ExternalEventValidationError(
385
+ f"{field} must be an RFC3339 timestamp"
386
+ ) from exc
387
+ if parsed.tzinfo is None:
388
+ raise ExternalEventValidationError(f"{field} requires a timezone")
389
+ return parsed.astimezone(timezone.utc)
390
+
391
+
392
+ def _iso(value: datetime) -> str:
393
+ return value.astimezone(timezone.utc).isoformat()
394
+
395
+
396
+ def _text(value: Any, *, field: str, maximum: int) -> str:
397
+ if not isinstance(value, str):
398
+ raise ExternalEventValidationError(f"{field} must be text")
399
+ result = " ".join(value.split()).strip()
400
+ if not result or len(result) > maximum:
401
+ raise ExternalEventValidationError(
402
+ f"{field} must contain 1-{maximum} characters"
403
+ )
404
+ if _SECRET_VALUE.search(result):
405
+ raise ExternalEventValidationError(f"{field} contains secret-like data")
406
+ return result
407
+
408
+
409
+ def _authority_id(value: Any, *, field: str, maximum: int) -> str:
410
+ """Accept one exact server authority identifier without collision-prone slicing."""
411
+
412
+ if (
413
+ type(value) is not str
414
+ or not value
415
+ or value != value.strip()
416
+ or len(value) > maximum
417
+ or not _SAFE_REF.fullmatch(value)
418
+ ):
419
+ raise ExternalEventValidationError(
420
+ f"{field} is outside the exact authority identifier boundary"
421
+ )
422
+ return value
423
+
424
+
425
+ def _attribute_scalar(value: Any, *, field: str) -> Any:
426
+ if value is None or isinstance(value, (bool, int)):
427
+ return value
428
+ if isinstance(value, float):
429
+ if not math.isfinite(value):
430
+ raise ExternalEventValidationError(f"{field} must be finite")
431
+ return value
432
+ if isinstance(value, str):
433
+ result = " ".join(value.split()).strip()
434
+ if len(result) > 500:
435
+ raise ExternalEventValidationError(f"{field} exceeds 500 characters")
436
+ if _SECRET_VALUE.search(result):
437
+ raise ExternalEventValidationError(f"{field} contains secret-like data")
438
+ return result
439
+ raise ExternalEventValidationError(
440
+ f"{field} supports only JSON scalars or scalar arrays"
441
+ )
442
+
443
+
444
+ def _attributes(value: Any) -> Dict[str, Any]:
445
+ if value is None:
446
+ return {}
447
+ if not isinstance(value, Mapping):
448
+ raise ExternalEventValidationError("attributes must be one object")
449
+ if len(value) > 32:
450
+ raise ExternalEventValidationError("attributes exceeds 32 fields")
451
+ result: Dict[str, Any] = {}
452
+ for raw_key, raw_value in value.items():
453
+ key = str(raw_key or "").strip().lower()
454
+ if not _SAFE_ATTRIBUTE.fullmatch(key):
455
+ raise ExternalEventValidationError("attributes contains an invalid key")
456
+ if key in _RESERVED_ATTRIBUTES or _AUTHORITY_ATTRIBUTE_ALIAS.search(key):
457
+ raise ExternalEventValidationError(
458
+ f"attributes.{key} attempts to assert server authority"
459
+ )
460
+ if _SECRET_KEY.search(key):
461
+ raise ExternalEventValidationError(
462
+ f"attributes.{key} is secret-bearing"
463
+ )
464
+ if isinstance(raw_value, list):
465
+ if len(raw_value) > 16:
466
+ raise ExternalEventValidationError(
467
+ f"attributes.{key} exceeds 16 list items"
468
+ )
469
+ result[key] = [
470
+ _attribute_scalar(item, field=f"attributes.{key}")
471
+ for item in raw_value
472
+ ]
473
+ else:
474
+ result[key] = _attribute_scalar(
475
+ raw_value, field=f"attributes.{key}",
476
+ )
477
+ if len(_canonical(result).encode("utf-8")) > 8192:
478
+ raise ExternalEventValidationError("attributes exceeds 8192 bytes")
479
+ return result
480
+
481
+
482
+ def _typed_attributes(kind: str, value: Any) -> Dict[str, Any]:
483
+ result = _attributes(value)
484
+ contract = _KIND_FIELDS[kind]
485
+ missing = set(contract["required"]) - set(result)
486
+ unknown = set(result) - set(contract["allowed"])
487
+ if kind == "delivery_outcome" and not (
488
+ result.get("delivery_ref") or result.get("message_ref")
489
+ ):
490
+ missing.add("delivery_ref_or_message_ref")
491
+ if missing:
492
+ raise ExternalEventValidationError(
493
+ f"{kind} attributes missing: {','.join(sorted(missing))}"
494
+ )
495
+ if unknown:
496
+ raise ExternalEventValidationError(
497
+ f"{kind} attributes unsupported: {','.join(sorted(unknown))}"
498
+ )
499
+ identifier_fields = {
500
+ "action_id", "action_digest", "delivery_ref", "message_ref", "service",
501
+ "request_id", "decision_ref", "target_ref", "turn_id",
502
+ }
503
+ for field in identifier_fields.intersection(result):
504
+ if not isinstance(result[field], str) or not _SAFE_REF.fullmatch(
505
+ result[field]
506
+ ):
507
+ raise ExternalEventValidationError(
508
+ f"{kind} attributes.{field} is invalid"
509
+ )
510
+ if "action_digest" in result and not (
511
+ isinstance(result["action_digest"], str)
512
+ and re.fullmatch(r"[a-f0-9]{64}", result["action_digest"])
513
+ ):
514
+ raise ExternalEventValidationError(
515
+ "action_outcome attributes.action_digest must be lowercase 64-hex"
516
+ )
517
+ for field in {
518
+ "channel", "detail", "error_code", "observation", "reason_code", "signal",
519
+ }.intersection(result):
520
+ if not isinstance(result[field], str):
521
+ raise ExternalEventValidationError(
522
+ f"{kind} attributes.{field} must be text"
523
+ )
524
+ if kind == "text_turn_observation":
525
+ result["observation"] = _text(
526
+ result["observation"],
527
+ field="text_turn_observation attributes.observation",
528
+ maximum=500,
529
+ )
530
+ numeric_bounds = {
531
+ "duration_ms": (0.0, 86_400_000.0, False),
532
+ "latency_ms": (0.0, 3_600_000.0, False),
533
+ "observed_samples": (1.0, 1_000_000.0, True),
534
+ "intensity": (0.0, 1.0, False),
535
+ }
536
+ for field, (minimum, maximum, integer_only) in numeric_bounds.items():
537
+ if field not in result:
538
+ continue
539
+ numeric = result[field]
540
+ if (
541
+ isinstance(numeric, bool)
542
+ or not isinstance(numeric, (int, float))
543
+ or not math.isfinite(float(numeric))
544
+ or not minimum <= float(numeric) <= maximum
545
+ or (integer_only and not isinstance(numeric, int))
546
+ ):
547
+ raise ExternalEventValidationError(
548
+ f"{kind} attributes.{field} is outside bounds"
549
+ )
550
+ for field, choices in contract["enum"].items():
551
+ if field not in result:
552
+ continue
553
+ if not isinstance(result.get(field), str):
554
+ raise ExternalEventValidationError(
555
+ f"{kind} attributes.{field} must be text"
556
+ )
557
+ normalized = result[field].strip().lower()
558
+ if normalized not in choices:
559
+ raise ExternalEventValidationError(
560
+ f"{kind} attributes.{field} is unsupported"
561
+ )
562
+ result[field] = normalized
563
+ return result
564
+
565
+
566
+ @dataclass(frozen=True)
567
+ class ExternalCognitionEventV1:
568
+ event_id: str
569
+ kind: str
570
+ occurred_at: str
571
+ summary: str
572
+ attributes: Mapping[str, Any]
573
+ producer_principal_id: str
574
+ producer_credential_id: str
575
+ producer_revision: str
576
+ subject_person_id: str
577
+ viewer_person_id: str
578
+ viewer_scope: str
579
+ shareability: str
580
+ audience_scope: tuple[str, ...]
581
+ scope_digest: str
582
+ boundary_attested: bool
583
+ event_digest: str
584
+ schema: str = "ExternalCognitionEventV1"
585
+ version: int = 1
586
+
587
+ @classmethod
588
+ def from_authority(
589
+ cls,
590
+ body: Mapping[str, Any],
591
+ *,
592
+ authority: Any,
593
+ now: Optional[datetime] = None,
594
+ ) -> "ExternalCognitionEventV1":
595
+ if not isinstance(body, Mapping):
596
+ raise ExternalEventValidationError("event body must be one object")
597
+ allowed = {"event_id", "kind", "occurred_at", "summary", "attributes"}
598
+ unknown = set(body) - allowed
599
+ if unknown:
600
+ raise ExternalEventValidationError(
601
+ "event body contains unsupported fields: "
602
+ + ",".join(sorted(str(item) for item in unknown))
603
+ )
604
+ if not (
605
+ getattr(authority, "authenticated", False)
606
+ and not getattr(authority, "legacy", False)
607
+ and not getattr(authority, "anonymous", False)
608
+ and authority.has_scope("cognition:events-ingest")
609
+ and getattr(authority, "principal_id", "")
610
+ and getattr(authority, "credential_id", "")
611
+ and getattr(authority, "viewer_person_id", "")
612
+ ):
613
+ raise ExternalEventValidationError(
614
+ "scoped principal with an exact viewer binding is required"
615
+ )
616
+ event_id = str(body.get("event_id") or "").strip()
617
+ if not _SAFE_ID.fullmatch(event_id):
618
+ raise ExternalEventValidationError("event_id is invalid")
619
+ kind = str(body.get("kind") or "").strip().lower()
620
+ if kind not in _KINDS:
621
+ raise ExternalEventValidationError("external event kind is unsupported")
622
+ observed = now or datetime.now(timezone.utc)
623
+ if observed.tzinfo is None:
624
+ observed = observed.replace(tzinfo=timezone.utc)
625
+ occurred = _parse_time(body.get("occurred_at"), field="occurred_at")
626
+ if occurred > observed.astimezone(timezone.utc) + timedelta(minutes=5):
627
+ raise ExternalEventValidationError("occurred_at is too far in the future")
628
+ if occurred < observed.astimezone(timezone.utc) - timedelta(days=366):
629
+ raise ExternalEventValidationError("occurred_at is outside retention bounds")
630
+ summary = _text(body.get("summary"), field="summary", maximum=1000)
631
+ attributes = _typed_attributes(kind, body.get("attributes"))
632
+
633
+ principal = _authority_id(
634
+ authority.principal_id, field="producer principal_id", maximum=128,
635
+ )
636
+ credential = _authority_id(
637
+ authority.credential_id, field="producer credential_id", maximum=192,
638
+ )
639
+ viewer = _authority_id(
640
+ authority.viewer_person_id, field="viewer_person_id", maximum=128,
641
+ )
642
+ configured_owner = (
643
+ os.environ.get("COLONY_OWNER_PERSON_ID", "")
644
+ or os.environ.get("COLONY_OWNER_CONTACT_ID", "")
645
+ or "owner"
646
+ )
647
+ owner = _authority_id(
648
+ configured_owner, field="configured owner_person_id", maximum=128,
649
+ )
650
+ owner_private = "owner" in authority.audiences and viewer == owner
651
+ sharing = "owner_private" if owner_private else "subject_private"
652
+ viewer_scope = "owner" if owner_private else f"person:{viewer}"
653
+ audience_scope = ("owner",) if owner_private else ()
654
+ scope = {
655
+ "schema": "ExternalCognitionScopeV1",
656
+ "version": 1,
657
+ "subject_person_id": viewer,
658
+ "viewer_person_id": viewer,
659
+ "viewer_scope": viewer_scope,
660
+ "shareability": sharing,
661
+ "audience_scope": list(audience_scope),
662
+ }
663
+ producer_revision = "external-principal:" + _digest({
664
+ "principal_id": principal,
665
+ "credential_id": credential,
666
+ "scopes": sorted(authority.scopes),
667
+ })[:24]
668
+ payload = {
669
+ "schema": "ExternalCognitionEventV1",
670
+ "version": 1,
671
+ "event_id": event_id,
672
+ "kind": kind,
673
+ "occurred_at": _iso(occurred),
674
+ "summary": summary,
675
+ "attributes": attributes,
676
+ "producer_principal_id": principal,
677
+ "producer_credential_id": credential,
678
+ "producer_revision": producer_revision,
679
+ "subject_person_id": scope["subject_person_id"],
680
+ "viewer_person_id": scope["viewer_person_id"],
681
+ "viewer_scope": scope["viewer_scope"],
682
+ "shareability": scope["shareability"],
683
+ "audience_scope": scope["audience_scope"],
684
+ "scope_digest": _digest(scope),
685
+ "boundary_attested": False,
686
+ }
687
+ return cls(
688
+ event_id=event_id,
689
+ kind=kind,
690
+ occurred_at=payload["occurred_at"],
691
+ summary=summary,
692
+ attributes=attributes,
693
+ producer_principal_id=principal,
694
+ producer_credential_id=credential,
695
+ producer_revision=producer_revision,
696
+ subject_person_id=viewer,
697
+ viewer_person_id=viewer,
698
+ viewer_scope=viewer_scope,
699
+ shareability=sharing,
700
+ audience_scope=audience_scope,
701
+ scope_digest=payload["scope_digest"],
702
+ boundary_attested=False,
703
+ event_digest=_digest(payload),
704
+ )
705
+
706
+ def payload(self) -> Dict[str, Any]:
707
+ payload = asdict(self)
708
+ payload["audience_scope"] = list(self.audience_scope)
709
+ payload["attributes"] = dict(self.attributes)
710
+ return payload
711
+
712
+ def journal_payload(self) -> Dict[str, Any]:
713
+ return {
714
+ "schema": "ExternalCognitionJournalProjectionV2",
715
+ "version": 2,
716
+ "external_event_id": self.event_id,
717
+ "external_event_digest": self.event_digest,
718
+ "external_occurred_at": self.occurred_at,
719
+ "kind": self.kind,
720
+ "summary": self.summary,
721
+ "attributes": dict(self.attributes),
722
+ "producer_principal_id": self.producer_principal_id,
723
+ "producer_revision": self.producer_revision,
724
+ "subject_person_id": self.subject_person_id,
725
+ "viewer_person_id": self.viewer_person_id,
726
+ "viewer_scope": self.viewer_scope,
727
+ "shareability": self.shareability,
728
+ "audience_scope": list(self.audience_scope),
729
+ "scope_digest": self.scope_digest,
730
+ "boundary_attested": False,
731
+ "evidence_status": "reported/unverified",
732
+ }
733
+
734
+
735
+ class ExternalEventInboxStore:
736
+ """Durable idempotency ledger for external cognition events."""
737
+
738
+ def __init__(self, db_path: str) -> None:
739
+ self._lock = threading.RLock()
740
+ self._conn = sqlite3.connect(str(db_path), check_same_thread=False)
741
+ self._conn.row_factory = sqlite3.Row
742
+ self._conn.execute("PRAGMA journal_mode=WAL")
743
+ with self._lock:
744
+ self._conn.execute(
745
+ """CREATE TABLE IF NOT EXISTS external_cognition_events (
746
+ event_id TEXT PRIMARY KEY,
747
+ event_digest TEXT NOT NULL,
748
+ event_json TEXT NOT NULL,
749
+ receipt_ref TEXT NOT NULL UNIQUE,
750
+ state TEXT NOT NULL,
751
+ receipt_json TEXT NOT NULL,
752
+ journal_seq INTEGER,
753
+ journal_event_id TEXT,
754
+ created_at REAL NOT NULL,
755
+ updated_at REAL NOT NULL
756
+ )"""
757
+ )
758
+ self._conn.commit()
759
+
760
+ def close(self) -> None:
761
+ with self._lock:
762
+ try:
763
+ self._conn.close()
764
+ except sqlite3.ProgrammingError:
765
+ pass
766
+
767
+ @staticmethod
768
+ def _row(row: sqlite3.Row) -> Dict[str, Any]:
769
+ result = dict(row)
770
+ result["event"] = json.loads(result.pop("event_json"))
771
+ result["receipt"] = json.loads(result.pop("receipt_json"))
772
+ return result
773
+
774
+ def reserve(
775
+ self,
776
+ event: ExternalCognitionEventV1,
777
+ *,
778
+ now: Optional[datetime] = None,
779
+ ) -> tuple[Dict[str, Any], bool]:
780
+ observed = now or datetime.now(timezone.utc)
781
+ if observed.tzinfo is None:
782
+ observed = observed.replace(tzinfo=timezone.utc)
783
+ stamp = observed.astimezone(timezone.utc).timestamp()
784
+ receipt_ref = f"external-event-receipt:{event.event_digest[:24]}"
785
+ accepted = {
786
+ "schema": "ExternalEventIntakeReceiptV1",
787
+ "version": 1,
788
+ "receipt_ref": receipt_ref,
789
+ "event_id": event.event_id,
790
+ "event_digest": event.event_digest,
791
+ "status": "accepted",
792
+ "subject_person_id": event.subject_person_id,
793
+ "viewer_person_id": event.viewer_person_id,
794
+ "shareability": event.shareability,
795
+ "scope_digest": event.scope_digest,
796
+ "journal_seq": None,
797
+ "journal_event_id": None,
798
+ "accepted_at": _iso(observed),
799
+ }
800
+ encoded_event = _canonical(event.payload())
801
+ with self._lock:
802
+ row = self._conn.execute(
803
+ "SELECT * FROM external_cognition_events WHERE event_id=?",
804
+ (event.event_id,),
805
+ ).fetchone()
806
+ if row is not None:
807
+ if (
808
+ row["event_digest"] != event.event_digest
809
+ or row["event_json"] != encoded_event
810
+ ):
811
+ raise ExternalEventConflict(
812
+ "event_id replay changed immutable content"
813
+ )
814
+ return self._row(row), False
815
+ self._conn.execute(
816
+ """INSERT INTO external_cognition_events
817
+ (event_id,event_digest,event_json,receipt_ref,state,
818
+ receipt_json,journal_seq,journal_event_id,created_at,updated_at)
819
+ VALUES (?,?,?,?, 'accepted',?,NULL,NULL,?,?)""",
820
+ (
821
+ event.event_id, event.event_digest, encoded_event,
822
+ receipt_ref, _canonical(accepted), stamp, stamp,
823
+ ),
824
+ )
825
+ self._conn.commit()
826
+ row = self._conn.execute(
827
+ "SELECT * FROM external_cognition_events WHERE event_id=?",
828
+ (event.event_id,),
829
+ ).fetchone()
830
+ return self._row(row), True
831
+
832
+ def complete_projection(
833
+ self,
834
+ event: ExternalCognitionEventV1,
835
+ journal_record: Mapping[str, Any],
836
+ ) -> Dict[str, Any]:
837
+ with self._lock:
838
+ row = self._conn.execute(
839
+ "SELECT * FROM external_cognition_events WHERE event_id=?",
840
+ (event.event_id,),
841
+ ).fetchone()
842
+ if row is None:
843
+ raise ValueError("external event receipt is unavailable")
844
+ if row["event_digest"] != event.event_digest:
845
+ raise ExternalEventConflict(
846
+ "event changed before journal projection"
847
+ )
848
+ if row["state"] == "projected":
849
+ return self._row(row)["receipt"]
850
+ receipt = json.loads(row["receipt_json"])
851
+ receipt.update({
852
+ "status": "projected",
853
+ "journal_seq": int(journal_record["seq"]),
854
+ "journal_event_id": str(journal_record["ulid"]),
855
+ "projected_at": str(journal_record["recordedAt"]),
856
+ "journal_retained": bool(journal_record.get("retained", True)),
857
+ })
858
+ self._conn.execute(
859
+ """UPDATE external_cognition_events
860
+ SET state='projected',receipt_json=?,journal_seq=?,
861
+ journal_event_id=?,updated_at=? WHERE event_id=?
862
+ AND state='accepted'""",
863
+ (
864
+ _canonical(receipt), receipt["journal_seq"],
865
+ receipt["journal_event_id"], time.time(), event.event_id,
866
+ ),
867
+ )
868
+ self._conn.commit()
869
+ return receipt
870
+
871
+
872
+ class ExternalEventIntake:
873
+ """Reserve one external receipt, then project it to the host journal."""
874
+
875
+ def __init__(
876
+ self,
877
+ store: ExternalEventInboxStore,
878
+ *,
879
+ journal_projector: Optional[Callable[..., Optional[Mapping[str, Any]]]] = None,
880
+ journal_acknowledger: Optional[Callable[[str], bool]] = None,
881
+ ) -> None:
882
+ self.store = store
883
+ if journal_projector is None:
884
+ from apsimo.events.journal import append_event_record
885
+ journal_projector = append_event_record
886
+ if journal_acknowledger is None:
887
+ from apsimo.events.journal import acknowledge_event_record
888
+ journal_acknowledger = acknowledge_event_record
889
+ self._project = journal_projector
890
+ self._acknowledge = journal_acknowledger
891
+
892
+ def close(self) -> None:
893
+ self.store.close()
894
+
895
+ def ingest(
896
+ self,
897
+ event: ExternalCognitionEventV1,
898
+ *,
899
+ now: Optional[datetime] = None,
900
+ ) -> Dict[str, Any]:
901
+ row, _created = self.store.reserve(event, now=now)
902
+ event_key = f"external-cognition:{event.event_id}"
903
+ if row["state"] == "projected":
904
+ # A process may have committed the inbox transaction and died
905
+ # before releasing the journal's metadata-only handshake marker.
906
+ self._acknowledge(event_key)
907
+ return dict(row["receipt"])
908
+ record = self._project(
909
+ f"cognition.external.{event.kind}",
910
+ event.journal_payload(),
911
+ occurred_at=event.occurred_at,
912
+ event_key=event_key,
913
+ )
914
+ if record is None:
915
+ raise ExternalEventProjectionError(
916
+ "external event receipt is durable; journal projection failed"
917
+ )
918
+ receipt = self.store.complete_projection(event, record)
919
+ # Only the durable inbox commit can acknowledge the keyed journal
920
+ # projection. A failed cleanup is harmless and retried on exact replay.
921
+ self._acknowledge(event_key)
922
+ return receipt
923
+
924
+
925
+ __all__ = [
926
+ "EXTERNAL_JOURNAL_EVENT_ID_MAX_CHARS",
927
+ "ExternalCognitionEventV1", "ExternalEventConflict",
928
+ "ExternalEventInboxStore", "ExternalEventIntake",
929
+ "ExternalEventProjectionError", "ExternalEventValidationError",
930
+ "validate_external_journal_event_id",
931
+ "validate_external_journal_projection",
932
+ ]