Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0017
ADR-0017: Governed writers are (binary, config)-attested pure functions, re-executed trusted-side
| field | value |
|---|---|
| Status | Accepted |
| Date | 2026-07-24 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0017-governed-writers-are-attested-pure-functions.md — permalink at bdafa201a (unchanged on master since that commit) |
| Wiki register entry | ADR-0017 (wiki status: Accepted) |
The record
- Status: Accepted
- Date: 2026-07-24
- Spec: SPEC-033 §3
Context
ADR-0001 makes the enforcement boundary a content-exact match between the committed tree and a registry of governed writers. That registry constrains who may produce a delta — it says nothing about what they may produce.
This is a classic confused deputy. A registered writer's output is a function of its configuration, and configurations live in the agent-writable tree:
- The agent edits
.pre-commit-config.yamlto add a hook that emits arbitrary content. - It invokes the registered writer.
- Every resulting delta has a registered producer; the state boundary reports clean.
Real allowlists also acquire make, npm run codegen, pre-commit — all of which execute
repo-controlled configuration, so this is the normal case, not an edge case.
A second erosion path: a full-filesystem diff sees every __pycache__, cache, and tool-scratch write as
an unregistered delta. The operational pressure will be to add wildcard allowlist entries — which is
exactly how allowlists rot into permit-all.
Decision
- A registered writer's identity is the pair (binary-hash, config-hash), not a name. A config change changes the identity and must go through the amendment path.
- Registered writers must be pure functions of the input tree, and their output is verified by re-execution on the trusted side (formatters and codegen are idempotent, so the resulting hash is checkable rather than trusted).
- Writer configuration files join the read-only protected set alongside
.pl, hooks, and gate config. - The registry's own mutation rule is a governance surface: adding a writer is a loosening (human-only, ADR-0009). Note §1's differential classifier operates over rulebases and does not cover the registry — this must be stated, or the registry becomes an ungoverned side door.
- Nondeterministic scratch paths are mounted outside the diffed tree (tmpfs, wiped per run) so the governed tree only ever needs deliberate producers, and no wildcard entries are required.
- The declared ignore set is itself an injection channel and is treated as protected configuration.
Alternatives rejected
- Name-based writer allowlist — the confused-deputy path above.
- Wildcard/ignore-based tolerance for scratch files — the rot path; replaced by mount scoping.
- Trusting writer output without re-execution — indistinguishable from trusting the agent, since the agent controls the writer's inputs.
Consequences
- Trusted-side re-execution costs build time and requires writers to be genuinely deterministic; a non-reproducible writer cannot be registered (which is a desirable forcing function).
- Formatter/codegen config changes become governed events — mildly annoying, correctly so.
- Mount scoping is an infrastructure requirement, coupling this ADR to the OS boundary (ADR-0018).
Provenance
Round-3 Fable (C-1: unattested writer identity, config-as-injection, allowlist rot) and Opus (#6:
confused deputy via .pre-commit-config.yaml, registry mutation as an uncovered governance surface).
The two converged independently on the same hole; this ADR takes the union of both fixes. Likely to be
informed or superseded by in-toto/SLSA prior art (research pending).
Related ADRs
Rulings cited
None.