Skip to content

How does config reach a planning run? Four claims were withdrawn because the framework reads it #694

Open

Description

@David-Noble-at-work

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:

  • platform claims deterministic because its values are supplied to the runtime environment via
    WithPlatform. The syscalls happen once, in Detect(), 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): NewGraph stopped
    reading the clock and takes RuntimeEnvironmentSpec.ConceivedAt instead. 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 resolves core.excludesfile and the XDG fallback once, and hands over the result.
  • SOPS — the config is supplied to sops.Encrypt rather 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area:ArchitectureSystem architecture and designEpic:UnifiedConfigurationUnified configurationPriority:P2Scheduled, not urgentSeverity:HighA supported path is broken with no workaroundfeatureDeliverable unit within an epic

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions