code-context-control 2.71.0__tar.gz → 2.73.0__tar.gz

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 (411) hide show
  1. {code_context_control-2.71.0/code_context_control.egg-info → code_context_control-2.73.0}/PKG-INFO +1 -1
  2. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/c3.py +1 -1
  3. code_context_control-2.73.0/cli/tools/_grants.py +61 -0
  4. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/compress.py +4 -1
  5. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/edit.py +10 -12
  6. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/filter.py +4 -1
  7. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/impact.py +3 -1
  8. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/locks.py +4 -7
  9. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/override.py +3 -4
  10. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/read.py +8 -1
  11. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/validate.py +5 -2
  12. {code_context_control-2.71.0 → code_context_control-2.73.0/code_context_control.egg-info}/PKG-INFO +1 -1
  13. {code_context_control-2.71.0 → code_context_control-2.73.0}/code_context_control.egg-info/SOURCES.txt +4 -0
  14. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/mobile_api.py +132 -8
  15. {code_context_control-2.71.0 → code_context_control-2.73.0}/pyproject.toml +1 -1
  16. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/override_policy.py +31 -0
  17. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/override_requests.py +24 -0
  18. code_context_control-2.73.0/services/override_wake.py +241 -0
  19. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mobile_api.py +113 -1
  20. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mobile_override_routes.py +39 -0
  21. code_context_control-2.73.0/tests/test_override_grants_mcp.py +189 -0
  22. code_context_control-2.73.0/tests/test_override_wake.py +278 -0
  23. {code_context_control-2.71.0 → code_context_control-2.73.0}/LICENSE +0 -0
  24. {code_context_control-2.71.0 → code_context_control-2.73.0}/README.md +0 -0
  25. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/__init__.py +0 -0
  26. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/_hook_utils.py +0 -0
  27. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/commands/__init__.py +0 -0
  28. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/commands/common.py +0 -0
  29. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/commands/parser.py +0 -0
  30. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/docs.html +0 -0
  31. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/edits.html +0 -0
  32. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/access.html +0 -0
  33. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/bitbucket.html +0 -0
  34. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/credentials.html +0 -0
  35. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/getting-started.html +0 -0
  36. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/index.html +0 -0
  37. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/masking.html +0 -0
  38. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/oracle.html +0 -0
  39. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/shared.css +0 -0
  40. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/tools.html +0 -0
  41. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/guide/workflow.html +0 -0
  42. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_access_guard.py +0 -0
  43. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_artifact.py +0 -0
  44. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_auto_snapshot.py +0 -0
  45. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_c3_signal.py +0 -0
  46. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_c3read.py +0 -0
  47. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_dispatch.py +0 -0
  48. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_edit_ledger.py +0 -0
  49. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_edit_unlock.py +0 -0
  50. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_filter.py +0 -0
  51. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_ghost_files.py +0 -0
  52. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_pretool_enforce.py +0 -0
  53. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_prompt_recall.py +0 -0
  54. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_read.py +0 -0
  55. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_session_stats.py +0 -0
  56. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hook_terse_advisor.py +0 -0
  57. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_server.py +0 -0
  58. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/app.js +0 -0
  59. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/add_project.js +0 -0
  60. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/config_editor.js +0 -0
  61. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  62. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_health.js +0 -0
  63. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_panel.js +0 -0
  64. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  65. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  66. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/drill_views.js +0 -0
  67. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/global_search.js +0 -0
  68. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/hub_credentials.js +0 -0
  69. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
  70. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/hub_locks.js +0 -0
  71. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  72. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/modals.js +0 -0
  73. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/project_card.js +0 -0
  74. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/project_tree.js +0 -0
  75. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/session_drawer.js +0 -0
  76. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/settings_modal.js +0 -0
  77. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/sidebar.js +0 -0
  78. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/summary_bar.js +0 -0
  79. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/task_board.js +0 -0
  80. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/toasts.js +0 -0
  81. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/components/topbar.js +0 -0
  82. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui/state.js +0 -0
  83. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/hub_ui.html +0 -0
  84. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/mcp_proxy.py +0 -0
  85. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/mcp_server.py +0 -0
  86. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/progress.py +0 -0
  87. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/server.py +0 -0
  88. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/__init__.py +0 -0
  89. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/_helpers.py +0 -0
  90. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/agent.py +0 -0
  91. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/artifacts.py +0 -0
  92. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/bitbucket.py +0 -0
  93. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/credentials.py +0 -0
  94. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/delegate.py +0 -0
  95. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/edits.py +0 -0
  96. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/federate.py +0 -0
  97. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/jira.py +0 -0
  98. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/memory.py +0 -0
  99. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/project.py +0 -0
  100. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/search.py +0 -0
  101. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/session.py +0 -0
  102. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/shell.py +0 -0
  103. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/status.py +0 -0
  104. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/tools/tasks.py +0 -0
  105. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/api.js +0 -0
  106. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/app.js +0 -0
  107. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/access.js +0 -0
  108. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/bitbucket.js +0 -0
  109. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/chat.js +0 -0
  110. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/credentials.js +0 -0
  111. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/dashboard.js +0 -0
  112. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/edits.js +0 -0
  113. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/enforcement.js +0 -0
  114. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/instructions.js +0 -0
  115. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/jira.js +0 -0
  116. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/memory.js +0 -0
  117. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/sessions.js +0 -0
  118. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/settings.js +0 -0
  119. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/sidebar.js +0 -0
  120. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/components/tasks.js +0 -0
  121. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/icons.js +0 -0
  122. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/pm_shared.js +0 -0
  123. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/shared.js +0 -0
  124. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui/theme.js +0 -0
  125. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui.html +0 -0
  126. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui_legacy.html +0 -0
  127. {code_context_control-2.71.0 → code_context_control-2.73.0}/cli/ui_nano.html +0 -0
  128. {code_context_control-2.71.0 → code_context_control-2.73.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  129. {code_context_control-2.71.0 → code_context_control-2.73.0}/code_context_control.egg-info/entry_points.txt +0 -0
  130. {code_context_control-2.71.0 → code_context_control-2.73.0}/code_context_control.egg-info/requires.txt +0 -0
  131. {code_context_control-2.71.0 → code_context_control-2.73.0}/code_context_control.egg-info/top_level.txt +0 -0
  132. {code_context_control-2.71.0 → code_context_control-2.73.0}/core/__init__.py +0 -0
  133. {code_context_control-2.71.0 → code_context_control-2.73.0}/core/config.py +0 -0
  134. {code_context_control-2.71.0 → code_context_control-2.73.0}/core/ide.py +0 -0
  135. {code_context_control-2.71.0 → code_context_control-2.73.0}/core/mcp_toml.py +0 -0
  136. {code_context_control-2.71.0 → code_context_control-2.73.0}/core/web_security.py +0 -0
  137. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/__init__.py +0 -0
  138. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/config.py +0 -0
  139. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/mcp_oracle.py +0 -0
  140. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/oracle_server.py +0 -0
  141. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/oracle_ui.html +0 -0
  142. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/__init__.py +0 -0
  143. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/activity_reporter.py +0 -0
  144. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/api_auth.py +0 -0
  145. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/c3_bridge.py +0 -0
  146. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/chat_engine.py +0 -0
  147. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/chat_store.py +0 -0
  148. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/cross_memory.py +0 -0
  149. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/discovery_audit.py +0 -0
  150. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/federated_graph.py +0 -0
  151. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/health_checker.py +0 -0
  152. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/insight_engine.py +0 -0
  153. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/local_session.py +0 -0
  154. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/memory_reader.py +0 -0
  155. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/memory_writer.py +0 -0
  156. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/project_scanner.py +0 -0
  157. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/review_agent.py +0 -0
  158. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/tool_executor.py +0 -0
  159. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/services/tool_registry.py +0 -0
  160. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/activity.js +0 -0
  161. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/agents.js +0 -0
  162. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/app.js +0 -0
  163. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/busy.js +0 -0
  164. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/conversations.js +0 -0
  165. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/input.js +0 -0
  166. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/markdown.js +0 -0
  167. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/send.js +0 -0
  168. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/stream_renderer.js +0 -0
  169. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/chat/toolbar.js +0 -0
  170. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/core.js +0 -0
  171. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/crossgraph.js +0 -0
  172. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/header.js +0 -0
  173. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/insights.js +0 -0
  174. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/projects.js +0 -0
  175. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/qrcode.js +0 -0
  176. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/settings.js +0 -0
  177. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/suggestions.js +0 -0
  178. {code_context_control-2.71.0 → code_context_control-2.73.0}/oracle/ui/theme_tabs.js +0 -0
  179. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/__init__.py +0 -0
  180. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/access_guard.py +0 -0
  181. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/access_telemetry.py +0 -0
  182. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/activity_log.py +0 -0
  183. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/agent_base.py +0 -0
  184. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/agent_locks.py +0 -0
  185. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/agents.py +0 -0
  186. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/artifact_defs.py +0 -0
  187. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/artifact_store.py +0 -0
  188. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/auto_memory.py +0 -0
  189. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bench/__init__.py +0 -0
  190. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bench/external/__init__.py +0 -0
  191. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bench/external/aider_polyglot.py +0 -0
  192. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bench/external/swe_bench.py +0 -0
  193. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/benchmark_dashboard.py +0 -0
  194. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bitbucket_client.py +0 -0
  195. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/bitbucket_credentials.py +0 -0
  196. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/circuit_breaker.py +0 -0
  197. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/claude_md.py +0 -0
  198. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/compressor.py +0 -0
  199. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/context_snapshot.py +0 -0
  200. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/conversation_store.py +0 -0
  201. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/credential_store.py +0 -0
  202. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/doc_index.py +0 -0
  203. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/e2e_benchmark.py +0 -0
  204. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/e2e_evaluator.py +0 -0
  205. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/e2e_tasks.py +0 -0
  206. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/edit_ledger.py +0 -0
  207. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/embedding_index.py +0 -0
  208. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/enforcement_policy.py +0 -0
  209. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/error_reporting.py +0 -0
  210. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/file_memory.py +0 -0
  211. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/git_context.py +0 -0
  212. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/hub_service.py +0 -0
  213. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/indexer.py +0 -0
  214. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/jira_client.py +0 -0
  215. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/jira_cloud.py +0 -0
  216. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/jira_credentials.py +0 -0
  217. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/jira_data_center.py +0 -0
  218. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/jira_links.py +0 -0
  219. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/mask_activation.py +0 -0
  220. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/mask_mirror.py +0 -0
  221. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/mask_presets.py +0 -0
  222. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory.py +0 -0
  223. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_consolidator.py +0 -0
  224. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_distiller.py +0 -0
  225. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_graph.py +0 -0
  226. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_grounder.py +0 -0
  227. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_queue.py +0 -0
  228. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/memory_scorer.py +0 -0
  229. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/metrics.py +0 -0
  230. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/notifications.py +0 -0
  231. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/ollama_bridge.py +0 -0
  232. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/ollama_client.py +0 -0
  233. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/ollama_credentials.py +0 -0
  234. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/output_filter.py +0 -0
  235. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/override_grants.py +0 -0
  236. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/parser.py +0 -0
  237. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/project_manager.py +0 -0
  238. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/project_runtime.py +0 -0
  239. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/protocol.py +0 -0
  240. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/proxy_state.py +0 -0
  241. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/repo_map.py +0 -0
  242. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/retention.py +0 -0
  243. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/retrieval_broker.py +0 -0
  244. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/router.py +0 -0
  245. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/runtime.py +0 -0
  246. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/scanner.py +0 -0
  247. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/session_benchmark.py +0 -0
  248. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/session_manager.py +0 -0
  249. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/session_preloader.py +0 -0
  250. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/subprojects.py +0 -0
  251. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/task_store.py +0 -0
  252. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/telemetry.py +0 -0
  253. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/text_index.py +0 -0
  254. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/time_tracker.py +0 -0
  255. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/tool_classifier.py +0 -0
  256. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/transcript_index.py +0 -0
  257. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/validation_cache.py +0 -0
  258. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/vector_store.py +0 -0
  259. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/watcher.py +0 -0
  260. {code_context_control-2.71.0 → code_context_control-2.73.0}/services/win_subprocess.py +0 -0
  261. {code_context_control-2.71.0 → code_context_control-2.73.0}/setup.cfg +0 -0
  262. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_builtin_optout.py +0 -0
  263. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_guard.py +0 -0
  264. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_guard_meta.py +0 -0
  265. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_guard_shell_project.py +0 -0
  266. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_guard_surfaces.py +0 -0
  267. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_access_guard_wiring.py +0 -0
  268. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_activity_reporter.py +0 -0
  269. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_agent_locks.py +0 -0
  270. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_aider_polyglot.py +0 -0
  271. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_antigravity_transition.py +0 -0
  272. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_artifact_store.py +0 -0
  273. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_artifact_tool.py +0 -0
  274. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  275. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_bitbucket_client.py +0 -0
  276. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_bitbucket_config_fallback.py +0 -0
  277. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_bitbucket_credentials.py +0 -0
  278. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_bitbucket_tool.py +0 -0
  279. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  280. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_c3_shell.py +0 -0
  281. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_circuit_breaker.py +0 -0
  282. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_claude_md_merge.py +0 -0
  283. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_cli_smoke.py +0 -0
  284. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_compressor_large_file.py +0 -0
  285. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_credential_store.py +0 -0
  286. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_credentials_routes.py +0 -0
  287. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_credentials_tool.py +0 -0
  288. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_creds_cli_smoke.py +0 -0
  289. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_delegate_cascade.py +0 -0
  290. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_delegate_version_probe.py +0 -0
  291. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_e2e_benchmark.py +0 -0
  292. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_edit_ledger_hook.py +0 -0
  293. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_edit_locking.py +0 -0
  294. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_edit_normalization.py +0 -0
  295. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_embedding_index_deadlock.py +0 -0
  296. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_enforcement_flip.py +0 -0
  297. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_enforcement_policy.py +0 -0
  298. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_enforcement_routes.py +0 -0
  299. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_federated_graph.py +0 -0
  300. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_filter_backoff.py +0 -0
  301. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_ghost_files.py +0 -0
  302. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_ghost_generation.py +0 -0
  303. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_git_branch_awareness.py +0 -0
  304. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_access_guard.py +0 -0
  305. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_dispatch.py +0 -0
  306. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_pretool_enforce.py +0 -0
  307. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_prompt_recall.py +0 -0
  308. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_smoke.py +0 -0
  309. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_state.py +0 -0
  310. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hook_utf8_stdio.py +0 -0
  311. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_credentials_route.py +0 -0
  312. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_credentials_routes_write.py +0 -0
  313. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_enforcement_routes.py +0 -0
  314. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_inspect_api.py +0 -0
  315. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_locks_routes.py +0 -0
  316. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_server_smoke.py +0 -0
  317. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_hub_subproject_links.py +0 -0
  318. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_ide_workflow_adaptation.py +0 -0
  319. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_init_scan.py +0 -0
  320. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_install_mcp_entrypoint.py +0 -0
  321. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_cli_smoke.py +0 -0
  322. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_client.py +0 -0
  323. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_config_fallback.py +0 -0
  324. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_credentials.py +0 -0
  325. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_links.py +0 -0
  326. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_routes.py +0 -0
  327. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_jira_tool.py +0 -0
  328. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_lazy_store_init.py +0 -0
  329. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mask_guard.py +0 -0
  330. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mask_routes.py +0 -0
  331. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mask_surfaces.py +0 -0
  332. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mcp_host_guard.py +0 -0
  333. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mcp_memory_timeout.py +0 -0
  334. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mcp_server_smoke.py +0 -0
  335. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mcp_toml.py +0 -0
  336. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_memory_distiller.py +0 -0
  337. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_memory_graph_api.py +0 -0
  338. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_memory_queue.py +0 -0
  339. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_memory_system.py +0 -0
  340. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_mobile_security_api.py +0 -0
  341. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_no_stale_model_pins.py +0 -0
  342. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_notification_dedup.py +0 -0
  343. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_notification_discipline.py +0 -0
  344. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_ollama_credentials.py +0 -0
  345. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_api_auth.py +0 -0
  346. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_apikey_api.py +0 -0
  347. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_c3_bridge.py +0 -0
  348. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_chat_engine.py +0 -0
  349. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_chat_store.py +0 -0
  350. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_discovery_api.py +0 -0
  351. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_discovery_audit.py +0 -0
  352. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_local_auth.py +0 -0
  353. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_ollama_bridge.py +0 -0
  354. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_scanner_cache.py +0 -0
  355. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_security_fixes.py +0 -0
  356. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_session_bootstrap.py +0 -0
  357. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_subproject_awareness.py +0 -0
  358. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_oracle_ui_bundle.py +0 -0
  359. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_output_filter.py +0 -0
  360. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_override_grants.py +0 -0
  361. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_override_requests.py +0 -0
  362. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_permissions.py +0 -0
  363. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_pm_api.py +0 -0
  364. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_project_manager.py +0 -0
  365. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_project_manager_merge.py +0 -0
  366. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_project_tool.py +0 -0
  367. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_read_coercion.py +0 -0
  368. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_read_edit_parity.py +0 -0
  369. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_related_facts.py +0 -0
  370. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_repo_map.py +0 -0
  371. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_response_boilerplate.py +0 -0
  372. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_retention.py +0 -0
  373. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_review_digest.py +0 -0
  374. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_service_durability.py +0 -0
  375. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_session_benchmark.py +0 -0
  376. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_session_budget.py +0 -0
  377. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_shell_creds.py +0 -0
  378. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_shell_robustness.py +0 -0
  379. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_subproject_exclusion.py +0 -0
  380. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_subproject_federation.py +0 -0
  381. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_subprojects.py +0 -0
  382. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_swe_bench.py +0 -0
  383. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_task_store.py +0 -0
  384. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_task_tool.py +0 -0
  385. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_time_tracker.py +0 -0
  386. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_token_telemetry.py +0 -0
  387. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_tool_registry.py +0 -0
  388. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_upgrade_and_version.py +0 -0
  389. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_validate.py +0 -0
  390. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_vault_write_guard.py +0 -0
  391. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_version_sync.py +0 -0
  392. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_web_security.py +0 -0
  393. {code_context_control-2.71.0 → code_context_control-2.73.0}/tests/test_windows_reliability.py +0 -0
  394. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/__init__.py +0 -0
  395. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/backend.py +0 -0
  396. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/main.py +0 -0
  397. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/__init__.py +0 -0
  398. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/benchmark_view.py +0 -0
  399. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/claudemd_view.py +0 -0
  400. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/compress_view.py +0 -0
  401. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/index_view.py +0 -0
  402. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/init_view.py +0 -0
  403. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/mcp_view.py +0 -0
  404. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/optimize_view.py +0 -0
  405. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/pipe_view.py +0 -0
  406. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/projects_view.py +0 -0
  407. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/search_view.py +0 -0
  408. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/session_view.py +0 -0
  409. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/stats.py +0 -0
  410. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/screens/ui_view.py +0 -0
  411. {code_context_control-2.71.0 → code_context_control-2.73.0}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.71.0
3
+ Version: 2.73.0
4
4
  Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
92
92
  # Config
93
93
  CONFIG_DIR = ".c3"
94
94
  CONFIG_FILE = ".c3/config.json"
95
- __version__ = "2.71.0"
95
+ __version__ = "2.73.0"
96
96
 
97
97
 
98
98
  def _compress_file_cli(compressor, path, mode="smart", **kw):
@@ -0,0 +1,61 @@
1
+ """Override grants on the MCP content surface (override-requests.md P2a).
2
+
3
+ The hooks have consulted grants since P1. The `c3_*` content tools have not:
4
+ they called the evaluator and refused without ever asking whether the user
5
+ had already approved this exact call. §13's coverage matrix recorded that as
6
+ "not yet — P2a", which meant an approved request did nothing for any agent
7
+ working through MCP — the grant was minted, and then no one looked at it.
8
+
9
+ This module is the single place those tools ask. It is deliberately thin:
10
+ all the policy ordering, TTL clamping, use accounting and audit lives in
11
+ `services.override_grants.gate_access`, which is the same call the hooks
12
+ make. Two implementations of "may I" is how the two surfaces drift apart.
13
+
14
+ `session_id` lives here for the same reason. It was duplicated in edit.py,
15
+ locks.py and override.py, each carrying a comment saying it must match the
16
+ others exactly — and P2a makes that invariant load-bearing rather than
17
+ merely tidy: a grant is minted under the session id `c3_override` computed
18
+ and consumed under the one `c3_edit` computes. If those two ever disagree,
19
+ every approval silently fails to apply.
20
+ """
21
+
22
+ import os
23
+
24
+
25
+ def session_id(svc) -> str:
26
+ """This agent's identity, for leases and for grants.
27
+
28
+ Falls back to the process id, never to "". Two agents that both resolved
29
+ to "" would count as ONE session and stop blocking each other — the exact
30
+ opposite of what a lock is for. Each Claude Code session runs its own
31
+ c3-mcp process, so the pid is a faithful stand-in.
32
+
33
+ It is also the right identity for c3_project: that proxy builds a runtime
34
+ for the TARGET project but runs inside the CALLER's process, so the pid
35
+ keeps the edit attributed to the agent that actually asked for it.
36
+ """
37
+ session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
38
+ return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
39
+
40
+
41
+ def allow(svc, denial, *, tool: str, op: str, path) -> str | None:
42
+ """The `[c3-override:granted]` line when a live grant permits this exact
43
+ call, or None to stay denied.
44
+
45
+ Consumes a use on success — so callers must invoke this only when they
46
+ are about to proceed, never speculatively. Ordering inside `gate_access`
47
+ is policy first, grants second, so a grant is void the instant the policy
48
+ or its layer is switched off (§12.8).
49
+
50
+ Fail-closed by construction: any error resolving the grant store leaves
51
+ the caller on its ordinary refusal path. A grant that cannot be read is
52
+ not a grant.
53
+ """
54
+ if denial is None:
55
+ return None
56
+ try:
57
+ from services import override_grants as og # noqa: PLC0415 — lazy
58
+ return og.gate_access(svc.project_path, denial, tool=tool, op=op,
59
+ path=str(path), session_id=session_id(svc))
60
+ except Exception:
61
+ return None
@@ -8,6 +8,7 @@ import sys
8
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
9
9
  from pathlib import Path
10
10
 
11
+ from cli.tools import _grants
11
12
  from cli.tools._helpers import finalize_with_tokens, show_token_ratios
12
13
  from core import count_tokens
13
14
  from services import access_guard
@@ -118,7 +119,9 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
118
119
  # Access Guard: read verdict up front — covers the map/dense_map paths
119
120
  # that never reach compressor.compress_file (docs/access-guard.md §3).
120
121
  _verdict = access_guard.verdict(file_path, "read", svc.project_path)
121
- if _verdict.denial:
122
+ if _verdict.denial and not _grants.allow(svc, _verdict.denial,
123
+ tool="c3_compress", op="read",
124
+ path=file_path):
122
125
  return finalize("c3_compress", {"file_path": file_path, "mode": mode},
123
126
  access_guard.refusal(_verdict.denial, file_path, "read"),
124
127
  "access-denied")
@@ -22,25 +22,22 @@ import threading
22
22
  from contextlib import contextmanager
23
23
  from pathlib import Path
24
24
 
25
+ from cli.tools import _grants
25
26
  from services import access_guard, agent_locks
26
27
  from services import credential_store as _cs
27
28
  from services.task_store import _FileLock
28
29
 
29
30
 
30
31
  def _session_id(svc) -> str:
31
- """This agent's lease identity.
32
+ """This agent's lease identity — see `cli.tools._grants.session_id`.
32
33
 
33
- Falls back to the process id, never to "". Two agents that both resolved
34
- to "" would count as ONE session and stop blocking each other — the exact
35
- opposite of what a lock is for. Each Claude Code session runs its own
36
- c3-mcp process, so the pid is a faithful stand-in.
37
-
38
- It is also the right identity for c3_project: that proxy builds a runtime
39
- for the TARGET project but runs inside the CALLER's process, so the pid
40
- keeps the edit attributed to the agent that actually asked for it.
34
+ Was defined here and copied into locks.py and override.py, each with a
35
+ comment saying it had to match. P2a makes that invariant load-bearing:
36
+ a grant is minted under the id c3_override computes and consumed under
37
+ the one c3_edit computes, so a divergence would make every approval
38
+ silently fail to apply. One definition, three callers.
41
39
  """
42
- session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
43
- return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
40
+ return _grants.session_id(svc)
44
41
 
45
42
  # ── Same-file serialization ───────────────────────────────────────────────
46
43
  # In-process locks, keyed by resolved absolute path string.
@@ -325,7 +322,8 @@ def handle_edit(file_path: str, old_string: str, new_string: str,
325
322
  # (never replaces) any dedicated vault-file guard.
326
323
  op = "write" if path.exists() else "create"
327
324
  denial = access_guard.check(str(path), op, svc.project_path)
328
- if denial:
325
+ if denial and not _grants.allow(svc, denial, tool="c3_edit", op=op,
326
+ path=str(path)):
329
327
  return finalize("c3_edit", {"file": file_path},
330
328
  access_guard.refusal(denial, file_path, op),
331
329
  "access-denied")
@@ -10,6 +10,7 @@ import json
10
10
  import re
11
11
  from pathlib import Path
12
12
 
13
+ from cli.tools import _grants
13
14
  from cli.tools._helpers import finalize_with_tokens, show_token_ratios
14
15
  from core import count_tokens
15
16
  from services import access_guard
@@ -31,7 +32,9 @@ def handle_filter(file_path: str, text: str, pattern: str, max_lines: int,
31
32
  # Access Guard: read verdict (docs/access-guard.md §3), checked before
32
33
  # existence so probes can't distinguish missing from denied (R2).
33
34
  denial = access_guard.check(file_path, "read", svc.project_path)
34
- if denial:
35
+ _granted = _grants.allow(svc, denial, tool="c3_filter", op="read",
36
+ path=file_path) if denial else None
37
+ if denial and not _granted:
35
38
  return finalize("c3_filter", {"file": file_path},
36
39
  access_guard.refusal(denial, file_path, "read"),
37
40
  "access-denied")
@@ -9,6 +9,7 @@ import subprocess
9
9
  import sys
10
10
  from pathlib import Path
11
11
 
12
+ from cli.tools import _grants
12
13
  from services import access_guard
13
14
 
14
15
  _SKIP_DIRS = frozenset({
@@ -115,7 +116,8 @@ def handle_impact(target: str, file_path: str, mode: str, svc, finalize) -> str:
115
116
  # Access Guard: refuse outright when the named source file is read-denied.
116
117
  if file_path:
117
118
  denial = access_guard.check(file_path, "read", str(project))
118
- if denial:
119
+ if denial and not _grants.allow(svc, denial, tool="c3_impact",
120
+ op="read", path=file_path):
119
121
  return finalize("c3_impact", {"target": target, "mode": mode},
120
122
  access_guard.refusal(denial, file_path, "read"),
121
123
  "access-denied")
@@ -12,17 +12,14 @@ and letting go early instead of waiting out the TTL.
12
12
  force_release is deliberately absent: it is a human override that bumps the
13
13
  fencing counter, and it lives in `c3 locks force-release` / the Hub tab.
14
14
  """
15
- import os
16
-
15
+ from cli.tools import _grants
17
16
  from services import agent_locks as al
18
17
 
19
18
 
20
19
  def _session_id(svc) -> str:
21
- """Must match cli/tools/edit.py._session_id exactly a lease taken by
22
- c3_edit has to be recognised as ours by c3_locks, and vice versa. Never ""
23
- (see the note there: two anonymous agents would stop blocking each other)."""
24
- session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
25
- return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
20
+ """A lease taken by c3_edit has to be recognised as ours by c3_locks, and
21
+ vice versa so both read the one definition in `_grants`."""
22
+ return _grants.session_id(svc)
26
23
 
27
24
 
28
25
  def _split(paths: str) -> list:
@@ -12,9 +12,9 @@ Layers that can never be escalated (the credential vault, Tier-0 denies, the
12
12
  dispatcher fail-closed deny, catastrophic shell blocks) are refused at
13
13
  creation with `[c3-override:not-escalatable]` and never reach a human.
14
14
  """
15
- import os
16
15
  import time
17
16
 
17
+ from cli.tools import _grants
18
18
  from services import access_guard as ag
19
19
  from services import override_policy as opol
20
20
  from services import override_requests as orq
@@ -28,9 +28,8 @@ _DEFAULT_TOOL = {"read": "Read", "write": "Edit"}
28
28
 
29
29
 
30
30
  def _session_id(svc) -> str:
31
- """Must match cli/tools/edit.py._session_id — the grant is bound to it."""
32
- session = getattr(getattr(svc, "session_mgr", None), "current_session", None) or {}
33
- return str(session.get("id", "") or "") or f"pid-{os.getpid()}"
31
+ """The identity the grant is bound to — one definition, in `_grants`."""
32
+ return _grants.session_id(svc)
34
33
 
35
34
 
36
35
  def _fmt(row: dict) -> str:
@@ -6,6 +6,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
+ from cli.tools import _grants
9
10
  from cli.tools._helpers import finalize_with_tokens, maybe_related_facts
10
11
  from core import count_tokens
11
12
  from services import access_guard
@@ -123,8 +124,14 @@ def handle_read(file_path: str, symbols: Any = None, lines: Any = None,
123
124
  # before existence so probes get the same refusal whether or not the
124
125
  # target exists (R2). Batch members hit this via the per-file dispatch,
125
126
  # so allowed members are served and denied ones carry the S1 line inline.
127
+ # A live grant (P2a) is consulted only on the DENIAL branch. The masked
128
+ # branch below is untouched on purpose: a mask is not a refusal to be
129
+ # lifted, it is a different view being served, and "approve once" has no
130
+ # meaning for it. Widening that is a separate decision, not a side effect.
126
131
  _verdict = access_guard.verdict(file_path, "read", svc.project_path)
127
- if _verdict.denial:
132
+ if _verdict.denial and not _grants.allow(svc, _verdict.denial,
133
+ tool="c3_read", op="read",
134
+ path=file_path):
128
135
  resp = access_guard.refusal(_verdict.denial, file_path, "read")
129
136
  if finalize is None:
130
137
  return resp
@@ -8,6 +8,7 @@ import subprocess
8
8
  import sys
9
9
  from pathlib import Path
10
10
 
11
+ from cli.tools import _grants
11
12
  from services import access_guard
12
13
 
13
14
 
@@ -169,7 +170,8 @@ async def _validate_one(file_path: str, svc) -> tuple:
169
170
  # Access Guard: read verdict (docs/access-guard.md §3) — the S1 refusal
170
171
  # becomes the batch line for this member; other members are still served.
171
172
  denial = access_guard.check(file_path, "read", svc.project_path)
172
- if denial:
173
+ if denial and not _grants.allow(svc, denial, tool="c3_validate",
174
+ op="read", path=file_path):
173
175
  return ("SKIP", access_guard.refusal(denial, file_path, "read"))
174
176
  full = Path(svc.project_path) / file_path
175
177
  if not full.exists():
@@ -250,7 +252,8 @@ async def _validate_single(file_path: str, svc, finalize) -> str:
250
252
  """Original single-file validation path."""
251
253
  # Access Guard: read verdict, checked before existence (R2 probe parity).
252
254
  denial = access_guard.check(file_path, "read", svc.project_path)
253
- if denial:
255
+ if denial and not _grants.allow(svc, denial, tool="c3_validate",
256
+ op="read", path=file_path):
254
257
  return finalize("c3_validate", {"file_path": file_path},
255
258
  access_guard.refusal(denial, file_path, "read"),
256
259
  "access-denied")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.71.0
3
+ Version: 2.73.0
4
4
  Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -69,6 +69,7 @@ cli/hub_ui/components/task_board.js
69
69
  cli/hub_ui/components/toasts.js
70
70
  cli/hub_ui/components/topbar.js
71
71
  cli/tools/__init__.py
72
+ cli/tools/_grants.py
72
73
  cli/tools/_helpers.py
73
74
  cli/tools/agent.py
74
75
  cli/tools/artifacts.py
@@ -222,6 +223,7 @@ services/output_filter.py
222
223
  services/override_grants.py
223
224
  services/override_policy.py
224
225
  services/override_requests.py
226
+ services/override_wake.py
225
227
  services/parser.py
226
228
  services/project_manager.py
227
229
  services/project_runtime.py
@@ -352,7 +354,9 @@ tests/test_oracle_subproject_awareness.py
352
354
  tests/test_oracle_ui_bundle.py
353
355
  tests/test_output_filter.py
354
356
  tests/test_override_grants.py
357
+ tests/test_override_grants_mcp.py
355
358
  tests/test_override_requests.py
359
+ tests/test_override_wake.py
356
360
  tests/test_permissions.py
357
361
  tests/test_pm_api.py
358
362
  tests/test_project_manager.py
@@ -46,6 +46,7 @@ import hashlib
46
46
  import json
47
47
  import os
48
48
  import threading
49
+ import time
49
50
  from datetime import datetime, timezone
50
51
  from pathlib import Path
51
52
 
@@ -58,10 +59,13 @@ from oracle.services.tool_registry import _c3_version
58
59
 
59
60
  # 2: /info reports EFFECTIVE capabilities (filtered by the config switches)
60
61
  # rather than a static list, so a client can gate its UI instead of probing.
61
- API_VERSION = 2
62
+ # 3: /feed accepts `wait` (long-poll). Advertised as the `feed_wait`
63
+ # capability so a client can choose to hold a connection open instead of
64
+ # polling, rather than discovering support by timing a request.
65
+ API_VERSION = 3
62
66
 
63
67
  CAPABILITIES = [
64
- "feed", "projects", "health", "pm", "pm_events", "digest",
68
+ "feed", "feed_wait", "projects", "health", "pm", "pm_events", "digest",
65
69
  "notifications_ack",
66
70
  "credentials", "credentials_write",
67
71
  "access", "access_write",
@@ -90,6 +94,38 @@ _SOURCE_CAP = 1000
90
94
  _MAX_LIMIT = 200
91
95
  _DEFAULT_LIMIT = 50
92
96
 
97
+ # ── Long-poll (`/feed?wait=`) ─────────────────────────────
98
+ #
99
+ # WHY: the phone's only delivery paths were two pollers — every 15s while the
100
+ # app is open, and Android WorkManager's ~15-MINUTE floor once it is closed.
101
+ # An override request with a 10-minute TTL could therefore expire before the
102
+ # phone was ever told it existed (observed 2026-08-07). `wait` lets a client
103
+ # hold one request open and be answered the moment something lands, turning
104
+ # "up to 15 seconds" into "about a second" without spending a request every
105
+ # 15s to get it.
106
+ #
107
+ # It is NOT push, and nothing here should be read as a claim that it is: a
108
+ # process Android has frozen cannot hold a socket. This narrows the live
109
+ # window. FCM or a foreground service is what would close the closed-app one.
110
+ _MAX_WAIT_S = 30
111
+ # Re-scan cadence while holding. The scan is skipped unless an mtime moved,
112
+ # so this is a stat() per second, not a feed rebuild per second.
113
+ _WAIT_TICK_S = 1.0
114
+ # Each waiter parks a server thread for up to _MAX_WAIT_S, and the Oracle
115
+ # serves the desktop UI and discovery off the same pool. Past the cap `wait`
116
+ # degrades to an ordinary immediate answer rather than queueing behind other
117
+ # waiters. One phone needs one slot; four leaves room for the watch and a
118
+ # spare.
119
+ _MAX_WAITERS = 4
120
+ _waiters = threading.Semaphore(_MAX_WAITERS)
121
+
122
+ #: Files whose mtime means "the feed may have changed" — the same set
123
+ #: ``_last_activity`` reports on, kept literal in both places on purpose. Here
124
+ #: it is a correctness input: a file missing from this tuple is a wake that
125
+ #: never returns early.
126
+ _FEED_FILES = ("activity_log.jsonl", "edit_ledger.jsonl",
127
+ "notifications.jsonl", "pm/pm.json")
128
+
93
129
  bp = Blueprint("mobile", __name__, url_prefix="/api/mobile")
94
130
 
95
131
  # Wired by oracle_server at import time (configure) and startup (init_services).
@@ -489,6 +525,54 @@ def _feed_items_for(path: str, name: str, types: set, severities: set,
489
525
  return items
490
526
 
491
527
 
528
+ def _feed_mtime(targets: list) -> float:
529
+ """Newest mtime across every target's feed files. 0.0 when none exist."""
530
+ latest = 0.0
531
+ for t in targets:
532
+ c3 = Path(t["path"]) / ".c3"
533
+ for rel in _FEED_FILES:
534
+ try:
535
+ latest = max(latest, (c3 / rel).stat().st_mtime)
536
+ except OSError:
537
+ continue
538
+ return latest
539
+
540
+
541
+ def _hold_for_items(targets: list, collect, wait_s: int):
542
+ """Block up to *wait_s* for the feed to produce something.
543
+
544
+ Returns ``(items, waited_seconds)``. Rebuilding the feed every tick would
545
+ be a full history read per target, so the loop watches mtimes and only
546
+ pays for a rebuild when one moves. An mtime that moves without producing a
547
+ matching item — a severity this client filters out, say — re-baselines
548
+ instead of re-scanning on every subsequent tick.
549
+
550
+ Returns immediately when every waiter slot is taken. The caller then
551
+ behaves exactly as if ``wait`` had not been passed: a slower client, not a
552
+ wrong one.
553
+ """
554
+ started = time.monotonic()
555
+ if not _waiters.acquire(blocking=False):
556
+ return [], 0.0
557
+ try:
558
+ seen = _feed_mtime(targets)
559
+ deadline = started + wait_s
560
+ while True:
561
+ remaining = deadline - time.monotonic()
562
+ if remaining <= 0:
563
+ return [], time.monotonic() - started
564
+ time.sleep(min(_WAIT_TICK_S, remaining))
565
+ current = _feed_mtime(targets)
566
+ if current == seen:
567
+ continue
568
+ seen = current
569
+ found = collect()
570
+ if found:
571
+ return found, time.monotonic() - started
572
+ finally:
573
+ _waiters.release()
574
+
575
+
492
576
  @bp.route("/feed")
493
577
  def mobile_feed():
494
578
  """Merged cross-project feed, newest first.
@@ -496,11 +580,14 @@ def mobile_feed():
496
580
  Query: types (csv of notification|activity|edit|session_stat, default
497
581
  all), project (optional single project), severity (csv, notifications
498
582
  only), since (exclusive watermark — "only newer than"), before
499
- (exclusive pagination cursor), limit (default 50, max 200).
583
+ (exclusive pagination cursor), limit (default 50, max 200), wait
584
+ (seconds, 0-30 — hold the request open until something matches).
500
585
 
501
586
  Response: {items, next_cursor, truncated}. Cursor is the ts of the last
502
587
  returned item; identical-ts items can repeat across pages, so clients
503
- dedup by item id.
588
+ dedup by item id. A `wait` request also carries `waited_s`, and an empty
589
+ `items` after a full hold is the normal, expected answer — the client
590
+ reconnects with the same watermark.
504
591
  """
505
592
  if _scanner is None:
506
593
  return jsonify({"error": "not initialized"}), 500
@@ -523,6 +610,18 @@ def mobile_feed():
523
610
  except (TypeError, ValueError):
524
611
  limit = _DEFAULT_LIMIT
525
612
 
613
+ try:
614
+ wait_s = max(0, min(_MAX_WAIT_S, int(request.args.get("wait", 0))))
615
+ except (TypeError, ValueError):
616
+ wait_s = 0
617
+ # Holding open only makes sense against a watermark. Without `since` the
618
+ # first scan matches history and returns instantly, so a client that
619
+ # forgot it would silently get the old behaviour and never learn why its
620
+ # push felt slow; with `before` this is a pagination cursor, where waiting
621
+ # is meaningless.
622
+ if before or not since:
623
+ wait_s = 0
624
+
526
625
  project_arg = (request.args.get("project") or "").strip()
527
626
  if project_arg:
528
627
  resolved, err = _project_or_404(project_arg)
@@ -536,10 +635,17 @@ def mobile_feed():
536
635
  if p.get("has_c3") and p.get("path")
537
636
  and (Path(p["path"]) / ".c3").is_dir()]
