Files
chemenu/docs/explanation/configuration-tiers.md
T
torbenandClaude Opus 5.5 e085a8b6c1
CI / verify (push) Successful in 8m4s
CI / pwsh (push) Successful in 2m31s
docs: settings pull-through after #207 - secrets file path on Windows and with XDG in the 1Password how-to; 1Password approval is per terminal session, not per value
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
2026-10-10 18:10:07 +02:00

5.1 KiB

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; the rules an agent follows are in 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). 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 - 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.