Skip to content

docs: rule that filesystem access is received, never constructed - #409

Merged
David-Noble-at-work merged 1 commit into
developfrom
docs-session-owned-fs
Aug 14, 2026
Merged

David-Noble-at-work merged 1 commit into
developfrom
docs-session-owned-fs

Conversation

@David-Noble-at-work

Copy link
Copy Markdown
Contributor

Summary

Phase 3's first call site exposed a wrong pattern, and the ruling it drew inserts phase 2b ahead
of the 29-site migration.

Migrating pkg/signing by having it open its own root looked reasonable and was not. The root is
set by whoever starts a starlark or graph execution
, carried on the RuntimeEnvironment, and
reached downstream through the activation record — ActivationRecord.RuntimeEnvironment is
documented as "always set during dispatch", and every provider already reads
p.RuntimeEnvironment().Root. A provider constructing its own root bypasses the session that owns
it.

The signing change is reverted rather than left as precedent for 29 more sites. Its
generateLocalKey now carries a TODO(#405) stating plainly that the private key's 0600 is not
enforced on Windows, and why.

Phase 2b — session-owned filesystem access

The invariant: code never constructs filesystem access — it receives it.

  • Scratch joins root on the session. An unclosed root leaks a handle; an unclosed scratch leaks
    a tree Windows cannot remove while a handle inside it is open.
  • Both allocate lazily — a planning-only session then holds no confined-root handle at all.
  • Root keeps eager preflight validation. Purely lazy allocation would move a bad anchor from
    "refused before dispatch" to "died halfway through a run".
  • Root() asserts on a post-validation mint failure, matching the checksum trust boundary and
    keeping ~40 call sites as one-liners.
  • Scratch anchors at the OS temp dir, no spec field — os.TempDir() already honors
    TMPDIR/TMP. The one exception, cross-device staging, is per-operation.

CLI root ownership

internal/cli owns purpose-named roots (state, config, install prefix), lazily opened and
closed at command exit; everything downstream receives one. That keeps the invariant whole rather
than carving an exception from it — the session owner constructs, everyone else receives, and for
CLI-side work the CLI layer is the session owner.

Rejected: a full RuntimeEnvironment for the CLI (heavy machinery to write a config file, and it
inverts an existing dependency — config loading precedes environment construction), and
per-command construction (makes the rule area-dependent exactly where a contributor would guess
wrong).

Docs plus one TODO comment; no behavior change. make test zero failures, lint-all green on all
three GOOS.

Assisted-by: Claude

Phase 3's first call site exposed a wrong pattern and drew a ruling that
inserts a phase ahead of the migration.

Migrating pkg/signing by having it open its own root looked reasonable
and was not: the root is set by whoever starts a starlark or graph
execution, carried on the RuntimeEnvironment, and reached downstream
through the activation record -- ActivationRecord.RuntimeEnvironment is
documented as "always set during dispatch", and every provider already
reads p.RuntimeEnvironment().Root. A provider constructing its own root
bypasses the session that owns it. The signing change is reverted rather
than left as precedent for 29 more sites; its generateLocalKey now
carries a TODO(#405) stating plainly that the private key's 0600 is NOT
enforced on Windows and why.

Phase 2b, inserted before the migration:

Scratch joins root on the session, communicated the same way. Scratch
has the sharper hazard -- an unclosed root leaks a handle, an unclosed
scratch leaks a TREE that Windows cannot remove while a handle inside it
is open -- so concentrating ownership where the teardown is already
proven matters more, not less.

Both allocate lazily, so a planning-only session holds no confined-root
handle at all, shrinking the exact surface #393 was about. Root keeps
eager preflight validation: purely lazy allocation would move a bad
anchor from "refused before dispatch" to "died halfway through a run",
undoing the ReasonPreflightFailed terminal phase 2 just built. Root
becomes an accessor that asserts on a post-validation mint failure,
consistent with the repository's checksum trust boundary, keeping ~40
provider call sites as one-liners.

Scratch anchors at the OS temp directory with no spec field.
os.MkdirTemp("") resolves through os.TempDir(), which already honors
TMPDIR on Unix and TMP/TEMP on Windows, so every configuration reason to
relocate scratch is already served and a spec field would reinvent an
environment variable. The one non-configuration case is cross-device
staging -- a stage-then-rename is atomic only on one filesystem, and
TMPDIR cannot know the target's device -- which is per-operation and
belongs inside the target root.

CLI root ownership ruled: internal/cli owns purpose-named roots (state,
config, install prefix), lazily opened and closed at command exit, with
everything downstream receiving one. That keeps the invariant whole
instead of carving an exception from it -- the session owner constructs,
everyone else receives, and for CLI-side work the CLI layer is the
session owner. Giving the CLI a full RuntimeEnvironment was rejected as
heavy machinery for writing a config file that also inverts an existing
dependency (config loading precedes environment construction), and
per-command construction was rejected because it makes the rule
area-dependent exactly where a contributor would guess wrong.

Assisted-by: Claude
Signed-off-by: David Noble <[email protected]>
@David-Noble-at-work David-Noble-at-work self-assigned this Aug 14, 2026
@David-Noble-at-work
David-Noble-at-work merged commit 883c764 into develop Aug 14, 2026
7 of 8 checks passed
@David-Noble-at-work
David-Noble-at-work deleted the docs-session-owned-fs branch August 14, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant