Repository navigation
feat(hub): the Access screen could decide override requests but not allow them - #178
Merged
Merged
Conversation
…llow them Dimitri, after the guard stopped me deleting a crashed .git/index.lock and `c3_override` answered "overrides are switched off for this project": "why is that not a c3 hub feature? is there another UI or way I can do it?" It wasn't, and there wasn't. The hub could already LIST override requests and DECIDE them — but nothing exposed `override.enabled`, which decides whether a request may exist at all. So the Access screen answered a question the agent was never allowed to ask, and the only ways to change that were the phone API or hand-editing .c3/config.json. WHAT SHIPS. - GET/POST /api/hub/overrides/policy, registered BEFORE the POST-only <request_id> route for the same reason `costs` carries that comment: a GET on `policy` must never fall through to a 405 there. - A "May agents ask?" panel on the Access screen, between the costing strip and the request list — project picker, the enable toggle, the seven escalatable layers, and the two notes a toggle screen must not let the reader forget (policy merges by TIGHTENING; the vault and the catastrophic shell blocks are never escalatable at any setting). THE SECURITY PROPERTY, and the reason this is not just a form. Widening — enabling the feature or turning a layer on — needs a typed confirmation; tightening never does, because making the guard stricter should always be one click. The panel asks in plain words BEFORE sending rather than surfacing a 400 afterwards, and the server refuses anyway if it doesn't. ONE IMPLEMENTATION, NOT TWO. Validation, widening detection and the write moved into services/override_policy (`apply_section`, `widenings`, `write_section`); the phone route now delegates to them. Two copies of "what counts as widening" would be two chances for one surface to quietly allow what the other refuses, and this is the check that decides whether a tap needs a challenge. The retired inline copy is kept as `_override_widenings_legacy` ONLY so a test can prove the move preserved behaviour rather than assert it: five sections, both implementations, same answer. `wake` stays asymmetric on purpose — refused from the phone, accepted from the hub. It names an argv this machine runs, and a bearer token is authentication, not physical presence. VERIFIED BY LOADING IT, because the bundle is JSX transpiled in the browser: a syntax error here is invisible to every static check in this repo (`node --check` fails on pre-existing line 51) and would show only as a blank hub. Served the worktree's hub on :3399, opened Access, selected a project: 8 checkboxes, layers rendered, both notes present, Save correctly disabled while clean. Screenshot taken. 14 new tests. Suite: 757 passed on this branch vs 743 on main for the same selection — and the ONE failure (test_mobile_clients::test_chat_poll_shares_the_credential_set, 404 != 401) is PRE-EXISTING test-ordering pollution: it passes in isolation and by file on both trees, and fails identically on clean main with the same -k. Not mine, and worth someone's attention separately. Not in this PR: the Oracle UI. It lacks the screen too, but shipping two surfaces when the hub was asked for is scope creep.
drknowhow
added a commit
that referenced
this pull request
Sep 10, 2026
Cuts a release for #178 so the feature can actually reach the box. The hub imports from site-packages, not the checkout (`~/.c3/hub_start.py` sets _REPO to the site-packages path), so a merge to main is invisible to the running process and a restart alone re-imports the same tree. Verified rather than assumed this time: GET /api/hub/overrides/policy on the live hub returned 405 — it fell through to the POST-only <request_id> route, which is exactly what "the route does not exist in this process" looks like. Both version sites bumped in one pass. test_version_sync exists because v2.56.0 shipped with pyproject.toml and cli/c3.py disagreeing, and it passes here — run AFTER the changelog edit, not before, since the last edit and the last verification have to be the same tree. 811 passed across the areas this release touches. The single failure, test_mobile_clients::test_chat_poll_shares_the_credential_set (404 != 401), is PRE-EXISTING test-ordering pollution: it passes in isolation and by file, and fails identically on clean main with the same -k. Proven before this branch existed, not asserted. Claude-Session: https://claude.ai/code/session_013z6ZciSk628VGRbLDZqB2n
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dimitri, after the guard stopped me deleting a crashed
.git/index.lockandc3_overrideanswered "overrides are switched off for this project":It wasn't, and there wasn't.
The gap
The hub could already list override requests and decide them:
But nothing exposed
override.enabled— which decides whether a request mayexist at all. So the Access screen answered a question the agent was never
allowed to ask, and the only ways to change that were the phone API or
hand-editing
.c3/config.json.What ships
GET/POST /api/hub/overrides/policy, registered before the POST-only<request_id>route — for the same reasoncostsalready carries thatcomment: a GET on
policymust never fall through to a 405 there.and the request list: project picker, the enable toggle, the seven
escalatable layers, and the two notes a toggle screen must not let the reader
forget — policy merges by tightening only, and the vault,
secrets.enc,cred_state.json, the dispatcher fail-closed deny and the catastrophic shellblocks are never escalatable at any setting.
The security property
Widening — enabling the feature, or turning a layer on — needs a typed
confirmation. Tightening never does, because making the guard stricter
should always be one click. The panel asks in plain words before sending
rather than surfacing a 400 afterwards, and the server refuses anyway if it
doesn't.
One implementation, not two
Validation, widening detection and the write moved into
services/override_policy(apply_section,widenings,write_section); thephone route now delegates to them.
Two copies of "what counts as widening" would be two chances for one surface to
quietly allow what the other refuses — and this is the check that decides
whether a tap needs a challenge.
The retired inline copy is kept as
_override_widenings_legacyonly so atest can prove the move preserved behaviour rather than assert it: five
sections, both implementations, same answer.
wakestays asymmetric on purpose — refused from the phone, accepted from thehub. It names an argv this machine runs, and a bearer token is authentication,
not physical presence.
Verified by loading it
The bundle is JSX transpiled in the browser, so a syntax error here is
invisible to every static check in this repo (
node --checkfails onpre-existing line 51) and would surface only as a blank hub.
Served the worktree's hub on
:3399, opened Access, selected a project:8 checkboxes, layers rendered, both notes present, Save correctly disabled
while clean. Screenshot attached to the conversation.
Tests
14 new. Suite for the same selection: 757 passed on this branch vs 743 on
main.
test_mobile_clients::TestClientAuth::test_chat_poll_shares_the_credential_set(
404 != 401) — is pre-existing test-ordering pollution, not this change:it passes in isolation and by file on both trees, and fails identically on
clean
mainwith the same-k. Flagging it because it's worth someone'sattention on its own.
Not in this PR
The Oracle UI. It lacks this screen too, but shipping two surfaces when the
hub was the one asked for is scope creep. (I built it there first by mistake
and reverted it — the service extraction above is the half that was needed
either way.)