From cf2a956efc745434b301079a8505da9e1c208145 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Mon, 27 Jul 2026 14:15:44 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 039454bde99f --- CONTRIBUTING.md | 2 +- UPSTREAM.json | 16 +- boatstack/content_effect_conformance_test.go | 149 ++++++++++++++++++ boatstack/safety.go | 60 ++++++- boatstack/safety_corpus_test.go | 23 +++ docs/evidence-engineered-coding.md | 2 +- docs/public-claims.json | 24 +-- labs/diagram-json/plan.lock.json | 2 +- .../2026-07-27-document-content-is-data.md | 7 + 9 files changed, 261 insertions(+), 24 deletions(-) create mode 100644 boatstack/content_effect_conformance_test.go create mode 100644 release-notes/2026-07-27-document-content-is-data.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34f01f3..7780ff7 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/8129a03686d1f9a997ae31326ede835899a74382/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/039454bde99f8059e1a8ee0356ef433f7837cd74/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 9abe621..92bdcd3 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "7470a53aa869b8e3e5db7da216873955c32084176403337fc3f62256b0fbb44a", + "CONTRIBUTING.md": "583ad367e1375a741b32879d96dbf084a8c6b3ed8edc6d701342f7b384fc27f1", "README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -50,6 +50,7 @@ "boatstack/command_test.go": "9f707abba3640add81c3e97ba7e72fedbf98f3394b1c060a9ca4b4a28e919968", "boatstack/compiled_artifact_resolution_test.go": "0748d67643263e698211eb04d46464e1dd3db15d94537f5fd5092b5aa689745b", "boatstack/config_documentation_test.go": "aaecea04ee178ecf7872fff23a64014a3968dce1e7624e61a9453567e99969a9", + "boatstack/content_effect_conformance_test.go": "ebf4f6d50af0a76722177c717c79d33921948b9c06bec2e9d062769dce32b8aa", "boatstack/context.go": "02510af176d2d040c0080086f06d1235e76a1d47fef5176f96f740ad18d27660", "boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c", "boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0", @@ -164,8 +165,8 @@ "boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8", "boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85", "boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc", - "boatstack/safety.go": "ae69a5ab0609767d69c7ca27e6093c77c6576a0e4860bfe5090f50daec1485f8", - "boatstack/safety_corpus_test.go": "bf8d8a4993c9598cecf371e53c245f88cad0ed29841a6b312262cf2db4caf43b", + "boatstack/safety.go": "405782eba91a1718a061faa65ff52c10d345cbf3bccf5ecd2cb8ed45d5df9c00", + "boatstack/safety_corpus_test.go": "e7d8c493d8cee957e4590f2c9034d4aa4af08bdcbe02c9889d0d98a0168bbcf9", "boatstack/safety_test.go": "500ad53cd5e3a700553eb781d9eaf4028ae27478796759a76bfd57321fff5a7c", "boatstack/safety_update_publisher_test.go": "ed3f8187036623694dfe7c395cdae00fdae14609bab6124d1fdfc6fe73fa2196", "boatstack/skill_frontmatter.go": "73364df463ce828c2d005aab55f72bb92f7a34d99cf3f53d4e0cd5a4da9dbd0e", @@ -192,10 +193,10 @@ "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0", - "docs/evidence-engineered-coding.md": "4a34055e046930643283281a6364c8e3a976ad56036909bb372b341daef9329d", + "docs/evidence-engineered-coding.md": "c81d462de78afc834b04acc99e6a816f97ac6e05c98f065a35167eb9feb4fce7", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8", - "docs/public-claims.json": "9c0c75c2ecb4828ef8ad9be21b46e114adb8ce9e251c8f6ec544af3aec71ad13", + "docs/public-claims.json": "79ddc45aebfbbaba1054a731413183a47da0d1c6c299e444869d6986a27f7498", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -209,7 +210,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": "95b94e46b1dd097b11f6aac765ecf5d1ca2d525e375a662971c7a16182cd3f4d", + "labs/diagram-json/plan.lock.json": "5807b7d5140a41e9db5a20256e4bd4adebb2820778db2f378803059dd111ea45", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -326,6 +327,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-document-content-is-data.md": "c6a35c222bf53ba465fadf21e7e95e1764b52e12e41852e51584ce9cb6513f4a", "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", @@ -336,7 +338,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "8129a03686d1f9a997ae31326ede835899a74382", + "commit": "039454bde99f8059e1a8ee0356ef433f7837cd74", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/content_effect_conformance_test.go b/boatstack/content_effect_conformance_test.go new file mode 100644 index 0000000..0ce2b8c --- /dev/null +++ b/boatstack/content_effect_conformance_test.go @@ -0,0 +1,149 @@ +package boatstack + +import ( + "path/filepath" + "strings" + "testing" +) + +// control-law: written-content-is-data-not-effect +// +// A file-writer tool's document body is inert until something executes it, so +// the guard grades a Write/Edit by its NAME and STRUCTURAL fields (paths, +// destinations) — never by prose inside the body. Previously the text +// classifiers ran over the full marshaled input, so a runbook that mentioned +// `terraform destroy` or docs that named `.git/boatstack/` were denied as if +// the words were the act. The floor is unchanged: the same strings as a Bash +// command still block (the text IS the command), a live SQL executor's +// arguments still block (they are executed), and a protected path in any +// structural field still blocks (redaction never launders the target). + +// contentDocCases name every non-sqlEffect destruction rule once, as prose. +var contentDocCases = []struct { + name string + content string +}{ + {"database-reset", "Ask the operator before `supabase db reset` on staging."}, + {"filesystem-destruction", "Cleanup appendix: `rm -rf ~` is the classic footgun; never script it."}, + {"git-history-destruction", "Policy: `git push --force origin main` is forbidden; use revert PRs."}, + {"infrastructure-destruction", "Teardown runbook: `terraform destroy -auto-approve` after sign-off only."}, + {"recovery-destruction", "Quarterly drill: delete backup snapshots older than the retention window."}, + {"guard-state-doc", "Delivery state lives under `.git/boatstack/deliveries/` — helper-owned."}, +} + +// Positive: documents whose bodies name every destruction rule (and the +// managed state tree) pass as file-writer tool calls. +func TestWrittenContentIsNotClassifiedAsEffect(t *testing.T) { + repo := safetyTestRepo(t) + for _, c := range contentDocCases { + c := c + t.Run("write/"+c.name, func(t *testing.T) { + findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, "docs", c.name+".md"), + "content": c.content, + }) + if len(findings) > 0 { + t.Fatalf("document content classified as effect: %#v", findings) + } + }) + t.Run("edit/"+c.name, func(t *testing.T) { + findings := ClassifyTool(repo, "Edit", map[string]any{ + "file_path": filepath.Join(repo, "docs", c.name+".md"), + "old_string": "TODO", + "new_string": c.content, + }) + if len(findings) > 0 { + t.Fatalf("edit content classified as effect: %#v", findings) + } + }) + } +} + +// Negative: the executor contexts keep the boundary — the same text blocks +// when it IS the command, and executed SQL arguments stay live. +func TestExecutorContextsStillBlockAfterRedaction(t *testing.T) { + repo := safetyTestRepo(t) + for _, command := range []string{ + `terraform destroy -auto-approve`, + `git push --force origin main`, + `supabase db reset`, + } { + if findings := ClassifyCommand(repo, command); len(findings) == 0 { + t.Fatalf("live command must still block: %q", command) + } + } + if findings := ClassifyTool(repo, "mcp__db__execute_sql", map[string]any{"query": "DROP TABLE users"}); len(findings) == 0 { + t.Fatal("SQL executor arguments are executed, not stored — must still block") + } +} + +// Relation: one table drives both outcomes for the identical hook-shaped tool +// call — content alone allows, a protected structural path denies. +func TestContentAllowsWhilePathDenies(t *testing.T) { + repo := safetyTestRepo(t) + content := "Ops note: state is under .git/boatstack/deliveries/ and terraform destroy is operator-only." + + if findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": filepath.Join(repo, "docs", "ops.md"), + "content": content, + }); len(findings) > 0 { + t.Fatalf("content-only mention must pass: %#v", findings) + } + + findings := ClassifyTool(repo, "Write", map[string]any{ + "file_path": ".git/boatstack/deliveries/checkout/state.json", + "content": content, + }) + if len(findings) == 0 { + t.Fatal("write INTO managed state must deny regardless of content") + } + if findings[0].Category != "workflow-state-tamper" { + t.Fatalf("wrong category for state tamper: %#v", findings) + } +} + +// Bypass: redaction drops only content fields — a protected path smuggled in +// any structural field (file_path, destination, nested) still blocks, and a +// non-writer tool keeps full-input grading. +func TestRedactionCannotLaunderProtectedTargets(t *testing.T) { + repo := safetyTestRepo(t) + + for name, input := range map[string]map[string]any{ + "file_path": {"file_path": ".git/boatstack/flow/trajectory.jsonl", "content": "x"}, + "destination": {"file_path": "notes.md", "destination": ".git/boatstack/deliveries/x", "content": "x"}, + "nested": {"file_path": "notes.md", "meta": map[string]any{"target_path": ".git/boatstack/runtimes/v1"}, "content": "x"}, + } { + if findings := ClassifyTool(repo, "Write", input); len(findings) == 0 { + t.Fatalf("structural field %s must survive redaction and deny: %#v", name, input) + } + } + + // A tool with no extracted path is not a file-writer: full-input grading holds. + if findings := ClassifyTool(repo, "mcp__infra__delete_resource", map[string]any{ + "kind": "database", "note": "drop the staging cluster", + }); len(findings) == 0 { + t.Fatal("non-writer destructive tool must keep full-input grading") + } +} + +// Failure-state: redaction is pure — the caller's input map is never mutated, +// and classification performs no I/O on the named document. +func TestRedactionIsPureAndReadOnly(t *testing.T) { + repo := safetyTestRepo(t) + input := map[string]any{ + "file_path": filepath.Join(repo, "docs", "ops.md"), + "content": "terraform destroy notes", + "meta": map[string]any{"body": "git reset --hard"}, + } + _ = ClassifyTool(repo, "Write", input) + + if input["content"] != "terraform destroy notes" { + t.Fatalf("caller input mutated: %#v", input) + } + if meta := input["meta"].(map[string]any); meta["body"] != "git reset --hard" { + t.Fatalf("nested caller input mutated: %#v", meta) + } + if strings.Contains(strings.ToLower("docs/ops.md"), "boatstack") { + t.Fatal("fixture invariant") + } +} diff --git a/boatstack/safety.go b/boatstack/safety.go index 8c4c54f..4962930 100644 --- a/boatstack/safety.go +++ b/boatstack/safety.go @@ -317,6 +317,50 @@ func attemptedRepositoryPath(repo string, input any) string { return visit(input) } +// contentInputKeys are the tool-input fields that carry a document body rather +// than structure. They are dropped before text classification of a file-writer +// tool call: the body is data (inert until executed), while structural fields +// — file_path, destination, url — survive redaction, so a protected path in +// any of them is still graded. +var contentInputKeys = map[string]bool{ + "content": true, "contents": true, "new_string": true, "old_string": true, + "new_str": true, "old_str": true, "patch": true, "diff": true, + "text": true, "body": true, "data": true, +} + +// redactContentFields deep-copies a decoded tool input with content-bearing +// fields removed, at every nesting depth. The original input is never mutated. +func redactContentFields(input any) any { + switch value := input.(type) { + case map[string]any: + out := make(map[string]any, len(value)) + for key, item := range value { + if contentInputKeys[strings.ToLower(key)] { + continue + } + out[key] = redactContentFields(item) + } + return out + case []any: + out := make([]any, 0, len(value)) + for _, item := range value { + out = append(out, redactContentFields(item)) + } + return out + default: + return input + } +} + +// fileWriterTool reports whether a tool call is a file write: it names a target +// path and its verb shape is a writer (write/edit/patch/create). Live SQL +// executors are excluded — their arguments are executed, not stored. Only +// file-writer calls get content redaction; everything else keeps full-input +// text grading. control-law: written-content-is-data-not-effect +func fileWriterTool(nameLower, attemptedPath string) bool { + return attemptedPath != "" && planningMutationToolPattern.MatchString(nameLower) && !toolExecutesLiveSQL(nameLower) +} + // 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 @@ -808,13 +852,25 @@ func ClassifyTool(repo, name string, input any) []SafetyFinding { if err != nil { return []SafetyFinding{{Category: "malformed-tool-input", Reason: "invalid-tool-input", Source: "tool-input"}} } + nameLower := strings.ToLower(name) + attemptedPath := attemptedRepositoryPath(repo, input) + // Written content is DATA, not effect: a file-writer tool's document body is + // inert until something executes it, so the text classifiers grade only the + // tool name and its structural fields (paths, destinations) — a runbook that + // MENTIONS `terraform destroy` or `.git/boatstack/` is not the act of running + // or tampering with either. Bash command strings stay fully text-scanned (the + // text IS the command), and live SQL-executor tools keep full-input grading. + // control-law: written-content-is-data-not-effect + if fileWriterTool(nameLower, attemptedPath) { + if redacted, redactErr := json.Marshal(redactContentFields(input)); redactErr == nil { + value = redacted + } + } combined := name + " " + string(value) // Bare SQL grammar in a tool's arguments is a live capability only when the tool // itself executes SQL (an MCP execute_sql / db query tool). A Write/Edit/Read // whose content merely contains DDL is a document, not an execution. findings := classifySafetyText(combined, "tool-input", toolExecutesLiveSQL(name)) - nameLower := strings.ToLower(name) - attemptedPath := attemptedRepositoryPath(repo, input) mutationCapable := mutationToolPattern.MatchString(nameLower) || (strings.HasPrefix(nameLower, "mcp__") && !externalReadOnlyToolPattern.MatchString(nameLower)) if mutationCapable { if finding, blocked := preActivationFinding(repo, attemptedPath); blocked { diff --git a/boatstack/safety_corpus_test.go b/boatstack/safety_corpus_test.go index 8736694..633f2cf 100644 --- a/boatstack/safety_corpus_test.go +++ b/boatstack/safety_corpus_test.go @@ -67,6 +67,13 @@ func TestGuardCorpusDualReward(t *testing.T) { }, "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}, + // Content redaction must never launder a protected TARGET: writing INTO + // managed runtime state is tampering regardless of what the body says. + // control-law: written-content-is-data-not-effect + {"write-into-delivery-state", "Write", map[string]any{ + "file_path": ".git/boatstack/deliveries/checkout/state.json", + "content": "{}", + }, "constitutional", true}, // --- routine: ordinary product work, MUST pass (false-positive axis) --- {"git-add-migration", "", `git add migrate.sql`, "routine", false}, @@ -90,6 +97,22 @@ func TestGuardCorpusDualReward(t *testing.T) { "file_path": filepath.Join(repo, "src", "app.ts"), "content": "export const x = 1\n", }, "routine", false}, + // Written content is data: documenting destructive commands or managed + // paths in a file body is not the act of running or tampering with them. + // control-law: written-content-is-data-not-effect + {"write-runbook-mentions-destroy", "Write", map[string]any{ + "file_path": filepath.Join(repo, "runbook.md"), + "content": "Recovery drill: run `terraform destroy -target=module.stale` then restore from the snapshot.", + }, "routine", false}, + {"write-doc-mentions-guard-state", "Write", map[string]any{ + "file_path": filepath.Join(repo, "docs", "ops.md"), + "content": "Delivery state lives under `.git/boatstack/deliveries/` and is owned by the helper.", + }, "routine", false}, + {"edit-note-mentions-git-reset", "Edit", map[string]any{ + "file_path": filepath.Join(repo, "notes.md"), + "old_string": "TODO", + "new_string": "Never run `git reset --hard` on shared branches; use revert PRs.", + }, "routine", false}, } var constTotal, constBlocked, routineTotal, routinePassed int diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 7c56d61..523a2f5 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 [`8129a03686d1f9a997ae31326ede835899a74382`](https://github.com/operatorstack/intelligence-flow/tree/8129a03686d1f9a997ae31326ede835899a74382/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 [`039454bde99f8059e1a8ee0356ef433f7837cd74`](https://github.com/operatorstack/intelligence-flow/tree/039454bde99f8059e1a8ee0356ef433f7837cd74/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 7315e90..fe35641 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "8129a03686d1f9a997ae31326ede835899a74382", + "source_commit": "039454bde99f8059e1a8ee0356ef433f7837cd74", "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" }, { "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:8129a03686d1f9a997ae31326ede835899a74382" + "last_verified_version": "source:039454bde99f8059e1a8ee0356ef433f7837cd74" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index 80f60b2..59dc000 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": "8129a03686d1f9a997ae31326ede835899a74382", + "source_commit": "039454bde99f8059e1a8ee0356ef433f7837cd74", "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-document-content-is-data.md b/release-notes/2026-07-27-document-content-is-data.md new file mode 100644 index 0000000..3e6a891 --- /dev/null +++ b/release-notes/2026-07-27-document-content-is-data.md @@ -0,0 +1,7 @@ +### Writing about a command is no longer treated as running it + +The guard used to scan a file write's entire body with the same text rules it applies to shell commands. Saving a runbook that mentions `terraform destroy`, a policy note about `git push --force`, or documentation that names `.git/boatstack/` paths was denied as if the words were the act. + +Written content is now graded as data: a file-writer tool call is judged by its name and structural fields — the target path, destinations — never by prose inside the document body. The boundary itself is unchanged. The same strings still block when they are a live command, a SQL executor's arguments are still executed capability, and a write that targets managed runtime state is still denied no matter what the body says: redaction can never launder the target. + +This extends the executed-effect philosophy — grade what a call does, not what its text mentions — from shell commands to host file tools, and removes the largest remaining source of false-positive denials during ordinary documentation work.