Files changed: - docs/explanation/configuration-tiers.md - docs/how-to/use-1password-for-settings.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
87 lines
5.1 KiB
Markdown
87 lines
5.1 KiB
Markdown
# Configuration tiers
|
|
|
|
Settings are split into three tiers: what belongs to the instance (`wikitool.json`, committed),
|
|
what belongs to one checkout (`.wikitool-local.json`, gitignored), and secrets, which sit
|
|
outside the working tree and are reached only by reference. The files, their fields and the
|
|
command that writes them are in [the configuration reference](../reference/configuration.md#tiered-settings);
|
|
the rules an agent follows are in [AGENTS.md § Settings](../../AGENTS.md#settings). This page is
|
|
about why it is built this way, and what it does not do.
|
|
|
|
## Three kinds of data that used to share files
|
|
|
|
Before the tiers, every feature kept its settings in a gitignored file of its own. Three kinds of
|
|
data ended up mixed in them:
|
|
|
|
- **What belongs to the instance.** The weekly review's thresholds are a decision about the
|
|
corpus, and every clone of it should make the same one. Gitignoring them meant each clone had
|
|
to be told again.
|
|
- **What belongs to one checkout.** A tool path, the push URLs a checkout may publish to, the
|
|
consent to record telemetry on this disk. Committing any of these would hand every other clone
|
|
an answer that is wrong for it rather than missing.
|
|
- **Secrets.** A tracker token sat in plain text in the working tree, protected only by
|
|
`.gitignore` - one wrong pattern away from a commit, and in plain view of every agent that read
|
|
the file in the course of ordinary work.
|
|
|
|
The tiers give each kind one place, declared per field, so the question "where does this go" is
|
|
answered once, by whoever declares the field, rather than each time by whoever writes a value.
|
|
|
|
## One place per value, not an override
|
|
|
|
A familiar design lets the local file override the committed one. It is deliberately not this
|
|
one: a value in the wrong file is an error. With two possible sources for one value, every
|
|
reader first has to work out which one wins, and the answer differs between clones - a reviewer
|
|
looking at `wikitool.json` would no longer know what the instance actually runs with. One place
|
|
per value keeps the committed file a true statement about every clone.
|
|
|
|
## What runs, and where it pushes, never travels with a pull
|
|
|
|
A field that names a program to run, or that arms or loosens a gate, is never in the committed
|
|
file - not even as a reference, since a resolved reference is still a value someone else chose.
|
|
Otherwise a `git pull` from another clone could change what `wikitool` executes on this machine,
|
|
or where `publish` sends this checkout's content. The rule is checked when a field is declared,
|
|
in code, so no reader has to remember it; the reasoning is the same as for the gates
|
|
([why-gates-are-code.md](why-gates-are-code.md)). The same fields are also not written by
|
|
`config set`: like the Publish-Remote Gate's allowlist, they change by the user's own edit.
|
|
|
|
## References instead of values
|
|
|
|
A secret is only ever a reference, and a private value - a host name, an internal URL - may be
|
|
one. A reference names where the value lives: a name, which resolves through a
|
|
`WIKITOOL_SECRET_<NAME>` variable or a secrets file in the user's own configuration directory, or
|
|
an `op://` URI, which resolves through the 1Password CLI.
|
|
|
|
That is what makes the committed file portable. An instance that is public commits
|
|
`wikitool.json` publicly, so a private value can never sit there as text; as an `op://`
|
|
reference it can, and every machine signed in to the same 1Password account resolves it to the
|
|
same value. In CI, `op` runs with a service-account token and resolves the same `op://`
|
|
references. Alternatively, 1Password's own action resolves values into variables before
|
|
`wikitool` starts, which serves references by name. This repository's CI does the second.
|
|
|
|
A reference is resolved only when a command asks for that one value, never when the files are
|
|
read, and the value stays in that process's memory. `doctor` reports whether each reference
|
|
resolves, never what it resolves to.
|
|
|
|
## What this does not protect against
|
|
|
|
Keeping secrets out of the tree protects against three things: a secret being committed, a
|
|
secret sitting in a file an agent reads while doing ordinary work, and a secret appearing in
|
|
`wikitool`'s output.
|
|
|
|
It does not protect against an agent with shell access that sets out to read one. If 1Password
|
|
is unlocked in the terminal, the agent can run `op read` itself. It can read the secrets file. In
|
|
general, whatever the `wikitool` process can read in a user account, an agent running in that same
|
|
account can read too. The stack does not claim otherwise.
|
|
|
|
What limits the exposure without preventing it:
|
|
|
|
- a 1Password vault of its own for this instance's entries, so an unlocked `op` reaches nothing
|
|
else;
|
|
- the approval prompt of the 1Password desktop app, which asks a human before the CLI may read
|
|
anything - per terminal session, not per value, so it stops a session nobody approved and not
|
|
one that already was;
|
|
- in CI, a service account that sees only that vault.
|
|
|
|
The rule in [AGENTS.md § Settings](../../AGENTS.md#settings) - a secret never passes through the
|
|
agent - is an instruction, not a barrier. The tiers make following it the easy path. They do not
|
|
make breaking it impossible.
|