538
637
 
539
- items: list[dict] = []
540
- for t in targets:
541
- items.extend(_feed_items_for(t["path"], t["name"], types, severities,
542
- since, before))
638
+ def collect() -> list:
639
+ out: list[dict] = []
640
+ for t in targets:
641
+ out.extend(_feed_items_for(t["path"], t["name"], types,
642
+ severities, since, before))
643
+ return out
644
+
645
+ items = collect()
646
+ waited = 0.0
647
+ if not items and wait_s:
648
+ items, waited = _hold_for_items(targets, collect, wait_s)
543
649
 
544
650
  items.sort(key=lambda i: i["ts"], reverse=True)
545
651
  truncated = len(items) > limit
@@ -548,6 +654,9 @@ def mobile_feed():
548
654
  "items": page,
549
655
  "next_cursor": page[-1]["ts"] if truncated and page else None,
550
656
  "truncated": truncated,
657
+ # Only when the client asked to hold, so the response shape is
658
+ # byte-identical for every client that did not.
659
+ **({"waited_s": round(waited, 2)} if wait_s else {}),
551
660
  })
552
661
 
553
662
 
@@ -1634,6 +1743,10 @@ def mobile_enforcement_set():
1634
1743
  # (e.g. `path_key`) cannot silently start crossing the network.
