estate_core — version 2.4
Source: presets/estate-core.satz
Purpose
The questions every estate has to answer on day 0, with the params they answer.
satz init writes these params — from its flags where it has one, from
its defaults otherwise; an INTERVIEW asks for
them one at a time, from the questions in this pack. Every param below has a
question, and every question says what changing the answer later costs, so a
default is offered where one exists and a value is required where none is
possible.
Two kinds of param, and the interview treats them differently. Seven have NO POSSIBLE DEFAULT — the directory customer id, the organisation id, the domain, the short name, the display name, the first admin, the billing account: the pack cannot know the answer, so these block until a value is typed. The rest have a DERIVED OR CONVENTIONAL default — project and bucket names built from the short name, the folder name, the service account names, the region — and accepting one is an answer, recorded by writing it. A derived default is offered only once what it derives from is answered.
Which packs make up the estate — the security-group model included — is the next pack, estate-map.satz: this one is the day-0 params and nothing else.
This pack declares no resource. It publishes the day-0 answers every project beside
the estate needs as the estate's core exports — values of every interface under
interfaces/, which a project's own HCL or Satz reads them from — so an estate has
them without writing an export of its own. Apart from those outputs, an estate that uses it and answers
everything compiles to exactly what satz init would have written.
workload_folder_name is the folder where the customer's and the projects' folders live,
named like infra_folder_name. It may be empty — the default — which is the
organisation itself, and nothing is created for it; its question says so (empty), so
"" is an answer the interview offers and the gate counts. Its export, workload_folder,
is not here: it is the organisation, a static value, or a folder the estate declares,
whose number is looked up, so the estate carries that section — satz init writes it
from --workload-folder-name, an interview when the question is answered. The folder's
parent is where its block stands in the estate, like any folder's.
Use it
params {
// the pack's own defaults — copy only the lines you change
customer_id = ""
customer_organization_id = ""
customer_domain = ""
customer_shortname = ""
first_admin = ""
billing_account_infra = ""
customer_longname = ""
infra_folder_name = "Infrastructure"
infra_project_name = "{customer_shortname}-infra-001"
infra_bucket_name = "{customer_shortname}-infra-001-state"
svc_iac_account = "svc-iac-001"
svc_iac_users_group = "svc-iac-users"
deployment_engine = "tofu"
deployment_mode = "local"
default_region = "europe-west3"
default_zone = "{default_region}-a"
workload_folder_name = ""
compliance_frameworks = [ "cis-gcp-5.0" ]
}
use "presets/estate-core.satz"
Params
| param | default |
|---|---|
customer_id |
"" |
customer_organization_id |
"" |
customer_domain |
"" |
customer_shortname |
"" |
first_admin |
"" |
billing_account_infra |
"" |
customer_longname |
"" |
infra_folder_name |
"Infrastructure" |
infra_project_name |
"{customer |
infra_bucket_name |
"{customer |
svc_iac_account |
"svc-iac-001" |
svc_iac_users_group |
"svc-iac-users" |
deployment_engine |
"tofu" |
deployment_mode |
"local" |
default_region |
"europe-west3" |
default_zone |
"{default_region}-a" |
workload_folder_name |
"" |
compliance_frameworks |
[ "cis-gcp-5.0" ] |
Questions
What to ask before this pack is configured, and what changing the answer costs.
| asks about | prompt | reversal | blast | recommends |
|---|---|---|---|---|
customer_id |
The Google Workspace / Cloud Identity directory customer id (C0…) | recreate | high | — |
customer_organization_id |
The organisation id (the number, not the domain) | recreate | high | — |
customer_domain |
The customer's primary domain | recreate | high | — |
customer_shortname |
A short name for this customer — lowercase, no spaces | recreate | high | — |
customer_longname |
The customer's display name, as it should appear in descriptions | edit | none | — |
first_admin |
The first admin's account, local part only (the domain is added) | edit | low | — |
billing_account_infra |
The billing account for the infrastructure project (XXXXXX-XXXXXX-XXXXXX) | state_surgery | low | — |
infra_folder_name |
Display name of the folder that holds the infrastructure project | edit | none | "Infrastructure" |
workload_folder_name |
Display name of the folder that holds the customer's and the projects' folders | state_surgery | high | — |
infra_project_name |
The infrastructure project id | recreate | high | — |
infra_bucket_name |
The state bucket name (globally unique) | recreate | high | — |
svc_iac_account |
Account id of the IaC service account (the part before @) | recreate | high | — |
svc_iac_users_group |
The group whose members may impersonate the IaC service account | recreate | low | — |
deployment_engine |
Which tool applies the emitted HCL: tofu or terraform | edit | none | "tofu" |
deployment_mode |
Where the state lives: local while bootstrapping, cloud once the bucket exists | state_surgery | low | "local" |
default_region |
The default region for regional resources | recreate | high | "europe-west3" |
default_zone |
The default zone, inside that region | edit | low | — |
compliance_frameworks |
Which compliance frameworks is this customer held to, as catalog ids: cis-gcp-4.0, cis-gcp-5.0, iso27001-2022 | edit | none | [ "cis-gcp-5.0" ] |
customer_id — Every group in the estate is created under this customer. There is no changing it afterwards; a different id is a different estate.
customer_organization_id — Every org policy, org grant and sink is scoped to it. A different organisation is a different estate.
customer_domain — Group addresses and the first admin are composed from it, and the Essential Contacts constraint allows it. Changing it renames every group.
customer_shortname — Project ids, the state bucket and group prefixes derive from it, and those ids are globally unique and immutable once created.
first_admin — This is the human satz bootstraps as, and the first member of the IaC users group — the one account that can act before any service account exists.
billing_account_infra — Relinking a project's billing is an update, but a project with no valid billing account cannot enable a single API — the bootstrap stops there.
workload_folder_name — Every project's folder takes it as its parent, through the core export workload_folder. Empty is the organisation itself, and nothing is created. A name declares the folder directly under the organisation; a folder the customer already has by that name is imported with satz adopt, because Google refuses a second one. Moving between the two later moves every folder the projects created under it.
An empty answer, "", is an answer: the organisation itself; no folder is created.
infra_project_name — A project id is immutable. Changing it creates a second project and abandons the first, with the state bucket inside it.
infra_bucket_name — The bucket holds the Terraform state for the whole estate. Renaming it means migrating state by hand and re-initialising every checkout.
svc_iac_account — Everything after init runs as this account and every provider block impersonates it. A different name is a new account, new grants, and a re-initialised backend.
svc_iac_users_group — Membership of this group is what lets a human operate the estate. Renaming it moves every member.
deployment_mode — satz migrate --mode cloud moves it and re-initialises the backend; starting in cloud mode needs a bucket that does not exist yet.
default_region — Regional resources — the alerting bucket, sinks with a location — cannot move regions; a change recreates them.
compliance_frameworks — This is what the customer answers to — a contract, an auditor, a regulator — and it is not the same as what the packs in this estate claim. satz report-compliance <estate> writes one report section per framework named here, satz prowler scans for them, and an audit pack reads them. cis-gcp-4.0 and cis-gcp-5.0 are the two versions of the CIS Google Cloud Foundation Benchmark; 5.0 is the current one and the answer unless a signed contract names 4.0. iso27001-2022 is ISO/IEC 27001:2022, covered as a cross-walk: the packs keep claiming CIS, every technically evidenced Annex A control reads its verdict through the CIS controls that stand as its evidence, and the controls that are a human duty carry that duty and never read as satisfied by configuration alone. Naming a framework here does not switch a pack on; it decides what the reports are written against. A value that names no catalog is refused.
Contributes
No resources — params and claims only.
Exports
Outputs of the root module and values of the interfaces under interfaces/ in every estate that uses this pack: a core export is carried by every interface, another one by its own interface and every interface that uses it. An interface a pack declares is common: every project's folder carries it.
| interface | output | value | description |
|---|---|---|---|
core |
organization_id |
customer_organization_id |
The organisation id |
core |
customer_domain |
customer_domain |
The customer's primary domain |
core |
customer_shortname |
customer_shortname |
The short name project ids, bucket names and group prefixes derive from |
core |
default_region |
default_region |
The default region for regional resources |
core |
infra_project_id |
infra_project_name |
The infrastructure project, which holds the state bucket |
core |
iac_service_account |
"{svc |
The service account the estate is applied as |
Claims
None — this pack proves no control by itself.
History
| version | date | change |
|---|---|---|
| 2.4 | 2026-09-26 | the header, the workload_folder_name question and the section comments say project where they said team, and name the interfaces under interfaces/; params and exports are unchanged |
| 2.3 | 2026-09-25 | workload_folder_name, the folder where the customer's and the teams' folders live, default "" — the organisation, for which nothing is created — with its question, whose empty says so, so "" is an answer. The estate publishes it as workload_folder in the section satz init or an interview writes. An estate that uses the pack answers the question before bootstrap or an apply |
| 2.2 | 2026-09-25 | the core exports: organization_id, customer_domain, customer_shortname, default_region, infra_project_id and iac_service_account, each a core export — an output of the root module and of every module under hcl/interfaces/ — all known at compile time. An estate that uses the pack gains outputs.tf and hcl/interfaces/; its resources do not change |
| 2.1 | 2026-09-21 | compliance_frameworks, the catalogs this customer is HELD TO — a contract, an auditor, a regulator — as a list of catalog ids, with the question that asks for them. What an estate CLAIMS comes from its packs and is a different fact: an estate can claim CIS controls while its customer is audited against ISO 27001. The default is ["cis-gcp-5.0"]; the values are the ids of the catalogs in presets/catalogs/ (cis-gcp-4.0, cis-gcp-5.0, iso27001-2022) and a value that names no catalog is refused by the compile, with the list. satz report-compliance <estate> reports one section per framework named here, satz prowler scans for them beside the frameworks the packs claim, and a pack reads the param like any other. An estate that binds nothing keeps working: report-compliance <framework> <estate> is unchanged |
| 2.0 | 2026-09-10 | the security-model choice moves to estate_map; this pack is the seventeen day-0 params and their questions, nothing else. A major bump because two params left — no estate in the fleet uses the pack, it exists for interview skeletons |
| 1.0 | 2026-09-09 | first version: the seventeen day-0 params satz init writes, each with its question — what to ask, why, and what changing it later costs — plus the security-group model as two booleans and a question oneof. Emits nothing; exists so an interview (satz interview --create, the MCP tool satz_interview) has something to ask before an estate exists. Seven params have no possible default and block until typed; the rest offer one, and a derived default ("{customer) is offered only once its inputs are answered |
The whole library's history: the changelog in presets/CHANGELOG.md.
Notes
The skeleton always uses this pack, ahead of estate_map: these are the values every later question assumes. Seven of the sixteen have no possible default and block until typed; the rest offer one, and a derived default appears only once what it derives from is answered — which is why an interview asks them in this order rather than all at once.
It emits nothing. satz init writes the params from its flags where it has one and from its defaults otherwise; satz interview --create and the MCP tool ask for them instead.