--- id: ACTIVITY-WP-0030 type: workplan title: "Daily bounded SBOM catch-up via sbom-nexus" domain: infotech repo: activity-core status: finished owner: grok topic_slug: infotech created: "2026-08-18" updated: "2026-08-22" parent_workplan: CUST-WP-0062 related: - CUST-WP-0062 - ACTIVITY-WP-0022 - ADHOC-2026-06-01 state_hub_workstream_id: "db3173ee-aaed-51dc-b674-af83f6a5f3fd" --- # Daily bounded SBOM catch-up via sbom-nexus ## Goal Replace `weekly-sbom-staleness` / `flag-stale-sbom` with a **daily** ActivityDefinition that: 1. Evaluates fleet SBOM freshness through **sbom-nexus** (one ranked catch-up call, not a per-repo walk and not a `for_each` over every stale repo). 2. **Updates N** SBOM snapshots for the repos that have lacked a current SBOM the longest. **N defaults to 3** (`catch_up_limit`). Parent coordination: **CUST-WP-0062**. Parked 2026-08-18 — do not start until that parent is unblocked. Do not enable this schedule until the catch-up API exists. ## Current failure mode Monday 2026-08-17 prod fire spawned **75** `Run SBOM rescan for {slug}` tasks. The rule is `for_each: context.repos.repos` where `context.repo.sbom_age_days > 30`. On 2026-08-18 the hub listed **111 / 111** repos stale or never scanned. The weekly check reports the backlog; it does not close it. `repo_sbom_status` bulk mode already uses `GET /repos/` (age from `last_sbom_at`) rather than N `/sbom/{slug}` calls. That is still the wrong shape for catch-up: the worker materialises every repo, then emits one task each. The replacement must ask sbom-nexus for **only N targets** and then **ingest** them. ## Tasks ### Draft definition and resolver contract ```task id: ACTIVITY-WP-0030-T01 status: done priority: high state_hub_task_id: "745c9960-4c8b-5c1f-a8b2-a30db51a0364" ``` Write `activity-definitions/daily-sbom-catchup.md` (`enabled: false` until cutover): weekday or daily cron, Berlin morning after triage is fine. Context source: sbom-nexus `catch_up` with `limit` default 3. Deterministic progress (`event_type: sbom_catchup`) listing selected / updated / skipped. No `for_each` over the full stale set. Resolver contract (implement against a test double until CUST-WP-0062-T03 lands): - input: `{limit: 3}` - output: `{repos: [...], stale_count, never_count, total_count, limit}` - each repo: `repo_slug`, `last_sbom_at`, `sbom_age_days`, `has_sbom`, `checkout_available` if known Done 2026-08-21 against a test double — CUST-WP-0062-T03 has not landed, so there is no live nexus yet and the definition stays `enabled: false`: - `activity-definitions/daily-sbom-catchup.md` — weekdays 09:15 Berlin, one `sbom-nexus / catch_up` source bound to `context.catchup`, **no rule block** (`tasks_spawned` stays 0 by construction), deterministic `sbom_catchup` progress sink. - `src/activity_core/context_resolvers/sbom_nexus.py` — source type `sbom-nexus`, query `catch_up`, `GET /sbom/catch-up?limit=N` against `SBOM_NEXUS_URL`. Read-only; ingest is T02. Limit is bounded 1..25 and the response is truncated to it so an over-long reply cannot widen T02's side-effect. - `_sbom_catchup_report` in `rules/executor.py` — the existing deterministic builder only special-cased `context.repos`, which would have emitted a contentless progress event. The new branch names selected repos and reads `updated` / `skipped` from context when T02 populates them. - `tests/test_sbom_nexus_context_resolver.py` — 17 tests: contract shape, default N=3, bounding, truncation, partial-entry normalisation, malformed responses, progress content, and definition boundedness. ### Implement ingest side-effect for N targets ```task id: ACTIVITY-WP-0030-T02 status: done priority: high state_hub_task_id: "fb121cc1-bd1a-5ad3-82de-601b047ff628" ``` Wait on CUST-WP-0062-T03. Bounded side-effect (SCOPE: declared purpose only): for each of the N repos, call sbom-nexus ingest using the registered checkout when present. Record skip reasons (`no-checkout`, `no-manifest`, `ingest-error`) in the run artefact and progress event. Do not spawn Forgejo issues (ACTIVITY-WP-0022). Do not retry a recorded skip as if it were still the oldest never-scanned repo. Done 2026-08-22 after SBOM-WP-0002 delivered the live Nexus contracts. The resolver truncates before writes, uses explicit `apply: true`, records `no-checkout` directly, and turns ingest/transport failures into terminal `ingest-error` skips. Focused coverage proves read-only default behavior, success, skip, error, and at-most-N processing. ### Retire weekly task flood ```task id: ACTIVITY-WP-0030-T03 status: done priority: high state_hub_task_id: "fa265eb9-8667-5e44-a78a-08693d20332f" ``` Emergency containment began 2026-08-20 under ACTIVITY-WP-0031: set `weekly-sbom-staleness` `enabled: false` in source and production before the bounded replacement is available. Keep or drop the weekly summary report only if it no longer emits one task per repo. After the daily job is proven on railiance01, update the runbook / playbook and prove one Monday window with zero SBOM task spawns and a successful daily catch-up the same week. Done 2026-08-22: production inventory shows `weekly-sbom-staleness` disabled and its Temporal schedule paused while `daily-sbom-catchup` is enabled and active. Both production proof fires spawned zero tasks. ### Prod enable and evidence ```task id: ACTIVITY-WP-0030-T04 status: done priority: medium state_hub_task_id: "6911e1f6-74fb-5179-8e15-4a9dc81b648b" ``` Project the definition into `k8s/railiance/20-runtime.yaml`, sync schedules, and capture prod evidence via `./scripts/prod_automation_status.sh` plus the `sbom_catchup` progress event. Confirm `tasks_spawned` is 0 and N updates (or documented skips) appear per fire. Done 2026-08-22. The Railiance ConfigMap projection is enabled with limit 3, `SBOM_NEXUS_URL` points to the in-cluster service, and the Nexus NetworkPolicy admits only the declared Activity Core caller in addition to State Hub. Two manual production fires processed six distinct repositories as terminal `no-checkout` skips, emitted two `sbom_catchup` progress events, and spawned zero tasks. See `docs/evidence/ACTIVITY-WP-0030-daily-sbom-catchup-2026-08-22.md`. ## Acceptance - [x] Daily definition exists; weekly flood is off - [x] Evaluation is one sbom-nexus catch-up call - [x] Each fire updates at most N repos (default 3) - [x] Progress names the repos and skip reasons - [x] Railiance01 evidence for at least two successful daily fires