Skip to content

Commit d7fa730

Browse files
Sync Boatstack from Intelligence Flow Labs @ 550a1c87fa41
1 parent 58a0d80 commit d7fa730

10 files changed

Lines changed: 174 additions & 28 deletions

File tree

CONTRIBUTING.md

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

33
# Contributing
44

5-
Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/60f76062f72185efddad1c22f6c1fc088aff0005/labs/12-product-engineering-loop).
5+
Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/550a1c87fa41edfc233c546105d5e4ee921290e7/labs/12-product-engineering-loop).
66

77
The Boatstack repository receives product/runtime changes through a generated pull request. Review the PR's `UPSTREAM.json`, tests, adapter diff, and context-size change; do not hand-edit generated output on `main`. `.github/workflows` is the exception: it is Boatstack's executable control plane, excluded from scheduled projection and changed only through a separate manually reviewed Boatstack PR.
88

UPSTREAM.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"canonical_context": {
3-
"characters": 78586,
4-
"estimated_tokens": 19647,
3+
"characters": 80016,
4+
"estimated_tokens": 20004,
55
"estimator": "ceil(total characters / 4); compactness signal, not provider billing",
66
"files": [
77
"product-engineering-loop/references/workflow.md",
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "2e8bb6baa538f3ea22f5d31d03511eac4ffea83842733ec2f2de731f66baae76",
15+
"CONTRIBUTING.md": "119288a73b70fd9ebfcdd0e15d1089e501cbd292a7349a8a1f8c7bb24642f20f",
1616
"README.md": "6b7402c5cef5b3b9b739281d3d4d576cdc995796ff127fc6aefb97c5743e0bac",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -44,10 +44,11 @@
4444
"boatstack/cmd/boatstack-helper/main_test.go": "ff73003b6a5157202fa09ddf1129fb13c3d79702b2e05a8721ce5a11bf5ab779",
4545
"boatstack/command.go": "4726ac515dedab4947be7eb48f88c6cb8b53d674124504b69f03e6396b080ee8",
4646
"boatstack/command_test.go": "9f707abba3640add81c3e97ba7e72fedbf98f3394b1c060a9ca4b4a28e919968",
47+
"boatstack/compiled_artifact_resolution_test.go": "0748d67643263e698211eb04d46464e1dd3db15d94537f5fd5092b5aa689745b",
4748
"boatstack/config_documentation_test.go": "0632366edc5e88145bb080083ea03c6515da07b0162ce404d63e51bb5bc0774e",
4849
"boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c",
4950
"boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0",
50-
"boatstack/delivery.go": "4b4e870335b4b45c1fbfdc5a2daeb65b14c41671bc489f7e75189b9fc2ae6cb9",
51+
"boatstack/delivery.go": "3d1580512c1922ae4ce349a790acfb3356e3c10105af7871457e37fffda39416",
5152
"boatstack/delivery_boundary_conformance_test.go": "800cd722d8d2a696a0529e8343d3523e453bb052f0917c8a2cad2990296ac1b3",
5253
"boatstack/delivery_reactivation_test.go": "573a2dba0034bc4290478414e3bdd8670b06a326128eb0295d77e748ecc8689e",
5354
"boatstack/delivery_test.go": "45c48ff7581c911bcaf821c3e4241d4ae2a9bb4aa682485cc58b6ad8fe1c85bf",
@@ -83,7 +84,7 @@
8384
"boatstack/plan_validation_test.go": "6cbde4ac719baef6b73aa569515d6a9daadcbf14b33f76fa78159826954e20fa",
8485
"boatstack/planning.go": "ef4507a9fecc900f0691372c50883f328c9232c3dfd6986fbde26fb7ef436ae3",
8586
"boatstack/planning_test.go": "c105a9c78c342be06614bf54d0bc1b661b0f7af64d63b79e43bd1fcc2769edd5",
86-
"boatstack/pr.go": "981a59288a0a90534f51a2378656b78bfdc4899810bce5fdeefaa6cde63eeffe",
87+
"boatstack/pr.go": "eae93f7a6e423f67336545d37181ec2ea350991a2d75985f1cf2cf4b7bc8382d",
8788
"boatstack/pr_test.go": "7f82954d94c1ceae848a581dda25e58af92251d78a5a94ed2d672bedf5a0349e",
8889
"boatstack/provenance.go": "d44dcd5421306269326f1202ba1d52df8c252490550270ef9d022e8ec2b65210",
8990
"boatstack/provision.go": "4882d49681f99b11ba9d182ca13772131b7f9a11a6c2b560800654ca14f5111e",
@@ -97,7 +98,7 @@
9798
"boatstack/reexec_windows.go": "f5335c8c28cb4e89048b058b1c4d12f78644f99acb4f6167ff60e622dfb9e742",
9899
"boatstack/references/artifacts.md": "5fa888ac519085d65cee1d04df5902761651bcf2d7af81711fa0f8ecd1fc0f59",
99100
"boatstack/references/config-schema.md": "0170b90f1d0a592f58e255ffeff642fa037676042443f74a0f1b6e39be5dbbb8",
100-
"boatstack/references/failure-moves.md": "35ac99fdf19eac823313b684b4a8a92990fb42392dc1a94447621d538c637fc7",
101+
"boatstack/references/failure-moves.md": "b65ef72035afa6ad0dce589a0b38f84bc40cde3864c9ecf973f08fc687f001c3",
101102
"boatstack/references/host-hook-contracts.md": "d68ae1556e7b1e29e9ac7cb4db767809d510aabf0be52e60e44665ea7abb980e",
102103
"boatstack/references/irreversible-operation-boundary.md": "e0076f0fea3bf729b2e9bdf353eaeaaf7cdafabfaf26b8d9b27287e5414c2441",
103104
"boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae",
@@ -135,10 +136,10 @@
135136
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
136137
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
137138
"docs/configuration.md": "df054f49d532c8b1b7d94184810d1b3b5bf18cdc30eb985b4b6d0639162e341a",
138-
"docs/evidence-engineered-coding.md": "8da580e5b910db5255944ba15aa23bea9356b9545e52430962abc265810f871c",
139+
"docs/evidence-engineered-coding.md": "2a6cee818ce3c1eaebca53384237e164aaea0da82d003749565bc022243f06f2",
139140
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
140141
"docs/getting-started.md": "1dd4f4e2e636cc5adfc2f79939629701e171087c3d5e558cf919548b9224adfd",
141-
"docs/public-claims.json": "8fd1c004d69856c2426263f96ce5c9c25cda37ac3d43f8f39bffec0f0913908c",
142+
"docs/public-claims.json": "2f221186fbb1c6694ac11a247d589e69e3193663b6e0cb98887ab9cfd5dd7093",
142143
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
143144
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
144145
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
@@ -152,7 +153,7 @@
152153
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
153154
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
154155
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
155-
"labs/diagram-json/plan.lock.json": "617523321b82841a59652e680bd163416507c84ddb727fc226ff5727175cef16",
156+
"labs/diagram-json/plan.lock.json": "77d311f5fc945b1e24de638d25f64cf1a5167109f76229207b0a29b46a57a849",
156157
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
157158
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
158159
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -234,13 +235,14 @@
234235
"release-notes/2026-07-24-repair-state-recovery.md": "daaa12deb51a5f647178d6164ea5b4bcd29bf5482b77d90002429f69e0da5dd0",
235236
"release-notes/2026-07-24-transactional-mutation-boundary.md": "38819a4811edbc99a9d8a77983aedbd0589bdbbf849da4991d3e21a1b319a65c",
236237
"release-notes/2026-07-25-boatstack-banner.md": "28e83f294de606211cfdc91b2586aa834e004dee76d5c4bee08859986ae86b5b",
238+
"release-notes/2026-07-25-evidence-path-resolution.md": "b32cb8a6e69f397f751c3a7fb62be254a7407a28bed25ae9108d6d773c863d11",
237239
"release-notes/2026-07-25-published-slice-correction-routing.md": "129cdd62c80c8b93060726027d68ba3abdb0bca1a1ce9e64d6053271af3fd082",
238240
"release-notes/2026-07-25-root-cause-operation.md": "5bf1f082e9123c5a7bcc8bc01b12e97b24b5ae15958577b4ff2a358994fca891"
239241
},
240242
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
241243
"schema_version": 1,
242244
"source": {
243-
"commit": "60f76062f72185efddad1c22f6c1fc088aff0005",
245+
"commit": "550a1c87fa41edfc233c546105d5e4ee921290e7",
244246
"path": "labs/12-product-engineering-loop",
245247
"repository": "operatorstack/intelligence-flow"
246248
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package boatstack
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
// Regression for the evidence-path split-brain: activate-plan writes the evidence
11+
// ledger under compiled/, and pr-context resolves it through the shared dual-layout
12+
// rule (feature-root canonical, compiled/ fallback). But the delivery-gate recorder
13+
// used to hand-join the feature-root path with no fallback, so once a real project
14+
// kept its ledger only at compiled/evidence.md the recorder could not find it and
15+
// the gate failed with "delivery gate requires current evidence". These tests pin
16+
// the recorder to the same resolver every other layer uses.
17+
18+
// TestFeatureEvidencePathResolvesBothLayouts locks the shared resolver's ordering:
19+
// feature-root (legacy canonical) first, compiled/ as the current-layout fallback,
20+
// and the canonical path returned even when neither exists so the caller reports a
21+
// clear error location.
22+
func TestFeatureEvidencePathResolvesBothLayouts(t *testing.T) {
23+
dir := t.TempDir()
24+
root := filepath.Join(dir, "evidence.md")
25+
compiled := filepath.Join(dir, "compiled", "evidence.md")
26+
27+
// With neither present the resolver returns its last candidate (the compiled
28+
// copy) so a missing-evidence error names the canonical write location.
29+
if got := featureEvidencePath(dir); got != compiled {
30+
t.Fatalf("with neither present, want the compiled error path %q, got %q", compiled, got)
31+
}
32+
33+
if err := os.MkdirAll(filepath.Dir(compiled), 0o755); err != nil {
34+
t.Fatal(err)
35+
}
36+
if err := os.WriteFile(compiled, []byte("x"), 0o644); err != nil {
37+
t.Fatal(err)
38+
}
39+
if got := featureEvidencePath(dir); got != compiled {
40+
t.Fatalf("with only compiled present, want %q, got %q", compiled, got)
41+
}
42+
43+
if err := os.WriteFile(root, []byte("x"), 0o644); err != nil {
44+
t.Fatal(err)
45+
}
46+
if got := featureEvidencePath(dir); got != root {
47+
t.Fatalf("with both present, legacy root must win to match pr-context, got %q", got)
48+
}
49+
}
50+
51+
// TestRecordGateResolvesCompiledEvidenceLedger is the proof of fix: a delivery whose
52+
// ledger lives ONLY at compiled/evidence.md (the taxweave state) must gate cleanly
53+
// with no explicit --evidence. This fails on the pre-fix recorder, which resolved
54+
// only the feature root.
55+
func TestRecordGateResolvesCompiledEvidenceLedger(t *testing.T) {
56+
repo, feature := activateTwoSliceDelivery(t)
57+
dir := filepath.Join(repo, ".product-loop", "features", feature)
58+
59+
// Move the edited gate ledger to the compiled layout and drop the feature-root
60+
// copy, so only compiled/evidence.md carries the gate outcomes.
61+
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"
62+
if err := os.WriteFile(filepath.Join(dir, "compiled", "evidence.md"), []byte(ledger), 0o644); err != nil {
63+
t.Fatal(err)
64+
}
65+
if err := os.Remove(filepath.Join(dir, "evidence.md")); err != nil {
66+
t.Fatal(err)
67+
}
68+
runGit(t, repo, "add", "-A")
69+
runGit(t, repo, "commit", "-m", "keep the evidence ledger only in the compiled layout")
70+
71+
receipt, err := RecordDeliveryGate(DeliveryGateOptions{Repo: repo, Feature: feature, SliceID: "phase-one", Gate: "test", Status: "PASS"})
72+
if err != nil {
73+
t.Fatalf("recorder could not resolve compiled-only evidence ledger: %v", err)
74+
}
75+
if !strings.HasSuffix(filepath.ToSlash(receipt.EvidencePath), "compiled/evidence.md") {
76+
t.Fatalf("recorder bound the wrong evidence path: %q", receipt.EvidencePath)
77+
}
78+
}
79+
80+
// TestRecordGateStillResolvesFeatureRootEvidence guards the legacy layout: a ledger
81+
// at the feature root (no compiled copy) must still gate, so the added fallback is
82+
// strictly additive.
83+
func TestRecordGateStillResolvesFeatureRootEvidence(t *testing.T) {
84+
repo, feature := activateTwoSliceDelivery(t)
85+
dir := filepath.Join(repo, ".product-loop", "features", feature)
86+
87+
// activateTwoSliceDelivery already writes the ledger at the feature root; remove
88+
// the compiled copy so only the legacy location remains.
89+
if err := os.Remove(filepath.Join(dir, "compiled", "evidence.md")); err != nil {
90+
t.Fatal(err)
91+
}
92+
runGit(t, repo, "add", "-A")
93+
runGit(t, repo, "commit", "-m", "keep the evidence ledger only at the feature root")
94+
95+
receipt, err := RecordDeliveryGate(DeliveryGateOptions{Repo: repo, Feature: feature, SliceID: "phase-one", Gate: "test", Status: "PASS"})
96+
if err != nil {
97+
t.Fatalf("recorder could not resolve feature-root evidence ledger: %v", err)
98+
}
99+
if strings.Contains(filepath.ToSlash(receipt.EvidencePath), "compiled/") {
100+
t.Fatalf("recorder ignored the legacy feature-root ledger: %q", receipt.EvidencePath)
101+
}
102+
}
103+
104+
// TestRecorderAndPRContextResolveSameEvidence is the anti-drift invariant: the
105+
// recorder's default evidence resolution and pr-context both route through
106+
// featureEvidencePath, so for any given feature they can never resolve different
107+
// evidence files. This is the structural guarantee the layers cannot re-diverge —
108+
// the compiled-artifact analogue of the published-slice addressability invariant.
109+
func TestRecorderAndPRContextResolveSameEvidence(t *testing.T) {
110+
dir := t.TempDir()
111+
compiled := filepath.Join(dir, "compiled", "evidence.md")
112+
if err := os.MkdirAll(filepath.Dir(compiled), 0o755); err != nil {
113+
t.Fatal(err)
114+
}
115+
if err := os.WriteFile(compiled, []byte("x"), 0o644); err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
// The recorder's default (empty --evidence) and pr-context's managedPRSources
120+
// both call featureEvidencePath(dir); assert that single resolver returns the
121+
// file that actually exists rather than a hand-joined feature-root guess.
122+
if got := featureEvidencePath(dir); got != compiled {
123+
t.Fatalf("shared resolver must return the existing ledger both consumers read, got %q", got)
124+
}
125+
}

boatstack/delivery.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ func RecordDeliveryGate(options DeliveryGateOptions) (DeliveryGateReceipt, error
10011001
}
10021002
evidencePath := strings.TrimSpace(options.EvidencePath)
10031003
if evidencePath == "" {
1004-
evidencePath = filepath.Join(repo, ".product-loop", "features", options.Feature, "evidence.md")
1004+
evidencePath = featureEvidencePath(filepath.Join(repo, ".product-loop", "features", options.Feature))
10051005
} else if !filepath.IsAbs(evidencePath) {
10061006
evidencePath = filepath.Join(repo, evidencePath)
10071007
}

boatstack/pr.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,17 @@ func featureArtifactPath(directory string, candidates ...string) string {
410410
return last
411411
}
412412

413+
// featureEvidencePath resolves a feature's evidence ledger through the one shared
414+
// dual-layout rule: the feature-root copy (canonical for legacy features) first,
415+
// the compiled/ copy as the current-layout fallback. The delivery-gate recorder
416+
// and pr-context MUST both call this so they can never resolve different evidence
417+
// files for the same feature — the compiled-artifact analogue of the per-slice
418+
// addressability resolver that closed the published-slice split-brain. Hand-joining
419+
// a single fixed path (as the recorder once did) is exactly the drift this prevents.
420+
func featureEvidencePath(featureDir string) string {
421+
return featureArtifactPath(featureDir, "evidence.md", filepath.Join("compiled", "evidence.md"))
422+
}
423+
413424
func managedPRSources(repo, feature string) ([]PRSource, map[string]string, error) {
414425
directory := filepath.Join(repo, ".product-loop", "features", feature)
415426
planPath := filepath.Join(directory, "plan.md")
@@ -441,7 +452,7 @@ func managedPRSources(repo, feature string) ([]PRSource, map[string]string, erro
441452
}); err != nil {
442453
return nil, nil, fmt.Errorf("managed PR requires a current build lock: %w", err)
443454
}
444-
evidencePath := featureArtifactPath(directory, "evidence.md", filepath.Join("compiled", "evidence.md"))
455+
evidencePath := featureEvidencePath(directory)
445456
if err := checkNonEmptyFile(evidencePath, "feature evidence"); err != nil {
446457
return nil, nil, err
447458
}

0 commit comments

Comments
 (0)