Implement post-triage operational hardening

This commit is contained in:
2026-06-04 12:15:07 +02:00
parent 8a33ec44b6
commit 20d4f26166
11 changed files with 775 additions and 31 deletions

View File

@@ -101,17 +101,58 @@ A Rule's action block specifies:
```yaml
action:
task_template: tasks/{template-slug}.md # required
target_repo: event.attributes.repo_slug # expression — attribute access only
priority: high # high | medium | low | literal
labels: ["onboarding", "security"] # literal list
due_in_days: 7 # optional, integer literal
task_template: "Run SBOM rescan for {context.repo.repo_slug}"
target_repo: context.repo.repo_slug
priority: medium
labels: ["sbom", "security", "{context.repo.repo_slug}"]
due_in_days: 7
```
`target_repo` and similar fields accept simple attribute access expressions
(no boolean logic — just path traversal). This allows dynamic routing to the
correct issue-core instance without arbitrary expression evaluation in action
fields.
`action.task_template` is the emitted task title template. It is not a path to a
repo-local file. Older design notes and the legacy `tasks/*.md` directory use
"task template" for materialized task-body templates; that is a separate legacy
surface. To avoid surprise, new rule actions should treat `task_template` as
`title_template` semantics until the field can be renamed in a schema-breaking
revision.
Action fields accept two deterministic rendering forms:
- Whole-field paths: if the whole string is a path like
`context.repo.repo_slug` or `event.attributes.repo_slug`, the rendered value
keeps the original scalar/list/object shape from that path. This is the
correct form for `target_repo` and other fields that should not become prose.
- Scalar placeholders: strings may include `{context.foo}` or `{event.foo}`
placeholders. Each placeholder must resolve to a scalar. Lists and objects are
rejected rather than stringified, which prevents accidental JSON blobs or
untrusted text from being embedded into task titles.
Unsafe action cases are rejected:
- Any action path outside `context.*` or `event.*`.
- Any path containing calls, indexing, arithmetic, filters, or boolean logic.
- Placeholder values that resolve to lists or objects.
- `for_each` values that are not a whole-field `context.*` or `event.*` path to
a list.
- `bind_as` names that are not simple identifiers.
Per-item rule expansion is explicit:
```yaml
for_each: context.repos.repos
bind_as: repo
condition: 'context.repo.sbom_age_days > 30'
action:
task_template: Run SBOM rescan for {context.repo.repo_slug}
target_repo: context.repo.repo_slug
priority: medium
labels: ["sbom", "security", "automated"]
```
The weekly SBOM staleness definition is the canonical pattern. The State Hub
bulk resolver exposes all repository entries at `context.repos.repos`, the rule
binds each item as `context.repo`, and the strict staleness definition is
`context.repo.sbom_age_days > 30`. Thirty days exactly is not stale; thirty-one
days is stale.
#### Evaluation semantics