diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6e2cb8..d11fecb 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/f740356bfc30b59038162ed3c7ca849f77c76e7f/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/e2ba11f185aa370ec4302ffcd1f4f7e622f60105/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 ba3f3fc..f4c62cc 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "9d31155e08bf2ec29b66a839c0a320e1e30f2c1f69448b89622b74ade3b0c5ed", + "CONTRIBUTING.md": "fe36d1fcccd0042e51ae8c590f21ec49d9fdb9a367ddfd31803a6fc1db1f0f24", "README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -54,7 +54,7 @@ "boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c", "boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0", "boatstack/delivery.go": "1cbc917eaa7df569f09c71352db157dff743827d5310dccb63acb7be72ccf9d5", - "boatstack/delivery_boundary_conformance_test.go": "c374eddf49b4597db78c0621f65f87199de9a26f1872ed88d9d790edf21fe3f2", + "boatstack/delivery_boundary_conformance_test.go": "53dde765046420b9119e82034d137742e600019938ed908c608f725d8a0c84c6", "boatstack/delivery_migrate.go": "7566e49f9c1838d4d563866e941c7aacd61ac918c9e886222282398d287ca780", "boatstack/delivery_migrate_conformance_test.go": "b8ba53681e1d0361ac62b06586c62b7763d55a65b5427976b5289e1fb1503bdc", "boatstack/delivery_reactivation_test.go": "573a2dba0034bc4290478414e3bdd8670b06a326128eb0295d77e748ecc8689e", @@ -160,7 +160,7 @@ "boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8", "boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85", "boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc", - "boatstack/safety.go": "56055f93cd9fe0f308b244111f2282d191c3ed522731047a194b06f21df64247", + "boatstack/safety.go": "b68ea12b2ee4c1f1782c1a6a8540fff908ef8ef6d80f0b391c2933a5f2fecb38", "boatstack/safety_test.go": "02260654d0b93ad48585c40391b310810876a6abcafc3d6c074f1f4e4e633f76", "boatstack/safety_update_publisher_test.go": "ed3f8187036623694dfe7c395cdae00fdae14609bab6124d1fdfc6fe73fa2196", "boatstack/skill_frontmatter.go": "73364df463ce828c2d005aab55f72bb92f7a34d99cf3f53d4e0cd5a4da9dbd0e", @@ -187,10 +187,10 @@ "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0", - "docs/evidence-engineered-coding.md": "c3301e5ef81642029935970eb4270c3667eeeacba3d7994d98407f65409dcac4", + "docs/evidence-engineered-coding.md": "525a3fd0cc83ad45ce494a10a94432c534283bbd0be424d320f4351cb78faad5", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8", - "docs/public-claims.json": "6c631d91ce3579a4a4fcf09a0d5c902a102de62b9793136e18e94032f67f73aa", + "docs/public-claims.json": "d1e8ae99275098ed44ac52322fc0fece3c8091a2a43e7bdf10de78a00a3649ba", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -204,7 +204,7 @@ "labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d", "labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71", "labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39", - "labs/diagram-json/plan.lock.json": "e059bbc4a5ab29c1dc16a3ae00a1486b57a245e767e2ebaef4cd572002e959a7", + "labs/diagram-json/plan.lock.json": "b404cd373f8b5c33cd6c3de992e2db9e94d34367a2e7681d08de5a1561263faa", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -318,12 +318,13 @@ "release-notes/2026-07-26-guard-hydrate-double-check.md": "83a5591aba6bf30c9f4008ba8d26bf1994ef3fd61145f46fcdd1678912b9990b", "release-notes/2026-07-26-hidden-jflow-design-note.md": "f60ed9dbbfb46a172ac9d33dd758a3166f820007b1673029f29f0fbefa0e5c0a", "release-notes/2026-07-26-workspace-reap.md": "e691d6a1c232cf218157880655413005fcb2c4f3113ededffdb80899a5054bb8", - "release-notes/2026-07-27-coreachable-recovery.md": "6ffc6b0e9a7d46c0f99a64112813c33d19571c73d02e98ac5573924f1663fd54" + "release-notes/2026-07-27-coreachable-recovery.md": "6ffc6b0e9a7d46c0f99a64112813c33d19571c73d02e98ac5573924f1663fd54", + "release-notes/2026-07-27-invalid-delivery-block-actionable.md": "8fac8e3921e2285291703efa46e624b72cb5bac1b8492beca4c4b633abb5ba16" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "f740356bfc30b59038162ed3c7ca849f77c76e7f", + "commit": "e2ba11f185aa370ec4302ffcd1f4f7e622f60105", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/delivery_boundary_conformance_test.go b/boatstack/delivery_boundary_conformance_test.go index f6ed212..9b40f1f 100644 --- a/boatstack/delivery_boundary_conformance_test.go +++ b/boatstack/delivery_boundary_conformance_test.go @@ -123,6 +123,37 @@ func TestActiveManagedDeliveriesStaysFailClosedOnInvalid(t *testing.T) { } } +// Same-Relation-Same-Law + Coreachability at the MUTATION boundary: invalid +// delivery state fails closed even when ignored (no laundering corrupt state), but +// the block is actionable — it prescribes discard-delivery (a reachable verb that +// clears it), not the opaque error or a verb that refuses. Ignoring quiets status; +// discarding unblocks mutation. This is the mutation-path twin of the read-only +// ResolveNext discard-remedy conformance above. +func TestPreActivationBlockOnInvalidDeliveryPrescribesDiscard(t *testing.T) { + repo := nextTestRepo(t) + writeInvalidDelivery(t, repo, "stale-one") + if _, err := IgnoreDelivery(repo, "stale-one"); err != nil { + t.Fatal(err) + } + finding, blocked := preActivationFinding(repo, "product.go") + if !blocked { + t.Fatal("invalid delivery state did not block mutation") + } + if finding.NextOperation != "discard-delivery" { + t.Fatalf("block prescribed %q, want the reachable discard-delivery", finding.NextOperation) + } + if !controlledPhaseTransition("boatstack-helper discard-delivery --repo . --feature stale-one", finding.WorkflowStage) { + t.Fatal("prescribed discard-delivery is not admitted for the block it was prescribed for") + } + // The prescribed verb clears the state, and mutation is then unblocked. + if _, err := DiscardDelivery(repo, "stale-one", true); err != nil { + t.Fatalf("discard-delivery refused the invalid delivery it was prescribed for: %v", err) + } + if _, stillBlocked := preActivationFinding(repo, "product.go"); stillBlocked { + t.Fatal("mutation still blocked after discarding the invalid delivery") + } +} + // Failure-state conformance: ResolveNext is read-only. A blocking decision must // leave the offending state file byte-for-byte unchanged (no partial repair). func TestResolveNextLeavesInvalidStateUntouched(t *testing.T) { diff --git a/boatstack/safety.go b/boatstack/safety.go index 71373f0..e7e991f 100644 --- a/boatstack/safety.go +++ b/boatstack/safety.go @@ -306,6 +306,20 @@ func planningMarkdownPath(path string) bool { func preActivationFinding(repo, attemptedPath string) (SafetyFinding, bool) { active, err := ActiveManagedDeliveries(repo) if err != nil { + // Same-Relation-Same-Law: the mutation boundary FAILS CLOSED on invalid + // delivery state — ignoring a delivery quiets status but never launders corrupt + // state into a mutation (TestActiveManagedDeliveriesStaysFailClosedOnInvalid). + // But the block must be Coreachable: distinguish a corrupt-delivery *plant* + // fault (a verb clears it — discard-delivery, named) from an *observation* + // (channel) fault (doctor to diagnose). Naming discard-delivery is what tells + // the operator how to actually unblock mutation, since ignoring will not. + if _, invalid, scanErr := scanManagedDeliveries(repo); scanErr == nil && len(invalid) > 0 { + return SafetyFinding{ + Category: "workflow-state-invalid", Source: "delivery-state", + Reason: "invalid managed delivery state blocks all mutation; ignoring it only quiets status — clear it with discard-delivery to continue", + NextOperation: "discard-delivery", BlockingFeature: invalid[0], AttemptedPath: attemptedPath, + }, true + } return SafetyFinding{Category: "workflow-observation-fault", Reason: "managed delivery state cannot be verified; diagnose the channel with doctor", Source: "delivery-state", NextOperation: "doctor"}, true } if len(active) > 0 { diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 6023c94..06f7a06 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 [`f740356bfc30b59038162ed3c7ca849f77c76e7f`](https://github.com/operatorstack/intelligence-flow/tree/f740356bfc30b59038162ed3c7ca849f77c76e7f/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 [`e2ba11f185aa370ec4302ffcd1f4f7e622f60105`](https://github.com/operatorstack/intelligence-flow/tree/e2ba11f185aa370ec4302ffcd1f4f7e622f60105/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 b09b41b..6180ea9 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "f740356bfc30b59038162ed3c7ca849f77c76e7f", + "source_commit": "e2ba11f185aa370ec4302ffcd1f4f7e622f60105", "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" }, { "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:f740356bfc30b59038162ed3c7ca849f77c76e7f" + "last_verified_version": "source:e2ba11f185aa370ec4302ffcd1f4f7e622f60105" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index b356862..9f64d40 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": "f740356bfc30b59038162ed3c7ca849f77c76e7f", + "source_commit": "e2ba11f185aa370ec4302ffcd1f4f7e622f60105", "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-invalid-delivery-block-actionable.md b/release-notes/2026-07-27-invalid-delivery-block-actionable.md new file mode 100644 index 0000000..a765c54 --- /dev/null +++ b/release-notes/2026-07-27-invalid-delivery-block-actionable.md @@ -0,0 +1,16 @@ +### A mutation blocked by invalid delivery state now names the step that clears it + +Invalid managed delivery state blocks all product mutation. This is deliberate: adding a delivery to +the ignore list quiets the status report, but it does not let corrupt state pass the mutation guard, +so bad state can never be laundered into a change. That protection stays. + +What was missing was a way forward. The block reported an opaque verification error and did not say how +to unblock work, and the operator's ignore action did not help on this path. The step to run was not +named. + +The block is now actionable. When invalid delivery state stops a mutation, the guard names +`discard-delivery` — the reachable step that archives the corrupt state reversibly and lets work +continue. It also separates a corrupt-state fault, which a step can clear, from a sensing fault such as +an unreadable store or a broken configuration, which is diagnosed by the read-only `doctor` because no +mutation step can repair a channel. The read-only status path keeps tolerating an ignored delivery; the +mutation path keeps failing closed — the same rule, each boundary in its own role.