1635
1744
 
1636
1745
  #: §3.3 field order, verbatim. The mobile client reads these names.
1746
+ #: The one `override` key no remote surface may write. See the refusal in
1747
+ #: `mobile_overrides_policy_set` for why it is a 403 rather than a 400.
1748
+ _WAKE_KEY = "wake"
1749
+
1637
1750
  _OVERRIDE_FIELDS = (
1638
1751
  "id", "project_path", "session_id", "created_at", "expires_at", "status",
1639
1752
  "layer", "rule", "rule_class", "scope", "tool", "op", "path", "refusal",
@@ -1809,6 +1922,17 @@ def mobile_overrides_policy_set():
1809
1922
  section = data.get("override")
1810
1923
  if not isinstance(section, dict):
1811
1924
  return jsonify({"error": "body needs an 'override' object"}), 400
1925
+ if _WAKE_KEY in section:
1926
+ # `wake` is an argv this machine will execute. Everything else on this
1927
+ # route widens what a tap can approve; this one would decide what runs
1928
+ # when it does. A bearer token from a phone is authentication, not
1929
+ # physical presence, so it stays a desktop edit to a human-owned file.
1930
+ return jsonify({
1931
+ "error": "'wake' cannot be set from the phone — it names a "
1932
+ "command this machine runs. Edit .c3/config.json on the "
1933
+ "desktop.",
1934
+ "key": _WAKE_KEY,
1935
+ }), 403
1812
1936
  unknown = sorted(set(section) - set(opol.DEFAULTS))
1813
1937
  if unknown:
1814
1938
  # §3.1: unknown keys are a hard error, never a silent no-op.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.71.0"
7
+ version = "2.73.0"
8
8
  description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -93,6 +93,11 @@ DEFAULTS = {
93
93
  "max_requests_per_hour": 20,
94
94
  "notify_severity": "critical",
95
95
  "allow_session_grants": False,
96
+ # The command run when a request is DECIDED, so the asking agent hears
97
+ # about it (services/override_wake.py). None = nobody is listening, which
98
+ # is the pre-2.73 behaviour and the reason a grant could expire unused
99
+ # while the user assumed their tap had done something.
100
+ "wake": None,
96
101
  }
97
102
 
98
103
  _VALID_KEYS = frozenset(DEFAULTS)
@@ -193,6 +198,7 @@ class OverridePolicy:
193
198
  max_requests_per_hour: int = 20
194
199
  notify_severity: str = "critical"
195
200
  allow_session_grants: bool = False
201
+ wake: dict | None = None
196
202
  warnings: tuple = ()
197
203
  corrupt_scopes: tuple = ()
198
204
 
@@ -221,6 +227,11 @@ class OverridePolicy:
221
227
  "max_requests_per_hour": self.max_requests_per_hour,
222
228
  "notify_severity": self.notify_severity,
223
229
  "allow_session_grants": self.allow_session_grants,
230
+ # The spec itself never crosses the wire. It is an argv the box
231
+ # will run, and it can carry a conversation id or a path that says
232
+ # more about this machine than a policy screen needs to. A phone
233
+ # only needs to know whether anything is listening.
234
+ "wake_configured": bool(self.wake),
224
235
  "warnings": list(self.warnings),
225
236
  "corrupt_scopes": list(self.corrupt_scopes),
226
237
  }
