From 71c2d8ef40cb68651fd636a8f186c5414e214619 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Sun, 26 Jul 2026 17:25:39 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 64b1891a9ecc --- CONTRIBUTING.md | 2 +- UPSTREAM.json | 16 +-- boatstack/plan.go | 7 ++ boatstack/workspace.go | 50 ++++++++ boatstack/worktree_activation_guard_test.go | 113 ++++++++++++++++++ docs/evidence-engineered-coding.md | 2 +- docs/public-claims.json | 24 ++-- labs/diagram-json/plan.lock.json | 2 +- .../2026-07-26-activate-in-cut-worktree.md | 16 +++ 9 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 boatstack/worktree_activation_guard_test.go create mode 100644 release-notes/2026-07-26-activate-in-cut-worktree.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index acd7357..b4c4db6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ # Contributing -Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/70614614df37db20107c65285b2e1550f9368065/labs/12-product-engineering-loop). +Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/64b1891a9eccda6ede3df1b5350462d8179ef112/labs/12-product-engineering-loop). The Boatstack repository receives product/runtime changes through a generated pull request. Review the PR's `UPSTREAM.json`, tests, adapter diff, and context-size change; do not hand-edit generated output on `main`. `.github/workflows` is the exception: it is Boatstack's executable control plane, excluded from scheduled projection and changed only through a separate manually reviewed Boatstack PR. diff --git a/UPSTREAM.json b/UPSTREAM.json index 322ee3e..6fc8ec0 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "2417adaabc13a2cd494dda791ac9c1b078c462212e236f36601acc1c8d7813d3", + "CONTRIBUTING.md": "57661de7b9cc07e16e5142bb2615a8eb8a0621632876b0ec713a3795403862dc", "README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -126,7 +126,7 @@ "boatstack/operation.go": "073113e1e7b6349417e70b704bd1a342b460604cbd97a7fab06b1a6494604112", "boatstack/operation_test.go": "59d3dc37319aa4d334c0cacbe886e2f757842e6a28dee8781448e528fecbde11", "boatstack/paths.go": "bc14901f9497bfa87f64eab80ff30cc7c3a31781e3fdb60826df2dc21eb2253b", - "boatstack/plan.go": "90b48262863e7733b669c4916bf713c9567681d5c080503050f10f348d3ce69d", + "boatstack/plan.go": "e1e4344f2aae24f56cc767291616947e1bd5ee08fb6e73e60f554215c799590c", "boatstack/plan_test.go": "53477515165a910910b9175bfa33574548cf0d0f3be48e175ec3a775a12d30bb", "boatstack/plan_validation.go": "99e40806fd579ff72de53f391cd6124acc9ff18707ecf9676c5e507b738d87d0", "boatstack/plan_validation_test.go": "6cbde4ac719baef6b73aa569515d6a9daadcbf14b33f76fa78159826954e20fa", @@ -177,19 +177,20 @@ "boatstack/visual_evidence_test.go": "0fe8f5154ef4398dfeba5e7f7b387b2d279ed75635ea35d93ff392269f2cc6d0", "boatstack/visual_publisher.go": "ec5e95228b48e4ec20731975606048f5e732c4e09a7fd175770d4b15e447cc88", "boatstack/visual_publisher_test.go": "979f600edae00c77569ae753b80530e8cbf2e3b342995efcd992d351ff382eaf", - "boatstack/workspace.go": "0d3bb9aedbbeac8ff57a6aa33dcc3671d1055e2caa6949b439d57b0b283c431f", + "boatstack/workspace.go": "79f472d0d10794bf5193518d0c2798d2f5e7e530226e02fb33f99840110f995f", "boatstack/workspace_reap_test.go": "a08f41ee1b2d158efe878082b5fcd65928e723afce4dd19e3a5a8a26b8f10874", "boatstack/workspace_sync.go": "0cc2f03fd1aa57c66b3d603d5ef30aa820f14ab60771a795cf10c46f3c9a71b8", "boatstack/workspace_sync_test.go": "a5fd532d23a6675c96fc5eb29a149c812717f21237a4050ae5f41afb34601273", "boatstack/workspace_test.go": "e0e38c14b7e218053eb6e8b900412ed4c6da8a19ecdde26cb22b1a2af7a3695d", + "boatstack/worktree_activation_guard_test.go": "0ec47e9670d785ca511357ddbee4edb17bd2cd5c824cab790e482f35d83c087b", "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0", - "docs/evidence-engineered-coding.md": "365b9be5776923e5f4b160b13d52ed5bb457acc2503dde64202a66dbb4e1c34d", + "docs/evidence-engineered-coding.md": "f2596fdb5e3628dc9cf19c3f29df177cd2fa63882c924e5c0253b8628c20a75b", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8", - "docs/public-claims.json": "0bdb4de459498a524aefee0f0149de2791fc25ea295fbcbdbc4869371c884169", + "docs/public-claims.json": "0bddadb9ba7bb813e383f32d1b3422002376f069deb0834db3e42c99d68a80ab", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -203,7 +204,7 @@ "labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d", "labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71", "labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39", - "labs/diagram-json/plan.lock.json": "f24f52a34341f69f0fdc54c64029f836421c3f17c985d8901e7f94a152021c8a", + "labs/diagram-json/plan.lock.json": "7bee0e448274d00342794f023a021816650085554dff4d6ed1288f59f0b2ab6b", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -307,6 +308,7 @@ "release-notes/2026-07-25-root-cause-operation.md": "5bf1f082e9123c5a7bcc8bc01b12e97b24b5ae15958577b4ff2a358994fca891", "release-notes/2026-07-25-runtime-simplified-technical-english.md": "917fbfb51ae56e5c6e0d9c705b84da492ef3b8f8782ea4b62635814259f11bb4", "release-notes/2026-07-25-update-publish-guard-unblock.md": "adf06ee02b8d3c995525bb9673c2f1fea66a147df8751d65885ced83da0e96e2", + "release-notes/2026-07-26-activate-in-cut-worktree.md": "485fdd86792587637147947a8b36a9c457aa0affcb5799103f030fadfc7a94c1", "release-notes/2026-07-26-calm-denials.md": "9e4a5fc23b02500cf2f124cb462a8d863ed953a899c9485a81e4971dd89c9576", "release-notes/2026-07-26-detached-activation.md": "97cf968c3bd57d5f88bd91c04672dae3cedba88e460758323cffe44532d2ec2c", "release-notes/2026-07-26-detached-context-and-guard.md": "ed58fc69752bd9b48403e3bdd8e3459fa84a663bc3caa1e8246be3abc3ac6d6c", @@ -319,7 +321,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "70614614df37db20107c65285b2e1550f9368065", + "commit": "64b1891a9eccda6ede3df1b5350462d8179ef112", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/plan.go b/boatstack/plan.go index 03b6168..1d2ae64 100644 --- a/boatstack/plan.go +++ b/boatstack/plan.go @@ -971,6 +971,13 @@ func ActivatePlan(options ActivationOptions) error { if err != nil { return fmt.Errorf("plan activation requires a valid Boatstack project configuration: %w", err) } + // Once a feature's workspace worktree is cut, activation must happen inside it, + // never from the main worktree on the base branch — otherwise the compiled + // artifacts and delivery ledger land on the base branch and compete with the + // cut worktree's own state. + if err := guardManagedActivationWorktree(repo, config, stringValue(check.Plan["feature_id"])); err != nil { + return err + } authorizationMode := "policy" structuredPlanStatus := "POLICY_ACTIVATED" receipt := ApprovalReceipt{} diff --git a/boatstack/workspace.go b/boatstack/workspace.go index 49b0e64..c10b7ac 100644 --- a/boatstack/workspace.go +++ b/boatstack/workspace.go @@ -90,6 +90,56 @@ func needsFreshCut(repo, feature string) bool { return strings.TrimSpace(current) == defaultPRBase(repo) } +// isMainWorktree reports whether repo is checked out in the repository's main +// worktree, whose Git directory aliases the common directory. A linked worktree's +// Git directory is .git/worktrees/, so the two differ there. +func isMainWorktree(repo string) bool { + gitDir, err := worktreeGitDir(repo) + if err != nil { + return false + } + common, err := gitCommonDir(repo) + if err != nil { + return false + } + return gitDir == common +} + +// guardManagedActivationWorktree refuses to activate a managed delivery from the +// main worktree once the feature already has a cut workspace. In worktree mode the +// delivery must be built inside its cut worktree; activating on the base branch in +// the main worktree strands compiled artifacts and a competing per-worktree +// delivery ledger on the base branch (the split-brain this guards against). It is +// inert unless workspace management is on in worktree mode, a workspace for the +// feature already exists, and the caller is on the base branch in the main +// worktree — so the normal flow (cut first, then activate inside the worktree) is +// unaffected. +func guardManagedActivationWorktree(repo string, config ProjectConfig, feature string) error { + policy := resolveWorkspace(config.Workspace) + if !policy.Enabled || policy.Mode != "worktree" { + return nil + } + branch := branchForFeature(feature) + if branch == "" { + return nil + } + worktreePath := worktreePathForBranch(repo, branch) + if worktreePath == "" && !branchExists(repo, branch) { + return nil // no workspace cut yet — this is the normal pre-cut path + } + if !isMainWorktree(repo) { + return nil // already inside a linked worktree + } + base := defaultPRBase(repo) + if current, _ := workspaceGit(repo, "branch", "--show-current"); strings.TrimSpace(current) != base { + return nil // not on the base branch + } + if worktreePath != "" { + return fmt.Errorf("feature %q already has a managed workspace at %s; activate and build there, not on the base branch %q. Run: cd %s and re-run — managed delivery must run in its cut worktree", feature, worktreePath, base, worktreePath) + } + return fmt.Errorf("feature %q already has a managed branch %q; activate and build in its worktree, not on the base branch %q — managed delivery must run in its cut worktree", feature, branch, base) +} + func loadWorkspacePolicy(repo string) (ResolvedWorkspace, error) { config, _, err := LoadConfig(WorkspaceFor(repo).ProjectConfigPath()) if err != nil { diff --git a/boatstack/worktree_activation_guard_test.go b/boatstack/worktree_activation_guard_test.go new file mode 100644 index 0000000..997ff7c --- /dev/null +++ b/boatstack/worktree_activation_guard_test.go @@ -0,0 +1,113 @@ +package boatstack + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// Conformance for the worktree-discipline guard. +// +// control-law: managed-delivery-activates-in-its-cut-worktree +// Once a feature's workspace worktree is cut, activation must happen inside it, +// never from the main worktree on the base branch (which would strand compiled +// artifacts + a competing per-worktree delivery ledger on the base branch). +// Activation inside the worktree, or when no workspace is cut, is unaffected. + +func workspaceActivationConfig() ProjectConfig { + config := testConfig() + config.Project.DefaultBranch = "main" + config.Workspace = Workspace{Enabled: true, Mode: "worktree", Cleanup: "confirm", CleanupAfter: "merge"} + return config +} + +// control-law: managed-delivery-activates-in-its-cut-worktree +func TestActivationGuardBlocksMainWorktreeWhenWorkspaceCut(t *testing.T) { + repo := workspaceRepo(t, defaultWorkspace()) + cut, err := CutFeatureWorkspace(WorkspaceCutOptions{Repo: repo, Feature: "sample-feature"}) + if err != nil || cut.VerificationStatus != "VERIFIED" || cut.WorktreePath == "" { + t.Fatalf("failed to cut workspace: %+v (%v)", cut, err) + } + config := workspaceActivationConfig() + + // From the main worktree on the base branch → blocked, naming the worktree. + err = guardManagedActivationWorktree(repo, config, "sample-feature") + if err == nil { + t.Fatal("activation from the main worktree after a cut must be blocked") + } + // The guard's path comes from `git worktree list` (always forward slashes); + // cut.WorktreePath comes from filepath.Join (OS separator). Normalize before + // comparing so this holds on Windows too. + if !strings.Contains(filepath.ToSlash(err.Error()), filepath.ToSlash(cut.WorktreePath)) || !strings.Contains(err.Error(), "cut worktree") { + t.Fatalf("block message should name the worktree and the rule: %v", err) + } + + // From inside the cut worktree → allowed. + if err := guardManagedActivationWorktree(cut.WorktreePath, config, "sample-feature"); err != nil { + t.Fatalf("activation inside the cut worktree must be allowed: %v", err) + } +} + +// control-law: managed-delivery-activates-in-its-cut-worktree +func TestActivationGuardIsInertWithoutWorktreeMode(t *testing.T) { + repo := workspaceRepo(t, defaultWorkspace()) + + // No workspace cut yet → the normal pre-cut path is allowed. + if err := guardManagedActivationWorktree(repo, workspaceActivationConfig(), "sample-feature"); err != nil { + t.Fatalf("pre-cut activation must be allowed: %v", err) + } + + // Cut a workspace, then confirm disabled/branch-mode policies do not guard. + if _, err := CutFeatureWorkspace(WorkspaceCutOptions{Repo: repo, Feature: "sample-feature"}); err != nil { + t.Fatal(err) + } + if err := guardManagedActivationWorktree(repo, ProjectConfig{Workspace: Workspace{Enabled: false}}, "sample-feature"); err != nil { + t.Fatalf("disabled workspace management must not guard: %v", err) + } + if err := guardManagedActivationWorktree(repo, ProjectConfig{Workspace: Workspace{Enabled: true, Mode: "branch"}}, "sample-feature"); err != nil { + t.Fatalf("branch mode must not guard: %v", err) + } +} + +// control-law: managed-delivery-activates-in-its-cut-worktree +// End-to-end: ActivatePlan refuses to run in the main worktree after a cut, and +// writes no lock/delivery state on the base branch. +func TestActivatePlanBlockedFromMainWorktreeAfterCut(t *testing.T) { + root := t.TempDir() + _, _, planPath := writePlanInputs(t, root, false) // policy mode: no approval needed + runGit(t, root, "init", "-b", "main") + runGit(t, root, "config", "user.name", "Boatstack Test") + runGit(t, root, "config", "user.email", "boatstack@example.invalid") + config := workspaceActivationConfig() + value, err := MarshalJSON(config) + if err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(root, ".product-loop"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, ".product-loop", "project.json"), value, 0o644); err != nil { + t.Fatal(err) + } + runGit(t, root, "add", ".") + runGit(t, root, "commit", "-m", "record planning inputs") + + if _, err := CutFeatureWorkspace(WorkspaceCutOptions{Repo: root, Feature: "feature-one"}); err != nil { + t.Fatal(err) + } + + lock := filepath.Join(root, "plan.lock.json") + options := ActivationOptions{PlanPath: planPath, OutDir: filepath.Join(root, "compiled"), OutputPath: lock, SourceCommit: "test"} + err = ActivatePlan(options) + if err == nil || !strings.Contains(err.Error(), "cut worktree") { + t.Fatalf("expected activation blocked from the main worktree, got %v", err) + } + if fileExists(lock) { + t.Fatal("guard was bypassed: a plan lock was written on the base branch") + } + deliveries, _ := deliveryStateDirectory(root) + if _, statErr := os.Stat(filepath.Join(deliveries, "feature-one")); statErr == nil { + t.Fatal("guard was bypassed: delivery state was written on the base branch") + } +} diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index cc8d6ed..1a397c1 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -146,6 +146,6 @@ Delivery and system improvement also remain separate. A failed task may suggest ## What is evidence-backed -The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`70614614df37db20107c65285b2e1550f9368065`](https://github.com/operatorstack/intelligence-flow/tree/70614614df37db20107c65285b2e1550f9368065/labs/12-product-engineering-loop). +The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`64b1891a9eccda6ede3df1b5350462d8179ef112`](https://github.com/operatorstack/intelligence-flow/tree/64b1891a9eccda6ede3df1b5350462d8179ef112/labs/12-product-engineering-loop). The evidence supports specific failure mechanisms and guardrails. It does not establish that Boatstack is optimal, that control-theory notation proves software quality, or that one workflow dominates every team. Those are evaluation questions, so the distribution preserves measurements, provenance, gaps, and negative results. diff --git a/docs/public-claims.json b/docs/public-claims.json index 88c05c4..48f8979 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "70614614df37db20107c65285b2e1550f9368065", + "source_commit": "64b1891a9eccda6ede3df1b5350462d8179ef112", "statuses": ["verified", "observed", "still_being_evaluated"], "claims": [ { @@ -12,7 +12,7 @@ "readable_evidence": "why-these-steps.md#portable-workflow-and-state", "implementation": ["../boatstack/export.go", "../boatstack/references/artifacts.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "human-decisions", @@ -23,7 +23,7 @@ "readable_evidence": "why-these-steps.md#human-decisions", "implementation": ["../boatstack/references/workflow.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "validation-provenance", @@ -34,7 +34,7 @@ "readable_evidence": "why-these-steps.md#validation-provenance", "implementation": ["validation-and-evidence.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "irreversible-operations", @@ -46,7 +46,7 @@ "readable_evidence": "why-these-steps.md#irreversible-operations", "implementation": ["safety.md", "../boatstack/safety.go", "../boatstack/hooks.go"], "verification": ["../boatstack/safety_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "reviewer-ready-pr", @@ -57,7 +57,7 @@ "readable_evidence": "why-these-steps.md#reviewer-ready-pr", "implementation": ["../boatstack/pr.go", "getting-started.md"], "verification": ["../boatstack/pr_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "phase-scoped-delivery", @@ -68,7 +68,7 @@ "readable_evidence": "why-these-steps.md#phase-scoped-delivery", "implementation": ["../boatstack/delivery.go", "../boatstack/safety.go", "../boatstack/hooks.go", "../boatstack/references/workflow.md"], "verification": ["../boatstack/delivery_test.go", "../boatstack/pr_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "model-neutral-contract", @@ -79,7 +79,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "cross-model-failures", @@ -90,7 +90,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "lower-cost-outcomes", @@ -101,7 +101,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "git-worktree-activation", @@ -112,7 +112,7 @@ "readable_evidence": "why-these-steps.md#git-worktree-activation", "implementation": ["../boatstack/runtime_cache.go", "../boatstack/hooks.go"], "verification": ["../boatstack/runtime_cache_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" }, { "id": "visible-updates", @@ -123,7 +123,7 @@ "readable_evidence": "why-these-steps.md#visible-updates", "implementation": ["../boatstack/update.go", "../boatstack/init.go"], "verification": ["../boatstack/update_test.go", "../boatstack/init_test.go", "../boatstack/export_test.go"], - "last_verified_version": "source:70614614df37db20107c65285b2e1550f9368065" + "last_verified_version": "source:64b1891a9eccda6ede3df1b5350462d8179ef112" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index 672095d..7026d0a 100644 --- a/labs/diagram-json/plan.lock.json +++ b/labs/diagram-json/plan.lock.json @@ -6,7 +6,7 @@ "plan_path": "labs/diagram-json/plan.md", "plan_sha256": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "schema_version": 1, - "source_commit": "70614614df37db20107c65285b2e1550f9368065", + "source_commit": "64b1891a9eccda6ede3df1b5350462d8179ef112", "source_plan_path": "labs/diagram-json/source-plan.md", "source_plan_sha256": "e10593ddaa7522ab80cc991d0a09399257139799e37f737794cd49d68a39985b", "spec_path": "labs/diagram-json/spec.md", diff --git a/release-notes/2026-07-26-activate-in-cut-worktree.md b/release-notes/2026-07-26-activate-in-cut-worktree.md new file mode 100644 index 0000000..4cd3a81 --- /dev/null +++ b/release-notes/2026-07-26-activate-in-cut-worktree.md @@ -0,0 +1,16 @@ +### A managed delivery activates inside its cut worktree, not on the base branch + +In worktree mode, Boatstack cuts a fresh worktree for each feature and expects the build to +happen there. It suggested this but never enforced it: if you drove the flow from the +repository root — the main worktree, still on the base branch — after the worktree had been +cut, activation ran there anyway. That stranded the compiled task graph and plan lock on the +base branch and created a second, competing delivery record, split off from the one in the cut +worktree. + +Activation now refuses to run in that situation. When workspace management is on in worktree +mode and a feature's workspace has already been cut, activating from the main worktree on the +base branch is blocked with a clear message: change into the cut worktree and run it there. +The guard is precise — it does nothing during the normal flow (which cuts the worktree first, +then activates inside it), when workspace management is off or in branch mode, or before a +workspace exists — so only the base-branch mistake is stopped. Delivery state stays +worktree-local as before; this simply keeps activation out of the wrong worktree.