From d7fa73047a45071cd65f11feb6803ba2ace16f26 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Sat, 25 Jul 2026 09:17:48 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 550a1c87fa41 --- CONTRIBUTING.md | 2 +- UPSTREAM.json | 22 +-- .../compiled_artifact_resolution_test.go | 125 ++++++++++++++++++ boatstack/delivery.go | 2 +- boatstack/pr.go | 13 +- boatstack/references/failure-moves.md | 1 + docs/evidence-engineered-coding.md | 4 +- docs/public-claims.json | 24 ++-- labs/diagram-json/plan.lock.json | 2 +- .../2026-07-25-evidence-path-resolution.md | 7 + 10 files changed, 174 insertions(+), 28 deletions(-) create mode 100644 boatstack/compiled_artifact_resolution_test.go create mode 100644 release-notes/2026-07-25-evidence-path-resolution.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d1b440..ed1880f 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/60f76062f72185efddad1c22f6c1fc088aff0005/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/550a1c87fa41edfc233c546105d5e4ee921290e7/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 dd826c4..95ff417 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -1,7 +1,7 @@ { "canonical_context": { - "characters": 78586, - "estimated_tokens": 19647, + "characters": 80016, + "estimated_tokens": 20004, "estimator": "ceil(total characters / 4); compactness signal, not provider billing", "files": [ "product-engineering-loop/references/workflow.md", @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "2e8bb6baa538f3ea22f5d31d03511eac4ffea83842733ec2f2de731f66baae76", + "CONTRIBUTING.md": "119288a73b70fd9ebfcdd0e15d1089e501cbd292a7349a8a1f8c7bb24642f20f", "README.md": "6b7402c5cef5b3b9b739281d3d4d576cdc995796ff127fc6aefb97c5743e0bac", "assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63", "assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5", @@ -44,10 +44,11 @@ "boatstack/cmd/boatstack-helper/main_test.go": "ff73003b6a5157202fa09ddf1129fb13c3d79702b2e05a8721ce5a11bf5ab779", "boatstack/command.go": "4726ac515dedab4947be7eb48f88c6cb8b53d674124504b69f03e6396b080ee8", "boatstack/command_test.go": "9f707abba3640add81c3e97ba7e72fedbf98f3394b1c060a9ca4b4a28e919968", + "boatstack/compiled_artifact_resolution_test.go": "0748d67643263e698211eb04d46464e1dd3db15d94537f5fd5092b5aa689745b", "boatstack/config_documentation_test.go": "0632366edc5e88145bb080083ea03c6515da07b0162ce404d63e51bb5bc0774e", "boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c", "boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0", - "boatstack/delivery.go": "4b4e870335b4b45c1fbfdc5a2daeb65b14c41671bc489f7e75189b9fc2ae6cb9", + "boatstack/delivery.go": "3d1580512c1922ae4ce349a790acfb3356e3c10105af7871457e37fffda39416", "boatstack/delivery_boundary_conformance_test.go": "800cd722d8d2a696a0529e8343d3523e453bb052f0917c8a2cad2990296ac1b3", "boatstack/delivery_reactivation_test.go": "573a2dba0034bc4290478414e3bdd8670b06a326128eb0295d77e748ecc8689e", "boatstack/delivery_test.go": "45c48ff7581c911bcaf821c3e4241d4ae2a9bb4aa682485cc58b6ad8fe1c85bf", @@ -83,7 +84,7 @@ "boatstack/plan_validation_test.go": "6cbde4ac719baef6b73aa569515d6a9daadcbf14b33f76fa78159826954e20fa", "boatstack/planning.go": "ef4507a9fecc900f0691372c50883f328c9232c3dfd6986fbde26fb7ef436ae3", "boatstack/planning_test.go": "c105a9c78c342be06614bf54d0bc1b661b0f7af64d63b79e43bd1fcc2769edd5", - "boatstack/pr.go": "981a59288a0a90534f51a2378656b78bfdc4899810bce5fdeefaa6cde63eeffe", + "boatstack/pr.go": "eae93f7a6e423f67336545d37181ec2ea350991a2d75985f1cf2cf4b7bc8382d", "boatstack/pr_test.go": "7f82954d94c1ceae848a581dda25e58af92251d78a5a94ed2d672bedf5a0349e", "boatstack/provenance.go": "d44dcd5421306269326f1202ba1d52df8c252490550270ef9d022e8ec2b65210", "boatstack/provision.go": "4882d49681f99b11ba9d182ca13772131b7f9a11a6c2b560800654ca14f5111e", @@ -97,7 +98,7 @@ "boatstack/reexec_windows.go": "f5335c8c28cb4e89048b058b1c4d12f78644f99acb4f6167ff60e622dfb9e742", "boatstack/references/artifacts.md": "5fa888ac519085d65cee1d04df5902761651bcf2d7af81711fa0f8ecd1fc0f59", "boatstack/references/config-schema.md": "0170b90f1d0a592f58e255ffeff642fa037676042443f74a0f1b6e39be5dbbb8", - "boatstack/references/failure-moves.md": "35ac99fdf19eac823313b684b4a8a92990fb42392dc1a94447621d538c637fc7", + "boatstack/references/failure-moves.md": "b65ef72035afa6ad0dce589a0b38f84bc40cde3864c9ecf973f08fc687f001c3", "boatstack/references/host-hook-contracts.md": "d68ae1556e7b1e29e9ac7cb4db767809d510aabf0be52e60e44665ea7abb980e", "boatstack/references/irreversible-operation-boundary.md": "e0076f0fea3bf729b2e9bdf353eaeaaf7cdafabfaf26b8d9b27287e5414c2441", "boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae", @@ -135,10 +136,10 @@ "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", "docs/configuration.md": "df054f49d532c8b1b7d94184810d1b3b5bf18cdc30eb985b4b6d0639162e341a", - "docs/evidence-engineered-coding.md": "8da580e5b910db5255944ba15aa23bea9356b9545e52430962abc265810f871c", + "docs/evidence-engineered-coding.md": "2a6cee818ce3c1eaebca53384237e164aaea0da82d003749565bc022243f06f2", "docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3", "docs/getting-started.md": "1dd4f4e2e636cc5adfc2f79939629701e171087c3d5e558cf919548b9224adfd", - "docs/public-claims.json": "8fd1c004d69856c2426263f96ce5c9c25cda37ac3d43f8f39bffec0f0913908c", + "docs/public-claims.json": "2f221186fbb1c6694ac11a247d589e69e3193663b6e0cb98887ab9cfd5dd7093", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -152,7 +153,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": "617523321b82841a59652e680bd163416507c84ddb727fc226ff5727175cef16", + "labs/diagram-json/plan.lock.json": "77d311f5fc945b1e24de638d25f64cf1a5167109f76229207b0a29b46a57a849", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -234,13 +235,14 @@ "release-notes/2026-07-24-repair-state-recovery.md": "daaa12deb51a5f647178d6164ea5b4bcd29bf5482b77d90002429f69e0da5dd0", "release-notes/2026-07-24-transactional-mutation-boundary.md": "38819a4811edbc99a9d8a77983aedbd0589bdbbf849da4991d3e21a1b319a65c", "release-notes/2026-07-25-boatstack-banner.md": "28e83f294de606211cfdc91b2586aa834e004dee76d5c4bee08859986ae86b5b", + "release-notes/2026-07-25-evidence-path-resolution.md": "b32cb8a6e69f397f751c3a7fb62be254a7407a28bed25ae9108d6d773c863d11", "release-notes/2026-07-25-published-slice-correction-routing.md": "129cdd62c80c8b93060726027d68ba3abdb0bca1a1ce9e64d6053271af3fd082", "release-notes/2026-07-25-root-cause-operation.md": "5bf1f082e9123c5a7bcc8bc01b12e97b24b5ae15958577b4ff2a358994fca891" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "60f76062f72185efddad1c22f6c1fc088aff0005", + "commit": "550a1c87fa41edfc233c546105d5e4ee921290e7", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/compiled_artifact_resolution_test.go b/boatstack/compiled_artifact_resolution_test.go new file mode 100644 index 0000000..6012f00 --- /dev/null +++ b/boatstack/compiled_artifact_resolution_test.go @@ -0,0 +1,125 @@ +package boatstack + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// Regression for the evidence-path split-brain: activate-plan writes the evidence +// ledger under compiled/, and pr-context resolves it through the shared dual-layout +// rule (feature-root canonical, compiled/ fallback). But the delivery-gate recorder +// used to hand-join the feature-root path with no fallback, so once a real project +// kept its ledger only at compiled/evidence.md the recorder could not find it and +// the gate failed with "delivery gate requires current evidence". These tests pin +// the recorder to the same resolver every other layer uses. + +// TestFeatureEvidencePathResolvesBothLayouts locks the shared resolver's ordering: +// feature-root (legacy canonical) first, compiled/ as the current-layout fallback, +// and the canonical path returned even when neither exists so the caller reports a +// clear error location. +func TestFeatureEvidencePathResolvesBothLayouts(t *testing.T) { + dir := t.TempDir() + root := filepath.Join(dir, "evidence.md") + compiled := filepath.Join(dir, "compiled", "evidence.md") + + // With neither present the resolver returns its last candidate (the compiled + // copy) so a missing-evidence error names the canonical write location. + if got := featureEvidencePath(dir); got != compiled { + t.Fatalf("with neither present, want the compiled error path %q, got %q", compiled, got) + } + + if err := os.MkdirAll(filepath.Dir(compiled), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(compiled, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + if got := featureEvidencePath(dir); got != compiled { + t.Fatalf("with only compiled present, want %q, got %q", compiled, got) + } + + if err := os.WriteFile(root, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + if got := featureEvidencePath(dir); got != root { + t.Fatalf("with both present, legacy root must win to match pr-context, got %q", got) + } +} + +// TestRecordGateResolvesCompiledEvidenceLedger is the proof of fix: a delivery whose +// ledger lives ONLY at compiled/evidence.md (the taxweave state) must gate cleanly +// with no explicit --evidence. This fails on the pre-fix recorder, which resolved +// only the feature root. +func TestRecordGateResolvesCompiledEvidenceLedger(t *testing.T) { + repo, feature := activateTwoSliceDelivery(t) + dir := filepath.Join(repo, ".product-loop", "features", feature) + + // Move the edited gate ledger to the compiled layout and drop the feature-root + // copy, so only compiled/evidence.md carries the gate outcomes. + ledger := "# Evidence ledger\n\n- Test gate (phase-one): `PASS`\n- Review gate (phase-one): `PASS`\n- Test gate (phase-two): `BLOCKED`\n- Review gate (phase-two): `BLOCKED`\n" + if err := os.WriteFile(filepath.Join(dir, "compiled", "evidence.md"), []byte(ledger), 0o644); err != nil { + t.Fatal(err) + } + if err := os.Remove(filepath.Join(dir, "evidence.md")); err != nil { + t.Fatal(err) + } + runGit(t, repo, "add", "-A") + runGit(t, repo, "commit", "-m", "keep the evidence ledger only in the compiled layout") + + receipt, err := RecordDeliveryGate(DeliveryGateOptions{Repo: repo, Feature: feature, SliceID: "phase-one", Gate: "test", Status: "PASS"}) + if err != nil { + t.Fatalf("recorder could not resolve compiled-only evidence ledger: %v", err) + } + if !strings.HasSuffix(filepath.ToSlash(receipt.EvidencePath), "compiled/evidence.md") { + t.Fatalf("recorder bound the wrong evidence path: %q", receipt.EvidencePath) + } +} + +// TestRecordGateStillResolvesFeatureRootEvidence guards the legacy layout: a ledger +// at the feature root (no compiled copy) must still gate, so the added fallback is +// strictly additive. +func TestRecordGateStillResolvesFeatureRootEvidence(t *testing.T) { + repo, feature := activateTwoSliceDelivery(t) + dir := filepath.Join(repo, ".product-loop", "features", feature) + + // activateTwoSliceDelivery already writes the ledger at the feature root; remove + // the compiled copy so only the legacy location remains. + if err := os.Remove(filepath.Join(dir, "compiled", "evidence.md")); err != nil { + t.Fatal(err) + } + runGit(t, repo, "add", "-A") + runGit(t, repo, "commit", "-m", "keep the evidence ledger only at the feature root") + + receipt, err := RecordDeliveryGate(DeliveryGateOptions{Repo: repo, Feature: feature, SliceID: "phase-one", Gate: "test", Status: "PASS"}) + if err != nil { + t.Fatalf("recorder could not resolve feature-root evidence ledger: %v", err) + } + if strings.Contains(filepath.ToSlash(receipt.EvidencePath), "compiled/") { + t.Fatalf("recorder ignored the legacy feature-root ledger: %q", receipt.EvidencePath) + } +} + +// TestRecorderAndPRContextResolveSameEvidence is the anti-drift invariant: the +// recorder's default evidence resolution and pr-context both route through +// featureEvidencePath, so for any given feature they can never resolve different +// evidence files. This is the structural guarantee the layers cannot re-diverge — +// the compiled-artifact analogue of the published-slice addressability invariant. +func TestRecorderAndPRContextResolveSameEvidence(t *testing.T) { + dir := t.TempDir() + compiled := filepath.Join(dir, "compiled", "evidence.md") + if err := os.MkdirAll(filepath.Dir(compiled), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(compiled, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + // The recorder's default (empty --evidence) and pr-context's managedPRSources + // both call featureEvidencePath(dir); assert that single resolver returns the + // file that actually exists rather than a hand-joined feature-root guess. + if got := featureEvidencePath(dir); got != compiled { + t.Fatalf("shared resolver must return the existing ledger both consumers read, got %q", got) + } +} diff --git a/boatstack/delivery.go b/boatstack/delivery.go index 44aaad5..37b481b 100644 --- a/boatstack/delivery.go +++ b/boatstack/delivery.go @@ -1001,7 +1001,7 @@ func RecordDeliveryGate(options DeliveryGateOptions) (DeliveryGateReceipt, error } evidencePath := strings.TrimSpace(options.EvidencePath) if evidencePath == "" { - evidencePath = filepath.Join(repo, ".product-loop", "features", options.Feature, "evidence.md") + evidencePath = featureEvidencePath(filepath.Join(repo, ".product-loop", "features", options.Feature)) } else if !filepath.IsAbs(evidencePath) { evidencePath = filepath.Join(repo, evidencePath) } diff --git a/boatstack/pr.go b/boatstack/pr.go index 3a38c8f..f40ff00 100644 --- a/boatstack/pr.go +++ b/boatstack/pr.go @@ -410,6 +410,17 @@ func featureArtifactPath(directory string, candidates ...string) string { return last } +// featureEvidencePath resolves a feature's evidence ledger through the one shared +// dual-layout rule: the feature-root copy (canonical for legacy features) first, +// the compiled/ copy as the current-layout fallback. The delivery-gate recorder +// and pr-context MUST both call this so they can never resolve different evidence +// files for the same feature — the compiled-artifact analogue of the per-slice +// addressability resolver that closed the published-slice split-brain. Hand-joining +// a single fixed path (as the recorder once did) is exactly the drift this prevents. +func featureEvidencePath(featureDir string) string { + return featureArtifactPath(featureDir, "evidence.md", filepath.Join("compiled", "evidence.md")) +} + func managedPRSources(repo, feature string) ([]PRSource, map[string]string, error) { directory := filepath.Join(repo, ".product-loop", "features", feature) planPath := filepath.Join(directory, "plan.md") @@ -441,7 +452,7 @@ func managedPRSources(repo, feature string) ([]PRSource, map[string]string, erro }); err != nil { return nil, nil, fmt.Errorf("managed PR requires a current build lock: %w", err) } - evidencePath := featureArtifactPath(directory, "evidence.md", filepath.Join("compiled", "evidence.md")) + evidencePath := featureEvidencePath(directory) if err := checkNonEmptyFile(evidencePath, "feature evidence"); err != nil { return nil, nil, err } diff --git a/boatstack/references/failure-moves.md b/boatstack/references/failure-moves.md index 3a6eb53..b1d2fe3 100644 --- a/boatstack/references/failure-moves.md +++ b/boatstack/references/failure-moves.md @@ -31,6 +31,7 @@ The `root-cause` operation operationalizes this taxonomy for a single bug: it cl | Non-transactional multi-file promote | A managed artifact spans files that must land together (e.g. the compiled `tasks.json`, `test-matrix.json`, `evidence.md`, and the `plan.lock.json` that binds them), but independent non-atomic writes can leave a partial set on a crash or a failed post-write check | Promote the whole set through the transactional mutation boundary as one mutation: base-hash preconditions, supervisor-authority binding, atomic all-or-nothing write, post-write verification with automatic rollback, and a reversible receipt whose inverse bytes make the boundary closed under inversion — `undo` re-applies the inverse as a mutation (with redo as undo-of-the-undo), and a domain guard refuses reversal once a delivery gate would be stranded | Patching consistency after the fact with hash guards instead of making the promote atomic, persisting a rejected identity so a corrected retry deadlocks, or undoing an activation that strands live delivery state | | Provenance-blind runtime install | A write path stamps an artifact's declared identity (version/commit) from one origin — the running process's compile-time globals — while binding its integrity proof (checksum) to a different origin — the passed bytes; every checksum gate passes because the lock is internally consistent, but the binary self-reports a third value and the version gate fail-closes (clone-wide when the runtime is shared). Symptom: `update -binary ` run by an older helper writes newer bytes into the older version's slot, then every worktree's guard denies at once | Derive the installed artifact's identity from the artifact itself (execute its `version` self-report) and enforce it at the *write* boundary: refuse to install a `-binary` whose self-report disagrees with the running process, and re-exec a cross-version candidate so it installs itself — running becomes installed, so its embedded bundle, constants, slot path, and receipts are all authoritative by construction. Re-hash the just-written slot against its manifest and roll back on mismatch | Executing an untrusted candidate (bounded, operator-invoked only), or converting a recoverable slot mismatch into a hard clone-wide refuse that blocks legitimate upgrades; a per-read self-report exec would tax every guard event, so identity is enforced where it is written, not on the hydration hot path | | Progress-erasing plan re-lock | Delivery state is keyed to the plan lock hash, so any re-activation that changes the lock (an amended tail, a reordered task, even a whitespace edit) re-derives the slice list — and the re-derivation reset the active pointer to `0`, dropping every already-published slice back to `BUILD`. The guard for this reset only covered the fully-published case, so a *partially* delivered feature (slice one merged, slice two mid-build) silently lost the record of what shipped. Downstream this deadlocks: `undo` refuses to reverse the reset because a live delivery gate would be stranded, and every forward verb re-derives the same wrong state. Symptom: after a benign plan edit the agent loops between `activate-plan` (which re-zeros progress) and `undo`/`run-preflight`/`record-delivery-gate` (all blocked) | Reconcile the amended plan against the existing delivery state instead of resetting it: preserve the published prefix `[0, ActiveIndex)` verbatim (status and PR bookkeeping intact), keep the pointer, and recompute only the recomputable tail (active slice → `BUILD`, rest → `PENDING`), recording the superseded lock. Refuse — before the transactional promote, so nothing half-applies — any amendment that drops, reorders, renames, or changes a *published* slice, or touches a fully-published (immutable) delivery, directing the operator to a corrective child delivery. "Published" is keyed off the pointer and `Status`, never `pr_state`, which real projects leave empty even on merged PRs | Keying delivery identity to the lock hash makes every plan edit a candidate reset, so the reconcile must be the only re-derivation path; treating `pr_state` as the published marker would mis-classify shipped slices as amendable; refusing legitimate tail amendments would push routine edits into unnecessary child deliveries | +| Canonical-artifact-location resolver bypass | A managed artifact has two on-disk layouts (a compiled-trio artifact like `evidence.md` lives under `compiled/` in the current layout, at the feature root in the legacy one). A shared dual-layout resolver exists — but one consumer hand-joins a single fixed path instead of calling it, so it diverges from every layer that does. Symptom: `activate-plan` writes `compiled/evidence.md` and pr-context resolves it, yet `record-delivery-gate` (which hardcoded the feature root with no fallback) fails with "delivery gate requires current evidence" on a project that keeps its ledger only at `compiled/` — the gate demanding the file at a path nothing writes | Route *every* consumer of the artifact through the one dual-layout resolver (`featureEvidencePath` → `featureArtifactPath`, canonical location first, the other layout as fallback), so the recorder and pr-context provably resolve the same file for a given feature. Add an anti-drift test asserting the two agree, and keep the fallback strictly additive so the legacy layout still resolves | Hand-joining an artifact path anywhere a shared resolver exists — the straggler silently diverges the moment the canonical layout moves; equally, "normalizing" every consumer to the newer layout when the writer/legacy contract still prefers the other, which trades a missing-file failure for reading the wrong (stale or template) copy | ## Lessons encoded from the benchmark campaign diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 8c83b67..dd6c282 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -96,7 +96,7 @@ subject to acceptance criteria pass approval is current ``` -That is why context trimming is not automatically an optimization. If removing state increases rework or false acceptance, total cost rises. The canonical runtime references are approximately **19647 estimated tokens**, while host adapters point to one operation at a time. +That is why context trimming is not automatically an optimization. If removing state increases rework or false acceptance, total cost rises. The canonical runtime references are approximately **20004 estimated tokens**, while host adapters point to one operation at a time. ## Control appears at transitions @@ -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 [`60f76062f72185efddad1c22f6c1fc088aff0005`](https://github.com/operatorstack/intelligence-flow/tree/60f76062f72185efddad1c22f6c1fc088aff0005/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 [`550a1c87fa41edfc233c546105d5e4ee921290e7`](https://github.com/operatorstack/intelligence-flow/tree/550a1c87fa41edfc233c546105d5e4ee921290e7/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 6e0d1ee..29eb28d 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "60f76062f72185efddad1c22f6c1fc088aff0005", + "source_commit": "550a1c87fa41edfc233c546105d5e4ee921290e7", "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" }, { "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:60f76062f72185efddad1c22f6c1fc088aff0005" + "last_verified_version": "source:550a1c87fa41edfc233c546105d5e4ee921290e7" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index f398e46..a1c6273 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": "60f76062f72185efddad1c22f6c1fc088aff0005", + "source_commit": "550a1c87fa41edfc233c546105d5e4ee921290e7", "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-25-evidence-path-resolution.md b/release-notes/2026-07-25-evidence-path-resolution.md new file mode 100644 index 0000000..6befabf --- /dev/null +++ b/release-notes/2026-07-25-evidence-path-resolution.md @@ -0,0 +1,7 @@ +### The delivery-gate recorder resolves the evidence ledger through the shared dual-layout rule + +A feature's compiled trio — `tasks.json`, `test-matrix.json`, and `evidence.md` — is written by `activate-plan` under the feature's `compiled/` subdirectory, while older features carry these artifacts at the feature root. To keep the two layouts from diverging, Boatstack resolves such an artifact through one shared rule (`featureArtifactPath`): try each layout in turn and use the one that exists. `pr-context` already resolved the evidence ledger this way. The **delivery-gate recorder** did not: `record-delivery-gate`, invoked with no explicit `--evidence`, hand-joined the feature-root path with no fallback. On a project that keeps its ledger only at `compiled/evidence.md` — the location `activate-plan` writes — the recorder could not find it, and the test and review gates failed with *"delivery gate requires current evidence: … no such file,"* demanding the ledger at a path nothing writes. This is the compiled-artifact analogue of the published-slice split-brain: a shared resolution rule that one straggler consumer bypassed, so it silently diverged from every layer that honored it. + +The recorder now resolves its default evidence path through the same `featureEvidencePath` helper `pr-context` uses — the feature-root copy first (canonical for legacy features), the `compiled/` copy as the current-layout fallback — so the two layers provably resolve the same ledger for a given feature and can never again disagree on where evidence lives. The change is strictly additive: an explicit `--evidence` argument and an absolute path are honored unchanged, a legacy feature-root ledger still resolves exactly as before, and the only new behavior is that a `compiled/`-only ledger is now found instead of reported missing. `tasks.json` resolution is unchanged. + +A regression suite pins the fix: the shared resolver's ordering (root canonical, `compiled/` fallback, and the canonical path returned when neither exists so a missing-evidence error names the right location); a proof-of-fix gate over a `compiled/`-only ledger with no `--evidence`; the preserved legacy feature-root path; and the anti-drift invariant that the recorder and `pr-context` route through the one resolver and cannot re-diverge.