Skip to content

fix: close control-plane auth gaps and hold daily budgets under concurrency - #20

Open
SairajMN wants to merge 4 commits into
theschoolofai:mainfrom
SairajMN:main
Open

SairajMN wants to merge 4 commits into
theschoolofai:mainfrom
SairajMN:main

Conversation

@SairajMN

Copy link
Copy Markdown

Summary

Four control-plane and budget bugs, each fixed in its own commit and covered by a
failing test that now passes:

  1. An unauthenticated write path could start a run, and unauthenticated read/write
    paths could leak or inject authority. The control plane now fails closed on
    every gate.
  2. The daily run ceiling / budget check recorded the run only after it finished,
    so overlapping events could all pass the check and all start runs. Ceilings now
    hold under concurrency.

Commits (merge-request scope)

  • 7f1d692 fix: gate POST /v1/action behind the control token

    • POST /v1/action resumed a waiting node and re-ran the runtime (spending money)
      with no auth. Same resume as the protected /v1/agent/completions and
      /v1/agent/runs/{id}/resume routes. Now gated with require_control.
    • Proof: tests/test_control_plane_auth.py::test_the_action_route_is_a_write_path_and_fails_closed
  • 8e0f9fe fix: gate /facts, /documents, /memory/search behind the control token

    • These memory write/read routes inject evidence the agent later treats as
      authorised, with no token. Now gated with require_control.
    • Proof: tests/test_control_plane_auth.py WRITE_PATHS (facts/documents)
  • a2a0a49 fix: gate GET /subscriptions behind the control token

    • GET /v1/agent/subscriptions returned every subscription's allowed side
      effects, budgets and instructions with no auth while the write path was gated.
      Now gated, matching the write path.
    • Proof: tests/test_control_plane_auth.py::test_the_subscription_read_path_is_gated_like_its_write_path
  • 2193fa8 fix: hold daily run ceiling and budget under concurrent events

    • admit_run checked max_runs_per_day / daily_budget against the window ledger,
      but the run was only recorded after runtime.run() finished. Concurrent
      process() calls could all observe count/spend = 0 and all start runs.
    • Fix: atomically reserve a run slot (and daily-budget remainder when set) under
      the event-store lock at admit time; settle actual spend after the run without
      double-counting. Failed runs refund the reservation but keep the consumed slot.
    • Proof: tests/test_autonomy_governor.py::test_the_daily_run_ceiling_holds_under_concurrent_events

What breaks (before)

  • Anonymous caller can POST /v1/action to approve/reject a pending decision and
    re-run the graph.
  • Anonymous caller can read every subscription's authority object
    (side effects, budgets, instructions) via GET /subscriptions.
  • Anonymous caller can inject facts/documents (evidence the agent trusts) and run
    memory searches.
  • With max_runs_per_day=1, firing several overlapping relevant events
    (asyncio.gather on engine.process) starts multiple runs and records zero (or too
    few) max_runs_per_day refusals — the ceiling is bypassed.

Fix

  • s16code/ui/routes.py — gate POST /v1/action with require_control.
  • s16code/routes.py — gate /facts, /documents, /memory/search with require_control.
  • s16code/events/routes.py — gate GET /subscriptions with require_control (match write path).
  • s16code/events/store.py — EventStore.window_try_admit_run (check + reserve in one
    locked section) and EventStore.window_settle_run (reservation -> metered spend).
  • s16code/events/governor.py — admit_run routes through the atomic reserve; settle_run added.
  • s16code/events/engine.py — settles actual spend after the run; failed runs refund
    reservation but keep the slot.

Test plan

  • pytest tests/test_control_plane_auth.py — auth gates fail closed (503 when token
    unset, 401 on wrong token) for every gated path; new read-path test red before, green after
  • pytest tests/test_autonomy_governor.py — new concurrent ceiling test red before
    fix on that case, green after
  • Sequential max_runs_per_day still bounds runs and records refusals
  • Sequential daily_budget still caps the window and shrinks effective per-run budget
  • Concurrent events with max_runs_per_day=1 -> exactly one run and three max_runs_per_day refusals
  • Self-trigger, source rate limit, triage ceiling, liveness, and morning-report tests still pass
  • Full suite: 362 passed (361 prior + new concurrent ceiling test)
  • Reviewer: confirm diff has no .env, tokens, credentials, or local EA wiring
  • Reviewer: optional — concurrent daily_budget stress (same pattern as max_runs) if desired beyond the unit test

No secrets, real messages, or API keys in this diff.

Bug: /v1/action resumed a waiting graph node and re-ran the runtime
(spending money) with no auth, while the equivalent /v1/agent/completions
and /v1/agent/runs/{id}/resume routes were gated. An anonymous caller
could approve/reject a pending approval and resume a run.
Proof: tests/test_control_plane_auth.py::test_the_action_route_is_a_write_path_and_fails_closed
Fix: add Depends(require_control) to the /v1/action route.
Bug: POST /facts, POST /documents, POST /memory/search were unauthenticated write paths that inject evidence the agent later treats as authorised. The README explicitly promises 'auth.py gates every write path and fails closed'. An anonymous caller could write facts and documents, expanding subscription authority without a token.

Proof: tests/test_control_plane_auth.py parametrized cases for facts and documents
Fix: add Depends(require_control) to /facts, /documents, /memory/search routes.
Bug: GET /v1/agent/subscriptions returned every subscription's allowed
side effects, budgets and instructions with no auth, while the write
path PUT /subscriptions/{id} required the control token. The
subscription is the authority object of the session; its read path
leaked it to anonymous callers.
Proof: tests/test_control_plane_auth.py::test_the_subscription_read_path_is_gated_like_its_write_path
Fix: add dependencies=[Depends(require_control)] to the GET route,
matching the write path.
Bug: admit_run checked max_runs_per_day / daily_budget against the
window ledger, but the run was only recorded after runtime.run()
finished. Concurrent process() calls could all observe count/spend = 0
and all start runs, so the daily ceilings did not hold under overlap.
Proof: tests/test_autonomy_governor.py::test_the_daily_run_ceiling_holds_under_concurrent_events
Fix: atomically reserve a run slot (and daily-budget remainder when set)
under the event-store lock at admit time; settle actual spend after the
run without double-counting. Failed runs refund the reservation but keep
the consumed slot.
@theschoolofai

Copy link
Copy Markdown
Owner

Session 16 — graded

Score: 0.

Duplicate on both halves. The daily-budget concurrency work is #3 (2026-08-09) and the control-plane auth gaps are #16 (2026-08-13 16:44), both earlier. Solid work, but points go to the earliest filer of each bug.

@theschoolofai

Copy link
Copy Markdown
Owner

Session 16 — regraded ✅

Score: +100. The verdict below stands: this is a duplicate of an earlier filing (or, for glc_v5 #23, an enhancement rather than a defect), and it is recorded as such.

What has changed is the credit. On review, the work here was genuinely done: the bug was found independently, the reproduction is real and the fix is sound. Losing a race you had no way of seeing is not a reason to earn nothing, so this is credited at the full 100 even though it does not carry the first-to-file claim.


Original assessment, unchanged:

Duplicate on both halves. The daily-budget concurrency work is #3 (2026-08-09) and the control-plane auth gaps are #16 (2026-08-13 16:44), both earlier. Solid work, but points go to the earliest filer of each bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants