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,250 @@
1
+ """Transaction types and chain state dataclasses for ColonyChain.
2
+
3
+ Each transaction type is fully typed. The ChainState dataclass holds the
4
+ complete materialized state derived from replaying all committed blocks.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timezone
14
+ from enum import Enum
15
+ from typing import Any, Callable
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Transaction type enum
20
+ # ---------------------------------------------------------------------------
21
+
22
+ class TxType(str, Enum):
23
+ COLONY_REGISTER = "colony_register"
24
+ COLONY_ROTATE_KEY = "colony_rotate_key"
25
+ COLONY_REVOKE_KEY = "colony_revoke_key"
26
+ COLONY_RELEASE_NAME = "colony_release_name"
27
+ TRUST_ATTEST = "trust_attest"
28
+ UNTRUST_ATTEST = "untrust_attest"
29
+ SENTINEL_REGISTER = "sentinel_register"
30
+ SENTINEL_DEREGISTER = "sentinel_deregister"
31
+ COLONY_SUSPEND = "colony_suspend"
32
+ COLONY_REINSTATE = "colony_reinstate"
33
+ PROTOCOL_UPGRADE = "protocol_upgrade"
34
+
35
+ # Plugin security extensions
36
+ PLUGIN_PUBLISH = "plugin_publish"
37
+ PLUGIN_ATTESTATION = "plugin_attestation"
38
+ PLUGIN_FLAG = "plugin_flag"
39
+ PLUGIN_QUARANTINE = "plugin_quarantine" # system-generated only
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Transaction
44
+ # ---------------------------------------------------------------------------
45
+
46
+ @dataclass
47
+ class Transaction:
48
+ """A single chain transaction with Ed25519 signature."""
49
+
50
+ tx_id: str
51
+ type: TxType
52
+ from_colony_id: str
53
+ timestamp: str
54
+ nonce: int
55
+ payload: dict[str, Any]
56
+ signature: str # Ed25519 hex
57
+
58
+ @classmethod
59
+ def create(
60
+ cls,
61
+ tx_type: TxType,
62
+ from_colony_id: str,
63
+ nonce: int,
64
+ payload: dict[str, Any],
65
+ sign_fn: Callable[[bytes], str],
66
+ ) -> "Transaction":
67
+ """Create and sign a transaction."""
68
+ tx_id = str(uuid.uuid4())
69
+ ts = datetime.now(timezone.utc).isoformat()
70
+ signing_payload = {
71
+ "tx_id": tx_id,
72
+ "type": tx_type.value,
73
+ "from_colony_id": from_colony_id,
74
+ "timestamp": ts,
75
+ "nonce": nonce,
76
+ "payload": payload,
77
+ }
78
+ canonical = json.dumps(signing_payload, sort_keys=True, separators=(",", ":"))
79
+ sig = sign_fn(canonical.encode())
80
+ return cls(
81
+ tx_id=tx_id,
82
+ type=tx_type,
83
+ from_colony_id=from_colony_id,
84
+ timestamp=ts,
85
+ nonce=nonce,
86
+ payload=payload,
87
+ signature=sig,
88
+ )
89
+
90
+ def signing_bytes(self) -> bytes:
91
+ """Return the canonical bytes that were/should be signed."""
92
+ payload = {
93
+ "tx_id": self.tx_id,
94
+ "type": self.type.value if isinstance(self.type, TxType) else self.type,
95
+ "from_colony_id": self.from_colony_id,
96
+ "timestamp": self.timestamp,
97
+ "nonce": self.nonce,
98
+ "payload": self.payload,
99
+ }
100
+ return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
101
+
102
+ def to_dict(self) -> dict[str, Any]:
103
+ return {
104
+ "tx_id": self.tx_id,
105
+ "type": self.type.value if isinstance(self.type, TxType) else self.type,
106
+ "from_colony_id": self.from_colony_id,
107
+ "timestamp": self.timestamp,
108
+ "nonce": self.nonce,
109
+ "payload": self.payload,
110
+ "signature": self.signature,
111
+ }
112
+
113
+ @classmethod
114
+ def from_dict(cls, d: dict[str, Any]) -> "Transaction":
115
+ try:
116
+ tx_type: Any = TxType(d["type"])
117
+ except (ValueError, KeyError):
118
+ tx_type = d.get("type", "unknown")
119
+ return cls(
120
+ tx_id=d["tx_id"],
121
+ type=tx_type,
122
+ from_colony_id=d["from_colony_id"],
123
+ timestamp=d["timestamp"],
124
+ nonce=d["nonce"],
125
+ payload=d["payload"],
126
+ signature=d.get("signature", ""),
127
+ )
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # Chain state dataclasses
132
+ # ---------------------------------------------------------------------------
133
+
134
+ @dataclass
135
+ class KeyHistoryEntry:
136
+ public_key_hex: str
137
+ active_from_height: int
138
+ rotated_at_height: int | None = None # None = still active
139
+ revoked_at_height: int | None = None # None = not revoked
140
+ revocation_tx: str | None = None
141
+
142
+
143
+ @dataclass
144
+ class ColonyRecord:
145
+ colony_id: str
146
+ name: str
147
+ active_public_key_hex: str
148
+ endpoint: str
149
+ description: str
150
+ capabilities: list[str]
151
+ protocol_version: str
152
+ registered_at_height: int
153
+ registered_at_tx: str
154
+ is_genesis_admin: bool
155
+ status: str # "active" | "suspended" | "deregistered"
156
+ metadata: dict[str, Any] = field(default_factory=dict)
157
+
158
+
159
+ @dataclass
160
+ class TrustEdge:
161
+ from_colony_id: str
162
+ to_colony_id: str
163
+ trust_level: int # 0-4
164
+ attested_at_height: int
165
+ attested_at_tx: str
166
+ valid_until: str | None = None # ISO-8601 or None
167
+
168
+
169
+ @dataclass
170
+ class UntrustEvent:
171
+ from_colony_id: str
172
+ target_colony_id: str
173
+ at_height: int
174
+ tx_id: str
175
+ report_abuse: bool
176
+ timestamp: str
177
+
178
+
179
+ @dataclass
180
+ class SentinelRecord:
181
+ sentinel_id: str
182
+ colony_id: str
183
+ host: str
184
+ port: int
185
+ public_key_hex: str
186
+ registered_at_height: int
187
+ status: str # "active" | "demoted" | "deregistered"
188
+ uptime_percent: float
189
+
190
+
191
+ @dataclass
192
+ class ProtocolConfig:
193
+ block_interval_secs: int = 30
194
+ untrust_threshold: int = 3
195
+ untrust_window_days: int = 30
196
+ uptime_requirement_percent: float = 99.0
197
+ min_sentinels: int = 1
198
+ min_protocol_version: str = "1.0.0"
199
+
200
+
201
+ @dataclass
202
+ class ProtocolUpgradeRecord:
203
+ upgrade_id: str
204
+ title: str
205
+ proposed_at_height: int
206
+ activation_height: int
207
+ ratified: bool
208
+ changes: dict[str, Any]
209
+
210
+
211
+ @dataclass
212
+ class ChainState:
213
+ """Complete materialized state of the chain at a given height."""
214
+
215
+ height: int = 0
216
+ last_block_hash: str = "0" * 64
217
+ colony_registry: dict[str, ColonyRecord] = field(default_factory=dict)
218
+ name_registry: dict[str, str] = field(default_factory=dict) # name -> colony_id
219
+ key_history: dict[str, list[KeyHistoryEntry]] = field(default_factory=dict)
220
+ trust_graph: dict[tuple[str, str], TrustEdge] = field(default_factory=dict)
221
+ untrust_counters: dict[str, list[UntrustEvent]] = field(default_factory=dict)
222
+ sentinel_roster: dict[str, SentinelRecord] = field(default_factory=dict)
223
+ suspended_colonies: set[str] = field(default_factory=set)
224
+ protocol_config: ProtocolConfig = field(default_factory=ProtocolConfig)
225
+ upgrade_history: list[ProtocolUpgradeRecord] = field(default_factory=list)
226
+ genesis_admin_id: str = ""
227
+ network_id: str = ""
228
+ # Plugin security registry
229
+ plugin_registry: dict[str, Any] = field(default_factory=dict) # plugin_hash -> PluginChainRecord
230
+ # Track publish counts per colony for rate limiting
231
+ plugin_publish_counts: dict[str, list[str]] = field(default_factory=dict) # colony_id -> list of timestamps
232
+
233
+ def colony_id_for_name(self, name: str) -> str | None:
234
+ return self.name_registry.get(name.lower())
235
+
236
+ def active_key_for_colony(self, colony_id: str) -> str | None:
237
+ record = self.colony_registry.get(colony_id)
238
+ if record:
239
+ return record.active_public_key_hex
240
+ return None
241
+
242
+ def is_active(self, colony_id: str) -> bool:
243
+ record = self.colony_registry.get(colony_id)
244
+ return record is not None and record.status == "active"
245
+
246
+ def is_suspended(self, colony_id: str) -> bool:
247
+ return colony_id in self.suspended_colonies
248
+
249
+ def active_sentinels(self) -> list[SentinelRecord]:
250
+ return [s for s in self.sentinel_roster.values() if s.status == "active"]
@@ -0,0 +1,397 @@
1
+ """Chain validation: block integrity, transaction signatures, state transitions.
2
+
3
+ All validation is deterministic — same state + same input = same result.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import json
10
+ import logging
11
+ import re
12
+ from dataclasses import dataclass
13
+ from datetime import datetime, timezone
14
+ from typing import Any
15
+
16
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
17
+ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
18
+ from cryptography.exceptions import InvalidSignature
19
+
20
+ from .block import Block, build_merkle_root
21
+ from .transactions import (
22
+ ChainState,
23
+ TxType,
24
+ Transaction,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ _NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9\-]{0,62}[a-z0-9]?$")
30
+
31
+
32
+ @dataclass
33
+ class ValidationResult:
34
+ ok: bool
35
+ reason: str = ""
36
+
37
+ @property
38
+ def valid(self) -> bool:
39
+ """Alias for ok — used by nonce validator tests and external callers."""
40
+ return self.ok
41
+
42
+ @classmethod
43
+ def success(cls) -> "ValidationResult":
44
+ return cls(ok=True)
45
+
46
+ @classmethod
47
+ def fail(cls, reason: str) -> "ValidationResult":
48
+ return cls(ok=False, reason=reason)
49
+
50
+
51
+ def _verify_ed25519(public_key_hex: str, data: bytes, signature_hex: str) -> bool:
52
+ """Verify an Ed25519 signature. Returns False on any error."""
53
+ try:
54
+ pub_bytes = bytes.fromhex(public_key_hex)
55
+ sig_bytes = bytes.fromhex(signature_hex)
56
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
57
+ pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
58
+ pub_key.verify(sig_bytes, data)
59
+ return True
60
+ except (InvalidSignature, ValueError, Exception):
61
+ return False
62
+
63
+
64
+ class TransactionValidator:
65
+ """Validates transactions against current chain state."""
66
+
67
+ def __init__(self, state: ChainState = None, chain_store=None) -> None:
68
+ self.state = state
69
+ self._chain_store = chain_store
70
+
71
+ def validate(self, tx: Transaction) -> ValidationResult:
72
+ """Full validation: signature, nonce, type-specific preconditions."""
73
+ # Basic structure
74
+ if not tx.tx_id:
75
+ return ValidationResult.fail("missing tx_id")
76
+ if not tx.from_colony_id:
77
+ return ValidationResult.fail("missing from_colony_id")
78
+
79
+ # System transactions (auto-generated) skip signature check
80
+ is_system = tx.from_colony_id == "system"
81
+
82
+ if not is_system:
83
+ nonce_result = self._check_nonce(tx)
84
+ if not nonce_result.ok:
85
+ return nonce_result
86
+
87
+ sig_result = self._verify_signature(tx)
88
+ if not sig_result.ok:
89
+ return sig_result
90
+
91
+ # Type-specific validation
92
+ return self._validate_by_type(tx)
93
+
94
+ def _verify_signature(self, tx: Transaction) -> ValidationResult:
95
+ """Verify Ed25519 signature against the colony's active key."""
96
+ colony = self.state.colony_registry.get(tx.from_colony_id)
97
+
98
+ # For colony_register, use the public key from the payload itself
99
+ if tx.type == TxType.COLONY_REGISTER:
100
+ pub_key_hex = tx.payload.get("public_key_hex", "")
101
+ elif colony is not None:
102
+ pub_key_hex = colony.active_public_key_hex
103
+ else:
104
+ return ValidationResult.fail(
105
+ f"colony {tx.from_colony_id!r} not registered"
106
+ )
107
+
108
+ if not pub_key_hex:
109
+ return ValidationResult.fail("no public key for colony")
110
+
111
+ data = tx.signing_bytes()
112
+ if not _verify_ed25519(pub_key_hex, data, tx.signature):
113
+ return ValidationResult.fail("invalid signature")
114
+ return ValidationResult.success()
115
+
116
+ def _check_nonce(self, tx: Transaction) -> ValidationResult:
117
+ """Verify nonce is strictly greater than colony's last accepted nonce.
118
+
119
+ The authoritative nonce check is enforced by ChainManager.submit_transaction()
120
+ via ChainStore.get_nonce() + DB UNIQUE(colony_id, nonce). This method is a
121
+ defence-in-depth layer for callers that use TransactionValidator directly
122
+ (e.g. federation sync, offline verification tools).
123
+
124
+ Fails closed when no ChainStore is injected: a missing store means replay
125
+ protection cannot be enforced, and validating without it would let
126
+ offline/federation callers accept replayed transactions.
127
+ """
128
+ if tx.type == TxType.COLONY_REGISTER:
129
+ # Genesis registrations have no prior nonce to compare against.
130
+ return ValidationResult.success()
131
+
132
+ if self._chain_store is None:
133
+ return ValidationResult.fail(
134
+ "TransactionValidator: chain_store not injected; cannot verify "
135
+ "nonce replay protection. Inject chain_store= when constructing "
136
+ "the validator."
137
+ )
138
+
139
+ last_nonce = self._chain_store.get_nonce(tx.from_colony_id)
140
+ if tx.nonce <= last_nonce:
141
+ return ValidationResult.fail(
142
+ f"Nonce replay: tx.nonce={tx.nonce} <= last_accepted={last_nonce}"
143
+ )
144
+ return ValidationResult.success()
145
+
146
+ def _validate_by_type(self, tx: Transaction) -> ValidationResult:
147
+ dispatch = {
148
+ TxType.COLONY_REGISTER: self._validate_colony_register,
149
+ TxType.COLONY_ROTATE_KEY: self._validate_colony_rotate_key,
150
+ TxType.COLONY_REVOKE_KEY: self._validate_colony_revoke_key,
151
+ TxType.COLONY_RELEASE_NAME: self._validate_colony_release_name,
152
+ TxType.TRUST_ATTEST: self._validate_trust_attest,
153
+ TxType.UNTRUST_ATTEST: self._validate_untrust_attest,
154
+ TxType.SENTINEL_REGISTER: self._validate_sentinel_register,
155
+ TxType.SENTINEL_DEREGISTER: self._validate_sentinel_deregister,
156
+ TxType.COLONY_SUSPEND: self._validate_colony_suspend,
157
+ TxType.COLONY_REINSTATE: self._validate_colony_reinstate,
158
+ TxType.PROTOCOL_UPGRADE: self._validate_protocol_upgrade,
159
+ }
160
+ handler = dispatch.get(tx.type)
161
+ if handler is None:
162
+ return ValidationResult.fail(f"unknown transaction type: {tx.type}")
163
+ return handler(tx)
164
+
165
+ def _validate_colony_register(self, tx: Transaction) -> ValidationResult:
166
+ p = tx.payload
167
+ name = p.get("name", "").lower()
168
+ colony_id = p.get("colony_id", "")
169
+ public_key_hex = p.get("public_key_hex", "")
170
+
171
+ if not name:
172
+ return ValidationResult.fail("colony_register: missing name")
173
+ if not _NAME_PATTERN.match(name):
174
+ return ValidationResult.fail(
175
+ f"colony_register: name {name!r} does not match required pattern"
176
+ )
177
+ if name in self.state.name_registry:
178
+ return ValidationResult.fail(
179
+ f"colony_register: name {name!r} already registered"
180
+ )
181
+ if colony_id in self.state.colony_registry:
182
+ return ValidationResult.fail(
183
+ f"colony_register: colony_id {colony_id!r} already registered"
184
+ )
185
+ # Verify colony_id = sha256(pubkey)
186
+ try:
187
+ expected_id = hashlib.sha256(bytes.fromhex(public_key_hex)).hexdigest()
188
+ except ValueError:
189
+ return ValidationResult.fail("colony_register: invalid public_key_hex")
190
+ if expected_id != colony_id:
191
+ return ValidationResult.fail(
192
+ "colony_register: colony_id does not match sha256(public_key)"
193
+ )
194
+ # Genesis admin can only appear once (in genesis block)
195
+ if p.get("genesis_admin", False) and self.state.genesis_admin_id:
196
+ return ValidationResult.fail(
197
+ "colony_register: genesis_admin=true only allowed in genesis block"
198
+ )
199
+ return ValidationResult.success()
200
+
201
+ def _validate_colony_rotate_key(self, tx: Transaction) -> ValidationResult:
202
+ colony = self.state.colony_registry.get(tx.from_colony_id)
203
+ if colony is None:
204
+ return ValidationResult.fail("colony_rotate_key: colony not registered")
205
+ if colony.status == "suspended":
206
+ return ValidationResult.fail("colony_rotate_key: colony is suspended")
207
+ p = tx.payload
208
+ new_key = p.get("new_public_key_hex", "")
209
+ if not new_key:
210
+ return ValidationResult.fail("colony_rotate_key: missing new_public_key_hex")
211
+ # New key must not be registered to another colony
212
+ for cid, record in self.state.colony_registry.items():
213
+ if cid != tx.from_colony_id and record.active_public_key_hex == new_key:
214
+ return ValidationResult.fail(
215
+ "colony_rotate_key: new key already registered to another colony"
216
+ )
217
+ return ValidationResult.success()
218
+
219
+ def _validate_colony_revoke_key(self, tx: Transaction) -> ValidationResult:
220
+ p = tx.payload
221
+ revoked_key = p.get("revoked_key_hex", "")
222
+ if not revoked_key:
223
+ return ValidationResult.fail("colony_revoke_key: missing revoked_key_hex")
224
+ # Key must be in history of the target colony
225
+ # from_colony_id is either the colony itself, or "system"/"genesis"
226
+ target_id = tx.from_colony_id
227
+ history = self.state.key_history.get(target_id, [])
228
+ if not any(e.public_key_hex == revoked_key for e in history):
229
+ return ValidationResult.fail(
230
+ "colony_revoke_key: key not found in colony key history"
231
+ )
232
+ return ValidationResult.success()
233
+
234
+ def _validate_colony_release_name(self, tx: Transaction) -> ValidationResult:
235
+ p = tx.payload
236
+ name = p.get("name", "").lower()
237
+ owner_id = self.state.name_registry.get(name)
238
+ if owner_id is None:
239
+ return ValidationResult.fail(
240
+ f"colony_release_name: name {name!r} not registered"
241
+ )
242
+ is_genesis = tx.from_colony_id == self.state.genesis_admin_id
243
+ if owner_id != tx.from_colony_id and not is_genesis:
244
+ return ValidationResult.fail(
245
+ "colony_release_name: not authorized (not owner or genesis admin)"
246
+ )
247
+ return ValidationResult.success()
248
+
249
+ def _validate_trust_attest(self, tx: Transaction) -> ValidationResult:
250
+ p = tx.payload
251
+ target_id = p.get("target_colony_id", "")
252
+ if not self.state.is_active(tx.from_colony_id):
253
+ return ValidationResult.fail("trust_attest: attesting colony not active")
254
+ if not self.state.is_active(target_id):
255
+ return ValidationResult.fail("trust_attest: target colony not active")
256
+ trust_level = p.get("trust_level", -1)
257
+ if trust_level not in range(5):
258
+ return ValidationResult.fail("trust_attest: trust_level must be 0-4")
259
+ return ValidationResult.success()
260
+
261
+ def _validate_untrust_attest(self, tx: Transaction) -> ValidationResult:
262
+ p = tx.payload
263
+ target_id = p.get("target_colony_id", "")
264
+ if not self.state.colony_registry.get(tx.from_colony_id):
265
+ return ValidationResult.fail("untrust_attest: attesting colony not registered")
266
+ if not self.state.colony_registry.get(target_id):
267
+ return ValidationResult.fail("untrust_attest: target colony not registered")
268
+ return ValidationResult.success()
269
+
270
+ def _validate_sentinel_register(self, tx: Transaction) -> ValidationResult:
271
+ colony = self.state.colony_registry.get(tx.from_colony_id)
272
+ if colony is None:
273
+ return ValidationResult.fail("sentinel_register: colony not registered")
274
+ if colony.status == "suspended":
275
+ return ValidationResult.fail("sentinel_register: colony is suspended")
276
+ p = tx.payload
277
+ sentinel_id = p.get("sentinel_id", "")
278
+ if not sentinel_id:
279
+ return ValidationResult.fail("sentinel_register: missing sentinel_id")
280
+ if sentinel_id in self.state.sentinel_roster:
281
+ return ValidationResult.fail(
282
+ f"sentinel_register: sentinel_id {sentinel_id!r} already registered"
283
+ )
284
+ return ValidationResult.success()
285
+
286
+ def _validate_sentinel_deregister(self, tx: Transaction) -> ValidationResult:
287
+ p = tx.payload
288
+ sentinel_id = p.get("sentinel_id", "")
289
+ record = self.state.sentinel_roster.get(sentinel_id)
290
+ if record is None:
291
+ return ValidationResult.fail(
292
+ f"sentinel_deregister: sentinel {sentinel_id!r} not in roster"
293
+ )
294
+ is_genesis = tx.from_colony_id == self.state.genesis_admin_id
295
+ is_system = tx.from_colony_id == "system"
296
+ if record.colony_id != tx.from_colony_id and not is_genesis and not is_system:
297
+ return ValidationResult.fail(
298
+ "sentinel_deregister: not authorized (not owner or genesis admin)"
299
+ )
300
+ return ValidationResult.success()
301
+
302
+ def _validate_colony_suspend(self, tx: Transaction) -> ValidationResult:
303
+ p = tx.payload
304
+ target_id = p.get("target_colony_id", "")
305
+ if not target_id:
306
+ return ValidationResult.fail("colony_suspend: missing target_colony_id")
307
+ if target_id not in self.state.colony_registry:
308
+ return ValidationResult.fail("colony_suspend: target colony not registered")
309
+ if self.state.is_suspended(target_id):
310
+ return ValidationResult.fail("colony_suspend: colony already suspended")
311
+ is_genesis = tx.from_colony_id == self.state.genesis_admin_id
312
+ is_system = tx.from_colony_id == "system"
313
+ if not is_genesis and not is_system:
314
+ return ValidationResult.fail(
315
+ "colony_suspend: only genesis admin or system may suspend"
316
+ )
317
+ return ValidationResult.success()
318
+
319
+ def _validate_colony_reinstate(self, tx: Transaction) -> ValidationResult:
320
+ p = tx.payload
321
+ target_id = p.get("target_colony_id", "")
322
+ if not target_id:
323
+ return ValidationResult.fail("colony_reinstate: missing target_colony_id")
324
+ if not self.state.is_suspended(target_id):
325
+ return ValidationResult.fail("colony_reinstate: colony is not suspended")
326
+ is_genesis = tx.from_colony_id == self.state.genesis_admin_id
327
+ is_system = tx.from_colony_id == "system"
328
+ if not is_genesis and not is_system:
329
+ return ValidationResult.fail(
330
+ "colony_reinstate: only genesis admin or system may reinstate"
331
+ )
332
+ return ValidationResult.success()
333
+
334
+ def _validate_protocol_upgrade(self, tx: Transaction) -> ValidationResult:
335
+ is_genesis = tx.from_colony_id == self.state.genesis_admin_id
336
+ if not is_genesis:
337
+ return ValidationResult.fail(
338
+ "protocol_upgrade: only genesis admin may propose upgrades"
339
+ )
340
+ p = tx.payload
341
+ if not p.get("upgrade_id"):
342
+ return ValidationResult.fail("protocol_upgrade: missing upgrade_id")
343
+ if not p.get("title"):
344
+ return ValidationResult.fail("protocol_upgrade: missing title")
345
+ activation_height = p.get("activation_height", 0)
346
+ if activation_height <= self.state.height:
347
+ return ValidationResult.fail(
348
+ "protocol_upgrade: activation_height must be in the future"
349
+ )
350
+ return ValidationResult.success()
351
+
352
+
353
+ class BlockValidator:
354
+ """Validates block structure, hash chain integrity, and merkle root."""
355
+
356
+ def validate_block(
357
+ self, block: Block, previous_block: Block | None
358
+ ) -> ValidationResult:
359
+ # Hash chain
360
+ if previous_block is None:
361
+ if block.index != 0:
362
+ return ValidationResult.fail("first block must have index 0")
363
+ if block.previous_hash != "0" * 64:
364
+ return ValidationResult.fail(
365
+ "genesis block previous_hash must be 64 zeros"
366
+ )
367
+ else:
368
+ if block.index != previous_block.index + 1:
369
+ return ValidationResult.fail(
370
+ f"expected index {previous_block.index + 1}, got {block.index}"
371
+ )
372
+ if block.previous_hash != previous_block.block_hash:
373
+ return ValidationResult.fail("previous_hash mismatch")
374
+
375
+ # Merkle root
376
+ tx_ids = [tx.tx_id for tx in block.transactions]
377
+ expected_merkle = build_merkle_root(tx_ids)
378
+ if block.merkle_root != expected_merkle:
379
+ return ValidationResult.fail(
380
+ f"merkle_root mismatch: {block.merkle_root!r} != {expected_merkle!r}"
381
+ )
382
+
383
+ return ValidationResult.success()
384
+
385
+ def validate_sentinel_signature(
386
+ self,
387
+ block: Block,
388
+ sentinel_public_key_hex: str,
389
+ ) -> ValidationResult:
390
+ """Verify the block's Ed25519 signature from the producing Sentinel."""
391
+ block_dict = block.to_dict()
392
+ block_dict.pop("signature", None)
393
+ canonical = json.dumps(block_dict, sort_keys=True, separators=(",", ":"))
394
+ data = canonical.encode()
395
+ if not _verify_ed25519(sentinel_public_key_hex, data, block.signature):
396
+ return ValidationResult.fail("block signature invalid")
397
+ return ValidationResult.success()
@@ -0,0 +1 @@
1
+ """Channel registration subsystem."""
@@ -0,0 +1,31 @@
1
+ """Channel manifest -- what a channel declares about itself on registration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ChannelManifest(BaseModel):
9
+ """Describes a channel's capabilities, identity policies, and delivery config."""
10
+
11
+ channel_key: str = Field(..., min_length=1, max_length=128)
12
+ display_name: str = Field(..., min_length=1, max_length=256)
13
+ gateway_family: str = Field(..., min_length=1, max_length=64)
14
+
15
+ supports_media: bool = False
16
+ supports_reactions: bool = False
17
+ supports_voice: bool = False
18
+ supports_rich_text: bool = False
19
+ max_message_length: int | None = None
20
+
21
+ phone_identity_unification: bool = False
22
+ session_isolation: bool = False
23
+ provides_channel_id: bool = False
24
+
25
+ delivery_webhook: str | None = None
26
+ delivery_protocol: str = "hermes"
27
+ delivery_aliases: list[str] = Field(default_factory=list)
28
+ home_chat_id: str | None = None
29
+
30
+ platform_hint: str = ""
31
+ pii_safe: bool = True
@@ -0,0 +1,12 @@
1
+ -- Channel registration store: any channel can self-register with Colony.
2
+ CREATE TABLE IF NOT EXISTS channels (
3
+ channel_key TEXT PRIMARY KEY,
4
+ display_name TEXT NOT NULL,
5
+ gateway_family TEXT NOT NULL,
6
+ manifest_json TEXT NOT NULL,
7
+ channel_token TEXT NOT NULL,
8
+ registered_at TEXT NOT NULL,
9
+ last_seen_at TEXT,
10
+ status TEXT NOT NULL DEFAULT 'active'
11
+ CHECK(status IN ('active', 'inactive', 'revoked'))
12
+ );