Compare commits

...

1 Commits

Author SHA1 Message Date
hongming 2e93df5d69 fix(scheduler): #1692 — mount ScheduleHandler HTTP routes
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 9s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 4s
Harness Replays / detect-changes (pull_request) Successful in 4s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
qa-review / approved (pull_request) Failing after 12s
security-review / approved (pull_request) Failing after 11s
sop-checklist / all-items-acked (pull_request) Successful in 10s
CI / Canvas (Next.js) (pull_request) Successful in 3s
sop-tier-check / tier-check (pull_request) Successful in 10s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 9s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m10s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Failing after 2m5s
audit-force-merge / audit (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Successful in 4m45s
CI / all-required (pull_request) Successful in 5m28s
The handler + Temporal cron worker are implemented in
workspace-server/internal/handlers/schedules.go +
platform/internal/scheduler, but the HTTP routes were never mounted.
Every call to /workspaces/:id/schedules returned 404, so:

- Canvas "Schedule" tab silently failed to load/save schedules
- Agent MCP set_schedule tool returned 404 → agents had no way to
  self-schedule wake-ups (PM in particular had no platform-side
  scheduler and was relying on the CTO orchestrator's /loop tick as
  its only heartbeat)

Routes are mounted under WorkspaceAuth so the per-workspace bearer
token scopes ownership, matching the IDOR-fix shape in
handlers/schedules.go (Update/Delete/RunNow/History rebind to
workspaceID before touching the row).

Endpoints exposed:
  GET    /workspaces/:id/schedules
  POST   /workspaces/:id/schedules
  PUT    /workspaces/:id/schedules/:scheduleId
  DELETE /workspaces/:id/schedules/:scheduleId
  POST   /workspaces/:id/schedules/:scheduleId/run
  GET    /workspaces/:id/schedules/:scheduleId/history
  GET    /workspaces/:id/schedules/health

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 17:17:28 -07:00
@@ -311,6 +311,24 @@ func Setup(hub *ws.Hub, broadcaster *events.Broadcaster, prov *provisioner.Provi
wsAuth.PATCH("/agent", ah.Replace)
wsAuth.DELETE("/agent", ah.Remove)
wsAuth.POST("/agent/move", ah.Move)
// Schedules (#1692). The handler + Temporal cron worker are
// implemented in workspace-server/internal/handlers/schedules.go +
// platform/internal/scheduler, but the HTTP routes were never
// mounted — every call returned 404 (Canvas "Schedule" tab + agent
// MCP set_schedule both silently failed). Mounting here puts the
// endpoints under WorkspaceAuth so the per-workspace bearer token
// scopes ownership, matching the IDOR-fix shape in handlers/schedules.go
// (Update/Delete/RunNow/History rebind to workspaceID before
// touching the row).
sch := handlers.NewScheduleHandler()
wsAuth.GET("/schedules", sch.List)
wsAuth.POST("/schedules", sch.Create)
wsAuth.PUT("/schedules/:scheduleId", sch.Update)
wsAuth.DELETE("/schedules/:scheduleId", sch.Delete)
wsAuth.POST("/schedules/:scheduleId/run", sch.RunNow)
wsAuth.GET("/schedules/:scheduleId/history", sch.History)
wsAuth.GET("/schedules/health", sch.Health)
}
// Registry