Repository navigation
How does config reach a planning run? Four claims were withdrawn because the framework reads it #694
Description
Epic: #441
Related: #530 (hermeticity), #677 (the claims that surfaced it)
The question
How does configuration reach a planning run?
Four provider methods had to give up +devlore:claim=sandboxed on 2026-08-26 because they read machine-local
configuration while planning. The reads are not wrong — the claims were — but the pattern is now visible twice,
and a third instance would make it a design gap rather than a coincidence.
| Method | Reads | Through |
|---|---|---|
file.Find, file.Glob, file.WalkTree |
the global gitignore | gitignore.NewTracker → xdg.ConfigPath → os.UserHomeDir |
encryption.EncryptFile |
the SOPS config | sops.locate → xdg.ConfigPath |
The strawman, and it is a strong one
The framework cannot read config. Config is processed by the application using the framework. Those are
inputs designed to guide planning. Anything the framework reads itself invalidates a claim to determinism —
config can change on a heartbeat.
This is already how the two things that work correctly work:
platformclaimsdeterministicbecause its values are supplied to the runtime environment via
WithPlatform. The syscalls happen once, inDetect(), in the host application. Had the provider read the
host per call, it could claim nothing.- The graph timestamp became deterministic on 2026-08-26 for exactly this reason (Every graph checksum embeds its construction timestamp, so no two identical plans agree #690):
NewGraphstopped
reading the clock and takesRuntimeEnvironmentSpec.ConceivedAtinstead. Supplied, not read.
Config would be the third application of one rule: the framework reads nothing; the application supplies
everything.
What it would take
For each instance, the configuration becomes an input rather than a lookup:
- gitignore — the ignore patterns are supplied to the tracker rather than discovered from
$HOME. The
application resolvescore.excludesfileand the XDG fallback once, and hands over the result. - SOPS — the config is supplied to
sops.Encryptrather than located by it.
Both then read only what the spec carries, and both can claim sandboxed again. Whether either can claim
deterministic is a separate question — see below.
One plainer bug found alongside it
pkg/gitignore/tracker.go:203's loadPatterns uses os.Open on an absolute path, and it is called for
in-root .gitignore files too, not only the global one. That read is inside the root and should go through
an fsroot.Dir regardless of how the config question is settled — the same class as the os.Chown escape fixed
in #687. It is smaller and independent, and worth doing first.
What this does not settle
deterministic is off for file.Find / Glob / WalkTree for a second, independent reason: their results
depend on which .gitignore files exist in the tree. Root contents are declared inputs per 2.4, but nothing
freezes them, so two runs can legitimately differ. Fixing the config question restores sandboxed, not
deterministic.
The distinction the vocabulary cannot currently express
xdg.ConfigPath is not arbitrary filesystem access — it is a bounded, well-known location holding declared user
configuration. To the claim checker it is indistinguishable from os.Open on anything else. If the strawman is
adopted the distinction stops mattering, because the framework does neither. If it is not, the vocabulary needs a
way to say reads its own configuration as against reads whatever it likes — and that is a fourth claim, which
should not be invented before the rule is decided.
Activity