Skip to content

The repository root

The root of a landing-zone repository holds meta: documentation, the pinned toolchain, the conftest policy, and the ignore rules. Everything deployable lives under live/. This page covers the root, minus live/ and .github/.

The root files

File Purpose You edit it
README.md What the repository is, how a change is made, the live/ layout Optional
ONBOARDING.md Step-by-step for standing up a new customer landing zone No
CHANGELOG.md Per-customer record of version bumps and new workloads Yes, on every version bump
LICENSE Placeholder proprietary licence Yes, before external distribution
mise.toml Pinned Terragrunt and Terraform versions Only to move the toolchain
.editorconfig UTF-8, LF, two-space indent, trailing-whitespace rules No
.gitignore Keeps generated and local files out of git Rarely
projects.yml Engine discovery and gate hooks, covered on the next page Yes

mise.toml: the pinned toolchain

The repository pins the two tools that matter, and nothing else:

[tools]
terragrunt = "1.0.7"
terraform = "1.15.5"

Run mise install in the repository root and you get exactly those versions locally. CI runs the same pins, so a local terragrunt plan and a plan in a pull request see the same binaries. To move the toolchain, change the versions here, open a pull request, and review the plans.

.gitignore: what must never be committed

The ignore list is short, and two entries carry real weight.

backend.tf, providers.tf, and context.tf are generated. live/root.hcl writes them into each unit at run time, from the shared backend, provider, and context contract. They are build output, not source. If you commit one, that unit stops taking the shared contract and starts carrying a stale copy, and the next contract change silently skips it. A committed context.tf is the worst of the three: the unit keeps deploying to the old region after region.hcl has moved.

.alzlib/ is the alz provider's local cache of the ALZ library it downloads at the pinned library_references. It is a cache, it is rebuilt on demand, and it is large.

The rest is routine: **/.terraform/, **/.terragrunt-cache/, **/.terragrunt-stack/, local state files, **/*.local.hcl and **/*.local.tfvars for local overrides, and editor directories.

LICENSE

The shipped LICENSE is a self-declared NRIT Solutions proprietary notice, and it says so in its own text: the binding terms live in the partnership agreement. Replace it with the agreed licence before the repository is distributed outside the customer.

policy/governance.rego: the active policy

policy/ is where the conftest gate looks. The repository ships one file, policy/governance.rego, in package main. It contains a single warn rule:

warn contains msg if {
    resource := input.resource_changes[_]
    "delete" in resource.change.actions
    msg := sprintf("%s will be destroyed", [resource.address])
}

That surfaces every resource a plan would destroy, so a destructive plan is obvious in review. It is a warn, so it reports and does not block. This is the file you extend: add rules here as your tenant gets clean, and promote them from warn to deny when you want them enforced. For when and in what order to enforce, see Enforcement strategy.

examples/policy/tags.rego: reference only

examples/policy/tags.rego is not loaded by the gate. It sits outside policy/ on purpose. Copy it into policy/ and adapt it when you want a first deny rule.

It exists mostly to document two plan-JSON patterns that are easy to get wrong:

  • change.actions is an array. A replace is ["delete","create"], an unchanged resource is ["no-op"]. Test membership with in, never actions[0].
  • change.after is null on a destroy. Guard it, or the rule evaluates to undefined and the deny fails open silently instead of firing.

Both bite the same way: the policy looks correct, the gate reports clean, and nothing was actually checked.

An unguarded deny fails open

Rego returns undefined, not false, when it walks into a null. A deny that reads change.after without a null guard produces no message on a destroy plan, which reads as a pass. Guard change.after != null in every rule that touches it.

The in-repo docs/ folder

The repository carries its own docs/ folder. These files ship inside the customer's repository, so the operating team has the reference offline and next to the code, without depending on this site.

File Covers
upgrade-guide.md The three pinned things: AVM module versions, ALZ library refs, and the engine pins
policy-gates.md The conftest, checkov, and infracost gates, and how to enforce them
foundation-structure.md Why the foundation sits above the subscriptions, and how it behaves per region
leaf-data-sharing.md dependencies versus dependency, and how to move values between units
operations-runbook.md Teardown ordering, AMBA remediation, the change process, adding a workload, drift, and credential rotation

The step-by-step for standing up a new customer is ONBOARDING.md at the repository root, not in docs/. There is no separate onboarding checklist file.

Next: Workflows and discovery config.