@@ -255,6 +266,18 @@ def _validate(section: dict):
255
266
  return _CORRUPT
256
267
  if any(not isinstance(v, bool) for v in layers.values()):
257
268
  return _CORRUPT
269
+ if "wake" in section:
270
+ # Lazy import: override_wake reads a resolved policy back out of this
271
+ # module on the fire path, and a module-level import would close that
272
+ # loop. A validator we cannot even load reads as corrupt, not as
273
+ # "probably fine" — this key names a command the machine will run.
274
+ try:
275
+ from services import override_wake as owake # noqa: PLC0415
276
+ valid = owake.validate_spec(section["wake"])
277
+ except Exception:
278
+ return _CORRUPT
279
+ if not valid:
280
+ return _CORRUPT
258
281
  return section
259
282
 
260
283
 
@@ -350,6 +373,14 @@ def resolve(project_path: str = ".") -> OverridePolicy:
350
373
  if key in s:
351
374
  values[key] = s[key]
352
375
 
376
+ # `wake` is not a permission, so it has no tightening direction to merge
377
+ # along — it is "who to tell", and the nearest scope is the one that knows.
378
+ # Project last, so a project's own wake replaces the machine-wide default
379
+ # rather than firing both.
380
+ for s in sections:
381
+ if "wake" in s:
382
+ values["wake"] = s["wake"]
383
+
353
384
  return OverridePolicy(warnings=tuple(warnings), **values)
