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,676 @@
1
+ """Deterministic chain state machine.
2
+
3
+ Applies blocks and transactions to produce the canonical ChainState.
4
+ Full replay from genesis is always possible; checkpoints speed up recovery.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import copy
10
+ import logging
11
+ from datetime import datetime, timezone
12
+ from typing import Any
13
+
14
+ from .block import Block
15
+ from .storage import ChainStore
16
+ from .transactions import (
17
+ ChainState,
18
+ ColonyRecord,
19
+ KeyHistoryEntry,
20
+ ProtocolConfig,
21
+ ProtocolUpgradeRecord,
22
+ SentinelRecord,
23
+ Transaction,
24
+ TrustEdge,
25
+ TxType,
26
+ UntrustEvent,
27
+ )
28
+ from .plugin_transactions import (
29
+ FindingSeverity,
30
+ PluginChainRecord,
31
+ PluginChainStatus,
32
+ ScanResult,
33
+ )
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ _CHECKPOINT_INTERVAL = 100
38
+
39
+
40
+ def _iso_to_ts(iso: str) -> float:
41
+ """Convert ISO-8601 string to UTC unix timestamp; returns 0.0 on failure."""
42
+ if not iso:
43
+ return 0.0
44
+ try:
45
+ dt = datetime.fromisoformat(iso)
46
+ if dt.tzinfo is None:
47
+ dt = dt.replace(tzinfo=timezone.utc)
48
+ return dt.timestamp()
49
+ except (ValueError, TypeError):
50
+ return 0.0
51
+
52
+
53
+ class ChainStateMachine:
54
+ """Deterministic state computation from the block sequence."""
55
+
56
+ def __init__(self, store: ChainStore) -> None:
57
+ self.store = store
58
+
59
+ def apply_block(self, state: ChainState, block: Block) -> ChainState:
60
+ """Apply all transactions in block to state. Returns new state."""
61
+ new_state = copy.deepcopy(state)
62
+ new_state.height = block.index
63
+ new_state.last_block_hash = block.block_hash
64
+
65
+ for tx in block.transactions:
66
+ new_state = self._apply_tx(new_state, tx, block.index)
67
+
68
+ # Check for pending protocol upgrades that activate at this height
69
+ new_state = self._apply_pending_upgrades(new_state, block.index)
70
+
71
+ return new_state
72
+
73
+ def compute_state_at(self, height: int) -> ChainState:
74
+ """Compute state at height using checkpoints to minimize replay."""
75
+ checkpoint = self.store.get_latest_checkpoint(height)
76
+ if checkpoint is not None:
77
+ state, start_height = checkpoint
78
+ start_index = start_height + 1
79
+ else:
80
+ state = ChainState()
81
+ start_index = 0
82
+
83
+ for idx in range(start_index, height + 1):
84
+ block = self.store.get_block(idx)
85
+ if block is None:
86
+ break
87
+ state = self.apply_block(state, block)
88
+
89
+ return state
90
+
91
+ def get_current_state(self) -> ChainState:
92
+ """Return state at the latest committed height."""
93
+ height = self.store.get_height()
94
+ if height < 0:
95
+ return ChainState()
96
+ return self.compute_state_at(height)
97
+
98
+ def rebuild_and_checkpoint(self) -> ChainState:
99
+ """Full replay from genesis, saving checkpoints every 100 blocks."""
100
+ state = ChainState()
101
+ height = self.store.get_height()
102
+ for idx in range(height + 1):
103
+ block = self.store.get_block(idx)
104
+ if block is None:
105
+ break
106
+ state = self.apply_block(state, block)
107
+ if idx > 0 and idx % _CHECKPOINT_INTERVAL == 0:
108
+ self.store.save_checkpoint(state, idx)
109
+ return state
110
+
111
+ # ── Transaction dispatch ────────────────────────────────────────────────
112
+
113
+ def _apply_tx(
114
+ self, state: ChainState, tx: Transaction, block_index: int
115
+ ) -> ChainState:
116
+ dispatch = {
117
+ TxType.COLONY_REGISTER: self._apply_colony_register,
118
+ TxType.COLONY_ROTATE_KEY: self._apply_colony_rotate_key,
119
+ TxType.COLONY_REVOKE_KEY: self._apply_colony_revoke_key,
120
+ TxType.COLONY_RELEASE_NAME: self._apply_colony_release_name,
121
+ TxType.TRUST_ATTEST: self._apply_trust_attest,
122
+ TxType.UNTRUST_ATTEST: self._apply_untrust_attest,
123
+ TxType.SENTINEL_REGISTER: self._apply_sentinel_register,
124
+ TxType.SENTINEL_DEREGISTER: self._apply_sentinel_deregister,
125
+ TxType.COLONY_SUSPEND: self._apply_colony_suspend,
126
+ TxType.COLONY_REINSTATE: self._apply_colony_reinstate,
127
+ TxType.PROTOCOL_UPGRADE: self._apply_protocol_upgrade,
128
+ TxType.PLUGIN_PUBLISH: self._apply_plugin_publish,
129
+ TxType.PLUGIN_ATTESTATION: self._apply_plugin_attestation,
130
+ TxType.PLUGIN_FLAG: self._apply_plugin_flag,
131
+ TxType.PLUGIN_QUARANTINE: self._apply_plugin_quarantine,
132
+ }
133
+ handler = dispatch.get(tx.type)
134
+ if handler is None:
135
+ logger.warning("Unknown tx type %s — skipping", tx.type)
136
+ return state
137
+ # Handler exceptions must propagate: silently returning the unmodified
138
+ # state on a handler bug would mean different replicas could disagree
139
+ # on which transactions actually applied, breaking consensus
140
+ # invariants. If a handler raises, fail the whole apply_block so the
141
+ # operator sees the corruption and can stop the node.
142
+ return handler(state, tx, block_index)
143
+
144
+ def _apply_colony_register(
145
+ self, state: ChainState, tx: Transaction, height: int
146
+ ) -> ChainState:
147
+ p = tx.payload
148
+ colony_id = p["colony_id"]
149
+ name = p["name"].lower()
150
+ is_genesis_admin = p.get("genesis_admin", False)
151
+
152
+ record = ColonyRecord(
153
+ colony_id=colony_id,
154
+ name=name,
155
+ active_public_key_hex=p["public_key_hex"],
156
+ endpoint=p.get("endpoint", ""),
157
+ description=p.get("description", ""),
158
+ capabilities=p.get("capabilities", []),
159
+ protocol_version=p.get("protocol_version", "1.0.0"),
160
+ registered_at_height=height,
161
+ registered_at_tx=tx.tx_id,
162
+ is_genesis_admin=is_genesis_admin,
163
+ status="active",
164
+ metadata=p.get("metadata", {}),
165
+ )
166
+ state.colony_registry[colony_id] = record
167
+ state.name_registry[name] = colony_id
168
+ state.key_history[colony_id] = [
169
+ KeyHistoryEntry(
170
+ public_key_hex=p["public_key_hex"],
171
+ active_from_height=height,
172
+ )
173
+ ]
174
+ if is_genesis_admin and not state.genesis_admin_id:
175
+ state.genesis_admin_id = colony_id
176
+ return state
177
+
178
+ def _apply_colony_rotate_key(
179
+ self, state: ChainState, tx: Transaction, height: int
180
+ ) -> ChainState:
181
+ p = tx.payload
182
+ colony_id = tx.from_colony_id
183
+ new_key = p["new_public_key_hex"]
184
+
185
+ record = state.colony_registry.get(colony_id)
186
+ if record is None:
187
+ return state
188
+
189
+ # Mark old key as rotated
190
+ history = state.key_history.get(colony_id, [])
191
+ for entry in history:
192
+ if entry.rotated_at_height is None and entry.revoked_at_height is None:
193
+ entry.rotated_at_height = height
194
+
195
+ # Add new key
196
+ history.append(KeyHistoryEntry(public_key_hex=new_key, active_from_height=height))
197
+ state.key_history[colony_id] = history
198
+ record.active_public_key_hex = new_key
199
+ return state
200
+
201
+ def _apply_colony_revoke_key(
202
+ self, state: ChainState, tx: Transaction, height: int
203
+ ) -> ChainState:
204
+ p = tx.payload
205
+ revoked_key = p["revoked_key_hex"]
206
+ cascade = p.get("cascade", False)
207
+ colony_id = tx.from_colony_id
208
+
209
+ history = state.key_history.get(colony_id, [])
210
+ for entry in history:
211
+ if entry.public_key_hex == revoked_key:
212
+ entry.revoked_at_height = height
213
+ entry.revocation_tx = tx.tx_id
214
+
215
+ if cascade:
216
+ # Downgrade trust attestations signed by revoked key to DISCOVERY
217
+ # (We don't track which key signed each attestation, so we downgrade
218
+ # all attestations FROM this colony)
219
+ for edge_key, edge in list(state.trust_graph.items()):
220
+ if edge.from_colony_id == colony_id:
221
+ edge.trust_level = 0 # downgrade to DISCOVERY
222
+
223
+ return state
224
+
225
+ def _apply_colony_release_name(
226
+ self, state: ChainState, tx: Transaction, height: int
227
+ ) -> ChainState:
228
+ p = tx.payload
229
+ name = p["name"].lower()
230
+ state.name_registry.pop(name, None)
231
+ return state
232
+
233
+ def _apply_trust_attest(
234
+ self, state: ChainState, tx: Transaction, height: int
235
+ ) -> ChainState:
236
+ p = tx.payload
237
+ target_id = p["target_colony_id"]
238
+ edge_key = (tx.from_colony_id, target_id)
239
+ state.trust_graph[edge_key] = TrustEdge(
240
+ from_colony_id=tx.from_colony_id,
241
+ to_colony_id=target_id,
242
+ trust_level=p["trust_level"],
243
+ attested_at_height=height,
244
+ attested_at_tx=tx.tx_id,
245
+ valid_until=p.get("valid_until"),
246
+ )
247
+ return state
248
+
249
+ def _apply_untrust_attest(
250
+ self, state: ChainState, tx: Transaction, height: int
251
+ ) -> ChainState:
252
+ p = tx.payload
253
+ target_id = p["target_colony_id"]
254
+ edge_key = (tx.from_colony_id, target_id)
255
+
256
+ # Remove trust edge
257
+ state.trust_graph.pop(edge_key, None)
258
+
259
+ # Track untrust event
260
+ if target_id not in state.untrust_counters:
261
+ state.untrust_counters[target_id] = []
262
+
263
+ # Check for duplicate from same colony
264
+ existing = [
265
+ e for e in state.untrust_counters[target_id]
266
+ if e.from_colony_id == tx.from_colony_id
267
+ ]
268
+ if not existing:
269
+ event = UntrustEvent(
270
+ from_colony_id=tx.from_colony_id,
271
+ target_colony_id=target_id,
272
+ at_height=height,
273
+ tx_id=tx.tx_id,
274
+ report_abuse=p.get("report_abuse", False),
275
+ timestamp=tx.timestamp,
276
+ )
277
+ state.untrust_counters[target_id].append(event)
278
+
279
+ # Auto-suspend if threshold reached
280
+ threshold = state.protocol_config.untrust_threshold
281
+ abuse_count = sum(
282
+ 1 for e in state.untrust_counters[target_id] if e.report_abuse
283
+ )
284
+ total_count = len(state.untrust_counters[target_id])
285
+
286
+ if total_count >= threshold and not state.is_suspended(target_id):
287
+ # Emit implicit suspend
288
+ record = state.colony_registry.get(target_id)
289
+ if record and record.status == "active":
290
+ record.status = "suspended"
291
+ state.suspended_colonies.add(target_id)
292
+ logger.info(
293
+ "Auto-suspended colony %s: untrust threshold %d reached",
294
+ target_id,
295
+ threshold,
296
+ )
297
+
298
+ return state
299
+
300
+ def _apply_sentinel_register(
301
+ self, state: ChainState, tx: Transaction, height: int
302
+ ) -> ChainState:
303
+ p = tx.payload
304
+ sentinel_id = p["sentinel_id"]
305
+ state.sentinel_roster[sentinel_id] = SentinelRecord(
306
+ sentinel_id=sentinel_id,
307
+ colony_id=p.get("colony_id", tx.from_colony_id),
308
+ host=p.get("host", ""),
309
+ port=p.get("port", 7744),
310
+ public_key_hex=p.get("public_key_hex", ""),
311
+ registered_at_height=height,
312
+ status="active",
313
+ uptime_percent=p.get("uptime_proof", {}).get("uptime_percent", 100.0),
314
+ )
315
+ return state
316
+
317
+ def _apply_sentinel_deregister(
318
+ self, state: ChainState, tx: Transaction, height: int
319
+ ) -> ChainState:
320
+ p = tx.payload
321
+ sentinel_id = p["sentinel_id"]
322
+ record = state.sentinel_roster.get(sentinel_id)
323
+ if record:
324
+ record.status = "deregistered"
325
+ return state
326
+
327
+ def _apply_colony_suspend(
328
+ self, state: ChainState, tx: Transaction, height: int
329
+ ) -> ChainState:
330
+ p = tx.payload
331
+ target_id = p["target_colony_id"]
332
+ record = state.colony_registry.get(target_id)
333
+ if record:
334
+ record.status = "suspended"
335
+ state.suspended_colonies.add(target_id)
336
+ return state
337
+
338
+ def _apply_colony_reinstate(
339
+ self, state: ChainState, tx: Transaction, height: int
340
+ ) -> ChainState:
341
+ p = tx.payload
342
+ target_id = p["target_colony_id"]
343
+ record = state.colony_registry.get(target_id)
344
+ if record:
345
+ record.status = "active"
346
+ state.suspended_colonies.discard(target_id)
347
+ # Reset untrust counter
348
+ state.untrust_counters.pop(target_id, None)
349
+ return state
350
+
351
+ def _apply_protocol_upgrade(
352
+ self, state: ChainState, tx: Transaction, height: int
353
+ ) -> ChainState:
354
+ p = tx.payload
355
+ title = p.get("title", "")
356
+ changes = p.get("changes", {})
357
+
358
+ # ── Special admin actions embedded as protocol_upgrade titles ────────
359
+
360
+ # Genesis admin transfer: title == "transfer_genesis_admin"
361
+ if title == "transfer_genesis_admin":
362
+ new_admin_id = changes.get("new_genesis_admin_id", "")
363
+ if new_admin_id and tx.from_colony_id == state.genesis_admin_id:
364
+ old_admin_id = state.genesis_admin_id
365
+ state.genesis_admin_id = new_admin_id
366
+ # Update ColonyRecord flags
367
+ if old_admin_id in state.colony_registry:
368
+ state.colony_registry[old_admin_id].is_genesis_admin = False
369
+ if new_admin_id in state.colony_registry:
370
+ state.colony_registry[new_admin_id].is_genesis_admin = True
371
+ logger.info(
372
+ "Genesis admin transferred: %s → %s",
373
+ old_admin_id[:16], new_admin_id[:16],
374
+ )
375
+ else:
376
+ logger.warning(
377
+ "transfer_genesis_admin rejected: sender %s is not genesis admin %s",
378
+ tx.from_colony_id[:16], state.genesis_admin_id[:16] if state.genesis_admin_id else "(none)",
379
+ )
380
+ # Record but do not queue for future activation
381
+ state.upgrade_history.append(ProtocolUpgradeRecord(
382
+ upgrade_id=p.get("upgrade_id", ""),
383
+ title=title,
384
+ proposed_at_height=height,
385
+ activation_height=height,
386
+ ratified=True,
387
+ changes=changes,
388
+ ))
389
+ return state
390
+
391
+ # Unanimous Sentinel strip of Genesis admin:
392
+ # title == "strip_genesis_admin" with co-signatures from ALL active Sentinels
393
+ if title == "strip_genesis_admin":
394
+ active_sentinels = state.active_sentinels()
395
+ n_sentinels = len(active_sentinels)
396
+ votes = p.get("votes", [])
397
+ sentinel_ids = {s.sentinel_id for s in active_sentinels}
398
+ yes_sentinel_ids = {
399
+ v.get("sentinel_id")
400
+ for v in votes
401
+ if v.get("vote") == "yes" and v.get("sentinel_id") in sentinel_ids
402
+ }
403
+ # Unanimous: ALL active Sentinels must have voted yes
404
+ unanimous = (n_sentinels > 0 and yes_sentinel_ids == sentinel_ids)
405
+ if unanimous:
406
+ old_admin = state.genesis_admin_id
407
+ if old_admin and old_admin in state.colony_registry:
408
+ state.colony_registry[old_admin].is_genesis_admin = False
409
+ state.genesis_admin_id = ""
410
+ logger.warning(
411
+ "Genesis admin STRIPPED by unanimous Sentinel vote (was %s). "
412
+ "Network enters no-admin mode.",
413
+ old_admin[:16] if old_admin else "(none)",
414
+ )
415
+ else:
416
+ logger.warning(
417
+ "strip_genesis_admin rejected: only %d/%d Sentinels voted yes",
418
+ len(yes_sentinel_ids), n_sentinels,
419
+ )
420
+ state.upgrade_history.append(ProtocolUpgradeRecord(
421
+ upgrade_id=p.get("upgrade_id", ""),
422
+ title=title,
423
+ proposed_at_height=height,
424
+ activation_height=height,
425
+ ratified=unanimous,
426
+ changes=changes,
427
+ ))
428
+ return state
429
+
430
+ # ── Standard protocol upgrade ────────────────────────────────────────
431
+
432
+ votes = p.get("votes", [])
433
+ active_sentinels = state.active_sentinels()
434
+ n_sentinels = len(active_sentinels)
435
+ required = max(1, (n_sentinels * 2 // 3) + 1)
436
+ yes_votes = [v for v in votes if v.get("vote") == "yes"]
437
+ ratified = len(yes_votes) >= required or n_sentinels == 0
438
+
439
+ upgrade = ProtocolUpgradeRecord(
440
+ upgrade_id=p["upgrade_id"],
441
+ title=title,
442
+ proposed_at_height=height,
443
+ activation_height=p.get("activation_height", height + 1),
444
+ ratified=ratified,
445
+ changes=changes,
446
+ )
447
+ state.upgrade_history.append(upgrade)
448
+ return state
449
+
450
+ # ── Plugin transaction handlers ─────────────────────────────────────────
451
+
452
+ # Default rate limits (may be overridden by protocol_upgrade)
453
+ _MAX_PUBLISHES_PER_DAY = 20
454
+ _MAX_PUBLISHES_PER_HOUR = 5
455
+ _AUTO_QUARANTINE_THRESHOLD = 3
456
+ _AUTO_QUARANTINE_WINDOW_HOURS = 72
457
+ _SAFE_CONSENSUS_THRESHOLD = 0.8
458
+ _FLAG_SUSPENSION_THRESHOLD = 3
459
+
460
+ def _apply_plugin_publish(
461
+ self, state: ChainState, tx: Transaction, height: int
462
+ ) -> ChainState:
463
+ p = tx.payload
464
+ plugin_hash = p.get("plugin_hash", "")
465
+ name = p.get("name", "")
466
+ version = p.get("version", "")
467
+ publisher_id = tx.from_colony_id
468
+
469
+ # Reject if publisher is suspended
470
+ if publisher_id in state.suspended_colonies:
471
+ logger.warning("plugin_publish rejected: publisher %s is suspended", publisher_id)
472
+ return state
473
+
474
+ # Check idempotency: same (name, version, hash) → no-op
475
+ for existing in state.plugin_registry.values():
476
+ if existing.name == name and existing.version == version:
477
+ if existing.plugin_hash == plugin_hash:
478
+ # Idempotent re-publish — no-op
479
+ return state
480
+ else:
481
+ # Different hash for same (name, version) → reject
482
+ logger.warning(
483
+ "plugin_publish rejected: version %s@%s already exists with different hash",
484
+ name, version,
485
+ )
486
+ return state
487
+
488
+ # Rate limiting: track publish timestamps per colony
489
+ now_iso = datetime.now(timezone.utc).isoformat()
490
+ timestamps = state.plugin_publish_counts.get(publisher_id, [])
491
+ # Keep only timestamps in the last 24 hours
492
+ cutoff_24h = datetime.now(timezone.utc).timestamp() - 86400
493
+ cutoff_1h = datetime.now(timezone.utc).timestamp() - 3600
494
+ recent_24h = [t for t in timestamps if _iso_to_ts(t) > cutoff_24h]
495
+ recent_1h = [t for t in timestamps if _iso_to_ts(t) > cutoff_1h]
496
+
497
+ if len(recent_24h) >= self._MAX_PUBLISHES_PER_DAY:
498
+ logger.warning("plugin_publish rejected: %s exceeds daily rate limit", publisher_id)
499
+ return state
500
+ if len(recent_1h) >= self._MAX_PUBLISHES_PER_HOUR:
501
+ logger.warning("plugin_publish rejected: %s exceeds hourly rate limit", publisher_id)
502
+ return state
503
+
504
+ # Add to registry
505
+ record = PluginChainRecord(
506
+ plugin_hash=plugin_hash,
507
+ name=name,
508
+ version=version,
509
+ publisher_id=publisher_id,
510
+ published_at_height=height,
511
+ status=PluginChainStatus.PENDING_SCAN,
512
+ source_url=p.get("source_url"),
513
+ capabilities=p.get("capabilities", []),
514
+ )
515
+ state.plugin_registry[plugin_hash] = record
516
+
517
+ # Update rate limit counters
518
+ recent_24h.append(now_iso)
519
+ state.plugin_publish_counts[publisher_id] = recent_24h
520
+
521
+ logger.info("plugin_publish: registered %s@%s hash=%s", name, version, plugin_hash[:16])
522
+ return state
523
+
524
+ def _apply_plugin_attestation(
525
+ self, state: ChainState, tx: Transaction, height: int
526
+ ) -> ChainState:
527
+ p = tx.payload
528
+ plugin_hash = p.get("plugin_hash", "")
529
+ scanner_id = tx.from_colony_id
530
+ scan_result = p.get("scan_result", ScanResult.ERROR.value)
531
+
532
+ record = state.plugin_registry.get(plugin_hash)
533
+ if record is None:
534
+ logger.warning("plugin_attestation for unknown hash %s", plugin_hash[:16])
535
+ return state
536
+
537
+ # Deduplicate: one attestation per (plugin_hash, scanner_colony_id) — supersede old
538
+ record.attestations = [
539
+ a for a in record.attestations
540
+ if a.get("scanner_colony_id") != scanner_id
541
+ ]
542
+ record.attestations.append({
543
+ "scanner_colony_id": scanner_id,
544
+ "scan_result": scan_result,
545
+ "scanned_at": p.get("scanned_at", ""),
546
+ "stages_completed": p.get("stages_completed", []),
547
+ })
548
+
549
+ # Recompute consensus using sentinel weights
550
+ sentinel_ids = {s.colony_id for s in state.sentinel_roster.values() if s.status == "active"}
551
+ scanner_weights = {sid: 2.0 for sid in sentinel_ids}
552
+ # Trusted non-sentinels (trust_level >= 3) get weight 1.0, others 0.25 (default)
553
+ record.recompute_consensus(scanner_weights)
554
+
555
+ # State transitions
556
+ if scan_result == ScanResult.FLAGGED.value:
557
+ # Any FLAGGED from trusted scanner (sentinel) → FLAGGED status
558
+ if scanner_id in sentinel_ids:
559
+ record.status = PluginChainStatus.FLAGGED
560
+ logger.info("plugin %s flagged by sentinel %s", plugin_hash[:16], scanner_id)
561
+ elif record.status == PluginChainStatus.PENDING_SCAN:
562
+ record.status = PluginChainStatus.FLAGGED
563
+ elif scan_result == ScanResult.SAFE.value:
564
+ # Check consensus threshold for SAFE transition
565
+ if (
566
+ record.status == PluginChainStatus.PENDING_SCAN
567
+ and record.safe_consensus >= self._SAFE_CONSENSUS_THRESHOLD
568
+ and record.has_sentinel_safe(frozenset(sentinel_ids))
569
+ ):
570
+ record.status = PluginChainStatus.SAFE
571
+ logger.info(
572
+ "plugin %s reached SAFE consensus (%.2f)",
573
+ plugin_hash[:16], record.safe_consensus,
574
+ )
575
+
576
+ return state
577
+
578
+ def _apply_plugin_flag(
579
+ self, state: ChainState, tx: Transaction, height: int
580
+ ) -> ChainState:
581
+ p = tx.payload
582
+ plugin_hash = p.get("plugin_hash", "")
583
+ reporter_id = tx.from_colony_id
584
+ severity = p.get("severity", FindingSeverity.INFO.value)
585
+
586
+ record = state.plugin_registry.get(plugin_hash)
587
+ if record is None:
588
+ logger.warning("plugin_flag for unknown hash %s", plugin_hash[:16])
589
+ return state
590
+
591
+ # Deduplicate: same (reporter, vuln_description) → idempotent
592
+ vuln_desc = p.get("vulnerability_description", "")
593
+ for existing_flag in record.flags:
594
+ if (
595
+ existing_flag.get("reporter_colony_id") == reporter_id
596
+ and existing_flag.get("vulnerability_description") == vuln_desc
597
+ ):
598
+ return state # duplicate
599
+
600
+ record.flags.append({
601
+ "reporter_colony_id": reporter_id,
602
+ "severity": severity,
603
+ "vulnerability_description": vuln_desc,
604
+ "flagged_at": datetime.now(timezone.utc).isoformat(),
605
+ "tx_id": tx.tx_id,
606
+ })
607
+
608
+ # Count trusted flags for auto-quarantine
609
+ sentinel_ids = {s.colony_id for s in state.sentinel_roster.values() if s.status == "active"}
610
+ high_severity = {FindingSeverity.HIGH.value, FindingSeverity.CRITICAL.value}
611
+ mid_and_above = {FindingSeverity.MEDIUM.value, FindingSeverity.HIGH.value, FindingSeverity.CRITICAL.value}
612
+
613
+ # Single Sentinel flag at medium+ → immediate quarantine
614
+ if reporter_id in sentinel_ids and severity in mid_and_above:
615
+ record.status = PluginChainStatus.QUARANTINED
616
+ logger.info("plugin %s quarantined by sentinel flag from %s", plugin_hash[:16], reporter_id)
617
+ return state
618
+
619
+ # Count distinct trusted reporters with high/critical severity in last 72h
620
+ window_ts = datetime.now(timezone.utc).timestamp() - self._AUTO_QUARANTINE_WINDOW_HOURS * 3600
621
+ trusted_high_reporters = set()
622
+ for flag in record.flags:
623
+ if (
624
+ flag["severity"] in high_severity
625
+ and _iso_to_ts(flag.get("flagged_at", "")) > window_ts
626
+ ):
627
+ trusted_high_reporters.add(flag["reporter_colony_id"])
628
+
629
+ record.flag_count_trusted = len(trusted_high_reporters)
630
+
631
+ if len(trusted_high_reporters) >= self._AUTO_QUARANTINE_THRESHOLD:
632
+ record.status = PluginChainStatus.QUARANTINED
633
+ logger.info(
634
+ "plugin %s auto-quarantined: %d trusted flags",
635
+ plugin_hash[:16], len(trusted_high_reporters),
636
+ )
637
+
638
+ return state
639
+
640
+ def _apply_plugin_quarantine(
641
+ self, state: ChainState, tx: Transaction, height: int
642
+ ) -> ChainState:
643
+ """Apply a system-generated plugin_quarantine transaction."""
644
+ p = tx.payload
645
+ plugin_hash = p.get("plugin_hash", "")
646
+
647
+ record = state.plugin_registry.get(plugin_hash)
648
+ if record is None:
649
+ logger.warning("plugin_quarantine for unknown hash %s", plugin_hash[:16])
650
+ return state
651
+
652
+ record.status = PluginChainStatus.QUARANTINED
653
+ logger.info(
654
+ "plugin %s quarantined via system tx (reason: %s)",
655
+ plugin_hash[:16], p.get("quarantine_reason", "unknown"),
656
+ )
657
+ return state
658
+
659
+ def _apply_pending_upgrades(self, state: ChainState, height: int) -> ChainState:
660
+ """Apply any ratified protocol upgrades whose activation height has arrived."""
661
+ for upgrade in state.upgrade_history:
662
+ if upgrade.ratified and upgrade.activation_height == height:
663
+ changes = upgrade.changes
664
+ pc = state.protocol_config
665
+ if "block_interval_secs" in changes:
666
+ pc.block_interval_secs = changes["block_interval_secs"]
667
+ if "untrust_threshold" in changes:
668
+ pc.untrust_threshold = changes["untrust_threshold"]
669
+ if "untrust_window_days" in changes:
670
+ pc.untrust_window_days = changes["untrust_window_days"]
671
+ if "uptime_requirement_percent" in changes:
672
+ pc.uptime_requirement_percent = changes["uptime_requirement_percent"]
673
+ if "min_protocol_version" in changes:
674
+ pc.min_protocol_version = changes["min_protocol_version"]
675
+ logger.info("Applied protocol upgrade %s at height %d", upgrade.upgrade_id, height)
676
+ return state