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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/052ce000ad11b193ea73e262592d59535043f73f/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/804141bc65d66fdb0a422a9c7c545a71180bffb1/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.

Expand Down
25 changes: 13 additions & 12 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "c7edd6bb227d0220f83ee5c00901723ed273ab144f3e1cf4bf77697ab63cbf72",
"CONTRIBUTING.md": "c7f3497bbe061860f2ecec201bbb9526f045585371c5f62db2b1b5ce17362bc5",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -50,7 +50,7 @@
"boatstack/config_documentation_test.go": "0632366edc5e88145bb080083ea03c6515da07b0162ce404d63e51bb5bc0774e",
"boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c",
"boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0",
"boatstack/delivery.go": "772793e493b97348c198692a282035a40257b7ca8e21bdea66a881a5c805c22a",
"boatstack/delivery.go": "9bdcfecae7564c34a0d374ddbba4f237b241085db5c9d5bda6c4afb801055b35",
"boatstack/delivery_boundary_conformance_test.go": "800cd722d8d2a696a0529e8343d3523e453bb052f0917c8a2cad2990296ac1b3",
"boatstack/delivery_migrate.go": "7566e49f9c1838d4d563866e941c7aacd61ac918c9e886222282398d287ca780",
"boatstack/delivery_migrate_conformance_test.go": "b8ba53681e1d0361ac62b06586c62b7763d55a65b5427976b5289e1fb1503bdc",
Expand Down Expand Up @@ -84,7 +84,7 @@
"boatstack/init.go": "1b2721ad64dcfba3e954b97bb46218238873f46ec29fc4dac327aa99980f9cf6",
"boatstack/init_test.go": "5fdf687205e7a5984a98a87336b7127e4ae9b651d57e21ec2dc8ca7e653ee602",
"boatstack/init_transaction.go": "112456c4e1c4db54c4137bcf4f7a9a9e63399a6f5971e9b3dc952d0c4b2aa4b6",
"boatstack/installation_repair.go": "6574f7133a9644843c9260b9b9daede641a14438f7357bae42fb8ec188890446",
"boatstack/installation_repair.go": "b887f27023aff46636b0e38d16e18e3ab9a7f5c94079b2c152aecee4edc0eae5",
"boatstack/installation_repair_test.go": "ae5a5ea1110836bd78cf20ade863a4d32cfd63d282559f92786f57b31869bd14",
"boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c",
"boatstack/internal/deliverycontrol/advise.go": "4f3a53a785a34f6c34858236a57d4114091141a463b51e5aaefae3336557ae5a",
Expand Down Expand Up @@ -115,13 +115,13 @@
"boatstack/next.go": "39d813c96a6a5119efcabda0f59ce7c6faf91e5e76c77c7be999bf85f43de284",
"boatstack/next_banner_test.go": "c431a6987ed1e479442fc9f5db4371632880b92aa790fa9dd0f5285293352c41",
"boatstack/next_test.go": "d442d22023831ba39fcfbbf73f1a2da4170a83fc2aab5ce1b44f10cf9d88e173",
"boatstack/operation.go": "62f97bf2091f33eb2ca91915bf08bee73d53387611b673e849355bfd516ca467",
"boatstack/operation_test.go": "2d624eaba342b2c81b45cdf50918a65a9c002b5376a02041b24180658ee6a25a",
"boatstack/operation.go": "35142d24e166bc400229d233757b6ab185f94b01a3c11be49da33b7f123189a2",
"boatstack/operation_test.go": "59d3dc37319aa4d334c0cacbe886e2f757842e6a28dee8781448e528fecbde11",
"boatstack/plan.go": "7209de3a97b134cd6e5224cf6e798b5af47b0a4163ae2f81a8ec0c1ff84031d6",
"boatstack/plan_test.go": "53477515165a910910b9175bfa33574548cf0d0f3be48e175ec3a775a12d30bb",
"boatstack/plan_validation.go": "412f06750832fe46f01190ea5e475fc6f6ea59c8ba78131f94ec031053a405d2",
"boatstack/plan_validation_test.go": "6cbde4ac719baef6b73aa569515d6a9daadcbf14b33f76fa78159826954e20fa",
"boatstack/planning.go": "ef4507a9fecc900f0691372c50883f328c9232c3dfd6986fbde26fb7ef436ae3",
"boatstack/planning.go": "6dfeb802a7cb615f159d87c86f338eab0897bda836417b07f8307695373c78ff",
"boatstack/planning_test.go": "c105a9c78c342be06614bf54d0bc1b661b0f7af64d63b79e43bd1fcc2769edd5",
"boatstack/pr.go": "eae93f7a6e423f67336545d37181ec2ea350991a2d75985f1cf2cf4b7bc8382d",
"boatstack/pr_test.go": "7f82954d94c1ceae848a581dda25e58af92251d78a5a94ed2d672bedf5a0349e",
Expand All @@ -148,7 +148,7 @@
"boatstack/run.go": "74967ad5b3ed3847baffec1231aae69a81a70f9cce3a9412fa05bcfdc4eca6d1",
"boatstack/run_test.go": "5b291510fa90cefdc26eb89e18a3443385456a6ebc73408325ac1945b7c084d6",
"boatstack/runtime.go": "d1e95895002ea2b27199b6e05b33c4c6e20f63455a44f63ca3cfeedecfc23420",
"boatstack/runtime_cache.go": "e40c8c43f410d781a7a4e7ebf68a1caa597ea9005190fd6cea02884f863e091e",
"boatstack/runtime_cache.go": "89834409b426dce292fd810a091de1433fefbfba9249d8c1e31ccc40f0d5dbd6",
"boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85",
"boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc",
"boatstack/safety.go": "24df06e3c0f5dd54ee361ebf7bfd2deca7b18193c2d58d1eb9331c012d342eda",
Expand All @@ -161,7 +161,7 @@
"boatstack/testdata/safety/safe_apply.py.txt": "c9ec7fb932cf21b6aa8df597c4d4c54d6ec65e796240e49118d699f583383975",
"boatstack/testdata/safety/unsafe_apply.py.txt": "42db1751865cc15c4dd69a03146b5deca8f21f916d258e433b27bbef5f884ab1",
"boatstack/update.go": "042c7e8141423e2cdb71c92f93cf73cc81d916116d76ab9928c56cf18bc6827a",
"boatstack/update_publication.go": "c8b7bd38019b1cbf8c523652e9e20e8c971c7e48b2f3972a0ac638648326fe63",
"boatstack/update_publication.go": "7cba2e993700190bcc7c3c1cb1a5fe4df84b2053f35bb4fa5e4b0673f397bca2",
"boatstack/update_publication_test.go": "c5f32578db53be65e35452d5e8b4520884354e4e80a370dcec19860ea644d091",
"boatstack/update_test.go": "bf5f19f8499db6dd7356917d867b113b790548ab89bfdea59e2adf4999a82a6a",
"boatstack/visual_evidence.go": "4d69f98adb6087d4830e6703273ae6e03b28ec8b3c496a6e432fd5e0e54d8a2f",
Expand All @@ -176,10 +176,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "df054f49d532c8b1b7d94184810d1b3b5bf18cdc30eb985b4b6d0639162e341a",
"docs/evidence-engineered-coding.md": "61449fce7dd97bdbe69e2cdbd5cdf53ee9bae52dfe6e1ce51e377007ba2f4c72",
"docs/evidence-engineered-coding.md": "8c9ac13f925f67db325db9163a1384aa01591d17af82e47e7447922585f62e16",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "1dd4f4e2e636cc5adfc2f79939629701e171087c3d5e558cf919548b9224adfd",
"docs/public-claims.json": "35c12ea50b1ae2a8403adcdd8e6e849e079a60f13822faa86bddff6543842010",
"docs/public-claims.json": "16d2327409e3bdaefab872aa46abeb88328a8ec263302ed0a4d5d48df431a392",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -193,7 +193,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": "b8249b7cea66cfba8b42b64551cdf4ca6d71ac7c4979a55e1fede27479cbd3e1",
"labs/diagram-json/plan.lock.json": "59951495e1695536035673cac5c799a174d43ac1091e455d4cf4a0e29c9f9763",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -289,6 +289,7 @@
"release-notes/2026-07-25-deliverycontrol-shadow-registry.md": "e7f8ca4e4f188eda3088e46cba77369d8ff0d903f29e43846103d986e79a2273",
"release-notes/2026-07-25-evidence-path-resolution.md": "b32cb8a6e69f397f751c3a7fb62be254a7407a28bed25ae9108d6d773c863d11",
"release-notes/2026-07-25-guard-hydration-serialization.md": "0bf43284b8063011d837dafb10dc77fda496133c14a31ee196dc0dd0e4ffeec4",
"release-notes/2026-07-25-per-worktree-operation-ledger.md": "d0d4495fe4406cf67e7475032e3fc9eb74ed02a3e68f4928c086b5518422b46c",
"release-notes/2026-07-25-published-slice-correction-routing.md": "129cdd62c80c8b93060726027d68ba3abdb0bca1a1ce9e64d6053271af3fd082",
"release-notes/2026-07-25-readme-simplified-technical-english.md": "c362f46702c38dda6b0301d05b95a067da617d170ddfcca22fd7eb9f6e2c1881",
"release-notes/2026-07-25-release-notes-simplified-technical-english.md": "70b273cbeb5ee46c49c10541540f31e8ca67a71102acfd47ae15451856c651db",
Expand All @@ -299,7 +300,7 @@
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "052ce000ad11b193ea73e262592d59535043f73f",
"commit": "804141bc65d66fdb0a422a9c7c545a71180bffb1",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
14 changes: 4 additions & 10 deletions boatstack/delivery.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,11 @@ func deliveryDefinitions(plan map[string]any) ([]DeliverySlice, error) {
}