354
385
 
355
386
 
@@ -460,6 +460,22 @@ def _notify_decision(project_path: str, row: dict, grant=None) -> None:
460
460
  pass # the decision already happened; a missing feed line never undoes it
461
461
 
462
462
 
463
+ def _wake(project_path: str, row: dict, grant=None, policy=None) -> dict:
464
+ """Tell the asking session a human answered (§7.1).
465
+
466
+ The notification feed tells the *user* what they decided. Nothing told the
467
+ *agent*, which is how an approved grant expired unused on 2026-08-08 with
468
+ everyone believing the tap had worked. Failure here is swallowed on
469
+ purpose: the decision is already on disk and the agent's ``action='status'``
470
+ path still works — a wake is a shortcut past waiting, not the mechanism.
471
+ """
472
+ try:
473
+ from services import override_wake as owake # noqa: PLC0415
474
+ return owake.fire(project_path, row, grant, policy=policy)
475
+ except Exception as exc:
476
+ return {"fired": False, "reason": f"wake unavailable: {exc}"}
477
+
478
+
463
479
  def withdraw(request_id: str, session_id: str) -> dict:
464
480
  """An agent cancelling its OWN pending request (e.g. it found another way)."""
465
481
  rows = load()
@@ -526,6 +542,10 @@ def decide(request_id: str, decision: str, *, uses: int | None = None,
526
542
  "decided_by": decided_by, "muted": bool(mute),
527
543
  })
528
544
  _notify_decision(project_path, row, grant=None)
545
+ # A denial is news too: without it the agent sits in `wait` until the
546
+ # request lapses, then reports a timeout for a question that was
547
+ # answered in two seconds.
548
+ row["wake"] = _wake(project_path, row)
529
549
  return row
530
550
 
531
551
  policy = op_policy.resolve(project_path)
@@ -569,4 +589,8 @@ def decide(request_id: str, decision: str, *, uses: int | None = None,
569
589
  row["grant_mode"] = mode
570
590
  _save(rows)
571
591
  _notify_decision(project_path, row, grant=grant)
592
+ # AFTER _save: the woken agent's very first move is to retry the blocked
593
+ # call, which reads this store. Waking before persisting would race the
594
+ # agent against the decision it was told about.
595
+ row["wake"] = _wake(project_path, row, grant, policy=policy)
572
596
  return row