The examples below use
local to bind a resource to a variable so it can be referenced later via .res. A local still has to be mentioned inside the forma block to actually be created. See Write your first forma for the full pattern.Reference, don’t store
A resource or target that needs a secret’s value references it:.secretValue.at("key") to pull one entry out of a map-shaped secret and .json("path") to reach into a JSON payload. See Resolvable for the reference mechanism itself.
Generators: credentials formae draws
Referencing solves reading a credential. A generator solves writing one: instead of minting a password at evaluation time and pinning it so it stops changing, declare a generator and bind properties to its output.forma { ... } alongside the resources that bind to it. formae draws the value with a cryptographically secure generator, writes it to every bound property in one command, and keeps only a digest. Without a rotation block the generator never rotates on a schedule, though an apply can still redraw it: binding a new destination draws a value for it, and changing the generator’s spec so the held generation no longer satisfies it forces a fresh draw. This directly replaces the eval-time pattern:
Rotation
Give the generator a cadence and formae rotates the credential on schedule, unattended:password references it, and anything referencing the role, together. Rotation uses ordinary update semantics, so it refuses when the stack, or any of those consumers, has drifted; a stack carrying an auto-reconcile policy is the standing opt-in to overwrite drift, and there rotation proceeds.
Inspect what rotates with formae inventory generators: each generator’s cadence, the instant of its last committed rotation, and the resources that take their value from it.
The window, and what your consumers must do
One thing no rotation scheme with a single credential can remove: there is no transaction spanning the secret store and the system that accepts the credential, so on every rotation there is a brief moment where the two disagree. Anything that reads the secret to authenticate has to be built for that, and the contract is short:- Re-read the credential at least once per rotation period. Resolve it per connection, or cache it with a bounded lifetime. A consumer that reads the credential once at startup breaks on the first rotation and stays broken until it restarts.
- Tolerate transient authentication failures around a rotation, for up to your own cache lifetime, by retrying or reconnecting. The failures end on their own once the cache refreshes.
datastore.postgres.passwordSecretArn at the secret and the agent resolves its own database password per new connection, riding out a rotation of that credential without a restart. See the configuration reference.
Only overlapping validity, two credentials alternating so one is always accepted, removes the window entirely. formae does not orchestrate that today.
Choosing a cadence
The schema floor is fifteen minutes, set by what the common secret store sustains rather than by the scheduler: AWS Secrets Manager retains every secret version from the last 24 hours against a fixed quota of 100 versions per secret and advises against sustained writes more often than once every 10 minutes, so a faster cadence exhausts the quota within hours and every rotation after that fails. Treat minutes-scale cadences as something for a drill, and pick a steady-state cadence in days: 30 or 90 days are common choices. A credential that must turn over faster than the floor wants short-lived credentials issued per use, not a scheduler revisiting a long-lived one.Key pairs
KeyPairGenerator draws an RSA key pair as two named outputs of one draw: gen.privateKey (a PKCS#8 PEM) and gen.publicKey (a PKIX PEM). Bind each half to its own destination and the two always hold halves of the same pair:
bits is 2048 (the default), 3072 or 4096. Both halves are stored opaquely. A binding that names an output the generator’s kind does not produce fails the apply with the destination and the output named, so the wrong half can never land silently. Once a key-pair generator is stored, the agent cannot be downgraded to a version that predates the kind.
