Skip to content

Commit 2e95e53

Browse files
Sync Boatstack from Intelligence Flow Labs @ d604f8325783
1 parent dfee3ac commit 2e95e53

10 files changed

Lines changed: 148 additions & 35 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/90356bed91109de2eb131fc95872bdb74abac6a7/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/d604f832578355bd5bc34eb66e8d54b062645629/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: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "16338a21fc8e849ca3a3a72f36c0163f4136f717b54df3cfc704ed75468b60ca",
15+
"CONTRIBUTING.md": "5060717c0e6f5337197d3c2b1acb6a047264ffab0ed6cf8af411ee498cd57051",
1616
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -38,8 +38,8 @@
3838
"boatstack/attach.go": "6a855440fac9acc63be857efef9d76210a4619774728684832dfbb08caf42a30",
3939
"boatstack/capability.go": "9d9a75086e88bb1d9d4170821436c686002fe3a125e6ee7d23eea5779aac5cfe",
4040
"boatstack/capability_test.go": "e8322903a843970d7f0317d2629466532cb05c3ffcb44b9423adf4219faa8021",
41-
"boatstack/capture.go": "6a279bab615a012de0b2c43aeae9b95d122365068dc54c8923c14c6a4c719449",
42-
"boatstack/capture_test.go": "63fa1177738081f1e862364d7a4257f5e259f8e9c36276ba1775b8085b277105",
41+
"boatstack/capture.go": "0e137d8251a658118bd56812040575d01f02fd2f862c80ef761b253db6b35bbe",
42+
"boatstack/capture_test.go": "31a8afa40a54ae716fea1ea44be3b41705ed171789a181d1ef26432855c05c9e",
4343
"boatstack/changelog.go": "6b06be7cd9738de29ba6e87aa2569f3b027a2e618b04524f5abd7abaa17945bf",
4444
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
4545
"boatstack/cmd/boatstack-helper/coverage_conformance_test.go": "f5a931d2b5cdb0d32af85acbbd499fa0f509ed36d62e772e6b6072e348200aca",
@@ -165,10 +165,10 @@
165165
"boatstack/planning_first_write_conformance_test.go": "873097aa9384b75bf01e74a475f3ec2ac7cca4a28f733e82f2c82959032c6a30",
166166
"boatstack/planning_test.go": "06ec7022222d926040c3ae28b84ab50c3d2f804ae6473e61b303804dd992d884",
167167
"boatstack/post_publish_prescribe_conformance_test.go": "3c20d359ff84648db7dedb227b4d64e6574d9f41d3cdca0adefec1c60bfbf4ae",
168-
"boatstack/pr.go": "0ddd88b364d141a9ccadd4d6b6751c13c56769feba5879bd896be46b0d5af5fd",
168+
"boatstack/pr.go": "511ae0025aae3d99a5fbb942bb078d68a04feb62190387b331d562f934902c41",
169169
"boatstack/pr_phase.go": "59f8cbb75b6b538a5345474acd6a725450979579bf8ecf9591956cbbe1cc4737",
170170
"boatstack/pr_phase_conformance_test.go": "bc9c834e9c4ed43b35d81abafd7b1bf2a264ea2a8c4a4ec9758ee18d1d438968",
171-
"boatstack/pr_test.go": "2e7709e2f163489a29ea3e7eb4bc932cfe6829b30d168f1aea9e942acbc3b4e7",
171+
"boatstack/pr_test.go": "dd223d9ddc50af2f34b20fd30246b9aaef7e4d9fd479bcaa3e87bb4de58bd4b3",
172172
"boatstack/provenance.go": "d44dcd5421306269326f1202ba1d52df8c252490550270ef9d022e8ec2b65210",
173173
"boatstack/provision.go": "eb7333a73331b011adc93f59a2d97415d850c2e588f0e2bbb5116984e2ef927d",
174174
"boatstack/provision_test.go": "214e9edb991a66d5bbb696a7c1b63876d2f799f2cab4e3f40785f4e8f1eac57b",
@@ -231,10 +231,10 @@
231231
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
232232
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
233233
"docs/configuration.md": "caa95f1dd484a71a9051951652d6457294c213999fe00fb764d05e8a509cc786",
234-
"docs/evidence-engineered-coding.md": "59b9d1296da97e2f72960082e951065a433cd4b9c27e507c6be0fe4594eb44be",
234+
"docs/evidence-engineered-coding.md": "bb8bd613b350be866929105ab96a3225f30f8e306b0b650a9b6655fb7c26af07",
235235
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
236236
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
237-
"docs/public-claims.json": "823614ba0cc01218915438d443d187826f80bc4bb4a1bcd925026632d954e6c7",
237+
"docs/public-claims.json": "ef823c6a3d27eee37856712bbb0bb4b9b0d266c97dcd2fd4568aa0c98416041a",
238238
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
239239
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
240240
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
@@ -248,7 +248,7 @@
248248
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
249249
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
250250
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
251-
"labs/diagram-json/plan.lock.json": "5adb15badc39a262755db561b097f7e09668ab9c462c904e55a36b2825a7663b",
251+
"labs/diagram-json/plan.lock.json": "72e44c5239a3ec0bb1f609d25083d152d43b81d8206aeafdef7ce8db04a37512",
252252
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
253253
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
254254
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -389,12 +389,13 @@
389389
"release-notes/2026-07-28-protected-native-auto-merge.md": "67dc76a6e7ce51034a0eadc541ba7a8946cfcabe5433cedc25db55321dfb8b62",
390390
"release-notes/2026-07-28-retro-derive-proposals.md": "c90797d76fb00816340475620b584ad150fa32d7d10bc14997a614529ef9b558",
391391
"release-notes/2026-07-28-retromine-recurrence-detector.md": "27790993a02e73f3a2700dce7340d044aeb0e52f785ded68271ff6673add9e25",
392-
"release-notes/2026-07-29-readiness-and-journey-control.md": "2411db76974990ad0128fcd90e795c4c1c5c70d6d6f15fccd452f7e1a242310b"
392+
"release-notes/2026-07-29-readiness-and-journey-control.md": "2411db76974990ad0128fcd90e795c4c1c5c70d6d6f15fccd452f7e1a242310b",
393+
"release-notes/2026-07-30-visual-evidence-survives-preview-commit.md": "71d19e9fabb40e8cb1e939f26bf288911d227979926f43f337046c6cf6b66551"
393394
},
394395
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
395396
"schema_version": 1,
396397
"source": {
397-
"commit": "90356bed91109de2eb131fc95872bdb74abac6a7",
398+
"commit": "d604f832578355bd5bc34eb66e8d54b062645629",
398399
"path": "labs/12-product-engineering-loop",
399400
"repository": "operatorstack/intelligence-flow"
400401
}

