Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wiring

Audience: operators who deploy Aperture, and anyone answering "is what is deployed what is in the repository?".

aperture wiring is the operator's window onto shared wiring: the part of a seed document that belongs to the deployment rather than to one instance. Push it once and every instance sharing that database reads the same wiring — including an instance that has no seed file at all.

Four subcommands, and the tree is complete:

CommandQuestion it answers
pushmake the deployment's wiring this document's four shared sections
showwhat is deployed, in words
pullwhat is deployed, as a file a push accepts unchanged
diffdoes the deployment match the repository

All four take --store and no acting principal. The store credential is the authority, exactly as it is for import, and the three read commands restate wiring that credential already grants full write access to. --store has no default on any of them: an in-memory store is private to the process that opened it, so there is nothing shared about its wiring to write or to read.

Throughout, the deployment is the house example: one account, acme, whose objects are spelled account:acme/project:atlas/document:42.

Four sections are shared and two are local

A seed document's ten model-state sections are applied by --seed (or import). Its six wiring sections say where a decision reads object metadata and subject attribute bags from, and they split four/two:

SectionHome
connections:shared — the manifest of connection names, and nothing else
providers: (with its references:)shared
field_types:shared
attribute_providers:shared
objects:local to the instance whose file lists it
attributes:local to the instance whose file lists it

The line is a pointer to data versus the data itself. A providers: or attribute_providers: entry says where to read metadata or an attribute bag from; a field_types: entry says how to read a field; a connections: name says which pool an entry cites. All four describe the deployment and all four are safe to copy to a second host. An objects: entry carries the metadata and an attributes: entry carries the bag — and Aperture's own database is never the source of truth for a host's domain data. Those two are not waiting for a later release; there is nowhere for them to go.

Wiring is also not model state, and the two are pushed by different commands. aperture export emits the model and no wiring; aperture wiring pull emits the wiring and no model. Wiring for a model that is not there is refused rather than stored — see push.

What is never stored

No DSN, no credential, not even the name of the environment variable holding one, and no filesystem path. This is a property of the schema — there is no column for any of it — rather than of a validator, and it is the reason the surface is safe at all:

  • A row is copied to a second instance that resolves its own credentials, so a secret column would put a credential in every backup of this database.
  • A path column would be a guess about another host's disk. A relative path: is resolved against the seed file's directory, which database-sourced wiring has none of.

kind: csv is therefore refused by push (APERTURE_WIRING_KIND_UNSHAREABLE), and stays perfectly legal in a local seed file — the instance that reads the path is the instance the path belongs to.

A shared connection carries its name, and each instance supplies its own route for that name: seed.WithConnectionOpener in a Go host, a connections: entry under the same name in this instance's own seed file, or the conventional APERTURE_CONNECTION_<NAME>_DSN. That is why a pulled document comes back with an empty dsn_env: for every connection: it is re-pushable but not bootable until the routes are filled in. The three routes are set out in full under It means the same thing for wiring.

push — deploy a document's shared wiring

aperture wiring push --store <dsn> --seed <file>

Reads providers:, field_types:, connections: and attribute_providers: out of --seed, validates every entry, and replaces the store's wiring with them in one transaction. The document's other sections are not read: no model state is applied, and the two local wiring sections are untouched.

bin/aperture wiring push --store 'postgres://…/acme' --seed model.yaml

The push is all or nothing. Every rule is checked before anything is written, so a refusal leaves the deployed wiring exactly as it was. Wiring is only meaningful whole — an entry citing a connection the manifest does not list is not half-valid wiring, it is broken wiring — and an instance booting against a half-written set would build a registry missing exactly the entries whose write failed, while reporting nothing.

It replaces rather than merges. An entry dropped from the document is dropped from the store. Push the whole wiring every time. There is deliberately no per-row edit.

A push is refused when:

RefusalBecause
APERTURE_WIRING_NO_MODEL_STATEthe store holds no model state at all, so there is nothing for the wiring to be wiring for. A typo'd DSN opens — and Setup creates — a perfectly valid, perfectly empty database, and without this the wiring lands there and the instance that serves decisions never sees it.
APERTURE_WIRING_KIND_UNSHAREABLEan entry selects kind: csv, whose only data source is a machine-local path.
APERTURE_WIRING_OBJECT_TYPE_UNKNOWNa provider entry serves an object_type the store has no row for. The refusal names the type.
APERTURE_WIRING_CONNECTION_UNDECLAREDan entry cites a connection: the pushed manifest does not declare. The refusal names it and lists the ones that were.
APERTURE_SQL_PROVIDER_DSN_LITERALanything carries a literal dsn:. Only dsn_env:, a variable name, is ever accepted — and shared wiring stores neither.
APERTURE_WIRING_EMPTY_PUSHthe document declares no wiring at all and --allow-empty was not given. See below: this is the one refusal about what the push would mean.

An all-empty document is refused

Because a push replaces, a document with no wiring in it is not "nothing to do" — it retires every entry the deployment is running. All five tables are emptied and every instance goes back to building its wiring from its own --seed file; an instance with no --seed file, which is the shape this whole feature exists to enable, then builds none. It still starts, with an empty object registry and an empty attribute registry. Every attribute slot is unregistered, an unregistered slot answers with an empty bag, and a rule that excluded on an attribute it can no longer see stops excluding — so the grant widens, with nothing in a verdict, a trace or a note to say so. Under --wiring-poll it lands without a restart.

The two documents that produce an empty set are indistinguishable from the store's side and both are usually mistakes: the wrong --seed path, and a file whose section key is mistyped. provider: for providers: is not an error — the seed reader does not refuse unknown keys, so the section is simply not there.

