satz examples
Two kinds of example. First the register of identifiers every example in this
repository is allowed to use — a rule the privacy gate enforces on every commit.
Then worked estates: real files in this repository, transpiled by
scripts/smoke.sh on every pull request and every push to main, so each one
compiles with the current binary.
Example customers
This repository is public. Real customer names, domains, Google Workspace
directory ids (C0…), GCP organisation, folder and project numbers, billing
account ids and repository URLs must never appear in a tracked file. Instead,
every example uses one of the four customers below, with exactly these
values. scripts/check-names.sh enforces it: any C0… id, any 11–13-digit
number, any billing-account id, any GUID, any 32-hex string, any project id, any
e-mail or .de domain that is not on this page fails the commit and the CI run.
The values themselves are listed in scripts, which the gate
and satz review-pack both read.
All domains are IANA-reserved (example.com/net/org, the .example TLD), so
nothing here can resolve. All numbers are visibly synthetic.
| Customer A | Customer B | Customer C | Customer D | |
|---|---|---|---|---|
| full name | Acme Corp. | Bolt Industries GmbH | Cedar Logistics AG | Delta Clinics |
customer_shortname |
acme |
bolt |
cedar |
delta |
customer_domain |
example.com |
example.org |
example.net |
delta.example |
| contacts domain (if different) | example.net |
– | – | mail.delta.example |
directory id (customer_id) |
C0example |
C0bolt002 |
C0cedar03 |
C0delta04 |
| organisation id | 123456789012 |
222222222222 |
333333333333 |
444444444444 |
| billing account | 012345-6789AB-CDEF01 |
0B0B0B-0B0B0B-0B0B02 |
0C0C0C-0C0C0C-0C0C03 |
0D0D0D-0D0D0D-0D0D04 |
| infra project | acme-infra-001 |
bolt-infra-001 |
cedar-infra-001 |
delta-infra-001 |
a team's deployer project (presets, team shop) |
acme-deploy-shop, its bucket acme-deploy-shop-state, its account svc-deploy-shop |
– | – | – |
| log project | acme-log-001 |
bolt-log-001 |
cedar-log-001 |
delta-log-001 |
| audit bucket | acme-organization-audit-bucket |
bolt-organization-audit-bucket |
cedar-organization-audit-bucket |
delta-organization-audit-bucket |
folder id (for import-id examples) |
123456789 |
222222222 |
333333333 |
444444444 |
| project number | 100000000001 |
200000000002 |
300000000003 |
400000000004 |
| estate file | satz/C0example.satz |
satz/C0bolt002.satz |
satz/C0cedar03.satz |
satz/C0delta04.satz |
| repo path in prose | ~/estates/acme |
~/estates/bolt |
~/estates/cedar |
~/estates/delta |
| Microsoft Entra tenant id | 11111111-1111-1111-1111-111111111111 |
22222222-2222-2222-2222-222222222222 |
33333333-3333-3333-3333-333333333333 |
44444444-4444-4444-4444-444444444444 |
| workload identity pool id (the tenant id without dashes) | 11111111111111111111111111111111 |
22222222222222222222222222222222 |
33333333333333333333333333333333 |
44444444444444444444444444444444 |
Vendor default identifiers
Some GUIDs are not anybody's secret: Microsoft and Google publish them, and every
customer's estate carries the same value. Those are allowed by name, listed here and
in the guid list of scripts, the gate's allow-lists.
| identifier | what it is |
|---|---|
2041288c-b303-4ca0-9076-9612db3beeb2 |
Microsoft Sentinel's application id, and Defender for Cloud's containers-streams audience. The audience Sentinel's GCP connector tokens carry (api://<id>), published in Microsoft's connector documentation and in its own Terraform; Defender for Cloud's generated GCP onboarding script carries the same id as the audience of its containers-streams provider. Identical for every customer. |
33e01921-4d64-4f8c-a055-5bdaffd5e33d |
Microsoft's commercial-cloud tenant. The sts.windows.net/<tenant> issuer every Defender for Cloud and Sentinel connector federates from — published in Microsoft's connector documentation for AWS and GCP alike, identical for every customer. It is not the customer's tenant, which is why it is allowed. |
d17a7d74-7e73-4e7d-bd41-8d9525e86cab |
Defender for Cloud's auto-provisioner application id, the api://… audience of the auto-provisioner OIDC provider. |
6e81e733-9e7f-474a-85f0-385c097f7f52 |
Defender for Cloud's CSPM application id, the audience of the CSPM provider. |
Adding one: confirm the vendor publishes it and that it does not vary per
customer — a tenant id that appears in the customer's generated script is theirs, not
the vendor's, and belongs in a param. Then add it to the guid and guid32 lists of
scripts and to this table
in the same commit. A GUID the gate does not know is assumed to identify a customer.
Other placeholders
These fictional values appear in existing files and are allowed there. They are not a fifth customer: new examples use the four above.
- project ids
corp-infra-001andcorp-log-infra-001(the smoke estate and the corpus fixtures); - the project name
payments(the docs' and the smoke matrix's example of a project beside an estate: an entry{ name = "payments" … }ofpresets,/project-onboarding .satz satz init --project payments); - directory ids
C01234567(README and tests) andC0abcd123(one test); - billing placeholders
A12345-B67890-C12345and123456-123456-123456(README and a preset).
What the gate cannot see
Shapes are enforceable; names are not. A project's or folder's DISPLAY name, a
company name in prose, a person's name in a comment — "Log Admins" and a real
customer's project name are the same kind of string, and no pattern separates them.
Two things cover that gap:
- Review. The rule is that no customer, company or person is named in a tracked file. The gate cannot check it; the author can.
- The local denylist.
$NAMES_DENYLISTnames a file outside the repository that holds one extended regex per line — real customer names, project names, internal words. It is never committed, so CI stays structural while the pre-commit hook on the maintainer's machine knows the actual words to refuse. Anything that is a name rather than a shape belongs there.
Which one to use. Customer A for any single-estate example. B, C, D only when an example needs a second, third or fourth organisation (cross-org grants, a fleet table, a partner directory). Customer A's separate contacts domain and Customer D's subdomain exist so that "the org and the contacts live on different domains" can be shown without inventing a value.
Fleet estates in working-state files (CLAUDE.local.md, the private roadmap, apply
worklists) use opaque codenames and never a domain, id or
path beyond ~/estates/E0n. The mapping to real customers lives outside the
repository.
Domains. Only IANA-reserved names (example.com/org/net, and the
.example, .test, .invalid, .localhost TLDs) and the vendor hosts the
project genuinely references (googleapis.com, gserviceaccount.com,
github.com, gcr.io, opentofu.org, terraform.io, cisecurity.org, zed.dev (the
editor the Satz extension is for), and Microsoft's
windows.net, microsoft.com, microsoftonline.com — a workload-identity
federation example cannot avoid naming the issuer it federates, and
sts.windows.net is Microsoft's, not a customer's) may appear anywhere. Any
other domain fails the gate, including ones that look fictional: most such
domains are registered by real businesses.
One entry is not a host at all: google.cloud is the package name of Google's own
audit protobufs, as it appears in a Cloud Logging filter
(type). The gate reads it as
a .cloud domain because that is what the shape says; it is identical for every
customer, so it is allowed by exact name.
Commit identity. Every commit's author and committer must be a GitHub
noreply address (<id>+<user>@users — enable "keep my email
address private" in GitHub settings — or noreply@github.com). A clone may
allow one more address through $NAMES_IDENT, an extended regex set in the
environment and never committed. Every other address, an employer's or a
customer's among them, is rejected by the pre-commit hook and by CI on every
pull request and push to main, so no contributor's affiliation reaches the
public history.
Other allowed identities: noreply@anthropic.com (Claude's co-author
trailer), *.iam.gserviceaccount.com service accounts built from the values
above (e.g. svc-iac@acme-infra-001), and
Google-owned agents of the form service-org-<org-id>@…gserviceaccount
with an org id from the table.
If an example genuinely needs a value this page does not define, add the value here first, in the same commit.
The smallest estate that compiles
An estate is a name and a params block. Nothing else is required: a file that
declares no resources emits an empty configuration, not an error.
tests is the smallest one this repository keeps:
estate greenfield_fixture
params {
customer_shortname = "acme"
customer_id = "C0example"
customer_domain = "example.com"
customer_organization_id = ""
billing_account_infra = "A12345-B67890-C12345"
infra_project_name = "acme-infra-001"
infra_bucket_name = "acme-infra-001"
default_region = "europe-west3"
}
Its customer_organization_id is empty: the state of a tenant whose Google Cloud
organization does not exist yet. satz bootstrap answers that with the greenfield
path — --greenfield, or satz init re-run with the id — rather than a bare "missing org
id". See from nothing to applied.
A working estate, end to end
tests/smoke/yaml/smoke.satz is the fixture every estate-consuming command runs
against in CI. In about 150 lines it carries:
- params — the twenty an estate normally binds;
terraformandprovidersblocks, which becomeproviders.tfand the backend;- four pristine packs, each in the position its entries fit: the CIS
baseline at the top level, because it declares its own resource type; the contacts
pack as the content of a resource map
(
google), because it is a bare list of labels — its_essential _contacts _contact { use "presets /essential-contacts-organization .satz" } paramsand itsquestionreach the estate from there, and the map receives the contact alone; the two monitoring packs at the top level, the audit archive naming the folder its project is created in; - a Cloud Identity group with a member, and organization IAM grants in the member → roles form;
- a folder holding a project, that project holding its enabled services and a storage bucket — the hierarchy from which scope attributes are derived rather than repeated.
Read it next to the language reference: it uses the reference's constructs in one file that produces HCL.
Every feature in one file
tests is the reference's own corpus:
docs/language.md cites this file by section, and
scripts/smoke.sh transpiles it on every pull request and push to main.
It is annotated in place and walks, in order: params and the question blocks that
say what to ask before one can be filled (including a oneof exclusive choice);
the estate configuration blocks; use in all three positions (top level, as, and
when on a declared param); suppress, declining one policy and one role of one
grant without forking the pack; a group, its member and the "import-id" of one
that already exists; IAM grants with a conditional role; the folder → project →
resource hierarchy; hcl { } raw passthrough; an action, the deployment step
that has no provider resource; and the claim blocks that say which controls the
estate implements and which it declines.
A new language feature gets its example here first: the reference cites this file, and CI compiles it.
An organisation and the projects beside it
examples/ holds estates that show one use of satz each. examples/acme/ is one
organisation — the estate satz init --workload-folder-name Workloads writes, with
presets/estate-core.satz in use — and each subfolder of it is one example: a project
that reads the organisation through interfaces/common/ copied to its vendor/.
examples/README.md gives the commands and names the values to replace.
examples is one Google project as a
template: its inputs { application stage cost_centre } are what a reader binds.
inputs {
// the application the project serves: lowercase letters, digits and `-`
application = "shop"
// the stage: dev, test or prod
stage = "dev"
// the four-digit cost centre the project is billed to
cost_centre = "4711"
}
use "vendor/common/core/satz/interface.satz"
google_project {
project {
name = "{application}-{stage}"
project_id = "${{interface.customer_shortname}}-{application}-{stage}"
folder_id = "${{interface.workload_folder}}"
billing_account = billing_account
labels = { cost_centre = cost_centre }
project_service = ["cloudresourcemanager.googleapis.com"]
google_project_iam_member {
owner {
role = "roles/owner"
member = "group:${{interface.customer_shortname}}-{application}-{stage}-owner@${{interface.customer_domain}}"
}
}
}
}
With the example values it is the Google project acme-shop-dev under the organisation's
workload folder, labelled cost_centre = "4711", with roles/owner (and, in the file,
roles/editor and roles/viewer) granted to groups named after the project at
example.com; the groups exist in the directory before the apply and are not declared.
Compiled as it stands it is the HCL team's form — variables.tf declares the inputs,
terraform.tfvars carries the example values, main.tf reads var.<name> and
interface.tf holds the organisation's values, so the team edits terraform.tfvars and
runs tofu without satz. examples is the Satz team's
form of the same template: a thin estate binding the inputs in its params { } and
use-ing the template, which compiles to the same main.tf. The grants are labelled
resources rather than a member map: a group name that reads an input is a value, and an
input is never a key.