boatstack/capture.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,9 @@ func CaptureEvidence(options CaptureEvidenceOptions) (PRVisualEvidenceManifest,
180180
}
181181

182182
// captureProductDiff reproduces the pr-context product-diff fingerprint so a
183-
// captured manifest is trusted (PASS) by resolvePRVisualEvidence: same head
184-
// commit and same product diff.
183+
// captured manifest is trusted (PASS) by resolvePRVisualEvidence: same product
184+
// diff. The head commit is recorded for provenance only — trust is keyed to
185+
// product identity so committing the reviewed pr.md never stales evidence.
185186
func captureProductDiff(repo, base, feature, head string) (headCommit, diffHash string, err error) {
186187
baseCommit, err := resolveBaseCommit(repo, base)
187188
if err != nil {

boatstack/capture_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,18 @@ func TestCaptureEvidenceProducesManifestTrustedByPRContext(t *testing.T) {
101101
t.Fatalf("capture mutated the product tree: %s", status)
102102
}
103103

104-
// The manifest must be trusted by the same resolver pr-context uses: identical
105-
// head commit and product diff → status is the manifest's PASS, not NOT_VERIFIED.
104+
// The manifest must be trusted by the same resolver pr-context uses: an
105+
// identical product diff → status is the manifest's PASS, not NOT_VERIFIED.
106106
head := runGit(t, repo, "rev-parse", "--abbrev-ref", "HEAD")
107-
headCommit, diffHash, err := captureProductDiff(repo, "main", "reviewer-ready", head)
107+
_, diffHash, err := captureProductDiff(repo, "main", "reviewer-ready", head)
108108
if err != nil {
109109
t.Fatal(err)
110110
}
111111
config, _, err := LoadConfig(filepath.Join(repo, ".product-loop", "project.json"))
112112
if err != nil {
113113
t.Fatal(err)
114114
}
115-
_, status, count, _, _, _, resolved, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", head, headCommit, diffHash)
115+
_, status, count, _, _, _, resolved, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", head, diffHash)
116116
if err != nil {
117117
t.Fatal(err)
118118
}

boatstack/pr.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func planVisualDecision(repo, feature string) (string, string, []PRVisualScenari
109109
return relevance, "managed-plan", scenarios, nil
110110
}
111111

112-
func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, head, headCommit, diffHash string) (string, string, int, string, string, string, *PRVisualEvidenceManifest, error) {
112+
func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, head, diffHash string) (string, string, int, string, string, string, *PRVisualEvidenceManifest, error) {
113113
policy := normalizedPRVisualEvidencePolicy(config.Workflow.PRVisualEvidence)
114114
relevance, source := "unresolved", "agent-proposed"
115115
var scenarios []PRVisualScenario
@@ -131,7 +131,12 @@ func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, h
131131
if loadErr == nil {
132132
manifest = &loaded
133133
relevance, source, scenarios = loaded.Relevance, loaded.RelevanceSource, loaded.Scenarios
134-
if loaded.SourceCommit == headCommit && loaded.ProductDiffSHA256 == diffHash {
134+
// Trust is keyed to product identity only: the preview pr.md is
135+
// excluded from the product diff yet must be committed before
136+
// publication, so a head-commit equality would invalidate every
137+
// PASS manifest on that mandatory commit. SourceCommit stays
138+
// recorded for provenance and the evidence comment.
139+
if loaded.ProductDiffSHA256 == diffHash {
135140
status = loaded.Status
136141
} else {
137142
status = "NOT_VERIFIED"
@@ -653,7 +658,7 @@ func PreparePRContext(options PRContextOptions) (PRContext, error) {
653658
return PRContext{}, err
654659
}
655660
visualPolicy, visualStatus, visualCount, visualFingerprint, visualRelevance, visualSource, visualManifest, err := resolvePRVisualEvidence(
656-
repo, config, mode, options.Feature, head, headCommit, SHA256Bytes(diff),
661+
repo, config, mode, options.Feature, head, SHA256Bytes(diff),
657662
)
658663
if err != nil {
659664
return PRContext{}, err
@@ -1240,11 +1245,18 @@ func PRPreviewTemplate(context PRContext) string {
12401245
"## Rollout and rollback", "", "Describe deployment impact and the smallest safe rollback.", "",
12411246
}
12421247
if context.PRVisualEvidenceStatus != "NOT_APPLICABLE" {
1248+
// The Commit column names the commit the pixels were captured from;
1249+
// evidence stays trusted across preview-only commits, so this can
1250+
// legitimately trail HeadCommit.
1251+
evidenceCommit := context.HeadCommit
1252+
if context.PRVisualEvidence != nil && strings.TrimSpace(context.PRVisualEvidence.SourceCommit) != "" {
1253+
evidenceCommit = context.PRVisualEvidence.SourceCommit
1254+
}
12431255
lines = append(lines,
12441256
"## Visual evidence", "",
12451257
"Screenshots are human-review evidence, not mechanical proof. Public-repository attachments are publicly accessible.", "",
12461258
"| Scenario | Viewport | Commit | Result | Publication |", "|---|---|---|---|---|",
1247-
"| Describe the approved state | viewport | "+context.HeadCommit+" | `"+context.PRVisualEvidenceStatus+"` | Boatstack evidence comment or manual fallback |", "",
1259+
"| Describe the approved state | viewport | "+evidenceCommit+" | `"+context.PRVisualEvidenceStatus+"` | Boatstack evidence comment or manual fallback |", "",
12481260
)
12491261
}
12501262
if context.TotalSlices > 1 {

boatstack/pr_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"strings"
1111
"testing"
12+
"time"
1213
)
1314

1415
func runGit(t *testing.T, repo string, arguments ...string) string {
@@ -648,6 +649,101 @@ func TestRequiredPRVisualEvidenceBlocksPublicationBeforeMutation(t *testing.T) {
648649
}
649650
}
650651

652+
// savePassVisualManifest records PASS evidence for the fixture feature's
653+
// approved scenario, bound to the given source commit and product diff.
654+
func savePassVisualManifest(t *testing.T, repo, feature, sourceCommit, diffHash string) {
655+
t.Helper()
656+
pngPath := filepath.Join(t.TempDir(), "warning.png")
657+
writeTestPNG(t, pngPath)
658+
if _, err := SavePRVisualEvidence(repo, PRVisualEvidenceManifest{
659+
Key: feature, Policy: "suggest", Relevance: "relevant", RelevanceSource: "managed-plan",
660+
Status: "PASS", SourceCommit: sourceCommit, ProductDiffSHA256: diffHash,
661+
Scenarios: []PRVisualScenario{{ID: "warning", Entry: "/onboarding", State: "picker open", Viewport: "1440x900", Expected: []string{"warning visible"}}},
662+
Items: []PRVisualEvidenceItem{{
663+
ScenarioID: "warning", Path: pngPath, Viewport: "1440x900",
664+
CapturedAt: time.Now().UTC().Truncate(time.Second).Format(time.RFC3339),
665+
Status: "captured", PrivacyStatus: "human-reviewed",
666+
}},
667+
Publication: PRVisualPublication{State: "pending"},
668+
}); err != nil {
669+
t.Fatal(err)
670+
}
671+
}
672+
673+
func TestCommittingPreviewNeverInvalidatesPassVisualEvidence(t *testing.T) {
674+
repo := prTestRepoConfigured(t, func(config *ProjectConfig) {
675+
config.Workflow.PRVisualEvidence = "suggest"
676+
})
677+
activateManagedFeature(t, repo, "reviewer-ready")
678+
captureCommit := runGit(t, repo, "rev-parse", "HEAD")
679+
context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
680+
if err != nil {
681+
t.Fatal(err)
682+
}
683+
savePassVisualManifest(t, repo, "reviewer-ready", captureCommit, context.ProductDiffSHA256)
684+
fresh, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
685+
if err != nil {
686+
t.Fatal(err)
687+
}
688+
if fresh.PRVisualEvidenceStatus != "PASS" || fresh.PRVisualEvidenceCount != 1 {
689+
t.Fatalf("recorded PASS evidence was not trusted: %#v", fresh)
690+
}
691+
previewPath := writePreview(t, repo, fresh, "Keep evidence trusted across the preview commit", visualEvidenceBody(managedPRBody(), fresh.PRVisualEvidenceStatus))
692+
runGit(t, repo, "add", fresh.PreviewPath)
693+
runGit(t, repo, "commit", "-m", "record exact PR preview")
694+
committed, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
695+
if err != nil {
696+
t.Fatal(err)
697+
}
698+
if committed.PRVisualEvidenceStatus != "PASS" {
699+
t.Fatalf("committing the reviewed pr.md invalidated PASS evidence: %s", committed.PRVisualEvidenceStatus)
700+
}
701+
if committed.PRVisualEvidenceFingerprint != fresh.PRVisualEvidenceFingerprint {
702+
t.Fatalf("preview commit changed the visual evidence fingerprint")
703+
}
704+
if _, _, err := CheckPRPreview(repo, previewPath); err != nil {
705+
t.Fatalf("committed preview no longer checks: %v", err)
706+
}
707+
if committed.PRVisualEvidence == nil || committed.PRVisualEvidence.SourceCommit != captureCommit {
708+
t.Fatalf("evidence provenance lost its capture commit: %#v", committed.PRVisualEvidence)
709+
}
710+
if template := PRPreviewTemplate(committed); !strings.Contains(template, captureCommit) {
711+
t.Fatalf("preview template does not name the capture commit")
712+
}
713+
}
714+
715+
func TestProductDiffChangeInvalidatesPassVisualEvidence(t *testing.T) {
716+
repo := prTestRepoConfigured(t, func(config *ProjectConfig) {
717+
config.Workflow.PRVisualEvidence = "suggest"
718+
})
719+
activateManagedFeature(t, repo, "reviewer-ready")
720+
context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
721+
if err != nil {
722+
t.Fatal(err)
723+
}
724+
savePassVisualManifest(t, repo, "reviewer-ready", runGit(t, repo, "rev-parse", "HEAD"), context.ProductDiffSHA256)
725+
config, _, err := LoadConfig(filepath.Join(repo, ".product-loop", "project.json"))
726+
if err != nil {
727+
t.Fatal(err)
728+
}
729+
changedDiff := strings.Repeat("c", 64)
730+
_, status, _, _, _, _, _, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff)
731+
if err != nil {
732+
t.Fatal(err)
733+
}
734+
if status != "NOT_VERIFIED" {
735+
t.Fatalf("product change did not stale the evidence: %s", status)
736+
}
737+
config.Workflow.PRVisualEvidence = "require"
738+
_, status, _, _, _, _, _, err = resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff)
739+
if err != nil {
740+
t.Fatal(err)
741+
}
742+
if status != "BLOCKED" {
743+
t.Fatalf("require did not coerce stale evidence to BLOCKED: %s", status)
744+
}
745+
}
746+
651747
func TestPublishPRRequiresExactConfirmationAndUsesBodyWithoutFrontmatter(t *testing.T) {
652748
if runtime.GOOS == "windows" {
653749
t.Skip("fake gh fixture uses a POSIX shell; publication behavior is covered by cross-platform pure-Go checks")

docs/evidence-engineered-coding.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,6 @@ Delivery and system improvement also remain separate. A failed task may suggest
146146

147147
## What is evidence-backed
148148

149-
The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`90356bed91109de2eb131fc95872bdb74abac6a7`](https://github.com/operatorstack/intelligence-flow/tree/90356bed91109de2eb131fc95872bdb74abac6a7/labs/12-product-engineering-loop).
149+
The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`d604f832578355bd5bc34eb66e8d54b062645629`](https://github.com/operatorstack/intelligence-flow/tree/d604f832578355bd5bc34eb66e8d54b062645629/labs/12-product-engineering-loop).
150150

151151
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.

0 commit comments

Comments
 (0)