## 1. Repository: bounded task listing - [x] 1.1 Add `list_tasks(*, status, limit, offset)` and `count_tasks(status)` to the `CloudRepository` Protocol in `repository.py` - [x] 1.2 Implement both methods in `sql_repository.py` using the existing SQLAlchemy query builder (no dialect-specific SQL), ordered most-recent-first - [x] 1.3 Add unit/integration tests covering status filtering, pagination bounds, and empty results against both SQLite and PostgreSQL ## 2. Platform SDK API: task listing & attempt history - [x] 2.1 Add response models (task summary list item, task attempt) to `cloud/sdk/models.py` - [x] 2.2 Implement `GET /v1/tasks` in `cloud/sdk/api.py`: `tasks:read` scope, optional `status` query param, `limit` (default 50, max 100) / `offset` query params, calling the new repository methods - [x] 2.3 Implement `GET /v1/tasks/{task_id}/attempts` in `cloud/sdk/api.py`: `tasks:read` scope, 404 on unknown task id, calling `list_task_attempts` - [x] 2.4 Add tests for both endpoints: filtered/unfiltered listing, page-size-exceeds-max rejection, attempts for known/unknown task id, and scope enforcement (401/403) ## 3. Python SDK client parity - [x] 3.1 Add `list_tasks(...)` and `get_task_attempts(task_id)` methods to `CloudClient` in `cloud/sdk/client.py` - [x] 3.2 Add client tests asserting parity with direct HTTP calls to the two new endpoints ## 4. Cloud API CORS configuration - [x] 4.1 Add a `cors_allowed_origins` field (env `CLOUD_CONSOLE_CORS_ORIGINS`, comma-separated, default empty) to `CloudControlConfig`/`load_control_config()` - [x] 4.2 Wire `CORSMiddleware` into `apps/cloud-api/cloud_api/app.py`'s `create_app()`, added only when the allow-list is non-empty - [x] 4.3 Add a config/app test confirming CORS headers are absent by default and present only for a configured origin ## 5. Cloud console frontend (independent SPA) - [x] 5.1 Scaffold an independent Vue 3 + Vite SPA project at `cloud-console/` (own `package.json`/build tooling, sibling to `console/`) - [x] 5.2 Implement the token-entry screen and an API client wrapper that stores the bearer token in `sessionStorage` and attaches it to every request, clearing it and returning to the entry screen on `401`/`403` - [x] 5.3 Implement the task view: filterable/paginated list against `GET /v1/tasks`, and a detail view with attempt history against `GET /v1/tasks/{id}/attempts` - [x] 5.4 Implement the device pool and host registry views against `GET /v1/devices` and `GET /v1/hosts` - [x] 5.5 Implement the plugin registry view (list) and registration form against `GET /v1/plugins` and `POST /v1/plugins`, surfacing validation/conflict/authorization errors from the API - [x] 5.6 Document how to run the frontend dev server against a Cloud API base URL (env config) and the CORS origin it needs configured ## 6. Documentation - [x] 6.1 Add a section to `docs/CLOUD_DEPLOYMENT.md` covering: running the console, provisioning an operator bearer token (least-privilege scopes), and configuring `CLOUD_CONSOLE_CORS_ORIGINS` ## 7. Verification - [x] 7.1 Run the full backend test suite (`uv run --all-packages pytest -m "not integration"`) and confirm no regressions - [ ] 7.2 Run the PostgreSQL-backed repository/integration tests for the new listing methods - [ ] 7.3 Manually verify end-to-end: submit a task via the existing SDK, confirm it appears in the console's task list, transitions status, and its attempt history renders; confirm device/host/plugin views render against a running Host Agent