Personal overrides
Some values belong in the environment but not to the team. Your own OAuth refresh token. The API key for your personal sandbox tenant. The port your machine happens to have free.
A personal override stores your value for a key alongside the shared one, encrypted so that only you can decrypt it — and it wins over the shared value when you run the app. Teammates keep seeing theirs.
Two scopes, one composed view
Section titled “Two scopes, one composed view”Every environment has a shared stream — the one everybody with a grant
sees — and, per member, an optional personal overlay. What
cendarum run injects is the effective map, composed under a frozen
precedence:
ambient < shared < personalYour real process environment is inherited, the shared values overlay it, and
your personal overrides overlay those. run takes no --scope flag,
deliberately: injecting the composition is the feature, and a flag would imply
it is optional. If you need a single stream as a file, that is
export --scope.
A personal override is a scope inside an environment, not a third level of hierarchy. It has no name of its own, no grants of its own, and no existence apart from the environment it overrides.
Nobody else can read a personal value
Section titled “Nobody else can read a personal value”Not your teammates. Not a project admin. Not the server, and not us.
The key that encrypts your overlay is derived on your device from your own private key. It is never wrapped, never uploaded, and exists nowhere else — the server stores no key material for this feature at all. And the API has no route that could serve your overlay to another user: neither personal endpoint takes a user id, so cross-user access is unrepresentable, not merely refused.
How the personal key is derived has the full construction.
The shadow warning
Section titled “The shadow warning”When a personal override shadows a shared value, every run (and every
export --scope effective) says so — one value-free stderr line, key names
only:
cendarum: personal overrides shadow the shared values of: API_URL — verify these are still current (a rotated team value stays shadowed by your override)The warning cannot know “stale” — only “winning”. If a teammate rotates a
shared credential, your override keeps winning silently for you; this line is
the disclosure. A personal-only key is not a shadow and is not named, and a
key excluded by --only or the loader denylist is not named either, because
it is not injected.
From the CLI
Section titled “From the CLI”import and export require --scope. There is no default, deliberately: a
merged default is how a personal secret ends up inside a .env you hand to a
teammate. You name the stream you are writing, every time, and a missing
--scope is refused before any password prompt or network call.
# Write the team's streamcendarum import --env dev --scope shared --file .env
# Write your own overlay — readable by you aloneprintf 'MY_TOKEN=personal-tok\n' | cendarum import --env dev --scope personalexport adds a third token, effective — exactly what run would inject,
shadows included. import --scope effective does not exist: a write targets
one stream. The details live in import and
export.
In the app
Section titled “In the app”A shadowed key renders as grouped rows: your personal (winning) row first, badged personal — overrides shared, with the shared twin adjacent — dimmed and marked overridden. Both stay revealable and editable; which scope wins is never ambiguous, because the badge is the disclosure.
The add and edit drawer carries a scope selector — Shared preselected, personal (only you) beside it. Adding a personal value for a key that already exists in shared is not an error; it is the feature, and the drawer says what will happen: “This key exists in shared — your personal value will override it, for you only.”
Who can write one
Section titled “Who can write one”A read grant is enough to write your own overlay. The feature exists for exactly the read-only collaborator who needs a personal token. The shared stream still requires a write grant, and no grant at all still means the uniform not-found on every surface — a personal write mutates nothing anyone else ever decrypts.
Sync, versions, and clearing
Section titled “Sync, versions, and clearing”Your overlay is a second encrypted blob, per member, with its own version counter — same optimistic concurrency as the shared payload, same client-side three-way merge, same refusal to pick a winner silently. Conflicts park per scope: a shared conflict and a personal conflict on the same environment are resolved separately.
There is no delete. Clearing your overlay is a write of an empty overlay — versions only ever grow.
That monotonicity powers an alarm. Once your client has seen your overlay
exist, it remembers that durably. A server that later claims you have none is
not believed: the CLI fails closed (cendarum: error: personal_missing) and
the app raises a stop-and-verify alarm rather than silently falling back to
the shared values — which is exactly the substitution that omission would
otherwise buy. A blob only one person ever reads has no honest second reader
to catch a suppression, so your own client carries the burden of proof.
What survives what
Section titled “What survives what”| Event | Your personal overrides |
|---|---|
| Password change | Survive — they are rooted in your key pair, not your password |
| Personal recovery (Recovery Key) | Survive — the key pair is unchanged |
| Admin-assisted recovery | Destroyed — a new key pair is minted; see below |
| Environment key rotation | Survive — deliberately not bound to the environment’s key epoch |
| Your grant is revoked | Retained server-side, unreachable until you are re-granted |
Admin-assisted recovery mints a new key pair. Your overlays were encrypted under a key derived from the old one, no admin ever held it — that was the point — and the server deletes the now-unreadable blobs when recovery completes, rather than leaving them to fail forever as apparent tampering. Gone by construction, not by policy. Rotation surviving cuts the other way: after a teammate rotates a shared credential, re-read the shadow warning.
Audited exactly once
Section titled “Audited exactly once”Writing a personal override records personal.write, account-scoped with
empty metadata — which means it does not appear in the project audit read
today, the same account-level gap as password changes and recoveries.
Personal reads are not recorded at all, by design: the only possible
reader is the owner, and a project-admin-readable trail of a colleague’s
personal-override activity is a surveillance surface this feature has no
reason to create.
Both halves are chosen trade-offs, stated rather than discovered: this feature’s one mutation currently has zero observability in the product.