Retiring a deployment's wiring is a legitimate act, so --allow-empty says it deliberately; the summary then spells out what was retired, because five zeroes read as a summary rather than as a retirement.

A key that names no section is reported

The refusal above only fires when the document has no wiring at all. A partial retirement — one mistyped key in a document that still declares other sections, so the set is not empty and the push legitimately proceeds — is invisible to it, and from the store's side indistinguishable from deliberately dropping an entry.

So push warns about every top-level key that names no section, before it writes:

warning: wiring.yaml carries 1 top-level key(s) that name no section: provider
         Nothing was read from them. If one is a mistyped wiring section, the push below
         retires that section for every instance of the deployment.

It is a warning and never a refusal, because a document may legitimately carry keys Aperture does not know — another tool's section, or a key from a newer Aperture than the binary reading it — and refusing those would break files that work today. Only the key names are printed: a value under an unknown key could be anything, including a credential somebody put in the wrong place.

diff is the other half, and the one that catches a section dropped without a typo.

A push is audited: one WiringPush mutation record against model.AccountWildcard, carrying the per-section counts and the deployed connection names, object types and attribute slots. All of that is deployment-wide wiring vocabulary; none of it is account data.

Full flags: wiring push.

show — read what is deployed

aperture wiring show --store <dsn>

Prints the five shared-wiring tables, one table per section, so "what is this deployment actually wired to?" is answerable without a database client. It takes no --seed: a listing that merged the document on the command line would answer "what would a push deploy?" while looking like it answered this question.

bin/aperture wiring show --store 'postgres://…/acme'

An empty store is an answer, not an error. A store nothing has been pushed to says so plainly, and every instance booting against it builds its wiring from its own --seed file instead. It is also what a mistyped --store looks like, because a DSN naming a database that does not exist yet is one Setup creates — so check the DSN before concluding a push was lost.

Two columns are spelled as words rather than left blank. ttl and max-size read (default) when the entry sets neither, because the registry's own default applies and 0 would read as "caches nothing". The declared key set distinguishes three states: (not declared) is a slot that opted out of key enforcement entirely, (declared empty) is a slot that opted in and permits no keys at all, and a list is the keys the slot guarantees.

Full flags: wiring show.

pull — snapshot what is deployed to a file

aperture wiring pull --store <dsn> --out <path> [--format json|yaml] [--force]

Reads the deployed wiring in one atomic snapshot and writes it as the same four sections a push reads. The file is a seed document push accepts unchanged, so push → pull → push deploys identical wiring, and two pulls of an unchanged deployment are byte-identical.

bin/aperture wiring pull --store 'postgres://…/acme' --out deployed.yaml
diff -u model.yaml deployed.yaml

show is the better command for reading; this one produces a file for a tool. Three behaviours are worth knowing before you script it:

  • The file is re-pushable but not bootable. Every connection comes back with an empty dsn_env:, because shared wiring holds a name and nothing else. Fill the routes in from your own deployment's environment before booting an instance from the file; one built from it as written refuses at registry build and names the unset variable.
  • An existing --out file is refused unless --force is given (APERTURE_WIRING_OUTPUT_EXISTS). The likeliest thing at that path is the version-controlled document the pull is meant to be compared with. The path is checked before the store is opened, so the refusal reads nothing.
  • A store with no wiring deployed is refused (APERTURE_WIRING_NOTHING_DEPLOYED) — the one place pull disagrees with show. A pull's file exists to be pushed back, and an empty one pushed back replaces the deployment's wiring with nothing.

Full flags: wiring pull.

diff — compare the deployment against the repository

aperture wiring diff --store <dsn> --seed <file>

Names, per section and per entry, what is only deployed, what is only local, and what differs. The section column is the document's own section key, so a reported entry is one you can go and find. Nothing is written, to the store or to a file.

bin/aperture wiring diff --store 'postgres://…/acme' --seed model.yaml

It is meant for a pipeline, and the exit codes are the interface:

ExitMeaning
0the deployment matches the repository
2it does not
1I could not tell — an unreadable document, a --store that names nothing, a schema this build cannot read

Drift is the answer to the question asked, not a failure to answer it, so it carries no APERTURE_* code — there is no remedy for a report, and which side is wrong is your call. It follows check's precedent: a clean DENY prints its verdict and exits non-zero, because a verdict is not an error either.

The code is 2 and not 1, and that is the useful half. Every coded refusal this binary makes exits 1, so a gate that failed closed on 1 and 2 alike would report drift for a typo in its own DSN.

Two more properties keep a gate green when it should be:

  • Formatting is never drift. Both sides are compared as wiring, in the one canonical order a read returns, so re-ordering the document's entries, re-ordering a fields: or references: map, or re-indenting the file changes nothing. The push timestamps are excluded too — a stamp is a fact about the last push, not about the wiring.
  • A kind: csv entry is reported as local-only by design, not as drift and not as an error. No push can make the deployment match it, so counting it as drift would leave a permanently red gate on every deployment that legitimately keeps a local csv provider — and a gate that cannot go green is a gate somebody switches off.

Make the deployment agree with the document by pushing it.

Full flags: wiring diff.

Booting against pushed wiring

An instance reads the shared wiring at startup, and what it does with what it finds is documented once, under Global options:

  • Two instances, one store — the four steps of standing up a second instance against a database that already has one.
  • Seed & portability — the six sections, and the three read-backs that answer three different questions.
  • Storage — the five apt_wiring_* tables and what they may never carry.
  • Global options — --seed / --store, and what an omitted --seed means for wiring.
  • Portability — export / import, the model half of the same story.
  • Command-Line Reference — the generated per-command flag tables.