These workplans exist only in the retired local hub. Their random pre-ADR-007 identifiers are refused by C-06 as stale references, so they cannot be registered. Deriving from the canonical record id takes no identity from anything: central does not hold them and the old ids die with the cache. Records central already holds were deliberately left untouched. Refs CUST-WP-0068-T06 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
295 lines
10 KiB
Markdown
295 lines
10 KiB
Markdown
---
|
|
id: ACTIVITY-WP-0024
|
|
type: workplan
|
|
title: "Operator automation console (status API + control plane + thin UI)"
|
|
domain: infotech
|
|
repo: activity-core
|
|
status: finished
|
|
owner: codex
|
|
topic_slug: activity-core
|
|
created: "2026-07-21"
|
|
updated: "2026-07-21"
|
|
state_hub_workstream_id: "555c6bb2-73e2-579b-837b-a7df0daad621"
|
|
---
|
|
|
|
# Operator automation console (status API + control plane + thin UI)
|
|
|
|
## Origin
|
|
|
|
2026-07-21 operator discussion after ACTIVITY-WP-0023 closeout. Operators need a
|
|
first-class surface for scheduled automations without SSH + SQL + Temporal UI
|
|
as the only path.
|
|
|
|
Builds on:
|
|
|
|
- **ACTIVITY-WP-0018** — own-infra automation status contract + CLI
|
|
- **ACTIVITY-WP-0019** — automation inventory Make targets
|
|
- **ACTIVITY-WP-0021** — prod automation status path (SSH helper)
|
|
|
|
Locked product decisions (founder):
|
|
|
|
| Fork | Choice |
|
|
| --- | --- |
|
|
| Surface | API-first, then thin UI **in activity-core** |
|
|
| Auth / exposure | Port-forward + operator token; mutations **fail-closed** |
|
|
| Mutations in MVP | **Run now + enable/disable/pause only** (no cron patch) |
|
|
|
|
## Goal
|
|
|
|
Ship an **operator automation console**: HTTP APIs (and a thin UI) so an
|
|
operator can:
|
|
|
|
1. See whether regular scheduled jobs ran properly in a time window
|
|
2. Review results (runs → evidence: tasks spawned, progress refs, report paths)
|
|
3. Run a defined activity instantly
|
|
4. Enable / disable / pause schedules without SSH + SQL
|
|
|
|
This is **not** an end-user task tracker and **not** a second scheduler. Durable
|
|
scheduling remains Temporal + ActivityDefinitions; the console is a consumer and
|
|
control plane over that truth.
|
|
|
|
## Out of scope
|
|
|
|
- Cron / timezone patching in the UI (schedules stay git-owned; edit files + sync)
|
|
- Authoring or editing rules / instructions / templates in the UI
|
|
- Public Ingress or full OIDC/key-cape (follow-up after MVP)
|
|
- Hosting the console in the state-hub dashboard
|
|
- Task lifecycle UI (create/assign/close tasks)
|
|
- Replacing Temporal UI for workflow debugging (deep-link only)
|
|
|
|
## Architecture sketch
|
|
|
|
```
|
|
Operator (browser / curl)
|
|
│ port-forward svc/actcore-api
|
|
│ header: X-Operator-Token (or agreed scheme)
|
|
▼
|
|
actcore-api
|
|
GET /ops/automations
|
|
GET /ops/automations/status?since=&until=
|
|
GET /ops/automations/{id}
|
|
GET /ops/automations/{id}/runs
|
|
POST /ops/automations/{id}/trigger
|
|
POST /ops/automations/{id}/enable|disable|pause|unpause
|
|
GET /ops/ui/… (static/HTMX after API stable)
|
|
│
|
|
├─ activity_core.automation_status + inventory
|
|
├─ activity_runs + activity_definitions
|
|
├─ Temporal schedule describe / pause
|
|
└─ audit (progress event or ops_audit; no secrets)
|
|
```
|
|
|
|
Deep-links into State Hub / edge when evidence lives there (G9-aware).
|
|
|
|
## Auth policy (MVP)
|
|
|
|
- Mutations require an operator token when
|
|
`ACTIVITY_CORE_OPERATOR_TOKEN` (or ESO-backed secret) is configured.
|
|
- If the token is **unset** under production posture, **refuse mutations**
|
|
(fail-closed). Do not leave unauthenticated Run now on a reachable API.
|
|
- Token never logged or returned by API.
|
|
- Access path: `kubectl -n activity-core port-forward` to the API Service.
|
|
- Ingress + OIDC is an explicit follow-up, not an MVP gate.
|
|
- Credential custody via `warden route` / OpenBao — never git, chat, or workplan.
|
|
|
|
## Mutation policy (MVP)
|
|
|
|
| Action | Behavior |
|
|
| --- | --- |
|
|
| Run now | Wrap existing trigger; return `workflow_id`; UI confirms for side-effect defs |
|
|
| Disable / pause | Coherent model: definition enabled flag and/or Temporal schedule paused |
|
|
| Enable / unpause | Inverse |
|
|
| Change cron / timezone | **Out of MVP** — definition files + sync; UI shows schedule read-only |
|
|
|
|
## Tasks
|
|
|
|
## Task: Product brief and SCOPE clarification
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "697113b1-793e-59b4-a86c-ecb22ad127b6"
|
|
```
|
|
|
|
1. Write a short product brief (personas, jobs-to-be-done, non-goals) in this
|
|
workplan or `docs/` if it outgrows the file.
|
|
2. Update `SCOPE.md`: **operator automation console is in scope**; end-user
|
|
task management remains out of scope. Clarify G10 (auth MVP vs public access).
|
|
3. Cross-link WP-0018/0019 as the evidence/inventory contracts this console wraps.
|
|
|
|
**Done when:** SCOPE and this workplan tell one story; non-goals are explicit.
|
|
|
|
## Task: Operator token auth and fail-closed mutations
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "0e709610-2599-593c-9341-1ca5b6ddd23e"
|
|
```
|
|
|
|
1. Design operator token check (e.g. `X-Operator-Token` header compared to
|
|
env/secret) for all `/ops` **mutations**.
|
|
2. Fail-closed: when production requires auth and token is missing/misconfigured,
|
|
mutations return 401/403 (not silent open).
|
|
3. Document env name, ESO/bootstrap option, and port-forward access in runbook.
|
|
4. Never put the token value in git, logs, or State Hub detail.
|
|
|
|
**Done when:** unauthenticated mutation is rejected under the configured posture;
|
|
runbook describes custody without leaking secrets.
|
|
|
|
## Task: REST inventory and status window
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "8adf73b7-d23e-5110-8dc4-a22fc0bde448"
|
|
```
|
|
|
|
1. `GET /ops/automations` — inventory equivalent to WP-0019 (cron/scheduled defs,
|
|
enabled, schedule expression, Temporal schedule id / paused hints).
|
|
2. `GET /ops/automations/status?since=&until=` — wraps WP-0018 automation-status
|
|
semantics (JSON).
|
|
3. `GET /ops/automations/{id}` — single definition + schedule visibility.
|
|
4. Prefer reusing `activity_core.automation_status` / inventory modules; do not
|
|
fork a second evidence contract.
|
|
|
|
**Done when:** curl against the API matches CLI status/inventory for the same
|
|
window within documented field mapping.
|
|
|
|
## Task: REST run history and evidence summary
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T04
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "26d50f76-67cc-5719-a94a-b2de9c1a1495"
|
|
```
|
|
|
|
1. `GET /ops/automations/{id}/runs?since=` (and optional global runs list).
|
|
2. Each run: `run_id`, `scheduled_for`, `fired_at`, `tasks_spawned`, version,
|
|
and evidence pointers when known (progress event types/ids, report paths).
|
|
3. No secrets, raw LLM output, or full prompts in responses.
|
|
|
|
**Done when:** operator can list recent runs for a definition without SSH/SQL.
|
|
|
|
## Task: REST Run now and enable/disable/pause with audit
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T05
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "aff1ddc5-4e78-5f6f-98da-2b2e0af5f2f9"
|
|
```
|
|
|
|
1. `POST /ops/automations/{id}/trigger` — wrap existing trigger path; require
|
|
operator token.
|
|
2. `POST .../enable|disable|pause|unpause` — one coherent model documented in
|
|
the PR (definition flag vs Temporal pause; prefer both consistent).
|
|
3. Audit trail: who/when/what (operator label optional; never store token). Prefer
|
|
State Hub progress `event_type` or a small `ops_audit` table.
|
|
4. Side-effect definitions (e.g. forgejo prune apply): document confirmation
|
|
expectation for UI; API may accept an explicit `confirm_side_effect=true`.
|
|
|
|
**Done when:** authenticated Run now returns `workflow_id` and produces run
|
|
evidence; pause/disable visible in inventory/status; unauthenticated mutate fails.
|
|
|
|
## Task: Contract and security tests
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T06
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "0c07917b-b195-5cbe-a739-bd3a1ae752bb"
|
|
```
|
|
|
|
1. Contract tests for inventory/status/runs JSON shapes.
|
|
2. Auth tests: missing token → reject mutations; valid token → allow.
|
|
3. Response fixtures scanned so secrets/token values never appear.
|
|
|
|
**Done when:** CI/unit tests cover the above without a live cluster.
|
|
|
|
## Task: Thin operator UI on actcore-api
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T07
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "d2002e29-a1c5-54ff-b2a8-e63db7fe22bb"
|
|
```
|
|
|
|
1. Static/HTMX (or minimal SPA) served from actcore-api under `/ops/ui/`.
|
|
2. Screens: automation list, status window, run history, Run now + enable/pause
|
|
actions (token supplied via header or session form — no token in URLs).
|
|
3. Schedule fields **read-only** in MVP.
|
|
4. Confirm dialog for side-effect activities.
|
|
|
|
**Done when:** usable via port-forward for the MVP jobs-to-be-done.
|
|
|
|
## Task: Prod access path (no public Ingress)
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T08
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "3b595a46-38e0-51e1-bfee-814de684f8f5"
|
|
```
|
|
|
|
1. Document port-forward (or equivalent) for railiance01.
|
|
2. Wire operator token into runtime secret path (bootstrap or ESO) without
|
|
committing values.
|
|
3. Explicitly **do not** add public Ingress in this workplan.
|
|
4. Optional: NetworkPolicy note that API remains ClusterIP.
|
|
|
|
**Done when:** runbook has a copy-paste operator access path; G10 public access
|
|
still deferred with a clear follow-up note.
|
|
|
|
## Task: Runbook daily checklist mapping
|
|
|
|
```task
|
|
id: ACTIVITY-WP-0024-T09
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "36a3cac3-388b-5f58-94ca-814e494d08f3"
|
|
```
|
|
|
|
1. Map “How did automations go since Sunday?” to `/ops/automations/status` and UI.
|
|
2. Map “Run X now” and “pause X” to mutation endpoints.
|
|
3. Keep CLI (`make automation-status`) as valid offline/alternate path.
|
|
|
|
**Done when:** runbook section points operators at console first, CLI second.
|
|
|
|
## Success criteria
|
|
|
|
- [x] `GET /ops/automations/status?since=…` matches CLI automation-status semantics
|
|
- [x] Operator can list last N runs for a definition without SSH/SQL
|
|
- [x] Authenticated Run now returns workflow_id and produces `activity_runs` evidence
|
|
- [x] Pause/disable visible in inventory/status and Temporal schedule state
|
|
- [x] Unauthenticated mutation returns 401/403 when token is configured
|
|
- [x] Thin UI usable via port-forward for the above flows
|
|
- [x] Runbook documents token custody without secrets in git
|
|
|
|
## Implementation order
|
|
|
|
1. T01 SCOPE/brief
|
|
2. T02 auth + T03/T04/T05 API (before UI)
|
|
3. T06 tests green
|
|
4. T07 thin UI
|
|
5. T08/T09 docs + prod token wiring
|
|
6. Deploy railiance01 image; no Ingress
|
|
|
|
## References
|
|
|
|
- Plan: session plan “Operator dashboard for activity-core scheduled automations”
|
|
- `src/activity_core/automation_status.py`, `src/activity_core/api.py`
|
|
- `docs/runbook.md`, `SCOPE.md` G10
|
|
- ACTIVITY-WP-0018, ACTIVITY-WP-0019, ACTIVITY-WP-0021
|
|
|
|
## Closeout 2026-07-21
|
|
|
|
Implemented `/ops` console: auth (fail-closed operator token), inventory/status/runs
|
|
REST, Run now + enable/disable/pause with audit buffer + optional State Hub progress,
|
|
thin HTML UI at `/ops/ui`, runbook + k8s access docs. Tests: `tests/test_ops_auth.py`,
|
|
`tests/test_ops_console_api.py`.
|