code-context-control 2.67.0__tar.gz → 2.71.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.
- {code_context_control-2.67.0/code_context_control.egg-info → code_context_control-2.71.0}/PKG-INFO +3 -1
- {code_context_control-2.67.0 → code_context_control-2.71.0}/README.md +2 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/c3.py +296 -1
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/commands/parser.py +102 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_access_guard.py +20 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_pretool_enforce.py +22 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_server.py +8 -18
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/mcp_server.py +24 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/delegate.py +78 -35
- code_context_control-2.71.0/cli/tools/override.py +173 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0/code_context_control.egg-info}/PKG-INFO +3 -1
- {code_context_control-2.67.0 → code_context_control-2.71.0}/code_context_control.egg-info/SOURCES.txt +13 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/config.py +30 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/oracle_server.py +11 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/oracle_ui.html +19 -0
- code_context_control-2.71.0/oracle/services/mobile_api.py +2087 -0
- code_context_control-2.71.0/oracle/ui/qrcode.js +2297 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/settings.js +43 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/pyproject.toml +1 -1
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/access_guard.py +32 -1
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/credential_store.py +37 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/embedding_index.py +79 -10
- code_context_control-2.71.0/services/override_grants.py +511 -0
- code_context_control-2.71.0/services/override_policy.py +391 -0
- code_context_control-2.71.0/services/override_requests.py +572 -0
- code_context_control-2.71.0/tests/test_delegate_version_probe.py +247 -0
- code_context_control-2.71.0/tests/test_embedding_index_deadlock.py +321 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_enforcement_policy.py +17 -4
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_enforcement_routes.py +10 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_enforcement_routes.py +9 -0
- code_context_control-2.71.0/tests/test_mobile_api.py +370 -0
- code_context_control-2.71.0/tests/test_mobile_override_routes.py +747 -0
- code_context_control-2.71.0/tests/test_mobile_security_api.py +719 -0
- code_context_control-2.71.0/tests/test_override_grants.py +442 -0
- code_context_control-2.71.0/tests/test_override_requests.py +329 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/LICENSE +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/_hook_utils.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/commands/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/commands/common.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/docs.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/edits.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/access.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/bitbucket.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/credentials.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/getting-started.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/index.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/masking.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/oracle.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/shared.css +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/tools.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/guide/workflow.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_artifact.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_auto_snapshot.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_c3_signal.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_c3read.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_dispatch.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_edit_ledger.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_edit_unlock.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_filter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_ghost_files.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_prompt_recall.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_read.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_session_stats.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hook_terse_advisor.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/app.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/add_project.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/config_editor.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_health.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_panel.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_tasks.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/drill_views.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/global_search.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/hub_credentials.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/hub_locks.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/mcp_manager.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/modals.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/project_card.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/project_tree.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/session_drawer.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/settings_modal.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/sidebar.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/summary_bar.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/task_board.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/toasts.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/components/topbar.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui/state.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/hub_ui.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/mcp_proxy.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/progress.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/server.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/_helpers.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/agent.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/artifacts.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/bitbucket.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/compress.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/edit.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/edits.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/federate.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/filter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/impact.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/jira.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/locks.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/memory.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/project.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/read.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/search.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/session.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/shell.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/status.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/tasks.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/tools/validate.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/api.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/app.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/access.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/bitbucket.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/chat.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/credentials.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/dashboard.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/edits.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/enforcement.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/instructions.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/jira.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/memory.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/sessions.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/settings.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/sidebar.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/components/tasks.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/icons.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/pm_shared.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/shared.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui/theme.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui_legacy.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/cli/ui_nano.html +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/code_context_control.egg-info/dependency_links.txt +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/code_context_control.egg-info/entry_points.txt +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/code_context_control.egg-info/requires.txt +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/code_context_control.egg-info/top_level.txt +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/core/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/core/config.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/core/ide.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/core/mcp_toml.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/core/web_security.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/mcp_oracle.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/activity_reporter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/api_auth.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/c3_bridge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/chat_engine.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/chat_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/cross_memory.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/discovery_audit.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/federated_graph.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/health_checker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/insight_engine.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/local_session.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/memory_reader.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/memory_writer.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/project_scanner.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/review_agent.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/tool_executor.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/services/tool_registry.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/activity.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/agents.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/app.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/busy.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/conversations.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/input.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/markdown.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/send.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/stream_renderer.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/chat/toolbar.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/core.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/crossgraph.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/header.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/insights.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/projects.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/suggestions.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/oracle/ui/theme_tabs.js +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/access_telemetry.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/activity_log.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/agent_base.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/agent_locks.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/agents.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/artifact_defs.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/artifact_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/auto_memory.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bench/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bench/external/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bench/external/aider_polyglot.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bench/external/swe_bench.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/benchmark_dashboard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bitbucket_client.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/bitbucket_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/circuit_breaker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/claude_md.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/compressor.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/context_snapshot.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/conversation_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/doc_index.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/e2e_benchmark.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/e2e_evaluator.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/e2e_tasks.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/edit_ledger.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/enforcement_policy.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/error_reporting.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/file_memory.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/git_context.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/hub_service.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/indexer.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/jira_client.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/jira_cloud.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/jira_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/jira_data_center.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/jira_links.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/mask_activation.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/mask_mirror.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/mask_presets.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_consolidator.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_distiller.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_graph.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_grounder.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_queue.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/memory_scorer.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/metrics.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/notifications.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/ollama_bridge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/ollama_client.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/ollama_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/output_filter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/parser.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/project_manager.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/project_runtime.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/protocol.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/proxy_state.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/repo_map.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/retention.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/retrieval_broker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/router.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/runtime.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/scanner.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/session_benchmark.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/session_manager.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/session_preloader.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/subprojects.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/task_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/telemetry.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/text_index.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/time_tracker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/tool_classifier.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/transcript_index.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/validation_cache.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/vector_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/watcher.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/services/win_subprocess.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/setup.cfg +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_builtin_optout.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_guard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_guard_meta.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_guard_shell_project.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_guard_surfaces.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_access_guard_wiring.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_activity_reporter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_agent_locks.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_aider_polyglot.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_antigravity_transition.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_artifact_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_artifact_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_bitbucket_cli_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_bitbucket_client.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_bitbucket_config_fallback.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_bitbucket_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_bitbucket_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_c3_shell.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_circuit_breaker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_claude_md_merge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_cli_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_compressor_large_file.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_credential_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_credentials_routes.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_credentials_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_creds_cli_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_delegate_cascade.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_e2e_benchmark.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_edit_ledger_hook.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_edit_locking.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_edit_normalization.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_enforcement_flip.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_federated_graph.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_filter_backoff.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_ghost_files.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_ghost_generation.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_git_branch_awareness.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_access_guard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_dispatch.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_pretool_enforce.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_prompt_recall.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_state.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hook_utf8_stdio.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_credentials_route.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_credentials_routes_write.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_inspect_api.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_locks_routes.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_server_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_hub_subproject_links.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_ide_workflow_adaptation.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_init_scan.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_install_mcp_entrypoint.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_cli_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_client.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_config_fallback.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_links.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_routes.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_jira_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_lazy_store_init.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mask_guard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mask_routes.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mask_surfaces.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mcp_host_guard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mcp_memory_timeout.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mcp_server_smoke.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_mcp_toml.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_memory_distiller.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_memory_graph_api.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_memory_queue.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_memory_system.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_no_stale_model_pins.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_notification_dedup.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_notification_discipline.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_ollama_credentials.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_api_auth.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_apikey_api.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_c3_bridge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_chat_engine.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_chat_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_discovery_api.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_discovery_audit.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_local_auth.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_ollama_bridge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_scanner_cache.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_security_fixes.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_session_bootstrap.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_subproject_awareness.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_oracle_ui_bundle.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_output_filter.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_permissions.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_pm_api.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_project_manager.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_project_manager_merge.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_project_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_read_coercion.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_read_edit_parity.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_related_facts.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_repo_map.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_response_boilerplate.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_retention.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_review_digest.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_service_durability.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_session_benchmark.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_session_budget.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_shell_creds.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_shell_robustness.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_subproject_exclusion.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_subproject_federation.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_subprojects.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_swe_bench.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_task_store.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_task_tool.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_time_tracker.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_token_telemetry.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_tool_registry.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_upgrade_and_version.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_validate.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_vault_write_guard.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_version_sync.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_web_security.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tests/test_windows_reliability.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/backend.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/main.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/__init__.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/benchmark_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/claudemd_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/compress_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/index_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/init_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/mcp_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/optimize_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/pipe_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/projects_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/search_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/session_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/stats.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/screens/ui_view.py +0 -0
- {code_context_control-2.67.0 → code_context_control-2.71.0}/tui/theme.tcss +0 -0
{code_context_control-2.67.0/code_context_control.egg-info → code_context_control-2.71.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.71.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
|
|
@@ -369,6 +369,8 @@ C3 also records real per-tool usage to `.c3/tool_telemetry.jsonl`, so estimates
|
|
|
369
369
|
## Security & privacy
|
|
370
370
|
|
|
371
371
|
- **All web servers bind `127.0.0.1` by default** and are guarded against browser-based attacks even on loopback — a Host-header allowlist (defeats DNS rebinding) plus an Origin/Referer check on every request (defeats cross-origin CSRF), with scoped, non-wildcard CORS. There is still **no user authentication**, so do not expose these servers to an untrusted network without auth/TLS in front. Binding to a non-loopback interface is opt-in and warned at startup. *(Hardening added in v2.33.0.)*
|
|
372
|
+
- **Mobile gateway (`/api/mobile/*`)** — the Oracle exposes a companion-app surface for the C3 mobile app: merged cross-project feed, project overview, PM board read/write, digest, notification ack, plus the security surface (credential vault, Access Guard rules and masks, tool discipline). Unlike the legacy Oracle GETs it requires the **Discovery Bearer token on every method, GETs included**, validates every project path against the registry, and audits every write to the project's activity log and edit ledger. Pair the app from Oracle Settings → *Mobile app* (QR code carrying URL + token). Remote reachability is the same recipe as the Discovery API: set `bind_host` to the Tailscale/LAN interface IP (avoid `0.0.0.0`) and list that address in `allowed_hosts`, both in `~/.c3/oracle/config.json`. Disable the whole surface with `"mobile_api_enabled": false`, or any subsystem with `mobile_credentials_enabled` / `mobile_access_enabled` / their `_write` twins.
|
|
373
|
+
- **The mobile gateway never returns a credential value.** It has no reveal route by design: entries cross the wire through an allowlist serializer, and a value can only be identified by its length and an on-demand fingerprint. Two further levers ship **off** — `mobile_creds_agent_readable_raise` (raising `agent_readable` from the phone) and `mobile_access_global_scope` (machine-wide access rules) — because each exceeds what a leaked Bearer token could otherwise reach. Deliberately absent from the surface: bulk `.env` import, denial-counter clearing, `set_builtin_disabled`, and the raw-content access preview.
|
|
372
374
|
- **No telemetry by default.** The OSS package collects nothing. Opt-in Sentry crash reporting requires the `[telemetry]` extra plus both `SENTRY_DSN` and `C3_TELEMETRY_OPT_IN=1`; even then request bodies, local variables, and prompts are stripped.
|
|
373
375
|
- **LLM memory distillation is local-first.** Cloud distillation (v2.51.0) is off by default and opt-in per project.
|
|
374
376
|
- **API keys** for third-party providers are read from the environment and never persisted by C3.
|
|
@@ -306,6 +306,8 @@ C3 also records real per-tool usage to `.c3/tool_telemetry.jsonl`, so estimates
|
|
|
306
306
|
## Security & privacy
|
|
307
307
|
|
|
308
308
|
- **All web servers bind `127.0.0.1` by default** and are guarded against browser-based attacks even on loopback — a Host-header allowlist (defeats DNS rebinding) plus an Origin/Referer check on every request (defeats cross-origin CSRF), with scoped, non-wildcard CORS. There is still **no user authentication**, so do not expose these servers to an untrusted network without auth/TLS in front. Binding to a non-loopback interface is opt-in and warned at startup. *(Hardening added in v2.33.0.)*
|
|
309
|
+
- **Mobile gateway (`/api/mobile/*`)** — the Oracle exposes a companion-app surface for the C3 mobile app: merged cross-project feed, project overview, PM board read/write, digest, notification ack, plus the security surface (credential vault, Access Guard rules and masks, tool discipline). Unlike the legacy Oracle GETs it requires the **Discovery Bearer token on every method, GETs included**, validates every project path against the registry, and audits every write to the project's activity log and edit ledger. Pair the app from Oracle Settings → *Mobile app* (QR code carrying URL + token). Remote reachability is the same recipe as the Discovery API: set `bind_host` to the Tailscale/LAN interface IP (avoid `0.0.0.0`) and list that address in `allowed_hosts`, both in `~/.c3/oracle/config.json`. Disable the whole surface with `"mobile_api_enabled": false`, or any subsystem with `mobile_credentials_enabled` / `mobile_access_enabled` / their `_write` twins.
|
|
310
|
+
- **The mobile gateway never returns a credential value.** It has no reveal route by design: entries cross the wire through an allowlist serializer, and a value can only be identified by its length and an on-demand fingerprint. Two further levers ship **off** — `mobile_creds_agent_readable_raise` (raising `agent_readable` from the phone) and `mobile_access_global_scope` (machine-wide access rules) — because each exceeds what a leaked Bearer token could otherwise reach. Deliberately absent from the surface: bulk `.env` import, denial-counter clearing, `set_builtin_disabled`, and the raw-content access preview.
|
|
309
311
|
- **No telemetry by default.** The OSS package collects nothing. Opt-in Sentry crash reporting requires the `[telemetry]` extra plus both `SENTRY_DSN` and `C3_TELEMETRY_OPT_IN=1`; even then request bodies, local variables, and prompts are stripped.
|
|
310
312
|
- **LLM memory distillation is local-first.** Cloud distillation (v2.51.0) is off by default and opt-in per project.
|
|
311
313
|
- **API keys** for third-party providers are read from the environment and never persisted by C3.
|
|
@@ -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.
|
|
95
|
+
__version__ = "2.71.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -261,6 +261,7 @@ _C3_MCP_ALLOW = [
|
|
|
261
261
|
"mcp__c3__c3_bitbucket",
|
|
262
262
|
"mcp__c3__c3_jira", "mcp__c3__c3_credentials",
|
|
263
263
|
"mcp__c3__c3_project", "mcp__c3__c3_task", "mcp__c3__c3_artifacts",
|
|
264
|
+
"mcp__c3__c3_override",
|
|
264
265
|
]
|
|
265
266
|
|
|
266
267
|
# Obsolete MCP tool names from earlier C3 versions. `c3 permissions clean`
|
|
@@ -6209,6 +6210,299 @@ def _enforce_audit(result: dict, project_path: str) -> None:
|
|
|
6209
6210
|
pass
|
|
6210
6211
|
|
|
6211
6212
|
|
|
6213
|
+
_OVERRIDE_DEFAULT_TOOL = {"read": "Read", "write": "Edit"}
|
|
6214
|
+
|
|
6215
|
+
|
|
6216
|
+
def cmd_override(args):
|
|
6217
|
+
"""`c3 override` — Override Requests: allow one blocked call, once.
|
|
6218
|
+
|
|
6219
|
+
The human half of docs/override-requests.md. Every verb here mints or
|
|
6220
|
+
inspects a *grant*; none of them edits policy. That separation is the
|
|
6221
|
+
entire point of the feature: today the only way to unblock an agent is to
|
|
6222
|
+
weaken a rule permanently, and a guard that spends its life in `advisory`
|
|
6223
|
+
is not a guard.
|
|
6224
|
+
|
|
6225
|
+
No agent surface reaches this command, and no approval here can touch the
|
|
6226
|
+
'never' rows of spec §2 — the credential vault, Tier-0 absolute denies, or
|
|
6227
|
+
the catastrophic shell blocks.
|
|
6228
|
+
"""
|
|
6229
|
+
sub = getattr(args, "override_cmd", None)
|
|
6230
|
+
if not sub:
|
|
6231
|
+
print("Usage: c3 override <policy|grant|list|revoke|check|sweep> ...")
|
|
6232
|
+
print(" c3 override policy # what is escalatable right now")
|
|
6233
|
+
return
|
|
6234
|
+
|
|
6235
|
+
from services import override_grants as og
|
|
6236
|
+
from services import override_policy as opol
|
|
6237
|
+
|
|
6238
|
+
project_path = str(Path(getattr(args, "project_path", ".") or ".").resolve())
|
|
6239
|
+
policy = opol.resolve(project_path)
|
|
6240
|
+
for warn in policy.warnings:
|
|
6241
|
+
print(f" [warn] {warn}")
|
|
6242
|
+
|
|
6243
|
+
if sub == "policy":
|
|
6244
|
+
_override_show(policy, project_path, opol)
|
|
6245
|
+
elif sub == "grant":
|
|
6246
|
+
_override_grant(args, policy, project_path, og, opol)
|
|
6247
|
+
elif sub == "list":
|
|
6248
|
+
_override_list(args, project_path, og)
|
|
6249
|
+
elif sub == "check":
|
|
6250
|
+
_override_check(args, policy, project_path, og, opol)
|
|
6251
|
+
elif sub == "revoke":
|
|
6252
|
+
gid = getattr(args, "grant_id", "")
|
|
6253
|
+
print(" Revoked." if og.revoke(project_path, gid)
|
|
6254
|
+
else f" No live grant with id '{gid}'.")
|
|
6255
|
+
elif sub == "sweep":
|
|
6256
|
+
print(f" Dropped {og.sweep_expired(project_path)} expired/spent grant(s).")
|
|
6257
|
+
elif sub == "requests":
|
|
6258
|
+
_override_requests(args, project_path)
|
|
6259
|
+
elif sub in ("approve", "deny"):
|
|
6260
|
+
_override_decide(args, sub, project_path)
|
|
6261
|
+
|
|
6262
|
+
|
|
6263
|
+
def _override_requests(args, project_path: str) -> None:
|
|
6264
|
+
from services import override_requests as orq
|
|
6265
|
+
|
|
6266
|
+
rows = orq.list_requests(
|
|
6267
|
+
project_path="" if getattr(args, "all_projects", False) else project_path,
|
|
6268
|
+
status=getattr(args, "status", "") or "")
|
|
6269
|
+
print_header("Override requests")
|
|
6270
|
+
if not rows:
|
|
6271
|
+
print(" (none)")
|
|
6272
|
+
return
|
|
6273
|
+
for r in rows:
|
|
6274
|
+
note = f" note: {r['decision_note']}" if r.get("decision_note") else ""
|
|
6275
|
+
print(f" {r['id']} {r['status']:<9} {r['tool']} {r['op']} {r['path']}")
|
|
6276
|
+
print(f" {'':18s} rule {r['rule']} ({r['rule_class']}) · session "
|
|
6277
|
+
f"{r['session_id']} · expires {r['expires_at']}{note}")
|
|
6278
|
+
if r.get("justification"):
|
|
6279
|
+
print(f" {'':18s} the agent wrote: \"{r['justification']}\"")
|
|
6280
|
+
print(f" {'':18s} (untrusted — it may be repeating text it read "
|
|
6281
|
+
f"from a file)")
|
|
6282
|
+
print("\n Approve: c3 override approve <id> Refuse: c3 override deny <id>")
|
|
6283
|
+
|
|
6284
|
+
|
|
6285
|
+
def _override_decide(args, decision: str, project_path: str) -> None:
|
|
6286
|
+
from services import override_requests as orq
|
|
6287
|
+
|
|
6288
|
+
try:
|
|
6289
|
+
row = orq.decide(getattr(args, "request_id", ""), decision,
|
|
6290
|
+
uses=getattr(args, "uses", None),
|
|
6291
|
+
ttl_s=getattr(args, "ttl_s", None),
|
|
6292
|
+
note=getattr(args, "note", "") or "",
|
|
6293
|
+
decided_by="cli",
|
|
6294
|
+
confirm=getattr(args, "confirm", None))
|
|
6295
|
+
except orq.OverrideError as exc:
|
|
6296
|
+
print(f" {exc}")
|
|
6297
|
+
return
|
|
6298
|
+
except ValueError as exc:
|
|
6299
|
+
print(f" Error: {exc}")
|
|
6300
|
+
return
|
|
6301
|
+
|
|
6302
|
+
if decision == "deny":
|
|
6303
|
+
print(f" Denied {row['id']}. The block stands.")
|
|
6304
|
+
return
|
|
6305
|
+
print_header("Override approved")
|
|
6306
|
+
print(f" Request : {row['id']} Grant: {row.get('grant_id', '?')}")
|
|
6307
|
+
print(f" Allows : {row['tool']} {row['op']} on {row['path']}")
|
|
6308
|
+
print(f" Session : {row['session_id']}")
|
|
6309
|
+
print(f"\n The rule {row['rule']} is still in force. One retry of this "
|
|
6310
|
+
"exact call, in")
|
|
6311
|
+
print(" that session, on that exact path — nothing else.")
|
|
6312
|
+
|
|
6313
|
+
|
|
6314
|
+
def _override_show(policy, project_path: str, opol) -> None:
|
|
6315
|
+
print_header("Override Requests")
|
|
6316
|
+
state = "on" if policy.enabled else "off"
|
|
6317
|
+
print(f" Enabled : {state} (channel: {policy.channel})")
|
|
6318
|
+
print(f" Project : {project_path}")
|
|
6319
|
+
print("\n Escalatable layers:")
|
|
6320
|
+
for key in opol.LAYER_KEYS:
|
|
6321
|
+
on = policy.escalatable(key)
|
|
6322
|
+
mark = "*" if on else " "
|
|
6323
|
+
extra = " (approval needs the rule typed by hand)" \
|
|
6324
|
+
if key in opol.TYPED_CONFIRM_LAYERS else ""
|
|
6325
|
+
print(f" {mark} {key:18s} {'yes' if on else 'no':3s}{extra}")
|
|
6326
|
+
print("\n Limits:")
|
|
6327
|
+
print(f" grant TTL : <= {policy.max_ttl_s}s (hard ceiling "
|
|
6328
|
+
f"{opol.HARD_MAX_TTL_S}s)")
|
|
6329
|
+
print(f" uses : {policy.default_uses} "
|
|
6330
|
+
f"(session grants: {'allowed' if policy.allow_session_grants else 'off'})")
|
|
6331
|
+
print(f" request TTL : {policy.request_ttl_s}s")
|
|
6332
|
+
print(f" rate : {policy.max_pending_per_session} pending/session, "
|
|
6333
|
+
f"{policy.max_requests_per_hour}/hour")
|
|
6334
|
+
if policy.corrupt_scopes:
|
|
6335
|
+
print(f"\n FAILING CLOSED — corrupt scope(s): "
|
|
6336
|
+
f"{', '.join(policy.corrupt_scopes)}")
|
|
6337
|
+
print("\n Never escalatable, at any setting: the credential vault, "
|
|
6338
|
+
".c3/secrets.enc,")
|
|
6339
|
+
print(" .c3/cred_state.json, the dispatcher fail-closed deny, and the "
|
|
6340
|
+
"catastrophic")
|
|
6341
|
+
print(" shell blocks. Policy lives in .c3/config.json 'override' "
|
|
6342
|
+
"(human-edited).")
|
|
6343
|
+
|
|
6344
|
+
|
|
6345
|
+
def _override_identify(args, project_path: str, opol):
|
|
6346
|
+
"""(gate, layer_key, rule, tool, op, error) for the call being approved.
|
|
6347
|
+
|
|
6348
|
+
Derived from the denial the path ACTUALLY produces, not from what the
|
|
6349
|
+
caller claims: a grant whose rule string does not match the rule that
|
|
6350
|
+
denies would never match at consume time, and a silently-useless grant is
|
|
6351
|
+
worse than a refusal.
|
|
6352
|
+
"""
|
|
6353
|
+
op = getattr(args, "op", "read") or "read"
|
|
6354
|
+
tool = getattr(args, "tool", None) or _OVERRIDE_DEFAULT_TOOL[op]
|
|
6355
|
+
layer = getattr(args, "layer", None)
|
|
6356
|
+
target = getattr(args, "target", "")
|
|
6357
|
+
|
|
6358
|
+
if layer == "discipline":
|
|
6359
|
+
return (opol.GATE_DISCIPLINE, opol.LAYER_DISCIPLINE,
|
|
6360
|
+
opol.RULE_DISCIPLINE, tool, "write", None)
|
|
6361
|
+
if layer == "shell":
|
|
6362
|
+
return (opol.GATE_SHELL, opol.LAYER_SHELL_WARN,
|
|
6363
|
+
opol.RULE_SHELL_WARN, tool, op, None)
|
|
6364
|
+
|
|
6365
|
+
from services import access_guard as ag
|
|
6366
|
+
denial = ag.check(target, op, project_path)
|
|
6367
|
+
if denial is None:
|
|
6368
|
+
return (None, None, None, tool, op,
|
|
6369
|
+
f"`{target}` is not blocked for {op} — nothing to approve.")
|
|
6370
|
+
layer_key = opol.rule_class_for_denial(denial)
|
|
6371
|
+
if layer_key is None:
|
|
6372
|
+
return (None, None, None, tool, op,
|
|
6373
|
+
f"{opol.TAG_NOT_ESCALATABLE} `{denial.rule}` can never be "
|
|
6374
|
+
f"overridden — no config value and no approval reaches it.")
|
|
6375
|
+
gate = opol.GATE_FOR_LAYER_KEY[layer_key]
|
|
6376
|
+
if layer and layer != gate:
|
|
6377
|
+
return (None, None, None, tool, op,
|
|
6378
|
+
f"--layer {layer} does not match the layer that denied "
|
|
6379
|
+
f"({gate}, rule `{denial.rule}`).")
|
|
6380
|
+
return gate, layer_key, denial.rule, tool, op, None
|
|
6381
|
+
|
|
6382
|
+
|
|
6383
|
+
def _override_grant(args, policy, project_path: str, og, opol) -> None:
|
|
6384
|
+
gate, layer_key, rule, tool, op, err = _override_identify(
|
|
6385
|
+
args, project_path, opol)
|
|
6386
|
+
if err:
|
|
6387
|
+
print(f" {err}")
|
|
6388
|
+
return
|
|
6389
|
+
if not policy.enabled:
|
|
6390
|
+
print(" Overrides are disabled for this project.")
|
|
6391
|
+
print(" Enable by hand in .c3/config.json — the 'override' section, "
|
|
6392
|
+
"\"enabled\": true,")
|
|
6393
|
+
print(f" plus \"layers\": {{\"{layer_key}\": true}}. Nothing is "
|
|
6394
|
+
"escalatable by default.")
|
|
6395
|
+
return
|
|
6396
|
+
if not policy.escalatable(layer_key):
|
|
6397
|
+
print(f" The '{layer_key}' layer is not escalatable "
|
|
6398
|
+
f"(override.layers.{layer_key} is false).")
|
|
6399
|
+
print(" Opt in per layer, deliberately — that is the point of the "
|
|
6400
|
+
"default.")
|
|
6401
|
+
return
|
|
6402
|
+
if (layer_key in opol.TYPED_CONFIRM_LAYERS
|
|
6403
|
+
and getattr(args, "confirm", None) != rule):
|
|
6404
|
+
print(f" This approves ONE call against an {layer_key} rule "
|
|
6405
|
+
f"({rule}).")
|
|
6406
|
+
print(f" Retype the rule to confirm: --confirm '{rule}'")
|
|
6407
|
+
return
|
|
6408
|
+
try:
|
|
6409
|
+
grant = og.mint(project_path, session_id=getattr(args, "session_id", ""),
|
|
6410
|
+
layer=gate, rule=rule, tool=tool, op=op,
|
|
6411
|
+
path=getattr(args, "target", ""),
|
|
6412
|
+
ttl_s=getattr(args, "ttl_s", None),
|
|
6413
|
+
uses=getattr(args, "uses", None),
|
|
6414
|
+
granted_by="cli", policy=policy)
|
|
6415
|
+
except ValueError as exc:
|
|
6416
|
+
print(f" Error: {exc}")
|
|
6417
|
+
return
|
|
6418
|
+
|
|
6419
|
+
print_header("Override granted")
|
|
6420
|
+
print(f" Id : {grant['id']}")
|
|
6421
|
+
print(f" Allows : {tool} {op} on {grant['path_key']}")
|
|
6422
|
+
print(f" Layer : {gate} Rule: {rule}")
|
|
6423
|
+
print(f" Session : {grant['session_id']}")
|
|
6424
|
+
print(f" Expires : {grant['expires_at']} Uses: "
|
|
6425
|
+
f"{grant['uses_remaining']}")
|
|
6426
|
+
if getattr(args, "ttl_s", None) and grant.get("expires_at"):
|
|
6427
|
+
print(f" (TTL clamped to override.max_ttl_s = {policy.max_ttl_s}s "
|
|
6428
|
+
f"where it exceeded it)")
|
|
6429
|
+
print(f"\n The rule {rule} is still in force. This grant authorises one "
|
|
6430
|
+
"retry of")
|
|
6431
|
+
print(" this exact call, in this session, on this exact path, and "
|
|
6432
|
+
"nothing else.")
|
|
6433
|
+
_override_audit(grant, project_path)
|
|
6434
|
+
|
|
6435
|
+
|
|
6436
|
+
def _override_list(args, project_path: str, og) -> None:
|
|
6437
|
+
grants = og.active(project_path, getattr(args, "session_id", "") or "")
|
|
6438
|
+
print_header("Live override grants")
|
|
6439
|
+
if not grants:
|
|
6440
|
+
print(" (none)")
|
|
6441
|
+
for g in grants:
|
|
6442
|
+
print(f" {g.get('id', '?'):18s} {g.get('tool', '?')} "
|
|
6443
|
+
f"{g.get('op', '?')} {g.get('path_key', '?')}")
|
|
6444
|
+
print(f" {'':18s} rule {g.get('rule', '?')} · session "
|
|
6445
|
+
f"{g.get('session_id', '?')} · expires {g.get('expires_at', '?')}"
|
|
6446
|
+
f" · {g.get('uses_remaining', 0)} use(s) left")
|
|
6447
|
+
tail = int(getattr(args, "audit", 0) or 0)
|
|
6448
|
+
if tail:
|
|
6449
|
+
print("\n Audit (.c3/overrides.jsonl):")
|
|
6450
|
+
for line in og.read_audit(project_path, tail):
|
|
6451
|
+
print(f" {line.get('ts', '?')} {line.get('event', '?'):12s} "
|
|
6452
|
+
f"{line.get('grant_id', '')} {line.get('path', '')}")
|
|
6453
|
+
|
|
6454
|
+
|
|
6455
|
+
def _override_check(args, policy, project_path: str, og, opol) -> None:
|
|
6456
|
+
gate, layer_key, rule, tool, op, err = _override_identify(
|
|
6457
|
+
args, project_path, opol)
|
|
6458
|
+
if err:
|
|
6459
|
+
print(f" {err}")
|
|
6460
|
+
return
|
|
6461
|
+
if not policy.escalatable(layer_key):
|
|
6462
|
+
print(f" Denied — the '{layer_key}' layer is not escalatable, so no "
|
|
6463
|
+
"grant is even consulted.")
|
|
6464
|
+
return
|
|
6465
|
+
hit = og.find(project_path, session_id=getattr(args, "session_id", ""),
|
|
6466
|
+
layer=gate, rule=rule, tool=tool, op=op,
|
|
6467
|
+
path=getattr(args, "target", ""))
|
|
6468
|
+
if hit:
|
|
6469
|
+
print(f" Allowed by {hit['id']} — {hit['uses_remaining']} use(s) "
|
|
6470
|
+
f"left, expires {hit['expires_at']}.")
|
|
6471
|
+
print(" (`check` never consumes a use.)")
|
|
6472
|
+
else:
|
|
6473
|
+
print(" Denied — no live grant matches this exact "
|
|
6474
|
+
"session/layer/rule/tool/op/path.")
|
|
6475
|
+
|
|
6476
|
+
|
|
6477
|
+
def _override_audit(grant: dict, project_path: str) -> None:
|
|
6478
|
+
"""Ledger + activity entry. The JSONL audit is written by the service."""
|
|
6479
|
+
try:
|
|
6480
|
+
from services.activity_log import ActivityLog
|
|
6481
|
+
ActivityLog(project_path).log("access_action", {
|
|
6482
|
+
"kind": "override", "action": "grant", "grant_id": grant["id"],
|
|
6483
|
+
"layer": grant["layer"], "rule": grant["rule"],
|
|
6484
|
+
"tool": grant["tool"], "op": grant["op"],
|
|
6485
|
+
"path": grant["path_key"], "session_id": grant["session_id"],
|
|
6486
|
+
"expires_at": grant["expires_at"],
|
|
6487
|
+
"uses": grant["uses_remaining"], "granted_by": grant["granted_by"],
|
|
6488
|
+
})
|
|
6489
|
+
except Exception:
|
|
6490
|
+
pass
|
|
6491
|
+
try:
|
|
6492
|
+
from services.edit_ledger import EditLedger
|
|
6493
|
+
EditLedger(project_path).log_edit(
|
|
6494
|
+
file=f"override://{grant['id']}",
|
|
6495
|
+
change_type="override_grant",
|
|
6496
|
+
summary=(f"granted {grant['tool']} {grant['op']} on "
|
|
6497
|
+
f"{grant['path_key']} once (rule {grant['rule']}, "
|
|
6498
|
+
f"expires {grant['expires_at']}) via `c3 override`"),
|
|
6499
|
+
tags=["override", "access"],
|
|
6500
|
+
detail={"kind": "override", **grant},
|
|
6501
|
+
)
|
|
6502
|
+
except Exception:
|
|
6503
|
+
pass
|
|
6504
|
+
|
|
6505
|
+
|
|
6212
6506
|
def cmd_access(args):
|
|
6213
6507
|
"""Access Guard rule management — human-only mutation surface (spec §1)."""
|
|
6214
6508
|
sub = getattr(args, "access_cmd", None)
|
|
@@ -7994,6 +8288,7 @@ def main():
|
|
|
7994
8288
|
"creds": cmd_creds,
|
|
7995
8289
|
"access": cmd_access,
|
|
7996
8290
|
"enforce": cmd_enforce,
|
|
8291
|
+
"override": cmd_override,
|
|
7997
8292
|
"locks": cmd_locks,
|
|
7998
8293
|
"oracle": cmd_oracle,
|
|
7999
8294
|
"upgrade": cmd_upgrade,
|
|
@@ -472,6 +472,108 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
472
472
|
lk_sweep = locks_subs.add_parser("sweep", help="Drop expired leases now")
|
|
473
473
|
lk_sweep.add_argument("--path", dest="project_path", default=".")
|
|
474
474
|
|
|
475
|
+
# ── Override Requests (v2.69.0, docs/override-requests.md) ──────────
|
|
476
|
+
# Human-only approval surface. A grant is single-use, session-bound,
|
|
477
|
+
# path-exact and TTL-capped, and it never edits policy: the rule that
|
|
478
|
+
# denied the call is still in force the moment the grant is spent.
|
|
479
|
+
# There is no agent-facing path to any verb in this command.
|
|
480
|
+
p_override = subparsers.add_parser(
|
|
481
|
+
"override",
|
|
482
|
+
help="Override Requests — allow one blocked call once, without weakening the rule",
|
|
483
|
+
)
|
|
484
|
+
override_subs = p_override.add_subparsers(dest="override_cmd")
|
|
485
|
+
|
|
486
|
+
ov_policy = override_subs.add_parser(
|
|
487
|
+
"policy", help="Show the effective `override` policy and which layers are escalatable"
|
|
488
|
+
)
|
|
489
|
+
ov_policy.add_argument("--path", dest="project_path", default=".",
|
|
490
|
+
help="Project directory (default: current)")
|
|
491
|
+
|
|
492
|
+
ov_grant = override_subs.add_parser(
|
|
493
|
+
"grant", help="Mint a single-use grant for one blocked call (human approval)"
|
|
494
|
+
)
|
|
495
|
+
ov_grant.add_argument("target", help="The exact path that was blocked")
|
|
496
|
+
ov_grant.add_argument("--session", dest="session_id", required=True,
|
|
497
|
+
help="Claude Code session id the grant is bound to — a grant never crosses sessions")
|
|
498
|
+
ov_grant.add_argument("--op", choices=["read", "write"], default="read",
|
|
499
|
+
help="Operation to allow (default: read)")
|
|
500
|
+
ov_grant.add_argument("--tool", default=None,
|
|
501
|
+
help="Exact tool name (default: Read for --op read, Edit for --op write)")
|
|
502
|
+
ov_grant.add_argument("--layer", choices=["access", "discipline", "mask", "shell"],
|
|
503
|
+
default=None,
|
|
504
|
+
help="Force a layer. Default: derive it from the denial the path actually produces")
|
|
505
|
+
ov_grant.add_argument("--ttl", type=int, default=None, dest="ttl_s",
|
|
506
|
+
help="Seconds until the grant expires (clamped to override.max_ttl_s, hard ceiling 900)")
|
|
507
|
+
ov_grant.add_argument("--uses", type=int, default=None,
|
|
508
|
+
help="Uses allowed (default 1; >1 needs override.allow_session_grants)")
|
|
509
|
+
ov_grant.add_argument("--confirm", default=None,
|
|
510
|
+
help="Required for deny/builtin rules: retype the rule glob by hand")
|
|
511
|
+
ov_grant.add_argument("--path", dest="project_path", default=".",
|
|
512
|
+
help="Project directory (default: current)")
|
|
513
|
+
|
|
514
|
+
ov_list = override_subs.add_parser("list", help="Live grants for this project")
|
|
515
|
+
ov_list.add_argument("--session", dest="session_id", default="",
|
|
516
|
+
help="Only grants bound to this session id")
|
|
517
|
+
ov_list.add_argument("--audit", type=int, default=0, metavar="N",
|
|
518
|
+
help="Also print the last N lines of .c3/overrides.jsonl")
|
|
519
|
+
ov_list.add_argument("--path", dest="project_path", default=".",
|
|
520
|
+
help="Project directory (default: current)")
|
|
521
|
+
|
|
522
|
+
ov_revoke = override_subs.add_parser("revoke", help="Drop a live grant before it is used")
|
|
523
|
+
ov_revoke.add_argument("grant_id", help="Grant id (grt_…)")
|
|
524
|
+
ov_revoke.add_argument("--path", dest="project_path", default=".",
|
|
525
|
+
help="Project directory (default: current)")
|
|
526
|
+
|
|
527
|
+
ov_check = override_subs.add_parser(
|
|
528
|
+
"check", help="Would a grant cover this call right now? (never consumes one)"
|
|
529
|
+
)
|
|
530
|
+
ov_check.add_argument("target", help="Path to test")
|
|
531
|
+
ov_check.add_argument("--session", dest="session_id", required=True)
|
|
532
|
+
ov_check.add_argument("--op", choices=["read", "write"], default="read")
|
|
533
|
+
ov_check.add_argument("--tool", default=None)
|
|
534
|
+
ov_check.add_argument("--layer", choices=["access", "discipline", "mask", "shell"],
|
|
535
|
+
default=None)
|
|
536
|
+
ov_check.add_argument("--path", dest="project_path", default=".",
|
|
537
|
+
help="Project directory (default: current)")
|
|
538
|
+
|
|
539
|
+
ov_sweep = override_subs.add_parser("sweep", help="Drop expired / spent grants now")
|
|
540
|
+
ov_sweep.add_argument("--path", dest="project_path", default=".",
|
|
541
|
+
help="Project directory (default: current)")
|
|
542
|
+
|
|
543
|
+
# Requests: what agents have ASKED for. Deciding is human-only and lives
|
|
544
|
+
# here (and, later, on the phone) — never on the c3_override agent tool.
|
|
545
|
+
ov_requests = override_subs.add_parser(
|
|
546
|
+
"requests", help="Override requests agents have asked for"
|
|
547
|
+
)
|
|
548
|
+
ov_requests.add_argument("--status", default="",
|
|
549
|
+
help="pending | approved | denied | expired | withdrawn")
|
|
550
|
+
ov_requests.add_argument("--all", dest="all_projects", action="store_true",
|
|
551
|
+
help="Every project, not just this one")
|
|
552
|
+
ov_requests.add_argument("--path", dest="project_path", default=".",
|
|
553
|
+
help="Project directory (default: current)")
|
|
554
|
+
|
|
555
|
+
ov_approve = override_subs.add_parser(
|
|
556
|
+
"approve", help="Approve one request — mints a single-use grant"
|
|
557
|
+
)
|
|
558
|
+
ov_approve.add_argument("request_id", help="Request id (ovr_…)")
|
|
559
|
+
ov_approve.add_argument("--ttl", type=int, default=None, dest="ttl_s",
|
|
560
|
+
help="Seconds until the grant expires (clamped to override.max_ttl_s)")
|
|
561
|
+
ov_approve.add_argument("--uses", type=int, default=None,
|
|
562
|
+
help="Uses allowed (default 1)")
|
|
563
|
+
ov_approve.add_argument("--note", default="",
|
|
564
|
+
help="Note recorded with the decision")
|
|
565
|
+
ov_approve.add_argument("--confirm", default=None,
|
|
566
|
+
help="Required for deny/builtin rules: retype the rule glob by hand")
|
|
567
|
+
ov_approve.add_argument("--path", dest="project_path", default=".",
|
|
568
|
+
help="Project directory (default: current)")
|
|
569
|
+
|
|
570
|
+
ov_deny = override_subs.add_parser("deny", help="Refuse one request")
|
|
571
|
+
ov_deny.add_argument("request_id", help="Request id (ovr_…)")
|
|
572
|
+
ov_deny.add_argument("--note", default="",
|
|
573
|
+
help="Note the agent will see with the refusal")
|
|
574
|
+
ov_deny.add_argument("--path", dest="project_path", default=".",
|
|
575
|
+
help="Project directory (default: current)")
|
|
576
|
+
|
|
475
577
|
# ── Tool discipline / enforcement mode (v2.66.0) ────────────────────
|
|
476
578
|
# LAYER C: how hard C3 pushes the agent toward c3_* tools. Distinct from
|
|
477
579
|
# `c3 access` (path policy — a security boundary) and from
|
|
@@ -81,6 +81,22 @@ def _record(denial, tool: str, operation: str, path: str,
|
|
|
81
81
|
pass
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
def _override_allows(base: str, denial, *, tool: str, op: str, path: str,
|
|
85
|
+
session_id: str):
|
|
86
|
+
"""A live override grant covering this exact call, or None (spec §5).
|
|
87
|
+
|
|
88
|
+
Lazy import and policy-before-grants ordering keep the hot path free: a
|
|
89
|
+
project that never turned overrides on pays one dict lookup and never
|
|
90
|
+
opens the grants file. Any failure returns None — the denial stands.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
94
|
+
return og.gate_access(base, denial, tool=tool, op=op, path=path,
|
|
95
|
+
session_id=session_id)
|
|
96
|
+
except Exception:
|
|
97
|
+
return None # fail closed: no grant, ordinary denial
|
|
98
|
+
|
|
99
|
+
|
|
84
100
|
def _target(tool_input: dict) -> str:
|
|
85
101
|
return str(
|
|
86
102
|
tool_input.get("file_path")
|
|
@@ -136,6 +152,10 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
136
152
|
op = "write" if tool in _WRITE_TOOLS else "read"
|
|
137
153
|
denial = ag.check(fp, op, base)
|
|
138
154
|
if denial:
|
|
155
|
+
granted = _override_allows(base, denial, tool=tool, op=op,
|
|
156
|
+
path=fp, session_id=session_id)
|
|
157
|
+
if granted:
|
|
158
|
+
return {"additionalContext": granted}
|
|
139
159
|
_record(denial, tool, op, fp, base, session_id)
|
|
140
160
|
return _deny(ag.refusal(denial, fp, op, surface="hook", tool=tool))
|
|
141
161
|
return None
|
|
@@ -360,6 +360,25 @@ def _vault_denial(tool_name: str, tool_input: dict) -> dict | None:
|
|
|
360
360
|
return None
|
|
361
361
|
|
|
362
362
|
|
|
363
|
+
def _override_allows(base: Path, tool_name: str, tool_input: dict,
|
|
364
|
+
session_id: str) -> str | None:
|
|
365
|
+
"""A live discipline grant for this exact write, or None (spec §5).
|
|
366
|
+
|
|
367
|
+
Runs AFTER `_vault_denial`, which stays unconditional: no grant, no
|
|
368
|
+
config value and no approval can open a native write path to the vault.
|
|
369
|
+
"""
|
|
370
|
+
target = str(tool_input.get("file_path")
|
|
371
|
+
or tool_input.get("notebook_path") or "")
|
|
372
|
+
if not target:
|
|
373
|
+
return None
|
|
374
|
+
try:
|
|
375
|
+
from services import override_grants as og # noqa: PLC0415 — lazy
|
|
376
|
+
return og.gate_discipline(base, tool=tool_name, path=target,
|
|
377
|
+
session_id=session_id)
|
|
378
|
+
except Exception:
|
|
379
|
+
return None # fail closed: no grant, ordinary block
|
|
380
|
+
|
|
381
|
+
|
|
363
382
|
def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
364
383
|
"""Core enforcement logic — importable by the dispatcher and tests.
|
|
365
384
|
|
|
@@ -421,6 +440,9 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
|
|
|
421
440
|
# the ledger stays complete either way; what is lost is the pre-edit
|
|
422
441
|
# snapshot c3_edit would have taken.
|
|
423
442
|
if tool_name in blocked_tools and mode == "strict":
|
|
443
|
+
granted = _override_allows(base, tool_name, tool_input, session_id)
|
|
444
|
+
if granted:
|
|
445
|
+
return _with_warnings({"additionalContext": granted}, policy)
|
|
424
446
|
_record_block(tool_name, tool_input, session_id, base)
|
|
425
447
|
reason = (
|
|
426
448
|
f"[c3:enforce] Native `{tool_name}` is blocked to preserve the edit "
|
|
@@ -1780,25 +1780,15 @@ def api_projects_config_get():
|
|
|
1780
1780
|
# returned by any hub route (no reveal exists here). `credentials` stays out of
|
|
1781
1781
|
# _CONFIG_WRITE_SECTIONS — these dedicated routes are the only hub write path.
|
|
1782
1782
|
|
|
1783
|
-
_CRED_PUBLIC_FIELDS = ("scope", "type", "value_len", "env_var", "inject",
|
|
1784
|
-
"agent_readable", "description", "storage", "created",
|
|
1785
|
-
"updated")
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
1783
|
def _cred_entry_public(name, entry, *, usage=None, shadows_global=None):
|
|
1789
|
-
"""Explicit allowlist serializer — structurally cannot emit a value.
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
rec["last_used"] = (usage.get(name) or {}).get("last_used", "")
|
|
1798
|
-
rec["use_count"] = (usage.get(name) or {}).get("use_count", 0)
|
|
1799
|
-
if shadows_global is not None:
|
|
1800
|
-
rec["shadows_global"] = bool(shadows_global)
|
|
1801
|
-
return rec
|
|
1784
|
+
"""Explicit allowlist serializer — structurally cannot emit a value.
|
|
1785
|
+
|
|
1786
|
+
Delegates to credential_store.public_entry so the hub, the per-project UI
|
|
1787
|
+
and the mobile gateway all serialize through one allowlist; a second copy
|
|
1788
|
+
is exactly the drift the write-only wire contract cannot survive."""
|
|
1789
|
+
from services import credential_store as cred_store
|
|
1790
|
+
return cred_store.public_entry(
|
|
1791
|
+
name, entry, usage=usage, shadows_global=shadows_global)
|
|
1802
1792
|
|
|
1803
1793
|
|
|
1804
1794
|
def _resolve_cred_target(path: str, scope: str, *, mutation: bool):
|
|
@@ -758,6 +758,30 @@ async def c3_locks(action: str = "list", paths: str = "", intent: str = "",
|
|
|
758
758
|
svc, finalize)
|
|
759
759
|
|
|
760
760
|
|
|
761
|
+
@mcp.tool()
|
|
762
|
+
async def c3_override(action: str = "list", path: str = "", tool: str = "",
|
|
763
|
+
op: str = "read", why: str = "", request_id: str = "",
|
|
764
|
+
layer: str = "", timeout_s: int = 60,
|
|
765
|
+
ctx: Context = None) -> str:
|
|
766
|
+
"""ASK A HUMAN to allow ONE blocked call — never a policy change.
|
|
767
|
+
actions: request, status, wait (blocks <=180s), list, withdraw.
|
|
768
|
+
request: path = the exact blocked path; why = one concrete sentence for the human.
|
|
769
|
+
A yes mints a single-use, session-bound, path-exact grant with a short TTL; the
|
|
770
|
+
rule that blocked you stays in force. There is NO approve action here — only the
|
|
771
|
+
user can decide, from their phone or `c3 override approve`.
|
|
772
|
+
Most denials are NOT escalatable (credential vault, Tier-0, catastrophic shell
|
|
773
|
+
commands); those are refused here and never shown to anyone. If a refusal did not
|
|
774
|
+
invite you to ask, do not ask — mark the step blocked and tell the user."""
|
|
775
|
+
svc = _svc(ctx)
|
|
776
|
+
|
|
777
|
+
def finalize(name, args, resp, summ, **kw):
|
|
778
|
+
return _finalize_response(ctx, name, args, resp, summ, **kw)
|
|
779
|
+
|
|
780
|
+
from cli.tools.override import handle_override
|
|
781
|
+
return await asyncio.to_thread(handle_override, action, path, tool, op, why,
|
|
782
|
+
request_id, layer, timeout_s, svc, finalize)
|
|
783
|
+
|
|
784
|
+
|
|
761
785
|
@mcp.tool()
|
|
762
786
|
async def c3_impact(target: str, file_path: str = "", mode: str = "symbol",
|
|
763
787
|
ctx: Context = None) -> str:
|