--- id: daily-sbom-catchup name: Daily SBOM Catch-up enabled: true owner: custodian-agent governance: custodian status: active trigger: type: cron cron_expression: "15 9 * * 1-5" # weekdays 09:15, after the daily triage window timezone: Europe/Berlin misfire_policy: skip context_sources: - type: sbom-nexus query: catch_up required: true params: limit: 3 # catch_up_limit — operator knob, not a nexus constant apply: true # declared bounded side-effect; manual runs require confirmation bind_to: context.catchup # One ranked call returns only the N repos that have lacked a current SBOM the # longest (never-scanned first) plus fleet counts. There is deliberately no # `for_each` over the stale set: the 2026-08-17 fire emitted 75 tasks that way. --- # Daily SBOM Catch-up > **Enabled after production proof on 2026-08-22.** Two bounded manual fires > processed three distinct repositories each, spawned zero tasks, and persisted > terminal `no-checkout` evidence. The superseded weekly schedule remains off. Replaces `weekly-sbom-staleness` / `flag-stale-sbom` (ACTIVITY-WP-0030). The weekly check reported the backlog — 111 / 111 repos stale on 2026-08-18, 93 never scanned — and spawned one task per stale repo, so it could never close it. This definition **updates N instead of reporting N**. Runs weekdays at 09:15 Berlin, after the daily triage window. ## Contract Context source `sbom-nexus / catch_up` (`context_resolvers/sbom_nexus.py`): | Direction | Shape | | --- | --- | | input | `{"limit": 3}` | | output | `{repos, selected_count, stale_count, never_count, total_count, limit}` | | each repo | `repo_slug`, `last_sbom_at`, `sbom_age_days`, `has_sbom`, `checkout_available` | Ranking (never-scanned first, then oldest `last_sbom_at`) belongs to sbom-nexus. The adapter truncates to `limit` so an over-long response can never widen the bounded side-effect below. ## Task emission None. This definition carries **no `rule` block** — that is the point of the replacement. `tasks_spawned` must stay 0 on every fire, and no Forgejo issues are emitted (ACTIVITY-WP-0022). ## Evidence ```instruction id: daily-sbom-catchup-report trusted_fields: [] model: deterministic temperature: 0 max_tokens: 1 prompt: | Deterministic SBOM catch-up report from context.catchup (no LLM). output_schema: "" review_required: false report_sinks: - type: state-hub-progress event_type: sbom_catchup author: activity-core topic_id: cee7bedf-2b48-46ef-8601-006474f2ad7a ``` The progress event names the repos selected, updated, and skipped with a reason (`no-checkout`, `no-manifest`, `ingest-error`), plus the fleet counters so `never_count` can be watched declining day over day. ## Bounded side-effect (ACTIVITY-WP-0030-T02 / ACTIVITY-WP-0033) `params.apply: true` declares the write, but context resolution remains read-only. The workflow records the already-truncated N targets in Temporal history, then a dedicated activity applies exactly that fixed set. Completed per-repository outcomes are heartbeated so an ordinary activity retry resumes after them instead of selecting or writing a second batch. A target with an explicitly unavailable checkout is recorded through `POST /sbom/{slug}/skip` as `no-checkout`; other targets use the terminal ingest route, which returns `ingested`, `no-manifest`, or `ingest-error`. Each request sends a stable per-run/per-repository `Idempotency-Key`. Transport failures, timeouts, and malformed responses fail visibly and are never converted into a synthetic skip because the remote write may have committed. Nexus must enforce the operation key to close the remaining crash window between a committed POST and Activity Core's next heartbeat. The resulting `updated` and `skipped` arrays are part of the run context and progress report. There is no task or issue emission. ## Enable checklist 1. CUST-WP-0062-T02/T03 done: `sbom-nexus` stood up, `GET /sbom/catch-up` returns oldest-N in one call. **Done 2026-08-22.** 2. `SBOM_NEXUS_URL` reachable from the railiance01 worker. 3. ACTIVITY-WP-0030-T02 ingest side-effect implemented and dry-run proven. 4. `weekly-sbom-staleness` confirmed off in source **and** production (ACTIVITY-WP-0030-T03). 5. Project into `k8s/railiance/20-runtime.yaml`, `enabled: true`, sync schedules, capture evidence via `./scripts/prod_automation_status.sh` (ACTIVITY-WP-0030-T04).