Evolve the MVP project registry (#427) into a versioned, fail-closed
project registry API for the console (Phase 1, read-only).
- Add schema version 2 with project `status`, per-step onboarding
`state`/`required`, optional redacted `last_seen_health`, and
`remote_name`. Version 1 files stay loadable and are normalized with
explicit defaults.
- Serve `/api/v1/projects` and `/api/v1/projects/{project_id}` with API
provenance (`api_version`, `schema_version`, `source`). `/api/projects`
is retained as an unversioned Phase 1 alias.
- Replace bare `ValueError` with `RegistryError`, carrying an operator
`remediation` and `field_path`; invalid registries fail closed as a
500 JSON payload or a dedicated HTML error page instead of a traceback.
- Reject credential-shaped keys before any DTO is built, reusing
`registry_safety.is_forbidden_key` as the single source of truth
shared with the worker registry (#798).
- Render HTML views from `project_to_dict`, so the console and the JSON
API cannot disagree about status or onboarding progress.
- Document the contract in docs/webui-project-registry-api.md.
Tests: registry load/validate (valid, missing project, schema
validation, credential rejection) and API route coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add the declarative worker registry that epic #797 makes the source of
truth for the scheduled multi-LLM worker fleet.
Providers and configured workers are modelled as separate entities so a
provider can be listed with no worker configured, and so provider facts
are not copied into every worker record. A worker records provider,
model, project, role, namespace, profile, workflow, schedule, timeout,
enabled state, and scheduler metadata.
Validation fails closed: unknown fields are refused rather than ignored,
so a typo cannot silently disable a timeout; a worker naming an
undeclared provider is rejected; worker ids, provider ids, and
LaunchAgent labels must be unique.
Persistence is atomic (temp file in the same directory, fsync, replace).
Every superseded document is retained as a numbered revision, and
rollback republishes a chosen revision as a new head, so history stays
append-only and a rollback is itself reversible.
The credential-rejection guard is extracted to webui/registry_safety.py
so both registries share one implementation instead of two copies of a
security check; project_registry.py keeps identical behaviour.
Scope: data model, validation, persistence only. No routes, scheduler,
process control, or provider probing - those are #799/#800/#804/#805.
The workers array ships empty because populating it is #808.
Tests: tests/test_webui_worker_registry.py, 44 cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>