ADR 0032: State branch access policies
- Status: Proposed
- Date: 2026-07-02
- Decider(s): @davesade (David Kubec)
- Relates to: ADR 0015, ADR 0016, ADR 0023, ADR 0029
Context
Kilolock is moving beyond "hosted HTTP backend" semantics and toward a state-engine model where the backend understands:
- resource addresses
- graph branches and dependency closure
- scoped reads and writes
- narrower reservations than whole-state locking
That creates a natural follow-up requirement: access control should eventually be able to follow the same granularity.
Today, the practical access boundary is still relatively coarse:
- a token or user gets access to an environment or state
- once admitted, the workflow may read or mutate broad parts of that state
For many teams that is fine. For larger organizations, it is not enough.
The long-term product idea is stronger:
- one logical state may contain a very large infrastructure monolith
- different teams may work on different graph branches within that monolith
- each team should be able to use one token and touch only the part it is
allowed to see or mutate
The desired policy model is intentionally more expressive than simple "state yes/no" authorization. Future policy may need to constrain access by:
- state branch or module subtree
- exact resource address or address prefix
- resource type
- selected values or labels inside resource attributes
- read vs write vs query vs repair capabilities
This ADR records that direction as a deliberate future enhancement so the state-engine work does not accidentally hard-code a permanently coarse security model.
Decision
Kilolock will evolve toward state branch access policies that can restrict what one token, user, or delegated role may read or mutate within one logical state.
The feature is a future enhancement, not an immediate implementation requirement.
The intent is:
- one credential can be authorized for a narrow part of one state
- access control can follow graph-aware state-engine boundaries
- policy granularity is not limited to only whole-state or whole-environment
scope
Decision details
D1. The policy attachment point is a principal, not a separate backend
The future policy system should attach rules to an authenticated principal such as:
- human PAT
- automation token
- delegated workspace/operator role
The goal is to keep the user's experience simple:
- use one token
- let policy determine which parts of the state are visible or mutable
This avoids forcing users to mint a separate backend or separate state copy only to express narrower permissions.
D2. Policy scope must be narrower than whole-state access
The future model must support at least state-internal scoping, such as:
- module prefix
- address prefix
- explicit resource allow/deny lists
That is the minimum useful boundary for the "one monolith, many teams" story.
D3. Policy granularity may extend beyond graph branches
Kilolock should not lock itself into only path-like branch policies.
The design should leave room for richer selectors such as:
- resource type selectors
- tag or label selectors
- attribute-value predicates
- future metadata selectors derived from normalized resource rows
Examples:
- allow
aws_s3_bucket.*in one module subtree - deny writes to any resource tagged
critical=true - allow read-only access to networking outputs but not mutation
This ADR does not choose a final policy language. It only records that unlimited granularity is a desired product direction.
D4. Policy decisions must distinguish read, write, and admin-like actions
Future branch policies must not collapse all access into one boolean.
The model should distinguish at least:
- read current state slice
- plan/query against a slice
- mutate/apply within a slice
- run stronger state-admin actions such as
rm,mv, rollback, or repair
This matters because many organizations may allow broad visibility while reserving mutation or repair for a smaller set of operators.
D5. State-engine selectors are the natural enforcement boundary
ADR 0029 already introduces backend-authored scope contracts for:
- fetched resources
- read closure
- write closure
- reservations
Future policy enforcement should happen against those same concepts whenever possible.
That means the backend should eventually be able to say:
- this requested slice is fully allowed
- this requested slice is partially allowed and must be narrowed
- this requested mutation is forbidden
This keeps security decisions close to the backend's graph-aware truth rather than trusting the client to self-police scope.
D6. Plain Terraform compatibility remains, but native policy can be richer
Terraform's HTTP backend model is coarse. It cannot naturally express the full future policy surface described here.
Therefore:
- coarse state/environment admission may remain the compatibility floor for
plain Terraform usage
- richer branch/resource/value-aware policy is primarily a Kilolock-native
capability
This is acceptable. The product does not need to wait for plain Terraform to understand fine-grained policies before Kilolock exposes them in its own protocol and CLI.
D7. Auditability is a first-class requirement
If Kilolock allows one state to be shared safely across many teams, policy evaluation must be auditable.
Future implementation should make it possible to answer:
- which principal accessed which branch or resource
- which policy allowed or denied the action
- whether the action was read-only, write, or repair-grade
Fine-grained access without audit would not satisfy the intended enterprise use case.
D8. This ADR is a design direction, not a frozen syntax
This ADR intentionally does not define:
- the final policy language
- the storage schema for policy rules
- whether policy evaluation starts in OSS, Cloud, or both
- whether selectors are expressed in SQL-like, CEL-like, or custom syntax
Those decisions should come later, after the state-engine lane and its scope contracts have matured further.
Consequences
Positive
- preserves the long-term "one enormous state, many teams" product story
- aligns authorization with state-engine slice boundaries instead of fighting
them
- allows a single token to be useful without granting whole-state power
- creates a path toward future enterprise-grade least-privilege controls
Negative
- increases future authorization complexity
- introduces policy-evaluation cost on slice expansion and mutation paths
- may create differences between what plain Terraform can do and what native
Kilolock can safely express
Neutral
- no immediate implementation is required by this ADR alone
- the current coarse authorization model may continue until a later ADR defines
the concrete policy engine