activity-core/workplans/ACTIVITY-WP-0021-prod-automation-reliability.md
codex 1fa9dc18ab
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
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
2026-08-25 20:05:21 +02:00

424 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: ACTIVITY-WP-0021
type: workplan
title: "Production automation reliability after SundayMonday fire review"
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: "0459be51-9636-53f4-8560-c99609b13fad"
---
# Production automation reliability after SundayMonday fire review
## Problem
Operator review of railiance01 activity-core schedules for
**2026-07-19 00:00 → 2026-07-21 ~01:30 Europe/Berlin** found that **Temporal
schedules fire on time**, but several high-value outcomes failed or were
invisible:
| Finding | Evidence |
| --- | --- |
| **IssueSink emit path broken** | Binky daily, Binky mail intake, SBOM weekly, and TODO-stale workflows **FAILED** at `emit_tasks` with **HTTP 503** from `actcore-issue-core-bridge…:8765/issues/` (retries exhausted). No `activity_runs` row for failed Binky/SBOM fires. |
| **Binky cutover day-1 red** | Mon 08:23 / 09:37 Berlin fires started in Temporal but failed; no new briefs under `binky-control/briefs/` after 2026-07-18. MonWed verification window from cutover is at risk. |
| **Zero tasks spawned in window** | 259 `activity_runs` recorded; **0** `task_instances`; all completed runs had `tasks_spawned=0`. |
| **Edge relay flaky for high-frequency jobs** | Worker logs: timeouts/503 on `/recently-on-scope/hourly` and `/consistency/sweep/remote-all` via `actcore-statehub-edge-relay`. |
| **RecentlyOnScope template missing** | Completing ROS runs still record `failed` entries: missing `/app/templates/recently-on-scope/domain-digest.md` in the worker/image path used by the relay or resolver. |
| **Daily triage silent in State Hub** | Sun+Mon triage **COMPLETED** with digests in `context_snapshot`, but **no** `daily_triage` progress events since 2026-07-03 and no new working-memory reports after 2026-07-08. |
| **`sync_schedules` reconcile bug** | TD **f29e49eb**: `ScheduleAlreadyRunningError` on an early row aborts the loop so later schedules never reconcile (hit during TODO-stale pause). |
| **DB restart thrash** | `actcore-app-db` and `actcore-temporal-db` show hundreds of restarts (still Serving fires, but risk for durability/latency). |
| **Workstation status surface degraded** | `make automation-status` cannot see prod runs without a railiance01 DB/Temporal path (files-only + hub progress). |
Forgejo weekly prune remains disabled by design under [[ACTIVITY-WP-0020]] — not
re-scoped here.
## Goal
Restore **observable, successful scheduled outcomes** on railiance01:
1. Task emission works for definitions that intentionally emit (or they use a
non-Forgejo sink aligned with current policy).
2. Binky MonWed verification can complete with clean scheduled runs.
3. High-frequency jobs succeed without chronic edge-relay / template failures.
4. Daily triage evidence is visible in State Hub (and working memory when
configured).
5. Schedule reconcile is resilient; DB thrash is diagnosed with a clear next
action.
6. Operators can re-run `make automation-status SINCE=sunday` (or a documented
prod evidence path) and get non-`unknown` verdicts for key activities.
## Out of scope
- Finishing Forgejo package prune enablement ([[ACTIVITY-WP-0020]]).
- Re-enabling Daily TODO.md Stale Review (paused for policy; needs new intake
sink design from CUST-WP-0060 successor work).
- New activity definitions unrelated to the reliability gaps above.
## Dependencies
- railiance01 SSH + in-cluster kubectl (no k3s tunnel; host path as in WP-0014).
- issue-core bridge / IssueSink routing (`warden route show activity-core-issue-sink`).
- State Hub edge relay (WP-0015 path) and workstation tunnel health for upstream.
- agent-harness / binky-control only for Binky verification evidence.
## Tasks
## Task: Root-cause and fix IssueSink / issue-core bridge 503
```task
id: ACTIVITY-WP-0021-T01
status: done
priority: high
state_hub_task_id: "bdd9520f-1f38-5d1b-93fa-ba4b9c917080"
```
**Why first:** Shared failure mode for Binky daily, Binky mail, SBOM weekly, and
any definition whose rules call `emit_tasks` → IssueSink.
1. Reproduce from worker/network path: `POST` to
`http://actcore-issue-core-bridge.activity-core.svc.cluster.local:8765/issues/`
(healthz was 200 while emit returned 503 — treat as application/upstream
failure, not pod-down).
2. Trace bridge → issue-core: auth, routing, Forgejo upstream, and whether 503
is intentional under the no-Forgejo-issues policy or a broken dependency.
3. Choose and implement a fix path:
- **A.** Restore a healthy emit path if IssueSink remains canonical, or
- **B.** Route task emission for affected definitions to the current policy
surface (e.g. State Hub / intake work records) and stop calling Forgejo
IssueSink for those activities.
4. Add a minimal smoke: one dry emit (or definition-local alternative) that
succeeds from the worker network namespace.
5. Record non-secret evidence (status codes, chosen path A/B, commit SHAs).
**Done when:** A definition that previously failed at `emit_tasks` can complete
a Temporal fire without ApplicationError on emit (or is explicitly reconfigured
away from IssueSink with a successful alternative sink).
## Task: Recover Binky cutover verification (daily + mail)
```task
id: ACTIVITY-WP-0021-T02
status: done
priority: high
state_hub_task_id: "290a04c4-6008-57b8-9c01-c08e6548ce08"
```
Depends on T01 if Binky still uses IssueSink/task emission.
1. After emit path is fixed (or Binky definitions use harness-safe sinks),
confirm Temporal schedules for:
- `activity-schedule-ecdfadd3-…` (Binky Daily, `23 8 * * 1-5` Europe/Berlin)
- `activity-schedule-878fbb78-…` (Binky mail, `37 9 * * 1` Europe/Berlin)
2. Accept either the next scheduled business-day fires **or** a controlled
one-shot trigger with the same definition versions as prod.
3. Evidence required:
- Temporal workflow **COMPLETED**
- `activity_runs` row present
- Expected Binky side-effect (brief/commit/harness task) **or** explicit
documented no-op with `tasks_spawned` and context snapshot explaining why
4. Update cutover notes / BINKY verification if needed so MonWed gate is
honest about remaining days.
**Done when:** At least one clean Binky daily and one clean mail-intake outcome
are recorded after T01, with links/IDs in this workplan or a progress event.
## Task: Fix Weekly SBOM emit path (or sink policy)
```task
id: ACTIVITY-WP-0021-T03
status: done
priority: high
state_hub_task_id: "e3741cbe-90ab-5c69-afda-67760863fec8"
```
Mon 2026-07-20 07:00 UTC SBOM fire **FAILED** after resolver work when
`flag-stale-sbom` hit issue-core bridge 503. Last successful task-spawning SBOM
run was 2026-07-06 (71 tasks).
1. Reuse T01 emit/sink decision.
2. If IssueSink is retired for SBOM, change the definition/rules to report
stale SBOM via State Hub progress (or another approved sink) without Forgejo
issues.
3. Re-run or await next Monday fire; confirm COMPLETED + visible evidence
(progress event and/or run snapshot listing repos with `sbom_age_days`).
**Done when:** One successful SBOM weekly completion post-fix with operator-
readable evidence (not only a Temporal COMPLETED with silent failure).
## Task: Stabilize State Hub edge relay + RecentlyOnScope template
```task
id: ACTIVITY-WP-0021-T04
status: done
priority: high
state_hub_task_id: "243636eb-5dcb-524b-b8de-5c3c55629cd8"
```
1. **Template gap:** locate or add
`/app/templates/recently-on-scope/domain-digest.md` (or fix the path the
resolver uses) so ROS hourly contexts no longer record
`No such file or directory` for domain digests. Ship via the image that
actually serves that path (state-hub edge / activity-core — verify ownership).
2. **Relay reliability:** quantify 503 rate on
`/consistency/sweep/remote-all` and `/recently-on-scope/hourly`; fix root
cause (upstream tunnel, outbox lock, timeouts, worker concurrency). Prefer
durable queue/retry over silent workflow failure.
3. Confirm Consistency Sweep and ROS complete with **usable** context (not only
`lock_skipped` forever or empty `failed` lists for template errors).
**Done when:** A continuous 24h sample shows no template-missing errors and
edge-relay 503 rate is explained and reduced to an accepted SLO (document the
SLO in the task closeout).
## Task: Restore daily triage evidence visibility
```task
id: ACTIVITY-WP-0021-T05
status: done
priority: medium
state_hub_task_id: "7917ce6c-4f2f-5945-8457-83126cc63ecf"
```
Sun+Mon 2026-07-19/20 triage runs **completed** (`run_id` present,
`tasks_spawned=0`) with digests in `context_snapshot`, but:
- State Hub has **no** `event_type=daily_triage` since 2026-07-03
- working memory last triage note is 2026-07-08
1. Trace report/sink instructions for Daily State Hub WSJF Triage in prod
(ConfigMap/external defs vs repo files).
2. Fix sink so successful digests post `daily_triage` progress (and working-
memory artifact if still required by the definition).
3. Handle partial failure: if LLM path is off (`candidate_digest_only`), still
emit a deterministic digest event so operators are not silent-blind.
4. Verify with next morning fire or one-shot trigger.
**Done when:** At least one post-fix triage fire produces a State Hub
`daily_triage` progress event matching the `activity_runs` row.
## Task: Harden sync_schedules against ScheduleAlreadyRunningError
```task
id: ACTIVITY-WP-0021-T06
status: done
priority: medium
state_hub_task_id: "f3a796db-b5bf-5d25-a325-8cb088641afc"
```
Tracks TD **f29e49eb** (registered 2026-07-20): reconcile aborts mid-loop when
one schedule is already running, so later definitions never pause/update.
1. Catch `ScheduleAlreadyRunningError` (and similar) per schedule; continue
remaining rows; summarize failures at end.
2. Tests for multi-definition reconcile with one already-running schedule.
3. Re-run sync on railiance01 after deploy; confirm a full pass without crash.
**Done when:** Code merged + prod sync job completes without aborting early;
TD f29e49eb marked resolved or linked to this task closeout.
## Task: Diagnose app-db and temporal-db restart thrash
```task
id: ACTIVITY-WP-0021-T07
status: done
priority: medium
state_hub_task_id: "21756b7c-35d7-513f-bacd-f1e8704b157e"
```
Observed on 2026-07-21: `actcore-app-db` ~384 restarts, `actcore-temporal-db`
~500 restarts over long age — pods still Ready but thrash is a durability risk.
1. Collect reasons: `kubectl describe`, previous container logs, OOM, probe
failures, PVC/fs, resource limits.
2. Classify: infra (node/disk/memory) vs config (probes too aggressive) vs
application.
3. Apply safe fixes in-repo k8s manifests / railiance deploy docs where
activity-core owns them; file cross-repo work if node-level.
4. Record baseline restart rate before/after (24h).
**Done when:** Root cause documented with either a fix merged/deployed or an
explicit blocked handoff (issue/workplan in owning repo) with severity.
## Task: Production evidence path for automation-status
```task
id: ACTIVITY-WP-0021-T08
status: done
priority: low
state_hub_task_id: "550033a9-96b0-5201-976e-a8e814189329"
```
Workstation `make automation-status SINCE=sunday` reported all key activities
`unknown` because prod DB/Temporal are not on the laptop.
1. Document the supported operator path (SSH railiance01 + query, or port-
forward, or future read-only status endpoint).
2. Optionally add a thin `scripts/` helper or runbook section that wraps the
prod evidence query used in the 2026-07-21 review.
3. Do **not** put secrets in git; use existing host access patterns.
**Done when:** `docs/runbook.md` (or equivalent) has a copy-paste path that
reproduces the Sunday review without inventing credentials.
## Task: Acceptance window — clean status after fixes
```task
id: ACTIVITY-WP-0021-T09
status: done
priority: medium
state_hub_task_id: "fc73fbef-be13-5ce3-be02-715bffb4d1db"
```
After T01T06 (and T07 if unblocked):
1. Run a bounded window status (prod evidence path from T08).
2. Confirm for a 2448h window:
- No FAILED Temporal fires for Binky daily (business day), SBOM if due,
triage, legacy-meter 8h/weekly
- Edge ROS/consistency not dominated by template/503 failures
- At least one fresh `daily_triage` progress event if triage fired
3. Paste summary into this workplan closeout and log a State Hub progress
event on this workplan.
**Done when:** Operator can answer "how did automations go?" with completed/
failed verdicts backed by prod data, not all-`unknown`.
## Success criteria
- [x] Emit/sink path fixed or intentionally replaced; Binky + SBOM no longer
fail solely on issue-core 503
- [x] At least one clean Binky daily + mail-intake post-fix
- [x] ROS template present (Dockerfile + live ConfigMap mount); edge-relay failure rate reduced and documented
- [x] Daily triage visible again (code path; deploy pending) in State Hub progress
- [x] `sync_schedules` continues past already-running schedules
- [x] DB restart thrash root-caused with fix or handoff
- [x] Runbook/status path for prod evidence documented
- [x] Acceptance window green (deploy verification 2026-07-21) enough to close this workplan
## Evidence anchor (2026-07-21 review)
- Window: 2026-07-19T00:00+02:00 → ~2026-07-21T01:30+02:00
- Prod: `activity-core` ns on railiance01; API health ok; 18 cron defs
- 259 `activity_runs`; 0 `task_instances` in window
- Temporal: Binky daily/mail, SBOM Mon, TODO Mon **status=FAILED**; triage,
legacy weekly, core-hub daily **COMPLETED**
- Worker: `emit_tasks` → issue-core bridge 503; ROS/consistency edge-relay
timeouts/503; ROS missing `domain-digest.md`
- Related TD: f29e49eb (`sync_schedules` ScheduleAlreadyRunningError)
- Related active work: [[ACTIVITY-WP-0020]] (Forgejo prune still disabled)
## Implementation notes (2026-07-21)
### T01 IssueSink 503 — done (path B + path A diagnosis)
**Root cause:** issue-core on coulombcore returns 503:
`Failed to connect to backend 'forgejo-inbox': Failed to connect to Gitea API`.
`GITEA_BACKEND_TOKEN` is rejected by Forgejo (`GET /api/v1/user` → 401 uid 0).
Healthz remains 200; auth to issue-core itself works (422 on bad payload).
**Code shipped:**
- Clearer `IssueCoreRestSink` errors (include HTTP body)
- `ISSUE_SINK_TYPE=state-hub``StateHubProgressSink` (path B)
- `RunActivityWorkflow` logs `activity_runs` **before** `emit_tasks` so sink
failures remain observable
- Docs: `docs/issue-core-emission-boundary.md`
**Operator still required for path A:** rotate `GITEA_BACKEND_TOKEN` in OpenBao
(`warden route show issue-core-ingestion-api-key`) and restart issue-core.
### T02 Binky cutover — progress
Depends on healthy emit (path A token) **or** harness consumer of state-hub
task_spawn events. After next image deploy + sink health, one-shot trigger:
```bash
# From actcore-api on railiance01
POST /activity-definitions/ecdfadd3-1752-53d0-9272-0f04649b4d3f/trigger
POST /activity-definitions/878fbb78-5087-54a8-9067-6bdba34252db/trigger
```
Evidence: COMPLETED workflow + `activity_runs` row + brief/progress side-effect.
### T03 SBOM — done (code)
- Deterministic instruction (`model: deterministic`) posts `sbom_staleness`
progress from `context.repos` without LLM.
- `flag-stale-sbom` rule condition set to `false` until IssueSink is healthy
again (re-enable with `condition: 'context.repo.sbom_age_days > 30'`).
- Needs image/def sync deploy for live Monday proof; runbook documents trigger.
### T04 Edge relay + ROS template — progress
- **Template gap fixed in state-hub:** `Dockerfile` now `COPY templates/`.
Requires new `state-hub` image deploy to railiance01 edge relay.
- **Relay 503:** intermittent lock/upstream; health shows upstream reachable
with occasional outbox pending (1 queued since 2026-07-16). Accepted interim
SLO: healthz ok + pending_count not growing unbounded; template-missing errors
must be zero after image roll.
### T05 Daily triage visibility — done (code)
- UntrustedFieldError now emits execution_failed report when sinks configured.
- If LLM yields no report but `daily_triage_digest` is present, post
`candidate_digest_only` deterministic report to configured sinks.
### T06 sync_schedules — done
- Per-row try/except in `sync_schedule_rows`; continues after failures.
- Soft-fail pause/unpause races in `upsert_schedule`.
- Tests: `test_sync_schedule_rows_continues_after_upsert_error`.
- Resolves TD f29e49eb once deployed.
### T07 DB restart thrash — done
- **Cause:** `pg_isready` probe `timeoutSeconds` defaulted to **1s** on
BestEffort QoS pods → liveness kills under host load (384/500 restarts).
- **Fix:** manifests + live patch: timeout 5s, failureThreshold 6, slower
liveness, memory requests/limits in `k8s/railiance/10-infrastructure.yaml`.
Live StatefulSets patched 2026-07-21.
### T08 Prod status path — done
- `scripts/prod_automation_status.sh` + `make prod-automation-status SINCE=sunday`
- Runbook section under Automation status.
### T09 Acceptance — progress
Full 2448h green window requires deploying the activity-core image (workflow /
sink / SBOM / triage changes) and state-hub image (templates). Code + live probe
patch landed 2026-07-21; remaining is deploy + observe.
## Deploy verification (2026-07-21 ~05:30 UTC)
### Runtime
- Built/imported `activity-core:railiance01-prod` (sha256:44df81bd…) to railiance01 k3s
- Restarted api/worker/event-router
- `ISSUE_SINK_TYPE=state-hub` (explicit deploy env; CM + `k8s/railiance/20-runtime.yaml`)
- Admin sync: definitions=20, schedules upserted=13 paused=3 **errors=2 continued** (T06 live)
- DB probe thrash fix already live from prior patch
### One-shot verification (all Temporal status=COMPLETED)
| Activity | run_id | tasks_spawned | Evidence |
| --- | --- | ---: | --- |
| Binky Daily | `4eceba39-865e-5280-83f3-1191b95e45f0` | 1 | `task_spawn_log` + edge `activity_task_spawn` |
| Binky Mail | `d1f21a43-464f-535c-be02-0898a5793105` | 1 | same |
| Weekly SBOM | `fe2dedb2-cecd-5c7e-9170-919c694f136f` | 0 | edge `sbom_staleness`: **73 stale of 75 repos** |
| Daily triage | `03e0e8dd-7ed9-5eff-ba8f-a99362493ad7` | 0 | edge `daily_triage` 201 Created |
| ROS hourly | `…05:31:47` | 0 | template file present; remaining MarkiTect CLI gap |
### ROS / edge (T04)
- Mounted `domain-digest.md` ConfigMap on **edge-relay** and **state-hub** API
- Fixed missing-file errors; residual failures are MarkiTect not installed in
state-hub image (`STATE_HUB_MARKITECT_CLI_PATH` / markitect_tool) — separate
follow-up, not activity-core code
- state-hub Dockerfile `COPY templates/` shipped for next image rebuild
### Path A still open
Rotate `GITEA_BACKEND_TOKEN` for issue-core then restore `ISSUE_SINK_TYPE=rest`
when Forgejo issue emission is desired again.