generated from coulomb/repo-seed
The state-hub resolver was calling GET /sbom/status?repo={slug}, which State
Hub does not expose. Real SBOM routes are /sbom/, /sbom/{slug},
/sbom/snapshots/, /sbom/snapshots/{id}, /sbom/ingest/, /sbom/report/licences/.
The weekly-sbom-staleness ActivityDefinition was passing params {repos: all}
and the resolver was reading params.get("repo_slug", ""), so the URL
collapsed to /sbom/status?repo= and 404'd. _fetch_json swallowed the error,
the rule context.repos.sbom_age_days > 30 evaluated against {} and never
matched, and the weekly SBOM check has been a silent no-op for as long as
the route mismatch has existed.
Resolver now supports two modes selected by params:
- single-repo: {repo_slug: foo} → GET /sbom/{foo}, returns
{repo_slug, last_sbom_at, sbom_age_days, has_sbom}
- bulk: {repos: all} → GET /repos/, computes per-repo age, returns the
worst repo's fields hoisted to the top of the result alongside
stale_count, total_count, worst_* fields, and the full per-repo list
Never-scanned repos get a 99999 sentinel age so threshold rules treat
them as very stale without forcing the rule to special-case None.
Hoisting the worst entry to the top preserves the existing rule
expression context.repos.sbom_age_days > 30 (and target_repo:
context.repos.repo_slug, though that field is a separate interpolation
gap tracked as ADHOC-2026-06-01-T02). The integration tests'
aspirational per-repo iteration model is left intact.
Live validation against State Hub on 2026-06-01:
- single: activity-core → 36 days since 2026-04-26 ingest
- bulk: 48 repos total, 46 stale (>30d), worst is info-tech-canon (never
scanned), rule expression evaluates True
Tests: 120 passed, 1 skipped.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
49 lines
1.6 KiB
Markdown
49 lines
1.6 KiB
Markdown
---
|
|
id: weekly-sbom-staleness
|
|
name: Weekly SBOM Staleness Check
|
|
enabled: true
|
|
owner: custodian-agent
|
|
governance: custodian
|
|
status: active
|
|
trigger:
|
|
type: cron
|
|
cron_expression: "0 9 * * 1"
|
|
timezone: Europe/Berlin
|
|
misfire_policy: skip
|
|
context_sources:
|
|
- type: state-hub
|
|
query: repo_sbom_status
|
|
params:
|
|
repos: all
|
|
bind_to: context.repos
|
|
# Resolver returns a summary keyed off the worst repo so the rule expression
|
|
# below can match without comprehensions (the sandboxed evaluator does not
|
|
# support them). See _repo_sbom_status in context_resolvers/state_hub.py.
|
|
---
|
|
|
|
# Weekly SBOM Staleness Check
|
|
|
|
Runs every Monday at 09:00 Berlin time. Checks all tracked repositories for
|
|
SBOM staleness and flags any repository whose SBOM is older than 30 days.
|
|
|
|
```rule
|
|
id: flag-stale-sbom
|
|
condition: 'context.repos.sbom_age_days > 30'
|
|
action:
|
|
task_template: tasks/sbom-rescan.md
|
|
target_repo: context.repos.repo_slug
|
|
priority: medium
|
|
labels: ["sbom", "security", "automated"]
|
|
```
|
|
|
|
NOTE: in the production bulk-mode resolver path the condition matches against
|
|
the **worst** repo's age (the resolver hoists the worst entry's
|
|
`sbom_age_days`, `repo_slug`, `last_sbom_at`, `has_sbom` to the top of
|
|
`context.repos` alongside the per-repo list and summary counts). The rule
|
|
therefore fires at most once per workflow run, not once per stale repo. The
|
|
aspirational per-stale-repo task fan-out is exercised by the integration
|
|
tests' simulated pipeline but is not delivered by the current workflow —
|
|
landing it requires (a) per-iteration context binding in the workflow and
|
|
(b) `context.*` interpolation in rule action fields. Both are tracked as
|
|
`ADHOC-2026-06-01-T02`.
|