activity-core/docs/adr/adr-007-bounded-operations.md
tegwick 26934e25b9
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 21s
Enforce bounded operation guardrails
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
2026-08-23 12:31:13 +02:00

128 lines
4.9 KiB
Markdown

---
id: ACT-ADR-007
type: architecture-decision-record
title: "Code-registered bounded operations are the only local mutation exception"
status: accepted
state_hub_decision_id: "2b1f0c01-1d6f-4fdc-b3e7-537ce0a0a1f8"
owner: activity-core
revision: "accepted-1"
last_reviewed: "2026-08-23"
review_interval: 6m
decided_by: Bernd Worsch
date: "2026-08-23"
scope: repo
affects:
- activity-core
- sbom-nexus
- railiance-platform
tags:
- architecture
- activity-core
- bounded-operation
- execution-boundary
---
# ACT-ADR-007: Code-registered Bounded Operations
## Status
**Accepted** (2026-08-23) for ACTIVITY-WP-0035.
## Context
The Event Bridge principle says activity-core answers when, what, and where and
does not execute domain work. Production nevertheless contains three useful
operations whose complete outcome is a small scheduled maintenance action:
- ingest at most three Repo Manager-selected immutable sources into SBOM Nexus;
- prune Forgejo package versions under live-image protection; and
- invoke the fixed CNPG Option A backup tool for an explicit target list.
Historically the latter two ran through a generic `shell` context resolver.
That made a mutating subprocess look like a read and left extension policy to
convention. Removing the operations would recreate bespoke cron; accepting
arbitrary shell would make activity-core a general executor.
## Decision
### 1. Bounded operations are a narrow implementation exception
The governing when/what/where responsibility does not gain a general “how.” A
bounded operation is allowed only when the operation itself is the declared
automation outcome and all admission requirements below are code-reviewable.
The initial allowlist is exactly:
1. `sbom_nexus_ingest`
2. `forgejo_package_prune`
3. `cnpg_option_a_backup`
Adding an operation requires updating the code-owned registry, this ADR (or a
successor), tests, credential route, and evidence contract. A string in a
definition cannot register an operation.
### 2. Admission is fail closed during file sync
Each registry entry declares:
- source type and query;
- whether mutation intent is `apply` or `dry_run` and that it is explicit;
- fixed or maximum target bounds;
- canonical implementation and allowed configuration;
- idempotency and Temporal retry semantics;
- maximum execution timeout;
- credential owner/route; and
- mandatory non-secret evidence mode.
Unknown `shell` queries are refused unless separately registered as read-only.
Known operation queries with missing, malformed, or over-limit safety fields
are refused before database projection or Temporal schedule reconciliation.
### 3. Context resolution is read-only
The workflow first resolves and freezes context. Mutations then run in an
explicit bounded-operation stage and merge only normalized outcomes into the
snapshot before evidence and rule/instruction evaluation.
SBOM selection remains a read in the context phase; its fixed selection is the
input to the operation stage. Package prune and backup have no discovery read
inside activity-core and bind a pending marker until their operation completes.
### 4. Retry behavior is operation-specific
- SBOM ingest uses stable per-run/per-repository idempotency keys and heartbeat
checkpoints, so Activity retries resume the frozen batch.
- Forgejo prune and CNPG backup have no activity-core-verifiable remote
idempotency receipt. Their operation activity therefore has one Temporal
attempt; a failure remains visible for operator reconciliation rather than
risking an automatic second mutation.
This does not preclude future safe retries after the platform tools expose a
durable operation receipt.
### 5. Evidence is mandatory and bounded
Every operation must produce an allowlisted summary through a configured
report/evidence sink. Raw subprocess output, tokens, provider payloads, archive
URLs, kubeconfigs, and credential material are not evidence.
## Rejected alternatives
- **Keep mutating shell resolvers.** Rejected because resolution should be a
replayable read and the generic dispatcher hides mutation admission.
- **Generic command activity.** Rejected because command text/path from a
definition is remote code execution by configuration.
- **Move every operation to a rein.** Rejected for these fixed platform
operations; it adds an agent execution constellation without judgement or
repository work. Operations that exceed this ADR's bounds do belong there.
- **Remove all local operations.** Rejected because it recreates scattered cron
and loses Temporal/evidence guarantees for established maintenance.
## Consequences
- Definition parsing gains a central operation-policy validator.
- The workflow gains an explicit operation stage.
- The generic `shell` resolver becomes read-only.
- Existing definitions migrate without widening targets or permissions.
- The registry is intentionally small and architectural review is required to
expand it.