Environment
One contract per repository states what your application requires of its operator. It is the sole authority over which variable names exist in any sandbox Certus runs.
Two files, one direction
The environment contract says what your application requires. The recipe, .certus/env.yaml, says how to run it. Different files, different owners, different lifetimes.
- .certus/env.contract.yaml
- Derived at index time, committed, machine-owned. Nothing a person writes here survives re-derivation.
- .certus/env.overrides.yaml
- Hand-written, committed, yours. Certus reads it and never writes it. Every entry carries a mandatory reason.
The effective contract is the derived file with your overlay applied field by field — overriding whether a variable is required does not discard what Certus worked out about its type. Where your overlay contradicts something observed at run time, the entry is marked contested: both claims are kept with their provenance, yours is used, and it is surfaced for a person to settle.
Certus never reads, ingests, stores or copies .env, .env.local, .env.production or any other file holding live values. A .env.example template contributes exactly three things: the variable names, their order, and the shape of each value. Never a value.
Re-derivation opens a pull request against the contract. It never rewrites it silently.
What an entry says about a variable
Each entry carries a type, which decides how Certus tries to fulfil the variable and whether the host it names gets a network route.
- database
- A stateful store your application owns. Substituted locally first.
- cache
- An ephemeral backing service — a cache or a broker. Substituted locally first.
- object_storage
- Blob storage: an endpoint and a credential together.
- external_connection
- A third-party API. No local substitute exists, so this is where a credential is genuinely needed.
- generated_config
- Does not exist until a run is allocated: sandbox URLs, ports, per-run keys. Certus synthesizes it.
- runtime_config
- A literal you or the author chose: log level, locale, a tuning knob.
- observability
- Telemetry egress — error reporting, tracing, application monitoring. The one type whose host is explicitly denied: a sandbox never ships telemetry into your own project.
- unknown
- Nothing determines the type. Treated conservatively.
Three independent properties sit alongside the type, and they are not statuses.
- secret
- Whether the value is redacted and leased rather than materialized. Assumed true unless there is positive evidence otherwise — a literal in a template, a public build-time prefix, or your own assertion. Absence of evidence is not evidence of publicness.
- required
- A statement about correctness: the code reads it with no default and no fallback. Can be inferred.
- boot_critical
- A statement about timing: the read happens at import or startup validation. Cannot be inferred — it needs an observed startup failure, a runtime observation, or your assertion. Appearing in a template never promotes it.
Only boot_critical can block a run. A merely required variable produces at most a deferral and a grade cap. The asymmetry is deliberate: a variable wrongly marked boot-critical turns a working run into a blocked one, which reads as Certus refusing to work.
Secrets are write-only
There is no form to fill in. Certus asks for a credential only when the contract says a variable is secret, and no rung below a customer-supplied credential — a local substitute, a recording — could resolve it. Supplying a name nobody asked for is refused. Certus does not ask for a credential it would not have used.
Stored values are encrypted per row, bound to your tenant, your repository, and that exact name, so a ciphertext cannot be replayed into another tenant, moved to another repository, or renamed.
Nothing ever returns a stored value. No route, no command, no tool, no support path, no break-glass read. What you can see is the name, the provider, which services read it, what it gates, when it was supplied and by whom, when it was last used. No digest, no prefix, no suffix, no length, no masked rendering — a digest you can compare against turns a guess into a confirmed value.
So a secret has exactly two states in the console: supplied, or not. Never a percentage.
Supplying many at once
You can upload a dotenv file. Certus records nothing about the file — not its name, not its contents — only about the names inside it, and only where a request exists for one. Each line resolves to one of seven outcomes, checked in order, so every way of not storing a value is tried before storing one.
- malformed
- The line is not an assignment. Nothing stored.
- duplicate_name
- The name appears more than once. Every occurrence is refused rather than picking one, because dotenv readers disagree about which wins and a credential chosen by coin flip surfaces as a provider auth error inside a run.
- control_plane_name
- A reserved name. Never stored.
- not_in_contract
- The contract does not know this variable. Nothing is stored, and Certus returns a proposed overlay entry for you to review and commit.
- not_requested
- In the contract, but Certus does not need a credential for it.
- empty_value
- Nothing on the right-hand side. Nothing stored.
- fulfils_request
- Stored.
A file that is too large or has too many assignments is refused whole, never truncated. Upload is safely repeatable: re-uploading the same file replaces the same rows with the same values.
Certus returns the proposed overlay and stops. It does not write to your repository, open a pull request, or apply the overlay for you.
What happens at run time
A name that is not in the contract is simply not set in the sandbox. That is what “sole authority” means.
A variable that is required and boot-critical with no reachable way to fulfil it blocks the run — never fails it, never leaves it inconclusive. A variable with no reachable fulfilment that is not boot-critical does not block: the run proceeds and the obligations that read it are deferred, with the variable named.
Reading a contract written by a newer version of Certus also blocks, naming both versions. Unknown fields are not ignored, because an unknown field in a future contract may be restrictive, and ignoring it would fail open on a security artifact. Failing closed costs a blocked run; failing open costs a leaked credential.
When it says something unexpected
- Certus asks for a credential you think it does not need
- It asked because no cheaper rung could resolve that variable. Check the entry's type: something typed as an external connection has no local substitute.
- A variable is marked secret that is not
- Assert it in
.certus/env.overrides.yamlwith a reason. Certus defaults to secret without evidence, and a public build-time prefix is the usual missing evidence. BLOCKEDnaming a variable- It was marked boot-critical and could not be fulfilled. Supply it, or correct the entry if the read does not actually happen at startup.
- An entry marked contested
- Your overlay and what was observed at run time disagree. Yours is being used. Settle it, because one of the two is wrong about your application.
- A variable your code reads that is unset
- It is not in the contract. Add it through the overlay; the contract, not the sandbox, decides what exists.
- A dotenv upload stored fewer values than lines
- Read the per-line outcomes. Duplicates, reserved names, empty values and names with no request are all refused by design.