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/check-names-allow.txt, 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/workload-deployer.satz, 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/check-names-allow.txt, 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/check-names-allow.txt 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.

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:

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.googleapis.com/google.cloud.audit.OrgPolicyViolationInfo). 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.noreply.github.com — 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.iam.gserviceaccount.com), and Google-owned agents of the form service-org-<org-id>@…gserviceaccount.com 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/smoke/yaml/greenfield.satz 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:

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/smoke/yaml/showcase.satz 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/acme/simple-project/satz/simple-project.satz 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/acme/shop-dev/satz/shop_dev.satz 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.