Skip to content

Commit 2ba156d

Browse files
Sync Boatstack from Intelligence Flow Labs @ 6f20a566ff2e
1 parent 2950e89 commit 2ba156d

13 files changed

Lines changed: 338 additions & 137 deletions

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Contributing
44

5-
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/bfaa855fddf392520adb0e2324d38aff0421a7fb/labs/12-product-engineering-loop).
5+
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/6f20a566ff2e5cbc5893273627cc019dcff9de16/labs/12-product-engineering-loop).
66

77
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.
88

UPSTREAM.json

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"canonical_context": {
3-
"characters": 57829,
4-
"estimated_tokens": 14458,
3+
"characters": 59068,
4+
"estimated_tokens": 14767,
55
"estimator": "ceil(total characters / 4); compactness signal, not provider billing",
66
"files": [
77
"product-engineering-loop/references/workflow.md",
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "2352ef6339506b5d3b6abaa1c81e35f33410add957cb10a05df8c3aa5f71f1a1",
15+
"CONTRIBUTING.md": "89f4914e5877a355e5f33c98d8386cda0261c6756049c2de623bb7a10ef846f4",
1616
"README.md": "125b47671a68556df382f19756fb61fa18925606cbbaf54d6bc9df8872b36870",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -50,10 +50,10 @@
5050
"boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017",
5151
"boatstack/hooks.go": "b88cedcd045e5217fedfac625ced2e4f691adb42e62155fcbc392a8f6d88366e",
5252
"boatstack/hooks_test.go": "c5786bc6463cf6932a6612b26cbe65d035008ecbca5c0063bea253d857c2f622",
53-
"boatstack/init.go": "a32ffdbc148a19aa556e36897d80e7d41ac04a976fef6b27d527b81246bcf32d",
53+
"boatstack/init.go": "302957edf2e663f806d3d5de4486fef5eaf04797270a6f797cfe0170a732f66d",
5454
"boatstack/init_test.go": "5fdf687205e7a5984a98a87336b7127e4ae9b651d57e21ec2dc8ca7e653ee602",
5555
"boatstack/init_transaction.go": "112456c4e1c4db54c4137bcf4f7a9a9e63399a6f5971e9b3dc952d0c4b2aa4b6",
56-
"boatstack/installation_repair.go": "0c896e7a5033211350eca2b2225bca638b3b0ac4adff550bc514090f7b789f23",
56+
"boatstack/installation_repair.go": "6574f7133a9644843c9260b9b9daede641a14438f7357bae42fb8ec188890446",
5757
"boatstack/installation_repair_test.go": "ae5a5ea1110836bd78cf20ade863a4d32cfd63d282559f92786f57b31869bd14",
5858
"boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c",
5959
"boatstack/migrate.go": "eaf589e2b266238068e42c6d78e01dc040266d28e342cb24f09e33e8541749b3",
@@ -74,11 +74,11 @@
7474
"boatstack/recovery_test.go": "29490e7477ba602491330036a491289dd9117b99ff862f66dae421ba17e04c9f",
7575
"boatstack/references/artifacts.md": "5fa888ac519085d65cee1d04df5902761651bcf2d7af81711fa0f8ecd1fc0f59",
7676
"boatstack/references/config-schema.md": "c07c2d532ef95ea6ae538a1fffefb92ded1f8dc6a06eb3b8d463e371d1ed8416",
77-
"boatstack/references/failure-moves.md": "36e4f1487a790d981055ee5554fb1ee829b183dc8ca8c2b93f24e678b7d8a680",
77+
"boatstack/references/failure-moves.md": "34a39aefb282b1b5d9ea387f8536bdc5e0145a240f102ae3d01c7ada6d4abebc",
7878
"boatstack/references/host-hook-contracts.md": "d68ae1556e7b1e29e9ac7cb4db767809d510aabf0be52e60e44665ea7abb980e",
7979
"boatstack/references/irreversible-operation-boundary.md": "631743991ace65977586e4537f8dd50f8ae88f8e16f27cf7baad93b2791a73df",
8080
"boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae",
81-
"boatstack/references/workflow.md": "86cc04ff440098ddf9f1a793da9e5dca7208fd24dcc3be4a7742c1d323e4866c",
81+
"boatstack/references/workflow.md": "3fc235d6b87d413796c806236c49452be664406450b7ee400ccf287d9a94b694",
8282
"boatstack/release.go": "82dcb4ca59e8c79a68d5333d650f90e64abd448d04e0c6f504fdf07f42b5ed76",
8383
"boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690",
8484
"boatstack/run.go": "fbdbf583c862c41f23d1a200f53d042842db72f19c29fe94e4288a69b0ac4a6b",
@@ -93,10 +93,10 @@
9393
"boatstack/testdata/reviewer-pr-body.md": "4c64e3788e5d61a377aeb0f797f7fc8d2316ab6e49572d15636eea7ba9e34ac4",
9494
"boatstack/testdata/safety/safe_apply.py.txt": "c9ec7fb932cf21b6aa8df597c4d4c54d6ec65e796240e49118d699f583383975",
9595
"boatstack/testdata/safety/unsafe_apply.py.txt": "42db1751865cc15c4dd69a03146b5deca8f21f916d258e433b27bbef5f884ab1",
96-
"boatstack/update.go": "edcf524ec103c62e07266aa70c3b7abba665ce1ed04dfa8eb3b26e330100172e",
96+
"boatstack/update.go": "042c7e8141423e2cdb71c92f93cf73cc81d916116d76ab9928c56cf18bc6827a",
9797
"boatstack/update_publication.go": "c8b7bd38019b1cbf8c523652e9e20e8c971c7e48b2f3972a0ac638648326fe63",
9898
"boatstack/update_publication_test.go": "c5f32578db53be65e35452d5e8b4520884354e4e80a370dcec19860ea644d091",
99-
"boatstack/update_test.go": "4cdd612356978edf718191f794be11590796bd5af1d524381b1a09243636cd51",
99+
"boatstack/update_test.go": "bf5f19f8499db6dd7356917d867b113b790548ab89bfdea59e2adf4999a82a6a",
100100
"boatstack/visual_evidence.go": "90a68d554e10ff4fb7afa45000912cdedd4cdf93b3d279055b50844401924f01",
101101
"boatstack/visual_evidence_test.go": "0fe8f5154ef4398dfeba5e7f7b387b2d279ed75635ea35d93ff392269f2cc6d0",
102102
"boatstack/workspace.go": "91b343400b3506a6f516c28fabc3f1575f22024a5b19f934a020be660a20482e",
@@ -105,14 +105,14 @@
105105
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
106106
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
107107
"docs/configuration.md": "f530c5dcbacf32dcb6fdab590901d8f658a4f29bd93f6264cec5d4f449c2cbd1",
108-
"docs/evidence-engineered-coding.md": "f722b9e3db8c3f8f9e8e34f8f9ee916ed22966dacc67965fc2acec17bd7fbc99",
108+
"docs/evidence-engineered-coding.md": "e6570313e611a97b30b41c0a53fe5f13e6758e6919e9a612de778d796a7ee2e0",
109109
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
110110
"docs/getting-started.md": "d5f0b170209e61518810a23b851bf9eb50b6755906703ca313e6e9faab20e1cd",
111-
"docs/public-claims.json": "7e844ed4a89ebdcf1834def12db0bb8cda83fe2fab511bfa54353304b4127193",
111+
"docs/public-claims.json": "b414e68cad00c21d5de0433a14f9d0ed791cb091fe29afd35aa68f12f204bbd6",
112112
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
113113
"docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f",
114114
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
115-
"docs/troubleshooting.md": "7e0106ab2b7642f0c7cf641a61c18287bbe8a6b61a89222ce745dabe804240c9",
115+
"docs/troubleshooting.md": "76f8cf2558345bbb873b19977fe27b455ef9f5f3e70e0386951fc462138706fa",
116116
"docs/validation-and-evidence.md": "e7d91ad49c6adb44784ebe7d94feceb6abd445857f9a0716f0758bf6b55296c5",
117117
"docs/why-these-steps.md": "cbe0d769db11ef15bb1dff888009378d6783776ad020e6f5139847a1dd62fa09",
118118
"install.ps1": "6f5857ec0feb502683c5781b9bfbbe31ed39556cd13384ddc66da622a8423cb7",
@@ -122,7 +122,7 @@
122122
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
123123
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
124124
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
125-
"labs/diagram-json/plan.lock.json": "b2cb5cdf6ed31f55d748ad5d62cba8d894d48a98857201818e553d9c0928358f",
125+
"labs/diagram-json/plan.lock.json": "268dc0a059c57bec2094f8aa779752b77ad71a989b0cf744b20aad49e1350cce",
126126
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
127127
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
128128
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -180,12 +180,13 @@
180180
"release-notes/2026-07-22-product-configuration-guide.md": "45e96862336bd53a2628ce3fe718c829ea20315555f53187eb7f04ba749f3a97",
181181
"release-notes/2026-07-22-projection-layout-validation.md": "fd0d2935f3f0c4caa41bda678e3bd9a9b496eb2652ab38d80c1c1434cbba3159",
182182
"release-notes/2026-07-22-value-translation-boundary.md": "9cf168ff7caaf3906b78533935bdfb2c86e753984ed1e5ec8204cb373d083390",
183-
"release-notes/2026-07-23-bootstrap-safe-update-repair.md": "d8e66c46ae05e3d228dfea45879f4d1166d5e3a253ac24bababd4c3e396c214b"
183+
"release-notes/2026-07-23-bootstrap-safe-update-repair.md": "d8e66c46ae05e3d228dfea45879f4d1166d5e3a253ac24bababd4c3e396c214b",
184+
"release-notes/2026-07-23-canonical-update-ownership.md": "7f34f890b252493797519389b23f1b56ec7ec16db7547aac8ea196f75f3b8c2c"
184185
},
185186
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
186187
"schema_version": 1,
187188
"source": {
188-
"commit": "bfaa855fddf392520adb0e2324d38aff0421a7fb",
189+
"commit": "6f20a566ff2e5cbc5893273627cc019dcff9de16",
189190
"path": "labs/12-product-engineering-loop",
190191
"repository": "operatorstack/intelligence-flow"
191192
}

boatstack/init.go

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -302,21 +302,12 @@ func updateChangedPaths(repo string) []string {
302302
return sortedKeys(seen)
303303
}
304304

305-
func checkUpdateDiffScope(repo string, currentFiles map[string][]byte, previous map[string]string, hookPaths []string) ([]string, error) {
306-
allowed := map[string]bool{".boatstack-project.json": true}
307-
for path := range currentFiles {
308-
allowed[filepath.ToSlash(path)] = true
309-
}
310-
for path := range previous {
311-
allowed[filepath.ToSlash(path)] = true
312-
}
313-
for _, path := range hookPaths {
314-
allowed[filepath.ToSlash(path)] = true
315-
}
305+
func checkUpdateDiffScope(repo string, currentFiles map[string][]byte, previous map[string]string, config ProjectConfig) ([]string, error) {
306+
ownership := newUpdateOwnershipProjection(config, currentFiles, previous)
316307
changed := updateChangedPaths(repo)
317308
unexpected := []string{}
318309
for _, path := range changed {
319-
if !allowed[path] {
310+
if err := ownership.verify(repo, path); err != nil {
320311
unexpected = append(unexpected, path)
321312
}
322313
}
@@ -502,6 +493,9 @@ func RunInit(options InitOptions) (returnErr error) {
502493
for _, path := range HostHookPaths(config.Adapters) {
503494
fmt.Fprintln(options.Output, " "+path+" (merge Boatstack safety hook; preserve existing settings)")
504495
}
496+
for _, path := range executionInterceptorPaths(config.Adapters) {
497+
fmt.Fprintln(options.Output, " "+path+" (replace only the marker-bounded Boatstack interceptor)")
498+
}
505499
if !configExists {
506500
fmt.Fprintln(options.Output, " .boatstack-project.json (editable repository facts)")
507501
}
@@ -632,7 +626,7 @@ func RunInit(options InitOptions) (returnErr error) {
632626
return fmt.Errorf("post-install smoke check failed: %w", err)
633627
}
634628
if options.Update {
635-
changed, scopeErr := checkUpdateDiffScope(repo, bundle.Files, previousGenerated, HostHookPaths(config.Adapters))
629+
changed, scopeErr := checkUpdateDiffScope(repo, bundle.Files, previousGenerated, config)
636630
if scopeErr != nil {
637631
return scopeErr
638632
}
@@ -669,6 +663,7 @@ func RunInit(options InitOptions) (returnErr error) {
669663
}
670664
}
671665
stagePaths = append(stagePaths, HostHookPaths(config.Adapters)...)
666+
stagePaths = append(stagePaths, executionInterceptorPaths(config.Adapters)...)
672667
stageSet := map[string]bool{}
673668
for _, path := range stagePaths {
674669
stageSet[path] = true
@@ -750,19 +745,9 @@ func injectExecutionInterceptor(repo, file string) error {
750745
}
751746

752747
func InstallExecutionInterceptors(repo string, adapters []string) error {
753-
for _, adapter := range adapters {
754-
if adapter == "gemini" {
755-
if err := injectExecutionInterceptor(repo, "GEMINI.md"); err != nil {
756-
return err
757-
}
758-
} else if adapter == "claude" {
759-
if err := injectExecutionInterceptor(repo, "CLAUDE.md"); err != nil {
760-
return err
761-
}
762-
} else if adapter == "cursor" {
763-
if err := injectExecutionInterceptor(repo, ".cursorrules"); err != nil {
764-
return err
765-
}
748+
for _, path := range executionInterceptorPaths(adapters) {
749+
if err := injectExecutionInterceptor(repo, path); err != nil {
750+
return err
766751
}
767752
}
768753
return nil
@@ -794,6 +779,12 @@ func RunUpdate(options InitOptions) error {
794779
return err
795780
}
796781
}
782+
// Validate the complete update workspace before creating a durable attempt.
783+
// Invalid branch or diff state must not consume a retry or leave an identity
784+
// that collides with the later, correctly prepared operation.
785+
if err := ValidateUpdateWorkspaceForRepair(repo, config, preflight, options.Repair); err != nil {
786+
return err
787+
}
797788
branch := strings.TrimSpace(gitOutput(repo, "branch", "--show-current"))
798789
repairAuthority := fmt.Sprintf("repair=%t\x00allow-downgrade=%t", options.Repair, options.AllowDowngrade)
799790
packageFingerprint := SHA256Bytes([]byte(Version + "\x00" + SourceCommit + "\x00" + ChecksumsSHA256 + "\x00" + repairAuthority))
@@ -819,7 +810,7 @@ func RunUpdate(options InitOptions) error {
819810
}
820811
options.Update = true
821812
if err := RunInit(options); err != nil {
822-
_, _ = CompleteOperation(repo, receipt.OperationID, begin.LeaseToken, "RETRYABLE", "the atomic update transaction rolled back", "")
813+
_, _ = CompleteOperation(repo, receipt.OperationID, begin.LeaseToken, "RETRYABLE", "the atomic update transaction rolled back: "+err.Error(), "")
823814
return err
824815
}
825816
_, err = CompleteOperation(repo, receipt.OperationID, begin.LeaseToken, "SUCCEEDED", "post-install doctor and generated projections passed", Version)

boatstack/installation_repair.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func classifyHookState(repo, host string) []InstallationRepairItem {
240240
}
241241

242242
func classifyExecutionInterceptor(repo, host string) []InstallationRepairItem {
243-
relative := map[string]string{"cursor": ".cursorrules", "claude": "CLAUDE.md", "gemini": "GEMINI.md"}[host]
243+
relative := executionInterceptorPath(host)
244244
if relative == "" {
245245
return nil
246246
}

boatstack/references/failure-moves.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Select a move only after locating the failure below its surface symptom. “Time
1515
| Review miss | Defect found after same-agent review | Independent reviewer; risk checklist; mechanical enforcement | Expensive review everywhere |
1616
| Scope drift | Diff no longer maps to approved outcomes | Re-scope; split PR; update spec with approval | Hiding product changes in implementation |
1717
| Update self-lockout | An installed helper, stale hook event, or damaged owned receipt blocks its own updater | Let the verified target helper classify state; migrate exact provenance automatically or offer fingerprinted `--repair` | Reinstalling blindly, overwriting user settings, or treating `--repair` as downgrade authority |
18+
| Ownership projection contradiction | Update admission classifies a path as Boatstack-owned, then final validation rejects the controller's own bounded mutation | Build one semantic ownership projection before execution; reuse it for admission, mutation, final verification, staging, and preview | Path-only allowlists accepting user content or independently maintained validators disagreeing after a side effect |
1819
| Security/tenancy | Trust boundary or data scope violated | Specialist review; invariant test; deny-by-default guard | Generic prompt mistaken for enforcement |
1920
| Integration/deploy | Local pass but runtime fails | Environment parity; canary; health checks; rollback | Treating staging as identical to production |
2021
| Documentation drift | Durable behavior and docs disagree | Update source-of-truth artifact; drift check | Growing instructions with unverified rules |
@@ -35,6 +36,7 @@ Select a move only after locating the failure below its surface symptom. “Time
3536
- **Tool failure must not create recovery authority.** The sanitized database incident moved from a partial schema apply failure to an invented reset path. The irreversible-operation guard is `PROPOSED`, not promoted: evaluate its deny corpus, safe corpus, latency, and workflow regressions against the unguarded baseline.
3637
- **Fail-closed controls need an available evaluator.** A linked worktree copied the safety hook but not its ignored helper, so the guard also denied its own repair command. Share only the verified runtime within the Git clone and hydrate local ignored state before judging the original event.
3738
- **A retry needs a new observation.** Identical in-flight calls wait. Unknown non-idempotent calls enter `RECONCILE_REQUIRED`; Git, GitHub, filesystem, browser, and MCP boundaries must observe their exact postcondition before another attempt consumes the persistent budget.
39+
- **Preconditions run before leases.** Wrong branch, stale base, or invalid diff state returns a recovery operation without creating a durable attempt. A rejected precondition cannot consume retry budget or leave an identity that collides with the corrected invocation.
3840

3941
## Move proposal schema
4042

boatstack/references/workflow.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ After successful publication only, the publisher may use the ignored 24-hour rel
383383

384384
For an available version, create `chore/update-boatstack-v<version>` and download and checksum-verify the target helper before consulting the installed runtime. The target helper classifies hook fragments, generated locks, helper provenance, and marker-bounded interceptors. Exact installed state migrates automatically. Recoverable owned drift is fingerprinted and, interactively, offered as **Repair Boatstack-owned state and continue the update? [y/N]**; noninteractive updates stop with one `--repair` retry. Repair backs up the exact paths in Git-common state and remains in the same update PR. User-owned, mixed, malformed, symlinked, or product state stays blocked. Downgrades require both `--repair` and `--allow-downgrade`.
385385

386+
Before a durable update attempt is created, Boatstack verifies the dedicated branch, base commit, repair classification, and current diff. Invalid workspace state consumes no retry budget. The update transaction then reuses one semantic ownership projection for admission, mutation, final verification, staging, and preview. Generated files must match their prepared bytes, host-hook files must preserve their non-Boatstack JSON, and `.cursorrules`, `CLAUDE.md`, and `GEMINI.md` must preserve everything outside their single Boatstack marker boundary.
387+
386388
The update transaction is a durable atomic-local operation. It preserves repository configuration, adapters, integrations, and unrelated host settings, then runs `doctor`. After installation, `prepare-update-pr` verifies that every changed path is Boatstack-owned and atomically stores the exact non-empty publication package in Git-common runtime state. Show release and repair provenance, the exact generated diff, checksums, changed paths, integration state, rollout, and rollback.
387389

388390
Use **Boatstack update ready** and exactly one action: Reply `o` to open update PR. Only the state-scoped `o` or compatible full reply authorizes `publish-update-pr` with that preview fingerprint. The publisher stages only the approved paths, reuses or creates the exact update commit, pushes normally, and reconciles the head branch before opening at most one PR. The PR body records release provenance, changed generated files, verification, rollout, and revert instructions. If a response is lost after GitHub accepted the request, the next invocation observes and returns the existing PR. If publication is unavailable, retain the prepared branch and provide one manual action. Never merge automatically.

0 commit comments

Comments
 (0)