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,483 @@
1
+ """API key authentication + request-size middleware for Colony sidecar."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hmac
6
+ import os
7
+ from starlette.middleware.base import BaseHTTPMiddleware
8
+ from starlette.requests import Request
9
+ from starlette.responses import JSONResponse
10
+ from starlette.routing import Match
11
+
12
+ from apsimo.api.auth_telemetry import AuthTelemetry
13
+ from apsimo.api.contact_grants import ContactGrantRegistry
14
+
15
+ from apsimo.api.authority import (
16
+ KeyringLoader,
17
+ anonymous_authority,
18
+ compatible_scopes,
19
+ legacy_authority,
20
+ query_person_is_granted,
21
+ required_scope,
22
+ scoped_authority,
23
+ )
24
+
25
+
26
+ # Default cap on request body size (10 MiB). Oversized uploads are rejected
27
+ # at the middleware layer before the handler buffers them. Override via
28
+ # COLONY_MAX_BODY_BYTES in the environment.
29
+ _DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024
30
+
31
+
32
+ # Endpoints reachable without a key, even in "dev mode" (no COLONY_API_KEY).
33
+ # Health + docs keep the first-run wizard smooth; everything else (including
34
+ # /configure, which accepts LLM credentials) requires an explicit key.
35
+ _DEV_MODE_ALLOWED = frozenset({
36
+ "/v1/host/health",
37
+ "/docs",
38
+ "/openapi.json",
39
+ "/redoc",
40
+ })
41
+
42
+ # Routes that must never be served without an API key, regardless of
43
+ # COLONY_API_KEY presence — these accept or return credential-grade state
44
+ # and must never be anonymously reachable. Agent registration / connect
45
+ # endpoints accept caller-supplied `is_primary` and capability lists, so
46
+ # they must fail closed in dev mode rather than letting an unauthenticated
47
+ # caller register a fully-privileged primary agent.
48
+ _ALWAYS_AUTH_REQUIRED = frozenset({
49
+ "/v1/host/configure",
50
+ "/v1/host/agents/register",
51
+ "/v1/host/agents/connect",
52
+ "/v1/host/queue/contract",
53
+ })
54
+
55
+
56
+ class ApiKeyMiddleware(BaseHTTPMiddleware):
57
+ """Reject requests that don't carry the correct Bearer token.
58
+
59
+ Without either auth mechanism, normal endpoints retain loopback dev-mode
60
+ behavior; ``_ALWAYS_AUTH_REQUIRED`` paths fail closed with 503 so an
61
+ operator cannot accidentally expose credential-handling endpoints.
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ app,
67
+ api_key: str | None = None,
68
+ keyring_path: str | None = None,
69
+ auth_telemetry: AuthTelemetry | None = None,
70
+ contact_grants: ContactGrantRegistry | None = None,
71
+ ) -> None:
72
+ super().__init__(app)
73
+ self._api_key = api_key
74
+ self._keyring = KeyringLoader(keyring_path)
75
+ self._telemetry = auth_telemetry or AuthTelemetry()
76
+ self._contact_grants = contact_grants or ContactGrantRegistry(None)
77
+
78
+ @staticmethod
79
+ def _route_template(request: Request) -> str:
80
+ """Resolve a framework template without persisting concrete path IDs."""
81
+
82
+ matched = request.scope.get("route")
83
+ template = getattr(matched, "path", None)
84
+ if isinstance(template, str) and template:
85
+ return template
86
+ for candidate in getattr(request.app, "routes", ()):
87
+ try:
88
+ match, _ = candidate.matches(request.scope)
89
+ except Exception:
90
+ continue
91
+ if match == Match.FULL:
92
+ template = getattr(candidate, "path", None)
93
+ if isinstance(template, str) and template:
94
+ return template
95
+ return "<unmatched>"
96
+
97
+ @staticmethod
98
+ def _auth_labels(authority) -> tuple[str, str]:
99
+ if authority.legacy:
100
+ return "legacy", "legacy"
101
+ if authority.anonymous:
102
+ return "anonymous", "anonymous-dev"
103
+ return "scoped", authority.principal_id
104
+
105
+ def _record(
106
+ self,
107
+ request: Request,
108
+ *,
109
+ authority=None,
110
+ auth_kind: str | None = None,
111
+ principal_id: str | None = None,
112
+ decision: str,
113
+ reason: str,
114
+ scope: str,
115
+ route: str,
116
+ ) -> None:
117
+ if authority is not None:
118
+ auth_kind, principal_id = self._auth_labels(authority)
119
+ self._telemetry.record(
120
+ auth_kind=auth_kind or "unauthenticated",
121
+ principal_id=principal_id or "unauthenticated",
122
+ method=request.method,
123
+ route=route,
124
+ required_scope=scope,
125
+ decision=decision,
126
+ reason=reason,
127
+ )
128
+
129
+ async def _call_authorized(
130
+ self,
131
+ request: Request,
132
+ call_next,
133
+ *,
134
+ authority,
135
+ scope: str,
136
+ route: str,
137
+ allow_reason: str = "allowed",
138
+ ):
139
+ try:
140
+ response = await call_next(request)
141
+ except BaseException:
142
+ self._record(
143
+ request, authority=authority, decision="allow",
144
+ reason="handler_exception", scope=scope, route=route,
145
+ )
146
+ raise
147
+ if response.status_code in (401, 403):
148
+ self._record(
149
+ request, authority=authority, decision="deny",
150
+ reason="authority_denied", scope=scope, route=route,
151
+ )
152
+ else:
153
+ self._record(
154
+ request, authority=authority, decision="allow",
155
+ reason=allow_reason, scope=scope, route=route,
156
+ )
157
+ return response
158
+
159
+ @staticmethod
160
+ def _query_scope_error(request: Request, authority):
161
+ # Several legacy list endpoints use query-string person selectors.
162
+ # Validate all common names globally so adding a new handler cannot
163
+ # accidentally turn a body-bound principal back into caller authority.
164
+ for field in ("person_id", "contact_id", "viewer_person_id"):
165
+ for value in request.query_params.getlist(field):
166
+ if value and not query_person_is_granted(authority, value):
167
+ return JSONResponse(
168
+ status_code=403,
169
+ content={
170
+ "detail": {
171
+ "code": "person_scope_not_granted",
172
+ "message": f"query parameter {field} exceeds principal authority",
173
+ }
174
+ },
175
+ )
176
+ return None
177
+
178
+ async def dispatch(self, request: Request, call_next):
179
+ path = request.url.path
180
+ request.state.colony_authority = anonymous_authority()
181
+ request.state.colony_auth_telemetry = self._telemetry
182
+ request.state.colony_contact_grants = self._contact_grants
183
+ request.state.colony_keyring_status = self._keyring.status()
184
+ request.state.colony_auth_configuration = {
185
+ "legacy_configured": bool(self._api_key),
186
+ "scoped_configured": self._keyring.configured,
187
+ "dual_accept": bool(self._api_key) and self._keyring.configured,
188
+ }
189
+ route = self._route_template(request)
190
+ scope = required_scope(request.method, path)
191
+ scope_compatibility = compatible_scopes(request.method, path)
192
+ approval_mode = os.environ.get(
193
+ "COLONY_APPROVAL_AUTHORITY_MODE", "shadow"
194
+ ).strip().lower()
195
+ exact_approval_scope = bool(
196
+ approval_mode != "shadow"
197
+ and (
198
+ scope.startswith("approvals:")
199
+ or scope.startswith("charter:approval-")
200
+ )
201
+ )
202
+
203
+ if exact_approval_scope and approval_mode not in {"shadow", "enforce"}:
204
+ self._record(
205
+ request, authority=request.state.colony_authority,
206
+ decision="deny", reason="approval_authority_mode_invalid",
207
+ scope=scope, route=route,
208
+ )
209
+ return JSONResponse(
210
+ status_code=503,
211
+ content={
212
+ "detail": {
213
+ "code": "approval_authority_mode_invalid",
214
+ "message": (
215
+ "COLONY_APPROVAL_AUTHORITY_MODE must be shadow or enforce"
216
+ ),
217
+ }
218
+ },
219
+ )
220
+
221
+ if path in _DEV_MODE_ALLOWED:
222
+ return await call_next(request)
223
+
224
+ auth_configured = bool(self._api_key) or self._keyring.configured
225
+ if not auth_configured:
226
+ if exact_approval_scope:
227
+ self._record(
228
+ request, authority=request.state.colony_authority,
229
+ decision="deny", reason="exact_scoped_principal_required",
230
+ scope=scope, route=route,
231
+ )
232
+ return JSONResponse(
233
+ status_code=403,
234
+ content={
235
+ "detail": {
236
+ "code": "exact_scoped_principal_required",
237
+ "required_scopes": ["api:access", scope],
238
+ }
239
+ },
240
+ )
241
+ if (
242
+ path in _ALWAYS_AUTH_REQUIRED
243
+ or path == "/v1/host/queue/work"
244
+ or path.startswith("/v1/host/queue/work/")
245
+ or (
246
+ path.startswith("/v1/host/queue/workers/")
247
+ and "/controls" in path
248
+ )
249
+ or path.startswith("/v1/host/queue/inspection/")
250
+ or path.startswith("/v1/host/queue/attestations/")
251
+ ):
252
+ self._record(
253
+ request, authority=request.state.colony_authority,
254
+ decision="deny", reason="auth_not_configured",
255
+ scope=scope, route=route,
256
+ )
257
+ return JSONResponse(
258
+ status_code=503,
259
+ content={
260
+ "detail": (
261
+ "Set COLONY_API_KEY or COLONY_API_KEYRING_PATH in "
262
+ "the sidecar environment to enable this endpoint."
263
+ )
264
+ },
265
+ )
266
+ query_error = self._query_scope_error(
267
+ request, request.state.colony_authority
268
+ )
269
+ if query_error is not None:
270
+ self._record(
271
+ request, authority=request.state.colony_authority,
272
+ decision="deny", reason="person_scope_not_granted",
273
+ scope=scope, route=route,
274
+ )
275
+ return query_error
276
+ return await self._call_authorized(
277
+ request, call_next, authority=request.state.colony_authority,
278
+ scope=scope, route=route,
279
+ )
280
+
281
+ # Both header styles are in active use: the gateway sends
282
+ # ``Authorization: Bearer``, the poller/queue-worker scripts send
283
+ # ``X-API-Key`` (and advertise it to agents in job payloads).
284
+ auth = request.headers.get("Authorization", "")
285
+ token = auth[7:] if auth.startswith("Bearer ") else request.headers.get("X-API-Key", "")
286
+
287
+ if token:
288
+ scoped_match = self._keyring.authenticate(token)
289
+ authority = None
290
+ if scoped_match is not None and scoped_match.accepts():
291
+ principal = scoped_match.principal
292
+ exact_grants = frozenset()
293
+ if principal.attested_contact_platforms:
294
+ exact_grants = self._contact_grants.person_ids(
295
+ principal.principal_id,
296
+ max_person_ids=principal.attested_contact_limit,
297
+ )
298
+ authority = scoped_authority(scoped_match, exact_grants)
299
+ elif self._api_key and hmac.compare_digest(
300
+ token.encode("utf-8"), self._api_key.encode("utf-8")
301
+ ):
302
+ authority = legacy_authority()
303
+
304
+ if authority is not None:
305
+ claimed_principal = request.headers.get("X-Colony-Principal", "").strip()
306
+ if claimed_principal and claimed_principal != authority.principal_id:
307
+ self._record(
308
+ request, authority=authority, decision="deny",
309
+ reason="principal_mismatch", scope=scope, route=route,
310
+ )
311
+ return JSONResponse(
312
+ status_code=403,
313
+ content={
314
+ "detail": {
315
+ "code": "principal_mismatch",
316
+ "message": "claimed principal does not match the credential",
317
+ }
318
+ },
319
+ )
320
+ if scope == "api:access" and not authority.allow_unscoped_api:
321
+ self._record(
322
+ request, authority=authority, decision="deny",
323
+ reason="unscoped_api_denied", scope=scope, route=route,
324
+ )
325
+ return JSONResponse(
326
+ status_code=403,
327
+ content={
328
+ "detail": {
329
+ "code": "unscoped_api_denied",
330
+ "message": (
331
+ "this principal may use only explicitly "
332
+ "mapped API routes"
333
+ ),
334
+ }
335
+ },
336
+ )
337
+ if exact_approval_scope and (
338
+ authority.legacy
339
+ or not authority.authenticated
340
+ or not authority.has_scope("api:access")
341
+ ):
342
+ self._record(
343
+ request, authority=authority, decision="deny",
344
+ reason="exact_scoped_principal_required",
345
+ scope=scope, route=route,
346
+ )
347
+ return JSONResponse(
348
+ status_code=403,
349
+ content={
350
+ "detail": {
351
+ "code": "exact_scoped_principal_required",
352
+ "required_scopes": ["api:access", scope],
353
+ }
354
+ },
355
+ )
356
+ exact_scope = authority.has_scope(scope)
357
+ compatible_scope = next(
358
+ (
359
+ candidate for candidate in sorted(scope_compatibility)
360
+ if authority.has_scope(candidate)
361
+ and not (
362
+ candidate == "api:access"
363
+ and not authority.allow_unscoped_api
364
+ )
365
+ ),
366
+ None,
367
+ )
368
+ if (
369
+ not exact_scope
370
+ and "api:access" in scope_compatibility
371
+ and authority.has_scope("api:access")
372
+ and not authority.allow_unscoped_api
373
+ ):
374
+ self._record(
375
+ request, authority=authority, decision="deny",
376
+ reason="unscoped_api_denied", scope=scope, route=route,
377
+ )
378
+ return JSONResponse(
379
+ status_code=403,
380
+ content={
381
+ "detail": {
382
+ "code": "unscoped_api_denied",
383
+ "message": (
384
+ "this principal may use only explicitly "
385
+ "mapped API routes"
386
+ ),
387
+ }
388
+ },
389
+ )
390
+ if not exact_scope and compatible_scope is None:
391
+ self._record(
392
+ request, authority=authority, decision="deny",
393
+ reason="insufficient_scope", scope=scope, route=route,
394
+ )
395
+ return JSONResponse(
396
+ status_code=403,
397
+ content={
398
+ "detail": {
399
+ "code": "insufficient_scope",
400
+ "required_scope": scope,
401
+ }
402
+ },
403
+ )
404
+ request.state.colony_authority = authority
405
+ query_error = self._query_scope_error(request, authority)
406
+ if query_error is not None:
407
+ self._record(
408
+ request, authority=authority, decision="deny",
409
+ reason="person_scope_not_granted", scope=scope, route=route,
410
+ )
411
+ return query_error
412
+ return await self._call_authorized(
413
+ request, call_next, authority=authority,
414
+ scope=scope, route=route,
415
+ allow_reason=(
416
+ "compatible_scope_allowed"
417
+ if not exact_scope else "allowed"
418
+ ),
419
+ )
420
+
421
+ # If scoped auth is the only configured method and its file is unsafe
422
+ # or malformed, report an operator-fixable outage rather than pretending
423
+ # a valid credential merely failed authentication.
424
+ if self._keyring.configured and self._keyring.error and not self._api_key:
425
+ self._record(
426
+ request, decision="deny", reason="scoped_auth_unavailable",
427
+ scope=scope, route=route,
428
+ )
429
+ return JSONResponse(
430
+ status_code=503,
431
+ content={
432
+ "detail": {
433
+ "code": "scoped_auth_unavailable",
434
+ "message": "Scoped API authentication is unavailable",
435
+ }
436
+ },
437
+ )
438
+
439
+ self._record(
440
+ request, decision="deny",
441
+ reason="invalid_key" if token else "missing_key",
442
+ scope=scope, route=route,
443
+ )
444
+ return JSONResponse(
445
+ status_code=401,
446
+ content={"detail": "Invalid or missing API key"},
447
+ )
448
+
449
+
450
+ class BodySizeLimitMiddleware(BaseHTTPMiddleware):
451
+ """Reject requests that declare a body larger than ``max_bytes``.
452
+
453
+ Short-circuits before the handler reads the payload, so oversized uploads
454
+ cannot be used to exhaust memory. Requests without a ``Content-Length``
455
+ header (e.g. chunked transfer encoding) are allowed to pass — FastAPI's
456
+ own buffer limits still apply downstream.
457
+ """
458
+
459
+ def __init__(self, app, max_bytes: int = _DEFAULT_MAX_BODY_BYTES) -> None:
460
+ super().__init__(app)
461
+ self._max_bytes = max_bytes
462
+
463
+ async def dispatch(self, request: Request, call_next):
464
+ cl = request.headers.get("content-length")
465
+ if cl is not None:
466
+ try:
467
+ length = int(cl)
468
+ except ValueError:
469
+ return JSONResponse(
470
+ status_code=400,
471
+ content={"detail": "Invalid Content-Length header"},
472
+ )
473
+ if length > self._max_bytes:
474
+ return JSONResponse(
475
+ status_code=413,
476
+ content={
477
+ "detail": (
478
+ f"Request body exceeds limit "
479
+ f"({length} > {self._max_bytes} bytes)"
480
+ )
481
+ },
482
+ )
483
+ return await call_next(request)
@@ -0,0 +1 @@
1
+ # routers