--- 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.