Commit Graph

2 Commits

Author SHA1 Message Date
Bo 8af560f678 fix(ci): reconcile hookless gates and flywheel snapshot
Squash merge PR #546. Local evidence: doc-release, smoke, snapshot gate, pre-push Bats, snapshot Bats, structural gates, and pre-push --fast passed. PR GitHub Actions were blocked in runner setup by GitHub codeload/action download failures before repository code executed. Snapshot records current corpus as DECAYING rather than preserving stale compounding evidence.
2026-05-26 08:19:50 -04:00
Boden Fuller 81f864da52 feat(daemon): add /v1/schedules routes + registerMutationRoute helper + bypass guard (soc-8inr.5)
PER PRE-MORTEM AMENDMENT A2 (soc-8inr.5): introduce a single registration
choke-point for mutation HTTP routes plus a build-time guard that asserts
no other code path bypasses it.

- registerMutationRoute(mux, path, policyFn, handler) wraps handler in
  AuthorizeMutationDecision and atomically appends path to the tracked
  mutationRoutes slice. Decision is stashed in request context so handlers
  can read TokenName for ledger actor attribution without re-running auth.
- registerReadOnlyRoute keeps read-only registrations in auth.go too, so
  ALL mux.HandleFunc callsites in cli/internal/daemon/ live in one file
  (the single audit surface).
- Existing /jobs, /v1/jobs, /jobs/cancel, /v1/jobs/cancel, and
  /openclaw/v1/triggers/jobs migrate to registerMutationRoute. Read-only
  /health, /ready, /status, /events, /openclaw/v1/* migrate to
  registerReadOnlyRoute. Behavior is preserved including 405 Method Not
  Allowed semantics (auth wrapper passes non-mutation methods through to
  the handler so requireMethod can still return 405).

- New /v1/schedules routes:
  - POST /v1/schedules creates a RecurringJobTemplate (admin capability).
  - GET /v1/schedules lists schedules — read-only, no auth.
  - DELETE /v1/schedules/{name} removes a schedule (admin capability).
  Validation: malformed JSON / missing fields / invalid cron return 400.
  Duplicate names return 409.

- mutationPathInSet + mutationCapabilityForPath add "/foo/*" wildcard
  support so DELETE /v1/schedules/{name} can be authorized without
  enumerating every name in AllowedPaths.

- Default legacy mutation tokens now carry MutationCapabilityAdmin (in
  addition to submit/cancel/openclaw_trigger). Single-token mode is
  local-only by construction; scoped tokens that need a narrower scope
  must use the JSON token-file format.

- scripts/check-mutation-route-coverage.sh greps cli/internal/daemon/
  for raw mux.HandleFunc callsites outside auth.go and fails the gate
  if any are found. Wired into scripts/pre-push-gate.sh as an always-on
  check (security gate; not skipped on local fast mode).

Tests:
- TestRegisterMutationRoute_AppendsAndWraps: helper appends to tracked
  list, rejects unauthenticated POST, runs handler with decision in
  context on valid token.
- TestMutation_AllRegisteredRoutesEnforcePolicy: parity test — every path
  in MutationRoutesForTest() returns 403 without a token.
- TestMutation_GetSchedulesIsReadOnly: GET /v1/schedules is 200 without
  auth and is NOT in the tracked mutation slice.
- TestRoute_PostSchedules_Creates / DuplicateNameReturns409 /
  MalformedJSONReturns400 / MissingFieldsReturns400.
- TestRoute_GetSchedules_ReturnsList.
- TestRoute_DeleteSchedule_Removes / AuthRequired.

Closes soc-8inr.5.
2026-05-01 17:35:13 -04:00