Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions boatstack/hydrate_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
Expand Down Expand Up @@ -51,6 +52,103 @@ func TestRunHydrateRuntimePopulatesSlotIdempotentlyWithoutTouchingCommittedState
}
}

// control-law: tracked-launcher-selects-only-the-pinned-runtime
// Positive and relation conformance: detached hydration must publish the
// Git-common bootstrap consumed by tracked launchers and the external shared
// runtime consumed by supervision-aware worktree activation.
func TestRunHydrateRuntimePopulatesDetachedBootstrapAndSharedSlots(t *testing.T) {
t.Setenv(stateRootEnv, t.TempDir())
invalidateWorkspaceCache()
repo := runtimeTestRepo(t)
result, err := AttachDetached(AttachOptions{Repo: repo, BinaryPath: os.Args[0]})
if err != nil || result.VerificationStatus != "VERIFIED" {
t.Fatalf("attach failed: %v %+v", err, result)
}
sharedBinary, _, err := sharedRuntimePaths(repo, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
bootstrapBinary, _, err := bootstrapRuntimePaths(repo, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
if filepath.Clean(sharedBinary) == filepath.Clean(bootstrapBinary) {
t.Fatalf("detached shared and bootstrap slots unexpectedly alias: %s", sharedBinary)
}
for _, path := range []string{filepath.Dir(sharedBinary), filepath.Dir(bootstrapBinary), filepath.Join(repo, ".product-loop", "bin")} {
if err := os.RemoveAll(path); err != nil {
t.Fatal(err)
}
}
before := readGeneratedLockBytes(t, repo)

if err := RunHydrateRuntime(repo); err != nil {
t.Fatalf("detached hydration failed: %v", err)
}
for name, path := range map[string]string{"Git-common bootstrap": bootstrapBinary, "detached shared runtime": sharedBinary} {
if _, err := os.Stat(path); err != nil {
t.Fatalf("%s was not populated: %v", name, err)
}
}
if err := verifyLocalRuntime(repo); err != nil {
t.Fatalf("detached hydration did not activate the local runtime: %v", err)
}
if after := readGeneratedLockBytes(t, repo); string(before) != string(after) {
t.Fatalf("detached hydration mutated committed generated.lock.json")
}
if err := RunHydrateRuntime(repo); err != nil {
t.Fatalf("second detached hydration was not idempotent: %v", err)
}
}

// control-law: tracked-launcher-selects-only-the-pinned-runtime
// Negative, bypass, and failure-state conformance: an unsafe detached shared
// path must fail before publishing an admissible bootstrap or local runtime.
func TestRunHydrateRuntimeRejectsUnsafeDetachedSharedSlotBeforeBootstrap(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlink creation requires privileges not guaranteed on Windows CI")
}
t.Setenv(stateRootEnv, t.TempDir())
invalidateWorkspaceCache()
repo := runtimeTestRepo(t)
result, err := AttachDetached(AttachOptions{Repo: repo, BinaryPath: os.Args[0]})
if err != nil || result.VerificationStatus != "VERIFIED" {
t.Fatalf("attach failed: %v %+v", err, result)
}
sharedBinary, _, err := sharedRuntimePaths(repo, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
bootstrapBinary, _, err := bootstrapRuntimePaths(repo, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
for _, path := range []string{filepath.Dir(sharedBinary), filepath.Dir(bootstrapBinary), filepath.Join(repo, ".product-loop", "bin")} {
if err := os.RemoveAll(path); err != nil {
t.Fatal(err)
}
}
if err := os.MkdirAll(filepath.Dir(filepath.Dir(sharedBinary)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(t.TempDir(), filepath.Dir(sharedBinary)); err != nil {
t.Fatal(err)
}

err = RunHydrateRuntime(repo)
if err == nil || !strings.Contains(err.Error(), "symlink") {
t.Fatalf("expected unsafe detached slot refusal, got %v", err)
}
for name, path := range map[string]string{
"bootstrap runtime": bootstrapBinary,
"worktree runtime": filepath.Join(repo, ".product-loop", "bin", helperName()),
} {
if _, statErr := os.Lstat(path); !os.IsNotExist(statErr) {
t.Fatalf("failed hydration partially published %s: %v", name, statErr)
}
}
}

// TestRunHydrateRuntimeRefusesRunningVersusPinMismatch pins the incident-
// prevention invariant: hydration must never populate a version-keyed slot with
// a binary whose identity disagrees with the worktree's committed pin.
Expand Down
4 changes: 2 additions & 2 deletions boatstack/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ func RunInit(options InitOptions) (returnErr error) {
}
}
}()
if _, err := installSharedRuntime(helperSource, repo, config.Integrations); err != nil {
if _, err := installCommandRuntime(helperSource, repo, config.Integrations); err != nil {
return fmt.Errorf("cannot install the repository-family Boatstack runtime: %w", err)
}
var states map[string]IntegrationState
Expand Down Expand Up @@ -644,7 +644,7 @@ func RunInit(options InitOptions) (returnErr error) {
if err := initCheckpoint("helper-written"); err != nil {
return fmt.Errorf("initialization checkpoint helper-written: %w", err)
}
if _, err := installSharedRuntime(helperSource, repo, states); err != nil {
if _, err := installCommandRuntime(helperSource, repo, states); err != nil {
return fmt.Errorf("cannot finalize the repository-family Boatstack runtime: %w", err)
}
if err := writeInstallLock(repo, binaryPath, binaryHash, states); err != nil {
Expand Down
47 changes: 47 additions & 0 deletions boatstack/launcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,53 @@ func TestTrackedLauncherActivatesFreshLinkedWorktreeWithoutHookTrust(t *testing.
}
}

// control-law: tracked-launcher-selects-only-the-pinned-runtime
// Relation conformance for the detached failure mode: tracked launcher -> exact
// hydrate operation -> Git-common bootstrap -> detached shared activation ->
// verified local command dispatch.
func TestTrackedLauncherHydratesDetachedRepositoryThroughGitCommonBootstrap(t *testing.T) {
t.Setenv(stateRootEnv, t.TempDir())
invalidateWorkspaceCache()
primary, _ := launcherTestRepository(t)
helper := buildLauncherTestHelper(t)
result, err := AttachDetached(AttachOptions{Repo: primary, BinaryPath: helper})
if err != nil || result.VerificationStatus != "VERIFIED" {
t.Fatalf("attach failed: %v %+v", err, result)
}
sharedBinary, _, err := sharedRuntimePaths(primary, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
bootstrapBinary, _, err := bootstrapRuntimePaths(primary, Version, SourceCommit)
if err != nil {
t.Fatal(err)
}
for _, path := range []string{filepath.Dir(sharedBinary), filepath.Dir(bootstrapBinary), filepath.Join(primary, ".product-loop", "bin")} {
if err := os.RemoveAll(path); err != nil {
t.Fatal(err)
}
}
hydrate := quotedLiteral(t, helper) + " hydrate-runtime --repo " + quotedLiteral(t, primary)
if runtime.GOOS == "windows" {
hydrate = "& " + hydrate
}
command := launcherCommand(primary, "version")
command.Dir = primary
command.Env = append(os.Environ(), "BOATSTACK_HYDRATE_COMMAND="+hydrate)
value, runErr := command.CombinedOutput()
if runErr != nil || !strings.Contains(string(value), Version) {
t.Fatalf("detached launcher hydration failed: %v\n%s", runErr, value)
}
for name, path := range map[string]string{"Git-common bootstrap": bootstrapBinary, "detached shared runtime": sharedBinary} {
if _, err := os.Stat(path); err != nil {
t.Fatalf("%s missing after launcher hydration: %v", name, err)
}
}
if err := verifyLocalRuntime(primary); err != nil {
t.Fatalf("launcher did not dispatch through a verified local runtime: %v", err)
}
}

func runErrString(err error, output []byte) string {
if err == nil {
return string(output)
Expand Down
20 changes: 20 additions & 0 deletions boatstack/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,23 @@ func (w WorkspaceContext) RuntimeDir(version, sourceCommit string) (string, erro
}
return filepath.Join(base, "runtimes", version, sourceCommit, platformKey()), nil
}

// BootstrapRuntimeDir holds the exact runtime used by tracked launchers and
// repository guards before supervision mode can be resolved by trusted Go code.
// It is always Git-common, including for detached supervision. The bootstrap
// helper then activates the mode-aware shared runtime through HydrateWorktree.
func (w WorkspaceContext) BootstrapRuntimeDir(version, sourceCommit string) (string, error) {
version, err := safeCacheSegment(version, "Boatstack version")
if err != nil {
return "", err
}
sourceCommit, err = safeCacheSegment(sourceCommit, "source commit")
if err != nil {
return "", err
}
common, err := gitCommonDir(w.RepoRoot)
if err != nil {
return "", err
}
return filepath.Join(common, controlDirName, "runtimes", version, sourceCommit, platformKey()), nil
}
1 change: 1 addition & 0 deletions boatstack/references/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ clone, `external` outside the repository (Detached Supervision).
| flow-logs | runtime-worktree | per-worktree | flow |
| guard-denial-ledger | runtime-worktree | per-worktree | safety-hook, ambient-safety-hook |
| runtime-slots | runtime-shared | git-common | init, update, hydrate-runtime |
| runtime-bootstrap-slots | runtime-shared | git-common | init, update, hydrate-runtime |
| mutation-receipts | runtime-shared | git-common | activate-plan, undo |
| update-previews | runtime-shared | git-common | prepare-update-pr, publish-update-pr |
| repair-receipts | runtime-shared | git-common | update |
Expand Down
54 changes: 51 additions & 3 deletions boatstack/runtime_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ func sharedRuntimeOwnedPaths(repo, version, sourceCommit string) (controllerPath
return binary, manifest, err
}

func bootstrapRuntimePaths(repo, version, sourceCommit string) (string, string, error) {
binary, manifest, err := bootstrapRuntimeOwnedPaths(repo, version, sourceCommit)
return binary.path, manifest.path, err
}

func bootstrapRuntimeOwnedPaths(repo, version, sourceCommit string) (controllerPath, controllerPath, error) {
ctx := WorkspaceFor(repo)
directory, err := ctx.BootstrapRuntimeDir(version, sourceCommit)
if err != nil {
return controllerPath{}, controllerPath{}, err
}
common, err := gitCommonDir(repo)
if err != nil {
return controllerPath{}, controllerPath{}, err
}
binary, err := newControllerPath(common, filepath.Join(directory, helperName()))
if err != nil {
return controllerPath{}, controllerPath{}, err
}
manifest, err := newControllerPath(common, filepath.Join(directory, "runtime.lock.json"))
return binary, manifest, err
}

func atomicWriteMode(path string, content []byte, mode fs.FileMode) error {
directory := filepath.Dir(path)
if err := os.MkdirAll(directory, 0o755); err != nil {
Expand Down Expand Up @@ -154,6 +177,31 @@ func installSharedRuntime(source, repo string, integrations map[string]Integrati
return writeRuntimeSlot(source, binaryPath, manifestPath, integrations)
}

// installCommandRuntime publishes the exact runtime needed by both sides of
// tracked command activation. The mode-aware shared slot is installed first so
// the Git-common bootstrap is never made admissible before it can activate the
// worktree-local helper. Embedded mode uses one physical slot; detached mode
// deliberately uses an external shared slot plus a Git-common bootstrap slot.
// control-law: tracked-launcher-selects-only-the-pinned-runtime
func installCommandRuntime(source, repo string, integrations map[string]IntegrationState) (runtimeManifest, error) {
sharedManifest, err := installSharedRuntime(source, repo, integrations)
if err != nil {
return runtimeManifest{}, err
}
sharedBinary, _, err := sharedRuntimePaths(repo, Version, SourceCommit)
if err != nil {
return runtimeManifest{}, err
}
bootstrapBinary, bootstrapLock, err := bootstrapRuntimeOwnedPaths(repo, Version, SourceCommit)
if err != nil {
return runtimeManifest{}, err
}
if filepath.Clean(sharedBinary) == bootstrapBinary.path {
return sharedManifest, nil
}
return writeRuntimeSlot(source, bootstrapBinary, bootstrapLock, integrations)
}

// installDetachedRuntime populates a detached repository's external shared-runtime
// slot from the running helper, so the developer-level ambient guard has a stable
// helper to invoke. Unlike installSharedRuntime it scopes the symlink check to the
Expand Down Expand Up @@ -369,7 +417,7 @@ func HydrateWorktree(repoPath string) error {
// this, the running binary equals the repo's committed pin by construction. The
// verifyGeneratedRuntime gate refuses to populate a slot for any other version,
// so hydration can never write a mislabeled runtime (the taxweave incident's
// invariant), and installSharedRuntime's own post-write verify+rollback is the
// invariant), and installCommandRuntime's own post-write verify+rollback is the
// backstop. The operation is idempotent and safe under concurrent first use.
func RunHydrateRuntime(repoPath string) error {
repo, err := ResolveRepository(repoPath)
Expand All @@ -387,8 +435,8 @@ func RunHydrateRuntime(repoPath string) error {
if err != nil {
return fmt.Errorf("load project configuration for runtime hydration: %w", err)
}
if _, err := installSharedRuntime(source, repo, config.Integrations); err != nil {
return fmt.Errorf("populate the repository-family Boatstack runtime: %w", err)
if _, err := installCommandRuntime(source, repo, config.Integrations); err != nil {
return fmt.Errorf("populate the Boatstack command runtime: %w", err)
}
return HydrateWorktree(repo)
}
Expand Down
11 changes: 11 additions & 0 deletions boatstack/statemap.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,17 @@ func StateRegistry() []StateEntry {
return filepath.Join(base, "runtime.lock.json"), nil
},
},
{
Name: "runtime-bootstrap-slots", Class: ClassRuntimeShared, Partition: "git-common", Gitignored: true, GuardProtected: true,
OwnerVerbs: []string{"init", "update", "hydrate-runtime"},
Sample: func(w WorkspaceContext) (string, error) {
base, err := w.BootstrapRuntimeDir("v0.0.0", "0000000")
if err != nil {
return "", err
}
return filepath.Join(base, "runtime.lock.json"), nil
},
},
{
Name: "mutation-receipts", Class: ClassRuntimeShared, Partition: "git-common", Gitignored: true, GuardProtected: true,
OwnerVerbs: []string{"activate-plan", "undo"},
Expand Down
3 changes: 3 additions & 0 deletions boatstack/statemap_conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ func TestEveryWorkspaceResolverIsDeclared(t *testing.T) {
"RuntimeDir": {resolve(func() (string, error) {
return w.RuntimeDir("v0.0.0", "0000000")
}), ClassRuntimeShared},
"BootstrapRuntimeDir": {resolve(func() (string, error) {
return w.BootstrapRuntimeDir("v0.0.0", "0000000")
}), ClassRuntimeShared},
}

for name, want := range resolverOutputs {
Expand Down
2 changes: 2 additions & 0 deletions release-notes/2026-08-09-detached-launcher-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Restore tracked commands in detached repositories
Tracked Boatstack launchers now hydrate their exact pinned runtime correctly when a repository uses detached supervision, so a missing local helper no longer leaves updates stuck in a recovery loop.