Terraform / Sensitive data, governance, and recovery / Lesson 17
State architecture and cross-team contracts
Choose root and state boundaries from ownership, change coupling, permissions, and recovery needs rather than directory count.
On this page
Lesson goals
- Design cohesive roots with explicit single-object ownership.
- Review cross-state dependencies, publication, and operational isolation.
A root is an operational change unit
A root configuration and its state form an important unit for planning, locking, permissions, and failure reconciliation. Group resources that genuinely share ownership and change relationships, while considering the blast radius of a large combined plan. Excessive splitting can introduce cross-state coordination and interface complexity. Excessive aggregation can tie unrelated teams and permissions together. There is no universal resource-count threshold that decides the answer. Start with ownership, change frequency, dependency direction, security boundaries, and recovery requirements, then make the tradeoffs explicit.
Make publication ownership visible
The pure HCL fixture below describes fictional owners and versioned publication labels. It is a catalogue of decisions, not a mechanism that creates teams, policies, backends, or authorization. A real architecture also needs enforced permissions and reliable publication storage. Explicit output contracts let consumers depend on a narrow stable interface rather than a producer’s entire private state. Review changes in those contracts as API changes, including type, meaning, freshness, confidentiality, and the behavior when the producer is unavailable.
terraform {
required_version = ">= 1.16.0, < 1.17.0"
}
locals {
publications = {
catalog = { owner = "catalog-team", contract = "catalog-name-v1" }
service = { owner = "service-team", contract = "service-id-v1" }
}
}
output "publication_owners" {
value = local.publications
}
The output describes declared metadata only. It establishes no real team permission or state isolation. Versioned labels make an intended interface change visible, but enforcement and compatibility testing remain separate responsibilities.
One remote object needs one deliberate management owner
Two states can use the same configuration address without conflict when they manage different objects. The danger is two independent states managing the same real identity, potentially with incompatible desired values. A state lock protects only its own coordination scope and cannot resolve that architectural conflict. Reading another team’s object through a supported data interface is different from declaring lifecycle ownership. Maintain an inventory that distinguishes managed identities from read dependencies, and review transfers as explicit state-aware changes rather than duplicated declarations.
roots = {'catalog-root': ['object-A'], 'service-root': ['object-B', 'object-A']}
owners = {}
for root, identities in roots.items():
for identity in identities:
owners.setdefault(identity, []).append(root)
for identity in sorted(owners):
if len(owners[identity]) > 1:
print('duplicate=' + identity + ' owners=' + ','.join(sorted(owners[identity])))
duplicate=object-A owners=catalog-root,service-root
The declared inventory assigns object-A to two roots. These are synthetic identity strings, not parsed state files or discovered infrastructure. The result flags a review question and does not transfer, remove, or adopt any object.
Cross-state edges require orchestration outside one graph
A reference within one configuration contributes to its graph, while consuming a published result from another state does not create one atomic multi-state transaction. The producer can be stale, unavailable, or independently updated. Circular root dependencies can make initial creation and recovery difficult even when each individual module looks valid. Prefer clear dependency direction and stable published contracts, and coordinate compatible releases where necessary. Do not expose full private state just to avoid designing a narrow interface or a shared ownership decision.
Workspace labels are not a security architecture
CLI workspaces select state contexts according to backend behavior; they do not automatically create different accounts, credentials, naming schemes, or approval authorities. HCP workspace concepts and permissions also need their own product-specific review. Where strong environment isolation is required, define explicit access, backend/state targets, provider configuration, and release controls. Rehearse partial failure, producer unavailability, and recovery under those boundaries. A neat directory hierarchy or environment variable name cannot substitute for knowing which identity can change which objects and how that authority is audited.
Common errors and fixes
Two locked states still overwrite the same object.
- Why it happens
- Independent coordination scopes duplicate lifecycle ownership of one real identity.
- How to fix it
- Reconcile the intended owner and transfer through the approved state-aware process.
- Prevent it next time
- Maintain a managed-identity inventory distinct from data consumers.
Root splitting creates an initialization dependency cycle.
- Why it happens
- Each root requires the other’s future publication to establish its own initial identity.
- How to fix it
- Redesign dependency direction or move stable input decisions to an appropriate owner.
- Prevent it next time
- Review creation and recovery order before separating states.
A production workspace name is assumed to enforce production permissions.
- Why it happens
- State labeling was confused with credentials and authorization boundaries.
- How to fix it
- Verify effective identity, backend target, provider settings, and approval policy.
- Prevent it next time
- Treat environment isolation as an explicit enforced contract.
Check your understanding
1. Is an identical resource address in two roots always duplicated ownership?
Show worked solution
No. Addresses are scoped to their configurations and states. Determine whether the real object identities are the same. The offline inventory flags object-A because its declared identity appears under two owners, not because the roots happen to use similar local labels.
2. Why can a narrow publication contract be safer than direct remote-state access?
Show worked solution
It can expose only intended stable values through an appropriate access-controlled channel, reducing coupling and unnecessary disclosure. Direct state access can reveal more data and private implementation details. The publication mechanism still needs authentication, confidentiality, freshness, compatibility, and unavailable-producer behavior; a small interface alone does not enforce those controls.
Key takeaways
- Choose root and state boundaries from ownership and change coupling.
- Enforce environment isolation and cross-state publication explicitly.