Inheritance & Worked Example
Both connections and environments support extends: for composition.
Connection inheritance — a base template
Section titled “Connection inheritance — a base template”extends: on a connection is a base-template pattern. Define the shared fields once:
type: zenvara/akeneokind: baseTimeoutSeconds: 120BatchSize: 100MaxConcurrency: 4AutoSanitizeCodes: true
# connections/akeneo-pim-prod.connection.yamltype: zenvara/akeneokind: productionextends: akeneo-baseBaseUrl: "https://prod.cloud.akeneo.com"ClientId: "${secret:akeneo/pim-prod/client-id}"ClientSecret: "${secret:akeneo/pim-prod/client-secret}"Username: "${secret:akeneo/pim-prod/username}"Password: "${secret:akeneo/pim-prod/password}"kind: and type: are required on every connection file — including a child that extends: a base — because they’re validated before inheritance is resolved (each file must independently declare the same type: as its parent). kind: base marks a template as extends-only: it is never bound directly into an environment and only a kind: base connection may be extends:ed.
Three overrides plus one base, instead of four near-identical files.
Environment inheritance
Section titled “Environment inheritance”extends: on an environment is a list of parent environments composed in order — single-level only (no transitive chains), to keep the resolution graph easy to read.
kind: stagingextends: - commonconnections: db: connection: warehouse-staging # overrides whatever `common` had under dbWorked example: dev → prod for one flow
Section titled “Worked example: dev → prod for one flow”Two connections (one Postgres, one SFTP), two environments, one flow:
type: zenvara/postgresqlkind: baseTimeoutSeconds: 30
# connections/audit-postgres-dev.connection.yamltype: zenvara/postgresqlkind: developmentextends: audit-postgres-baseConnectionString: "${secret:audit/dev:connection-string}"
# connections/audit-postgres-prod.connection.yamltype: zenvara/postgresqlkind: productionextends: audit-postgres-baseConnectionString: "${secret:audit/prod:connection-string}"
# environments/dev.environment.yamlkind: developmentconnections: audit: connection: audit-postgres-dev drop: connection: drop-zone-dev
# environments/prod.environment.yamlkind: productionconnections: audit: connection: audit-postgres-prod drop: connection: drop-zone-prodstatus: active
using: - environment/dev - environment/prod - zenvara/postgresql - zenvara/sftp
output: archived: !int
steps: - $rows: invoke: postgresql.query on: audit with: { Query: "SELECT * FROM incidents WHERE archived = false" }
- $upload: create: sftp.file on: drop with: Path: "/incidents/${= str(_run-id)}.json" Input: "${= toJson(rows.rows)}"
- return: archived: "${= len(rows.rows)}"Run it twice:
zen run-flow incident-archive --env devzen run-flow incident-archive --env prodSame flow, different audit DB, different SFTP drop zone. The environment is the only thing that changed — which is the whole point of the model.
When the definition itself changes
Section titled “When the definition itself changes”This example is the run loop: one finished flow, swapped between environments at run time. The moment you need to edit the flow — or the connections, or the environments — and ship the new version safely, you are in the change loop, and that runs on a different axis: you stage the edit on a branch, review the diff, and promote. Environments answer which systems; branches answer which version. From Dev to Prod walks both loops end to end, and Environments vs Branches explains why they never collide.