diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82b2929..34f01f3 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/2126fde051cc11a8c3de9fff4fc17b5397240383/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/8129a03686d1f9a997ae31326ede835899a74382/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 506bdcc..9abe621 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "7338b0102e28024ac659eebe01be35bbcdf31aecb92e8c8dea15e5aa3abcf317", + "CONTRIBUTING.md": "7470a53aa869b8e3e5db7da216873955c32084176403337fc3f62256b0fbb44a", "README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -134,6 +134,7 @@ "boatstack/plan_validation.go": "99e40806fd579ff72de53f391cd6124acc9ff18707ecf9676c5e507b738d87d0", "boatstack/plan_validation_test.go": "6cbde4ac719baef6b73aa569515d6a9daadcbf14b33f76fa78159826954e20fa", "boatstack/planning.go": "7e5def2fa4edff78f9164a9f117cff65dd8280b3173cdf01d4429b21a3407bb0", + "boatstack/planning_first_write_conformance_test.go": "873097aa9384b75bf01e74a475f3ec2ac7cca4a28f733e82f2c82959032c6a30", "boatstack/planning_test.go": "06ec7022222d926040c3ae28b84ab50c3d2f804ae6473e61b303804dd992d884", "boatstack/pr.go": "b6df3e000dd6d34ecb6575385e44b2f6ee84a8f35ad5696e299177a7cddd7629", "boatstack/pr_test.go": "2e7709e2f163489a29ea3e7eb4bc932cfe6829b30d168f1aea9e942acbc3b4e7", @@ -163,8 +164,8 @@ "boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8", "boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85", "boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc", - "boatstack/safety.go": "e21cc46048e51ed973096a4c4f1c32908aff79cfba8f2f75a301c5f6521656c8", - "boatstack/safety_corpus_test.go": "6f39e7b286fef2cd948fdacaeffa07ba906a56b9f97cdffb6666963a76729a18", + "boatstack/safety.go": "ae69a5ab0609767d69c7ca27e6093c77c6576a0e4860bfe5090f50daec1485f8", + "boatstack/safety_corpus_test.go": "bf8d8a4993c9598cecf371e53c245f88cad0ed29841a6b312262cf2db4caf43b", "boatstack/safety_test.go": "500ad53cd5e3a700553eb781d9eaf4028ae27478796759a76bfd57321fff5a7c", "boatstack/safety_update_publisher_test.go": "ed3f8187036623694dfe7c395cdae00fdae14609bab6124d1fdfc6fe73fa2196", "boatstack/skill_frontmatter.go": "73364df463ce828c2d005aab55f72bb92f7a34d99cf3f53d4e0cd5a4da9dbd0e", @@ -191,10 +192,10 @@ "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0", - "docs/evidence-engineered-coding.md": "e5c48eb66d9ac2967aa41b8ba972f0d85d7fb95cdaa2fe1019f45bd544d73eb6", + "docs/evidence-engineered-coding.md": "4a34055e046930643283281a6364c8e3a976ad56036909bb372b341daef9329d", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8", - "docs/public-claims.json": "b723b764dcb7ca02f339a36e837a6fcf7d9b59ab881878e0ea1aab9c3a101070", + "docs/public-claims.json": "9c0c75c2ecb4828ef8ad9be21b46e114adb8ce9e251c8f6ec544af3aec71ad13", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -208,7 +209,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": "4c31e6695c8cc726d795e8dd39d8f7ba84d208a615786d674ef445e91e60b52b", + "labs/diagram-json/plan.lock.json": "95b94e46b1dd097b11f6aac765ecf5d1ca2d525e375a662971c7a16182cd3f4d", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -325,6 +326,7 @@ "release-notes/2026-07-27-constitutional-boundary-floor.md": "41514cbea53867c264e354f149638444ab0d80f64896b9cf720f3979ae78b3de", "release-notes/2026-07-27-coreachable-recovery.md": "6ffc6b0e9a7d46c0f99a64112813c33d19571c73d02e98ac5573924f1663fd54", "release-notes/2026-07-27-discoverable-planning-errors.md": "d8099d1a6cd1805c3fcd446d9fa95739dec93e57ff421ecf4f99562b143c9966", + "release-notes/2026-07-27-first-planning-write-owned-channel.md": "7a37e7abf7fd5f8612aca4323d55af1748c9e668bb294518619a1c39e195309f", "release-notes/2026-07-27-guard-dual-reward-corpus.md": "6bec0385c6c553f00517259821e502796ca1b1907aeab718a287560e3e0fa0d6", "release-notes/2026-07-27-invalid-delivery-block-actionable.md": "8fac8e3921e2285291703efa46e624b72cb5bac1b8492beca4c4b633abb5ba16", "release-notes/2026-07-27-prescriptive-planning-closure.md": "e544408e1c3cceb0cb1979833ea120853c38020933439b39e7f009f454b9661e", @@ -334,7 +336,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "2126fde051cc11a8c3de9fff4fc17b5397240383", + "commit": "8129a03686d1f9a997ae31326ede835899a74382", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/planning_first_write_conformance_test.go b/boatstack/planning_first_write_conformance_test.go new file mode 100644 index 0000000..b8bdef8 --- /dev/null +++ b/boatstack/planning_first_write_conformance_test.go @@ -0,0 +1,181 @@ +package boatstack + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// control-law: first-planning-write-uses-the-owned-channel +// +// No host-raw byte ever lands in .product-loop/features/ — before the first +// plan candidate exists or after. Previously the guard latched only once a +// (possibly malformed) draft registered, so the very first raw Write/cp of a +// planning artifact was allowed and the agent discovered planning-write only +// by failing into INVALID_STATE and a quarantine loop. The invariant these +// tests hold: the managed planning tree is authored exclusively through the +// owned channel at every stage; the deny is path-scoped (ordinary product +// writes stay unlatched at zero candidates); and the denial names the verb +// the guard itself admits at that stage (Coreachability). + +// Positive: the owned channel clears the cause the denial reports — the +// helper verbs pass the guard at zero candidates, and planning-write actually +// creates the artifact. +func TestFirstPlanningWriteOwnedChannelStaysOpen(t *testing.T) { + repo := safetyTestRepo(t) + + for _, command := range []string{ + "boatstack-helper planning-write --repo . --feature checkout --artifact plan.md", + "boatstack-helper check-source-plan --repo . --plan docs/plan.md", + } { + if findings := ClassifyCommand(repo, command); len(findings) > 0 { + t.Fatalf("owned channel denied at zero candidates: %q -> %#v", command, findings) + } + } + + written, err := WritePlanningArtifact(PlanningWriteOptions{ + Repo: repo, Feature: "checkout", Artifact: "source-plan.md", + Content: []byte("# Source plan\n"), + }) + if err != nil { + t.Fatalf("planning-write must author the first artifact: %v", err) + } + if _, err := os.Stat(filepath.Join(repo, filepath.FromSlash(written))); err != nil { + t.Fatalf("owned write did not land: %v", err) + } +} + +// Negative: the first raw write is denied with the exact finding — category, +// source, stage, prescribed verb, and the slug parsed from the path — at any +// depth and for any name under the planning tree. +func TestFirstRawPlanningWriteIsDenied(t *testing.T) { + repo := safetyTestRepo(t) + + finding := func(path string) SafetyFinding { + findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, filepath.FromSlash(path)), + "content": "# Draft\n", + }) + if len(findings) == 0 { + t.Fatalf("raw write of %q must be denied at zero candidates", path) + } + return findings[0] + } + + got := finding(".product-loop/features/checkout/plan.md") + if got.Category != "workflow-phase-bypass" || got.Source != "planning-state" { + t.Fatalf("wrong finding identity: %#v", got) + } + if got.WorkflowStage != "NOT_STARTED" || got.NextOperation != "planning-write" { + t.Fatalf("finding must carry the real stage and the owned verb: %#v", got) + } + if got.BlockingFeature != "checkout" { + t.Fatalf("slug must be parsed from the path for a copy-pasteable denial: %#v", got) + } + + // Non-allowlisted names and nested depths are still inside the latch. + for _, path := range []string{ + ".product-loop/features/checkout/notes/scratch.md", + ".product-loop/features/checkout/random.txt", + } { + if got := finding(path); got.NextOperation != "planning-write" { + t.Fatalf("latch must cover %q: %#v", path, got) + } + } +} + +// Relation: the same law reaches both guard entry paths — a Write tool and a +// raw shell write yield the same finding, and the rendered denial names the +// owned channel in full. +func TestFirstWriteLatchCoversToolAndCommandPaths(t *testing.T) { + repo := safetyTestRepo(t) + + toolFindings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, ".product-loop", "features", "checkout", "plan.md"), + "content": "# Draft\n", + }) + commandFindings := ClassifyCommand(repo, "cp draft.md .product-loop/features/checkout/plan.md") + if len(toolFindings) == 0 || len(commandFindings) == 0 { + t.Fatalf("both entry paths must deny: tool=%#v command=%#v", toolFindings, commandFindings) + } + for _, pair := range []struct { + label string + finding SafetyFinding + }{{"tool", toolFindings[0]}, {"command", commandFindings[0]}} { + if pair.finding.Category != "workflow-phase-bypass" || pair.finding.NextOperation != "planning-write" || pair.finding.WorkflowStage != "NOT_STARTED" { + t.Fatalf("%s path finding drifted: %#v", pair.label, pair.finding) + } + } + + rendered := denialFor("claude", toolFindings[0]).Render(RenderPlain) + if !strings.Contains(rendered, "planning-write --repo . --feature checkout --artifact ") { + t.Fatalf("denial must name the owned channel: %q", rendered) + } + if !strings.Contains(rendered, "NOT_STARTED") { + t.Fatalf("denial must name the real stage: %q", rendered) + } +} + +// Bypass: shell write idioms and mutation-capable MCP tools cannot slip the +// latch, while product writes outside the planning tree stay unlatched — the +// deny is path-scoped, never a blanket zero-candidate interlock. +func TestFirstWriteLatchBypassAndScope(t *testing.T) { + repo := safetyTestRepo(t) + + for _, command := range []string{ + `tee .product-loop/features/checkout/plan.md < draft.md`, + `printf '# Plan' > .product-loop/features/checkout/plan.md`, + `mv draft.md .product-loop/features/checkout/source-plan.md`, + `mkdir -p .product-loop/features/checkout && cp draft.md .product-loop/features/checkout/plan.md`, + } { + if findings := ClassifyCommand(repo, command); len(findings) == 0 { + t.Fatalf("shell write bypass allowed: %q", command) + } + } + + if findings := ClassifyTool(repo, "mcp__files__update", map[string]any{ + "path": ".product-loop/features/checkout/plan.md", "content": "# Draft\n", + }); len(findings) == 0 { + t.Fatal("mutation-capable MCP tool must not bypass the latch") + } + + // Scope: ordinary product writes remain free at zero candidates. + for _, path := range []string{"src/app.ts", "product.go"} { + if findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, path), "content": "package main\n", + }); len(findings) > 0 { + t.Fatalf("path-scoped latch leaked onto product write %q: %#v", path, findings) + } + } +} + +// Failure-state: a denial is decided before any effect — the working tree is +// byte-identical afterward and the planning tree still does not exist. +func TestFirstWriteDenialLeavesTreeUntouched(t *testing.T) { + repo := safetyTestRepo(t) + before, err := exec.Command("git", "-C", repo, "status", "--short").CombinedOutput() + if err != nil { + t.Fatal(err) + } + + findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, ".product-loop", "features", "checkout", "plan.md"), + "content": "# Draft\n", + }) + if len(findings) == 0 { + t.Fatal("expected denial") + } + + after, err := exec.Command("git", "-C", repo, "status", "--short").CombinedOutput() + if err != nil { + t.Fatal(err) + } + if string(before) != string(after) { + t.Fatalf("classification mutated the tree: before=%q after=%q", before, after) + } + if _, err := os.Stat(filepath.Join(repo, ".product-loop", "features")); !os.IsNotExist(err) { + t.Fatalf("planning tree must not exist after a denial: %v", err) + } +} diff --git a/boatstack/safety.go b/boatstack/safety.go index ea7a59f..8c4c54f 100644 --- a/boatstack/safety.go +++ b/boatstack/safety.go @@ -134,6 +134,14 @@ var mutationToolPattern = regexp.MustCompile(`(?i)(?:write|edit|apply[_-]?patch| var planningMutationToolPattern = regexp.MustCompile(`(?i)(?:write|edit|apply[_-]?patch|create)`) var externalReadOnlyToolPattern = regexp.MustCompile(`(?i)(?:^|[_-])(?:get|list|read|search|find|status|inspect|query|fetch|open)(?:[_-]|$)`) +// featuresCommandPathPattern extracts a .product-loop/features/… operand from a +// shell command so the first-write latch can see raw shell writes (cp, tee, >) +// the same way ClassifyTool sees a Write tool's file_path. Mirrors the +// deliveryStatePathPattern law for .git/boatstack: only owned channels may name +// managed planning paths in a mutating command. +// control-law: first-planning-write-uses-the-owned-channel +var featuresCommandPathPattern = regexp.MustCompile(`(?i)(?:^|[\s"'=(])((?:\./)?\.product-loop[/\\]features[/\\][^\s"';&|)]+)`) + func controlledPhaseTransition(command, stage string) bool { if strings.ContainsAny(command, "\n`><;&|") || strings.Contains(command, "$(") { return false @@ -200,6 +208,12 @@ func controlledPhaseTransition(command, stage string) bool { return fields[1] == "planning-write" || fields[1] == "record-approval" case "APPROVED", "POLICY_READY": return fields[1] == "activate-plan" || fields[1] == "workspace-cut" + case "NOT_STARTED": + // The first-write latch denies raw writes into .product-loop/features/ + // before any candidate exists and prescribes planning-write; Coreachability + // requires the guard to admit that verb at the very stage that names it. + // record-approval is NOT admitted here — there is no plan to approve yet. + return fields[1] == "planning-write" default: return false } @@ -303,8 +317,29 @@ func attemptedRepositoryPath(repo string, input any) string { return visit(input) } +// featureScopedPath reports whether a repo-relative path lands anywhere under +// the managed planning tree. Broader than planningMarkdownPath on purpose: the +// first-write latch covers every depth and name, while planningMarkdownPath +// stays the exact allowlist for the bounded DRAFT_PLAN carve-out. +func featureScopedPath(path string) bool { + return strings.HasPrefix(filepath.ToSlash(path), ".product-loop/features/") +} + +// featuresPathInCommand extracts the first .product-loop/features/… operand a +// shell command names, normalized to a slash-form repo-relative path, or "" +// when none is named. It is the ClassifyCommand analogue of a Write tool's +// extracted file_path, feeding the same first-write latch. +func featuresPathInCommand(command string) string { + match := featuresCommandPathPattern.FindStringSubmatch(command) + if match == nil { + return "" + } + path := filepath.ToSlash(match[1]) + return strings.TrimPrefix(path, "./") +} + func planningMarkdownPath(path string) bool { - if !strings.HasPrefix(path, ".product-loop/features/") { + if !featureScopedPath(path) { return false } parts := strings.Split(filepath.ToSlash(path), "/") @@ -347,6 +382,25 @@ func preActivationFinding(repo, attemptedPath string) (SafetyFinding, bool) { return SafetyFinding{Category: "workflow-observation-fault", Reason: "saved feature plans cannot be verified; diagnose the channel with doctor", Source: "planning-state", NextOperation: "doctor"}, true } if len(candidates) == 0 { + // First-write latch: even before any plan candidate exists, the managed + // planning tree is authored only through the owned channel. Without this, + // the very first raw host write of plan.md registers a malformed draft and + // the agent discovers planning-write only by failing into INVALID_STATE. + // The deny is path-scoped — ordinary product writes stay unlatched at zero + // candidates. Stage NOT_STARTED is what ResolveNext reports here, and + // controlledPhaseTransition admits planning-write at that stage, so the + // denial names a verb the guard accepts (Coreachability). + // control-law: first-planning-write-uses-the-owned-channel + if featureScopedPath(attemptedPath) { + finding := SafetyFinding{ + Category: "workflow-phase-bypass", Reason: "planning Markdown is created through the owned channel; a raw first write into .product-loop/features/ is denied", Source: "planning-state", + WorkflowStage: "NOT_STARTED", AttemptedPath: attemptedPath, NextOperation: "planning-write", + } + if parts := strings.Split(filepath.ToSlash(attemptedPath), "/"); len(parts) > 2 && featureSlugPattern.MatchString(parts[2]) { + finding.BlockingFeature = parts[2] + } + return finding, true + } return SafetyFinding{}, false } status, err := ResolveNext(repo, "") @@ -710,7 +764,9 @@ func ClassifyCommand(repo, command string) []SafetyFinding { return dedupeFindings(findings) } if !isPureReadOnlyCommand(command) { - if finding, blocked := preActivationFinding(repo, ""); blocked && !controlledPhaseTransition(command, finding.WorkflowStage) && !controlledWorkspaceSync(repo, command) { + // Feed any named .product-loop/features/ operand so the first-write latch + // sees raw shell writes (cp/tee/redirect) the same way it sees a Write tool. + if finding, blocked := preActivationFinding(repo, featuresPathInCommand(command)); blocked && !controlledPhaseTransition(command, finding.WorkflowStage) && !controlledWorkspaceSync(repo, command) { return []SafetyFinding{finding} } } diff --git a/boatstack/safety_corpus_test.go b/boatstack/safety_corpus_test.go index 5132340..8736694 100644 --- a/boatstack/safety_corpus_test.go +++ b/boatstack/safety_corpus_test.go @@ -58,6 +58,15 @@ func TestGuardCorpusDualReward(t *testing.T) { {"gcloud-sql-delete", "", `gcloud sql instances delete primary`, "constitutional", true}, {"aws-delete-snapshot", "", `aws rds delete-db-snapshot --db-snapshot-identifier b1`, "constitutional", true}, {"mcp-execute-sql-drop", "mcp__db__execute_sql", map[string]any{"query": "DROP TABLE users"}, "constitutional", true}, + // First-write latch: the managed planning tree is authored only through + // planning-write — a raw first write is denied even before any candidate + // exists. control-law: first-planning-write-uses-the-owned-channel + {"raw-write-first-plan", "Write", map[string]any{ + "file_path": filepath.Join(repo, ".product-loop", "features", "checkout", "plan.md"), + "content": "# Plan\n", + }, "constitutional", true}, + {"cp-first-plan", "", `cp draft.md .product-loop/features/checkout/plan.md`, "constitutional", true}, + {"redirect-first-source-plan", "", `printf x > .product-loop/features/checkout/source-plan.md`, "constitutional", true}, // --- routine: ordinary product work, MUST pass (false-positive axis) --- {"git-add-migration", "", `git add migrate.sql`, "routine", false}, @@ -73,6 +82,14 @@ func TestGuardCorpusDualReward(t *testing.T) { "file_path": filepath.Join(repo, "notes.md"), "content": "Runbook: operator runs `psql -c \"DROP SCHEMA public CASCADE\"` off-hours.", }, "routine", false}, + // The owned planning channel and ordinary product writes stay open at zero + // candidates — the first-write latch is path-scoped, never a blanket deny. + {"planning-write-first-artifact", "", `boatstack-helper planning-write --repo . --feature checkout --artifact plan.md`, "routine", false}, + {"check-source-plan", "", `boatstack-helper check-source-plan --repo . --plan docs/plan.md`, "routine", false}, + {"write-product-source", "Write", map[string]any{ + "file_path": filepath.Join(repo, "src", "app.ts"), + "content": "export const x = 1\n", + }, "routine", false}, } var constTotal, constBlocked, routineTotal, routinePassed int diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index e699801..7c56d61 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 [`2126fde051cc11a8c3de9fff4fc17b5397240383`](https://github.com/operatorstack/intelligence-flow/tree/2126fde051cc11a8c3de9fff4fc17b5397240383/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 [`8129a03686d1f9a997ae31326ede835899a74382`](https://github.com/operatorstack/intelligence-flow/tree/8129a03686d1f9a997ae31326ede835899a74382/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 4d8a79f..7315e90 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "2126fde051cc11a8c3de9fff4fc17b5397240383", + "source_commit": "8129a03686d1f9a997ae31326ede835899a74382", "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" }, { "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:2126fde051cc11a8c3de9fff4fc17b5397240383" + "last_verified_version": "source:8129a03686d1f9a997ae31326ede835899a74382" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index eb11f1b..80f60b2 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": "2126fde051cc11a8c3de9fff4fc17b5397240383", + "source_commit": "8129a03686d1f9a997ae31326ede835899a74382", "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-27-first-planning-write-owned-channel.md b/release-notes/2026-07-27-first-planning-write-owned-channel.md new file mode 100644 index 0000000..76901b9 --- /dev/null +++ b/release-notes/2026-07-27-first-planning-write-owned-channel.md @@ -0,0 +1,7 @@ +### The first planning write goes through the owned channel + +Until now the guard latched the planning tree only after a plan draft had registered. The very first raw write of a planning file — a host Write tool, a `cp`, a shell redirect — landed unchecked, and if it produced a malformed draft the workflow fell into INVALID_STATE and a repair loop before the agent discovered the owned `planning-write` channel. + +The latch now covers the first write too. Any raw write into `.product-loop/features/` is denied even before a candidate exists, and the denial names the exact command that succeeds: `boatstack-helper planning-write` with the document on stdin. The deny is path-scoped — ordinary product writes are untouched — and the guard admits `planning-write` at that same stage, so the prescribed exit is always reachable. + +This closes the loop the earlier prescriptive-closure release opened: planning guidance, denials, and now enforcement all point at the same single authoring channel, so the wrong first move is redirected immediately instead of discovered through failure.