fix(catalog): admit a prepared plane without runtime workspace dirs - #427
Merged
schickling-assistant merged 1 commit intoSep 3, 2026
Conversation
`.workspace` is runtime-only: the agent owns its contents, nothing declares them, and #425 made the raw preimage capture none of them. The apply side kept requiring every declared workspace fact to already exist in the prepared plane, so the exact repair path the raw preimage exists for became unreachable: a prepared plane built from a raw snapshot has no such directory, and `digest --prepared` failed with inspect prepared workspace fact <prepared>/agents/<host>/<id>/.workspace Caused by: No such file or directory (os error 2) Admission now tolerates an absent directory and keeps every other rule: a directory that IS there must still be a real, empty one, so prepared state cannot smuggle runtime bytes through the declaration plane. The fact itself stays in the projection, so `materialize_projection` republishes it as an empty directory and the applied catalog still satisfies the live invariant that a declared workspace fact is present. The relaxation is uniform across prepared planes rather than raw-only: `catalog digest --prepared` has no raw mode, so the deployer's digest-then-apply sequence cannot express the distinction, and an absent empty directory carries no information the CAS does not already cover — the declared fact is hashed either way. Regression test covers the deployer's shape end to end: raw-snapshot an invalid live plane whose declared workspace is runtime-only, publish valid declaration bytes into that snapshot, and bind the apply to the opaque preimage. Without this fix it reproduces the error above verbatim. The existing workspace-fact test loses its "missing" case, which asserted the contract this fixes. Its remaining "nonempty" rejection is unchanged. agent-identity: dev3.direct.omp.quzh99p3 agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.1.2 agent-runtime: OMP 18.1.2 tooling-profile: dotfiles@9323c3a
schickling-assistant
marked this pull request as ready for review
September 3, 2026 12:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
.workspaceis runtime-only: the agent owns its contents, no declarationdescribes them, and #425 made the raw preimage capture none of them. The apply
side never learned that.
project_excludingstill demanded that every declaredworkspace fact already exist in the prepared plane, which makes the exact repair
path the raw preimage exists for unreachable — a prepared plane built from a raw
snapshot has no such directory:
This is not theoretical. It is currently blocking a dev3 supervisor repair: the
deployer raw-snapshots an invalid live catalog, publishes valid declaration bytes
into that snapshot, and applies against the opaque preimage. On
main(fbd1ff6)that sequence dies at the digest step for any agent whose declared workspace
directory does not happen to exist.
Goal
A prepared plane may omit the directory for a declared workspace fact. Every
other rule holds, and the applied catalog still satisfies the live invariant that
a declared workspace fact is present.
Decisions
dropped.
materialize_projectionalready publishesworkspace_dirs, so amissing one is republished as an empty directory and
rootSha256is unchanged— the fact is hashed from the declarations either way.
smuggle runtime bytes (or a symlink) through the declaration plane. That
rejection is untouched and still covered.
catalog digest --preparedhas no raw mode, so the deployer's digest-then-apply sequence cannot express
the distinction at the boundary where it fails. An absent empty directory also
carries no information the CAS does not already cover, so a raw-only carve-out
would add a mode without adding a guarantee.
A raw preimage exists precisely because the declarations may be invalid, so it
cannot know which directories are workspace facts — that is why fix(catalog): exclude workspaces from raw preimages #425 excluded
them structurally.
Verification
New regression test reproduces the deployer's shape end to end (raw snapshot of
an invalid live plane → valid declaration bytes written into it → raw apply),
and asserts the fact is republished while live runtime content survives.
Negative control, same test with the source fix reverted:
With the fix:
Whole-file comparison against pristine
mainat fbd1ff6, same machine, samecommand (
nix develop -c cargo test --test catalog_apply):The 14 failures are identical in name on both sides — this change adds no
failure and fixes none of them. See Concerns.
Complexity
No new abstraction. One admission predicate goes from "must exist" to "must be a
real empty directory if it exists", plus the spec sentence that stated the old
rule.
Concerns
catalog_applyhas 14 pre-existing failures onmain, unchanged here. Twoshapes: several time out waiting for a helper's
readyfile (this machine wasunder load average ~90), and
workspace_facts_are_empty_in_prepared_admitted_against_live_and_never_appliedfails at its final apply with
[root-count]: host 'host' must declare exactly one root agent; found 2— itsfixture declares two unsupervised agents. Reproduced with this branch's changes
reverted, so it is not from this PR, and repairing that fixture is out of scope
here. Worth a follow-up: it hides real breakage in exactly this file.
is rejected" case asserted the contract this PR removes, so it is gone. The
"nonempty is rejected" case stays.
Friction & bottlenecks
inspect prepared workspace fact— an I/O contextstring on a
NotFound— which reads like a permissions or path problem ratherthan "this admission rule is unsatisfiable for raw preimages". A rule that
cannot be satisfied by a supported input shape deserves its own message.
downstream Nix check rather than from st2's own tests.
Follow-ups
workspace_facts_...fixture (two root agents) and getcatalog_applygreen so regressions in it are visible.References
Refs #425, #424
Posted on behalf of @schickling
agent_identitysessionagent_personaagent_supervisoragent_toolagent_tool_versionagent_runtimetooling_profile