Estate layout
An estate repository holds the authored objects that describe your collection topology, and the artefacts the renderer generates from them. This page is the map: which paths you write, which paths Telecraft writes, and how a file's place in the tree gives it the id every reference uses.
The tree
teams.yaml # the Team-tree seam
telemetry.yaml # the self-telemetry destination
live-check.yaml # optional: the live-check destination
allow-lists.yaml # optional: every team's declared list
grants.yaml # optional: every Grant
users.yaml # optional: the sign-in seam
auth.yaml # optional: the sign-in providers and group mapping
<idp>-metadata.xml # optional: a SAML identity provider's metadata
activations.yaml # optional: the active Catalogue and Schema Registry
catalogues/ # imported Catalogue versions, retained side by side
schema-registries/ # imported Schema Registry versions, likewise
teams/
<team-id>/
components/<name>.yaml # shared Components
blueprints/<name>.yaml # Blueprints, local Components inline
tiers/<name>.yaml # Tiers
services/<name>.yaml # Services
rollouts/<name>.yaml # Rollouts
rendered/
<team-id>/<tier>.yaml # generated: one artefact per Tier
<team-id>/<tier>.supervisor.yaml # generated: served Tiers only
<team-id>/<tier>@next.yaml # generated: Tiers under an active Rollout
_estate/unmatched.yaml # generated: the Unmatched artefact
CODEOWNERS # generated: the code-ownership projection
Team directories are flat. The hierarchy lives only in teams.yaml, so
moving a team under a new parent rewrites no path and breaks no id.
Root files
teams.yaml- The Team tree. Every command that resolves ownership needs it. Teams nest
under a
teams:key; each node carriesid, an optionalname, an optionalownerslist, and an optional nestedteamslist. A team id appearing twice is a load error, because a Team has at most one parent. An Owner named under two teams is a load error, because an Owner belongs to exactly one Team. telemetry.yaml-
The estate-level self-telemetry destination, under a
self_telemetry:key. Required: the renderer refuses an estate that doesn't say where self-telemetry goes. Fields areendpoint(required),protocol(grpcorhttp/protobuf, defaulthttp/protobuf),environments(a map from Environment name to an overriding endpoint), andnew_pipeline_telemetry(boolean, defaultfalse).endpointis the base endpoint, the same string you would give a pipeline's OTLP exporter. Overhttp/protobufthe renderer appends the signal path in each block, sohttps://otlp.example:4318renders ashttps://otlp.example:4318/v1/metricsin the metrics reader andhttps://otlp.example:4318/v1/logsin the logs processor. The exporters underservice::telemetrytreat the endpoint as the complete URL and append nothing themselves, so the renderer writes the path in for you. Trailing slashes are dropped. An endpoint or an override that already ends in/v1/metrics,/v1/logs, or/v1/tracesis a load error: declare the base endpoint instead. Overgrpcthere is no request path, so the endpoint renders exactly as you wrote it. live-check.yaml-
The estate-level live-check destination, under a
live_check:key. Optional: the file only matters once a Tier opts in to the Live-check tap with alive_checkblock in its own file, and a Tier opting in while this file is absent is a render error naming both files. Fields areendpoint(required),protocol(grpc, the default and the only accepted value),environments(a map from Environment name to an overriding endpoint), andsample_percent(the default sample rate for every opted-in Tier, 10 unless set, above 0 and at most 100).endpointis the base OTLP endpoint of the live-check service, host and port. An endpoint or an override that ends in/v1/metrics,/v1/logs, or/v1/tracesis a load error: declare the base endpoint. See Tiers for what opting in renders. allow-lists.yaml,grants.yaml- The Allow-list policy. Both are optional. Without them, every team can use the whole active Catalogue. See Allow-lists and Grants.
users.yaml- The sign-in mapping from authenticated identities to the Owner each acts
as. Each entry carries
email,name,owner, and an optionalpasswordhash produced bytelecraft passwd. auth.yaml- The ways of signing in this Instance offers, in the order the sign-in
surface shows them, and the optional mapping from a group the identity
provider asserts to the Owner its members act as. Each entry carries
kind, one ofbasic,oidcorsaml, and an optionalnamethat defaults to the kind. No field here takes a secret value: a provider that needs one names it, and the deployment places a file of that name. Optional, and without it an Instance offers basic auth alone. Asamlentry names a metadata document saved beside this file. See Sign-in. activations.yaml-
Which imported version of the Catalogue and of the Schema Registry your estate is judged against, and every activation that has happened. Optional: without it nothing is active, and a command that needs an active version says so.
telecraft activatewrites it, and you commit it like any other authored file. See Activate a version.Each substrate carries
activeand a list ofactivations, oldest first. Each activation records theversionit designated, thepreviousversion it replaced, when it happened, the Owner who decided it, and theimpactreport the decision was taken on. The file fails to load if the active version is not the one the last activation designated, or if any activation carries no report: a version is active because somebody activated it on a reading of what would change.
# activations.yaml
catalogue:
active: v0.159.0
activations:
- version: v0.155.0
at: 2026-06-02T09:15:00Z
by: engineering-lead
impact:
summary: 'Catalogue v0.155.0: nothing in this estate is affected.'
- version: v0.159.0
previous: v0.155.0
at: 2026-07-14T11:30:00Z
by: engineering-lead
impact:
summary: 'Catalogue v0.155.0 to v0.159.0: 1 entry is newly deprecated.'
lines:
- 'processor/batch is deprecated for logs in this version. 1 Blueprint uses it: data-flow/gateway-standard (Data flow).'
# teams.yaml
teams:
- id: engineering
name: Engineering
teams:
- id: platform
name: Platform Engineering
owners: [platform-observability]
teams:
- id: data-flow
name: Data Flow
owners: [gateway-owners]
# telemetry.yaml
self_telemetry:
endpoint: https://otlp.observability.internal:4318
environments:
production: https://otlp-prod.observability.internal:4318
Team directories
Each subdirectory of teams/<team-id>/ holds one kind of authored object, one
object per file:
| Directory | Object | Id it derives | Documented in |
|---|---|---|---|
components/ |
shared Component | <team-id>/<name> |
Blueprints |
blueprints/ |
Blueprint | <team-id>/<name> |
Blueprints |
tiers/ |
Tier | <team-id>/<name> |
Tiers |
services/ |
Service | <team-id>/<name> |
Tiers |
rollouts/ |
Rollout | <team-id>/<name> |
Tiers |
The loaders apply these rules to every one of them:
- The file's place in the layout gives it its id.
teams/infosec/components/pii-redaction.yamlis the shared Componentinfosec/pii-redaction. - Only
*.yamland*.ymlfiles directly inside the directory are read. Subdirectories are ignored. - A file holds exactly one object, as a YAML mapping. A list, a second YAML document, or an empty file is a load error.
- Unknown fields are rejected. A misspelled key fails the load, and the message names the file and the field.
- The layout decides the id, not the body. A
name:in the body must match the filename when present. Blueprints and shared Components require it; Tiers, Services, and Rollouts take the filename when it's absent. - A team directory name containing
/,@, or whitespace is a load error: those characters are reserved separators inside references. - A missing directory is empty. A team doesn't have to author every kind.
A team directory can hold other files; the loaders read only the five directories above.
The rendered tree
telecraft render writes rendered/. Never commit into it by hand: CI
recomputes the tree from the authored sources and fails on a mismatch.
| Path | When it exists | What it holds |
|---|---|---|
rendered/<team>/<tier>.yaml |
one per Tier, always | The Tier's plain otelcol config, commit-stamped. |
rendered/<team>/<tier>.supervisor.yaml |
the Tier declares serving |
The OpAMP Supervisor config for that Tier. |
rendered/<team>/<tier>@next.yaml |
a Rollout targets the Tier | The candidate render under the Rollout's to binding. |
rendered/_estate/unmatched.yaml |
always | The Unmatched artefact, served to a collector matching no Tier selector. |
The @next.yaml artefact exists exactly while the Rollout does, and is
removed with it. See Tiers for dual binding.
Every artefact opens with a generated header naming what it is and the commit
SHA it was rendered at, and stamps telecraft.commit onto the collector's
self-telemetry resource. A Tier's artefact also stamps telecraft.tier with
the Tier's team-qualified id; the Unmatched artefact stamps
telecraft.unmatched: true instead. These attributes ride only on the
collector's self-telemetry resource, never on customer data.
Generated CODEOWNERS
CODEOWNERS at the repository root is a projection of teams.yaml and the
objects' owner: fields, written in the forge's dialect. It's a cache: losing
it loses nothing, because the tree is the source.
For each team with at least one owner on its chain, the renderer writes two lines:
/teams/<team-id>/ @owner @ancestor-owner
/rendered/<team-id>/ @owner @ancestor-owner
Handles are the team's own owners first, then each ancestor's owners, nearest ancestor first, deduplicated. Review reach follows the team tree, not the directory shape, so an ancestor keeps its say over a team nested several levels below it. A team whose whole chain has no owners gets no line at all, which leaves the path to the repository default.
When the root Team or Teams carry owners, the renderer writes one further line:
/rendered/_estate/ @root-owner
Authored versus generated
| Path | Written by |
|---|---|
teams.yaml, telemetry.yaml, live-check.yaml, allow-lists.yaml, grants.yaml, users.yaml, auth.yaml |
you |
teams/** |
you |
rendered/** |
telecraft render |
CODEOWNERS |
telecraft render |
Catalogue artefacts are never estate repository content. They're instance-side artefacts with their own import pipeline; see Catalogue.
The ownership directory
telecraft check -ownership takes a directory with a different shape from the
estate root: teams.yaml plus flat authored-object files beside it, each
holding one object or a list of objects with kind, id, and owner fields.
allow-lists.yaml, grants.yaml, users.yaml and auth.yaml are skipped,
so one directory can carry the whole authored set.
kind is one of component, blueprint, tier, hop, path, service,
requirement, or exemption. collector is rejected: a collector is never
authored, and it inherits its owner from the Tier it matched into. An object
with no owner, or with an owner the team tree doesn't know, is a load error,
because a finding routed to it would reach nobody.
Source sets and satellite repositories
An estate is a set of repositories, each mapped to exactly one Team subtree.
A single repository is the ordinary case. Every loader that walks teams/
accepts several roots, and blueprint-check takes them as positional
arguments.
A satellite repository has the same internal layout as a monorepo subtree, so
moving it back into the primary repository is a mechanical move. Governance
stays in the primary repository: a satellite team appears in the primary
teams.yaml like everyone else, and Grants targeting the subtree live with
their ancestors there. References run from satellite to primary only, never
primary to satellite and never satellite to satellite.