Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
128 lines
4.9 KiB
Markdown
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.
|