ADR 0032: State branch access policies

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