func deliveryStateDirectory(repo string) (string, error) {
gitDirectory := gitOutput(repo, "rev-parse", "--path-format=absolute", "--git-dir")
if gitDirectory == "" {
gitDirectory = gitOutput(repo, "rev-parse", "--git-dir")
}
if gitDirectory == "" {
return "", fmt.Errorf("cannot resolve the Git worktree directory")
}
if !filepath.IsAbs(gitDirectory) {
gitDirectory = filepath.Join(repo, gitDirectory)
gitDir, err := worktreeGitDir(repo)
if err != nil {
return "", err
}
return filepath.Join(filepath.Clean(gitDirectory), "boatstack", "deliveries"), nil
return filepath.Join(gitDir, "boatstack", "deliveries"), nil
}

func deliveryStatePath(repo, feature string) (string, error) {
Expand Down
17 changes: 11 additions & 6 deletions boatstack/installation_repair.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,17 @@ func repairOwnedPaths(result InstallationRepairResult) map[string]bool {
}

func writeInstallationRepairBackup(repo string, result InstallationRepairResult) (string, error) {
common, err := gitCommonDir(repo)
// Repair backups capture this worktree's pre-repair working-tree bytes. Two
// worktrees repairing to the same version share a package fingerprint but hold
// different pre-repair content, so a clone-shared directory would let one
// clobber the other's restore point. Key the backup and its receipt by the
// per-worktree Git directory instead, and keep them co-located.
gitDir, err := worktreeGitDir(repo)
if err != nil {
return "", err
}
directory := filepath.Join(common, "boatstack", "repair-backups", result.PackageFingerprint)
if err := rejectSymlinkComponents(common, directory); err != nil {
directory := filepath.Join(gitDir, "boatstack", "repair-backups", result.PackageFingerprint)
if err := rejectSymlinkComponents(gitDir, directory); err != nil {
return "", err
}
if err := os.MkdirAll(directory, 0o700); err != nil {
Expand Down Expand Up @@ -449,23 +454,23 @@ func writeInstallationRepairBackup(repo string, result InstallationRepairResult)
if err != nil {
return "", err
}
receiptPath := filepath.Join(common, "boatstack", "updates", version, "repair.json")
receiptPath := filepath.Join(gitDir, "boatstack", "updates", version, "repair.json")
if err := atomicWriteMode(receiptPath, receiptValue, 0o600); err != nil {
return "", err
}
return directory, nil
}

func loadInstallationRepairReceipt(repo, version string) (*InstallationRepairResult, error) {
common, err := gitCommonDir(repo)
gitDir, err := worktreeGitDir(repo)
if err != nil {
return nil, err
}
segment, err := safeCacheSegment(version, "repair target version")
if err != nil {
return nil, err
}
path := filepath.Join(common, "boatstack", "updates", segment, "repair.json")
path := filepath.Join(gitDir, "boatstack", "updates", segment, "repair.json")
value, err := os.ReadFile(path)
if os.IsNotExist(err) {
return nil, nil
Expand Down
33 changes: 27 additions & 6 deletions boatstack/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,33 @@ func operationTimestamp() string {
return operationNow().UTC().Truncate(time.Second).Format(time.RFC3339)
}

// operationDirectory holds the operation ledger. It is per-worktree: an operation
// records a mutation performed by one worktree at one time, so worktree A's ledger
// must never sit on worktree B's read/write path (a shared ledger produced false
// "operation identity does not match" blocks across worktrees). The "v2" segment
// is intentional: the main worktree's Git directory aliases the common directory,
// so bumping the version cleanly orphans the legacy clone-shared "v1" ledger for
// every worktree — including main — instead of silently inheriting its receipts.
func operationDirectory(repo string) (string, error) {
common, err := gitCommonDir(repo)
gitDir, err := worktreeGitDir(repo)
if err != nil {
return "", err
}
return filepath.Join(common, "boatstack", "operations", "v1"), nil
return filepath.Join(gitDir, "boatstack", "operations", "v2"), nil
}

// pruneLegacyOperationLedger removes the pre-isolation clone-shared "v1" ledger
// under the Git common directory. Once operations moved to the per-worktree "v2"
// ledger those receipts are orphaned for every worktree (including main, whose
// Git directory aliases the common directory). This is best-effort hygiene: the
// v2 path already guarantees correctness, so any failure here is ignored.
func pruneLegacyOperationLedger(repo string) {
common, err := gitCommonDir(repo)
if err != nil {
return
}
legacy := filepath.Join(common, "boatstack", "operations", "v1")
_ = os.RemoveAll(legacy)
}

func operationPath(repo, operationID string) (string, error) {
Expand All @@ -126,11 +147,11 @@ func operationPath(repo, operationID string) (string, error) {
return "", err
}
path := filepath.Join(directory, id+".json")
common, err := gitCommonDir(repo)
gitDir, err := worktreeGitDir(repo)
if err != nil {
return "", err
}
if err := rejectSymlinkComponents(common, path); err != nil {
if err := rejectSymlinkComponents(gitDir, path); err != nil {
return "", err
}
return path, nil
Expand Down Expand Up @@ -211,11 +232,11 @@ func withOperationLock(repo, id string, apply func() error) error {
return err
}
lock := strings.TrimSuffix(path, ".json") + ".lock"
common, err := gitCommonDir(repo)
gitDir, err := worktreeGitDir(repo)
if err != nil {
return err
}
if err := rejectSymlinkComponents(common, lock); err != nil {
if err := rejectSymlinkComponents(gitDir, lock); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(lock), 0o700); err != nil {
Expand Down
84 changes: 56 additions & 28 deletions boatstack/operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
Expand Down Expand Up @@ -264,41 +263,70 @@ func TestExpiredLeaseBecomesUnknownAndBudgetPersists(t *testing.T) {
}
}

func TestOperationReceiptsAreSharedAcrossLinkedWorktreesAndSerialized(t *testing.T) {
// TestOperationLedgerIsolatedPerWorktree encodes the deliberate contract reversal
// from the pre-isolation design: the operation ledger is now per-worktree, so one
// worktree neither observes nor blocks another worktree's operations. Two worktrees
// running the same operation identity each hold their own receipt and their own lock.
func TestOperationLedgerIsolatedPerWorktree(t *testing.T) {
repo := operationTestRepo(t)
linked := filepath.Join(t.TempDir(), "linked")
runGit(t, repo, "worktree", "add", "-b", "linked-test", linked)

// An operation prepared in the main worktree is invisible in a linked worktree:
// the linked worktree cannot resolve it from its own separate ledger.
receipt := preparedOperation(t, repo, "package-d", "ATOMIC_LOCAL", 2)
status, err := ResolveOperationStatus(linked, receipt.OperationID)
if err != nil || status.Operation == nil || status.Operation.OperationID != receipt.OperationID {
t.Fatalf("linked worktree did not observe shared operation: %+v %v", status, err)
if _, err := ResolveOperationStatus(linked, receipt.OperationID); err == nil {
t.Fatal("linked worktree observed another worktree's operation ledger")
}

// The same identity begins independently in each worktree — no cross-worktree
// in-flight serialization and no identity collision.
if _, err := BeginOperation(repo, receipt.OperationID, "main-attempt", "Write"); err != nil {
t.Fatalf("main worktree could not begin its own operation: %v", err)
}
linkedReceipt := preparedOperation(t, linked, "package-d", "ATOMIC_LOCAL", 2)
if linkedReceipt.OperationID != receipt.OperationID {
t.Fatalf("same identity produced different operation ids: %s vs %s", linkedReceipt.OperationID, receipt.OperationID)
}
if _, err := BeginOperation(linked, linkedReceipt.OperationID, "linked-attempt", "Write"); err != nil {
t.Fatalf("linked worktree blocked by another worktree's in-flight operation: %v", err)
}
}

// TestSameVersionUpdateFromTwoWorktreesDoesNotCollide reproduces the reported
// incident: a same-version install-update prepared from a second worktree used to
// fail with "existing operation identity does not match the prepared package"
// because the clone-shared ledger compared a differing scope. Per-worktree ledgers
// make each prepare independent.
func TestSameVersionUpdateFromTwoWorktreesDoesNotCollide(t *testing.T) {
repo := operationTestRepo(t)
linked := filepath.Join(t.TempDir(), "linked")
runGit(t, repo, "worktree", "add", "-b", "update-b", linked)

var wait sync.WaitGroup
errorsSeen := make(chan error, 2)
for _, root := range []string{repo, linked} {
wait.Add(1)
go func(path string) {
defer wait.Done()
_, beginErr := BeginOperation(path, receipt.OperationID, "same-attempt", "Write")
errorsSeen <- beginErr
}(root)
}
wait.Wait()
close(errorsSeen)
successes, inFlight := 0, 0
for beginErr := range errorsSeen {
switch {
case beginErr == nil:
successes++
case errors.Is(beginErr, ErrOperationInFlight):
inFlight++
default:
t.Fatalf("unexpected concurrent begin error: %v", beginErr)
options := func(root, scopeWorktree, branch string) OperationPrepareOptions {
return OperationPrepareOptions{
Repo: root,
Kind: "install-update",
Target: "boatstack-install:v9.9.9",
PackageFingerprint: "fp-999",
AuthorizationFingerprint: "auth-999",
RetryClass: "ATOMIC_LOCAL",
MaxAttempts: 2,
ExpectedPostcondition: "generated runtime matches the pinned release",
Scope: OperationScope{Worktree: scopeWorktree, HeadBranch: branch},
}
}
if successes != 1 || inFlight != 1 {
t.Fatalf("operation lock admitted %d executions and %d in-flight reports", successes, inFlight)

a, err := PrepareOperation(options(repo, "update-a", "chore/update-boatstack-v9.9.9"))
if err != nil {
t.Fatalf("worktree A prepare failed: %v", err)
}
b, err := PrepareOperation(options(linked, "update-b", "chore/update-boatstack-v9.9.9"))
if err != nil {
t.Fatalf("worktree B blocked by worktree A's operation: %v", err)
}
if a.OperationID != b.OperationID {
t.Fatalf("expected identical operation ids across worktrees, got %s vs %s", a.OperationID, b.OperationID)
}
}

Expand Down
Loading
Loading