Skip to content

Let a session compose with its environment instead of contradicting it - #31

Merged
jiashuoz merged 1 commit into
mainfrom
feat/control-session-composition
Sep 2, 2026
Merged

Let a session compose with its environment instead of contradicting it#31
jiashuoz merged 1 commit into
mainfrom
feat/control-session-composition

Conversation

@jiashuoz

@jiashuoz jiashuoz commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

A coordinated change to the frozen control contract, surfaced by the self-hosted recomposition (gate O8, plan #29): CreateSession said an environment and a scratch spec together were "contradictory and refused," but rainier new has composed --env with --image, --egress, and a trailing command since Plan 4 (cmd/rainier/main.go:575, design §4.3), and a bare rainier new names no image at all. Recomposing controld over the contract could only refuse those flows or drop their fields silently.

  • control.PortableSpec now states the rule once: an environment is a template, a session an instance; every set field overrides, every unset field is inherited. Image overrides and forgoes the snapshot; Cmd is the session's (an environment carries none); EgressAllow extends the environment's list rather than replacing it; Repos keeps the nil/empty/list rule the contract already composed this way.
  • CreateSession.Validate refuses only malformation (a repository reference naming no repository). No combination of environment and spec is refused; a scratch create with no image asks the host for its default. A host that wants to forbid overrides or require an image does so in its own policy — the contract says what is possible, not what a host allows.
  • controlapp: portableSpecFor layers accordingly; the zero-spec refusal is gone; createSpec decides whether setup runs by whether the row boots the snapshot, so an image override gets the setup the snapshot would have carried.
  • Pinned three ways: the public contract test (TestCreateSessionComposesEnvironmentAndOverrides, a table), through the service (TestCreateSessionLayersOverridesOnTheEnvironment), and at dispatch (TestCreateSpecSendsSetupUnlessTheRowBootsTheSnapshot).

One deliberate product refinement rides along: per-session egress now extends an environment's list instead of replacing it. The environment's egress is what it needs to work (its registry, its init hook's API); a session adding hosts is the intent behind the flag, and silently removing the environment's was the surprising behavior.

No exported identifier is added or removed; the public-control guard's allowlist is unchanged. Merge before the O8 branch, which then drops its interim D2/D11 refusals.

Verification

  • go test ./control ./controlapp -race -count=1
  • ./scripts/check-public-control.sh
  • make verify
  • git diff --check

🤖 Generated with Claude Code

https://claude.ai/code/session_01GiKRhqhzgJ1UDaLBynMyj3

…ting it

The frozen CreateSession said an environment and a scratch spec together
were "contradictory and refused." The product has never agreed: rainier
new composes --env with --image, --egress, and a trailing command
("the environment supplies everything the flags don't", design §4.3),
and a bare rainier new names no image at all. Recomposing self-hosted
controld over the contract (gate O8) is what surfaced the conflict —
the branch could only refuse those flows or drop their fields silently.

The contract now states the rule the product implements, once, on
PortableSpec: an environment is a template and a session an instance
of it; every field that is set overrides, every field that is unset is
inherited. Image overrides the environment's (and forgoes its snapshot,
which was built from the environment's own image and setup); Cmd is the
session's, since an environment carries none; EgressAllow extends the
environment's list rather than replacing it, because that list is what
the environment needs to work; Repos keeps its nil/empty/list rule,
which the contract already composed this way. Validate refuses only
malformation. A scratch create with no image asks the host for its
default; a host that wants to forbid an override or require an image
does so in its own policy, not here.

controlapp's portableSpecFor layers accordingly, its zero-spec refusal
is gone, and createSpec decides whether setup runs by whether the row
boots the snapshot — so an image override gets the setup the snapshot
would have carried. The composition is pinned as a table in the public
contract's tests, through the service, and at dispatch.

This is a coordinated change to the frozen surface, made because the
first host composed against it showed the rule was a guess the product
contradicts. It makes the contract more permissive, which is the safe
direction for a pre-v1 surface.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiKRhqhzgJ1UDaLBynMyj3
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