Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/90356bed91109de2eb131fc95872bdb74abac6a7/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/d604f832578355bd5bc34eb66e8d54b062645629/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.

Expand Down
21 changes: 11 additions & 10 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "16338a21fc8e849ca3a3a72f36c0163f4136f717b54df3cfc704ed75468b60ca",
"CONTRIBUTING.md": "5060717c0e6f5337197d3c2b1acb6a047264ffab0ed6cf8af411ee498cd57051",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand All @@ -38,8 +38,8 @@
"boatstack/attach.go": "6a855440fac9acc63be857efef9d76210a4619774728684832dfbb08caf42a30",
"boatstack/capability.go": "9d9a75086e88bb1d9d4170821436c686002fe3a125e6ee7d23eea5779aac5cfe",
"boatstack/capability_test.go": "e8322903a843970d7f0317d2629466532cb05c3ffcb44b9423adf4219faa8021",
"boatstack/capture.go": "6a279bab615a012de0b2c43aeae9b95d122365068dc54c8923c14c6a4c719449",
"boatstack/capture_test.go": "63fa1177738081f1e862364d7a4257f5e259f8e9c36276ba1775b8085b277105",
"boatstack/capture.go": "0e137d8251a658118bd56812040575d01f02fd2f862c80ef761b253db6b35bbe",
"boatstack/capture_test.go": "31a8afa40a54ae716fea1ea44be3b41705ed171789a181d1ef26432855c05c9e",
"boatstack/changelog.go": "6b06be7cd9738de29ba6e87aa2569f3b027a2e618b04524f5abd7abaa17945bf",
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
"boatstack/cmd/boatstack-helper/coverage_conformance_test.go": "f5a931d2b5cdb0d32af85acbbd499fa0f509ed36d62e772e6b6072e348200aca",
Expand Down Expand Up @@ -165,10 +165,10 @@
"boatstack/planning_first_write_conformance_test.go": "873097aa9384b75bf01e74a475f3ec2ac7cca4a28f733e82f2c82959032c6a30",
"boatstack/planning_test.go": "06ec7022222d926040c3ae28b84ab50c3d2f804ae6473e61b303804dd992d884",
"boatstack/post_publish_prescribe_conformance_test.go": "3c20d359ff84648db7dedb227b4d64e6574d9f41d3cdca0adefec1c60bfbf4ae",
"boatstack/pr.go": "0ddd88b364d141a9ccadd4d6b6751c13c56769feba5879bd896be46b0d5af5fd",
"boatstack/pr.go": "511ae0025aae3d99a5fbb942bb078d68a04feb62190387b331d562f934902c41",
"boatstack/pr_phase.go": "59f8cbb75b6b538a5345474acd6a725450979579bf8ecf9591956cbbe1cc4737",
"boatstack/pr_phase_conformance_test.go": "bc9c834e9c4ed43b35d81abafd7b1bf2a264ea2a8c4a4ec9758ee18d1d438968",
"boatstack/pr_test.go": "2e7709e2f163489a29ea3e7eb4bc932cfe6829b30d168f1aea9e942acbc3b4e7",
"boatstack/pr_test.go": "dd223d9ddc50af2f34b20fd30246b9aaef7e4d9fd479bcaa3e87bb4de58bd4b3",
"boatstack/provenance.go": "d44dcd5421306269326f1202ba1d52df8c252490550270ef9d022e8ec2b65210",
"boatstack/provision.go": "eb7333a73331b011adc93f59a2d97415d850c2e588f0e2bbb5116984e2ef927d",
"boatstack/provision_test.go": "214e9edb991a66d5bbb696a7c1b63876d2f799f2cab4e3f40785f4e8f1eac57b",
Expand Down Expand Up @@ -231,10 +231,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "caa95f1dd484a71a9051951652d6457294c213999fe00fb764d05e8a509cc786",
"docs/evidence-engineered-coding.md": "59b9d1296da97e2f72960082e951065a433cd4b9c27e507c6be0fe4594eb44be",
"docs/evidence-engineered-coding.md": "bb8bd613b350be866929105ab96a3225f30f8e306b0b650a9b6655fb7c26af07",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
"docs/public-claims.json": "823614ba0cc01218915438d443d187826f80bc4bb4a1bcd925026632d954e6c7",
"docs/public-claims.json": "ef823c6a3d27eee37856712bbb0bb4b9b0d266c97dcd2fd4568aa0c98416041a",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -248,7 +248,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": "5adb15badc39a262755db561b097f7e09668ab9c462c904e55a36b2825a7663b",
"labs/diagram-json/plan.lock.json": "72e44c5239a3ec0bb1f609d25083d152d43b81d8206aeafdef7ce8db04a37512",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -389,12 +389,13 @@
"release-notes/2026-07-28-protected-native-auto-merge.md": "67dc76a6e7ce51034a0eadc541ba7a8946cfcabe5433cedc25db55321dfb8b62",
"release-notes/2026-07-28-retro-derive-proposals.md": "c90797d76fb00816340475620b584ad150fa32d7d10bc14997a614529ef9b558",
"release-notes/2026-07-28-retromine-recurrence-detector.md": "27790993a02e73f3a2700dce7340d044aeb0e52f785ded68271ff6673add9e25",
"release-notes/2026-07-29-readiness-and-journey-control.md": "2411db76974990ad0128fcd90e795c4c1c5c70d6d6f15fccd452f7e1a242310b"
"release-notes/2026-07-29-readiness-and-journey-control.md": "2411db76974990ad0128fcd90e795c4c1c5c70d6d6f15fccd452f7e1a242310b",
"release-notes/2026-07-30-visual-evidence-survives-preview-commit.md": "71d19e9fabb40e8cb1e939f26bf288911d227979926f43f337046c6cf6b66551"
},
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "90356bed91109de2eb131fc95872bdb74abac6a7",
"commit": "d604f832578355bd5bc34eb66e8d54b062645629",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
5 changes: 3 additions & 2 deletions boatstack/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ func CaptureEvidence(options CaptureEvidenceOptions) (PRVisualEvidenceManifest,
}

// captureProductDiff reproduces the pr-context product-diff fingerprint so a
// captured manifest is trusted (PASS) by resolvePRVisualEvidence: same head
// commit and same product diff.
// captured manifest is trusted (PASS) by resolvePRVisualEvidence: same product
// diff. The head commit is recorded for provenance only — trust is keyed to
// product identity so committing the reviewed pr.md never stales evidence.
func captureProductDiff(repo, base, feature, head string) (headCommit, diffHash string, err error) {
baseCommit, err := resolveBaseCommit(repo, base)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions boatstack/capture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,18 @@ func TestCaptureEvidenceProducesManifestTrustedByPRContext(t *testing.T) {
t.Fatalf("capture mutated the product tree: %s", status)
}

// The manifest must be trusted by the same resolver pr-context uses: identical
// head commit and product diff → status is the manifest's PASS, not NOT_VERIFIED.
// The manifest must be trusted by the same resolver pr-context uses: an
// identical product diff → status is the manifest's PASS, not NOT_VERIFIED.
head := runGit(t, repo, "rev-parse", "--abbrev-ref", "HEAD")
headCommit, diffHash, err := captureProductDiff(repo, "main", "reviewer-ready", head)
_, diffHash, err := captureProductDiff(repo, "main", "reviewer-ready", head)
if err != nil {
t.Fatal(err)
}
config, _, err := LoadConfig(filepath.Join(repo, ".product-loop", "project.json"))
if err != nil {
t.Fatal(err)
}
_, status, count, _, _, _, resolved, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", head, headCommit, diffHash)
_, status, count, _, _, _, resolved, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", head, diffHash)
if err != nil {
t.Fatal(err)
}
Expand Down
20 changes: 16 additions & 4 deletions boatstack/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func planVisualDecision(repo, feature string) (string, string, []PRVisualScenari
return relevance, "managed-plan", scenarios, nil
}

func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, head, headCommit, diffHash string) (string, string, int, string, string, string, *PRVisualEvidenceManifest, error) {
func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, head, diffHash string) (string, string, int, string, string, string, *PRVisualEvidenceManifest, error) {
policy := normalizedPRVisualEvidencePolicy(config.Workflow.PRVisualEvidence)
relevance, source := "unresolved", "agent-proposed"
var scenarios []PRVisualScenario
Expand All @@ -131,7 +131,12 @@ func resolvePRVisualEvidence(repo string, config ProjectConfig, mode, feature, h
if loadErr == nil {
manifest = &loaded
relevance, source, scenarios = loaded.Relevance, loaded.RelevanceSource, loaded.Scenarios
if loaded.SourceCommit == headCommit && loaded.ProductDiffSHA256 == diffHash {
// Trust is keyed to product identity only: the preview pr.md is
// excluded from the product diff yet must be committed before
// publication, so a head-commit equality would invalidate every
// PASS manifest on that mandatory commit. SourceCommit stays
// recorded for provenance and the evidence comment.
if loaded.ProductDiffSHA256 == diffHash {
status = loaded.Status
} else {
status = "NOT_VERIFIED"
Expand Down Expand Up @@ -653,7 +658,7 @@ func PreparePRContext(options PRContextOptions) (PRContext, error) {
return PRContext{}, err
}
visualPolicy, visualStatus, visualCount, visualFingerprint, visualRelevance, visualSource, visualManifest, err := resolvePRVisualEvidence(
repo, config, mode, options.Feature, head, headCommit, SHA256Bytes(diff),
repo, config, mode, options.Feature, head, SHA256Bytes(diff),
)
if err != nil {
return PRContext{}, err
Expand Down Expand Up @@ -1240,11 +1245,18 @@ func PRPreviewTemplate(context PRContext) string {
"## Rollout and rollback", "", "Describe deployment impact and the smallest safe rollback.", "",
}
if context.PRVisualEvidenceStatus != "NOT_APPLICABLE" {
// The Commit column names the commit the pixels were captured from;
// evidence stays trusted across preview-only commits, so this can
// legitimately trail HeadCommit.
evidenceCommit := context.HeadCommit
if context.PRVisualEvidence != nil && strings.TrimSpace(context.PRVisualEvidence.SourceCommit) != "" {
evidenceCommit = context.PRVisualEvidence.SourceCommit
}
lines = append(lines,
"## Visual evidence", "",
"Screenshots are human-review evidence, not mechanical proof. Public-repository attachments are publicly accessible.", "",
"| Scenario | Viewport | Commit | Result | Publication |", "|---|---|---|---|---|",
"| Describe the approved state | viewport | "+context.HeadCommit+" | `"+context.PRVisualEvidenceStatus+"` | Boatstack evidence comment or manual fallback |", "",
"| Describe the approved state | viewport | "+evidenceCommit+" | `"+context.PRVisualEvidenceStatus+"` | Boatstack evidence comment or manual fallback |", "",
)
}
if context.TotalSlices > 1 {
Expand Down
96 changes: 96 additions & 0 deletions boatstack/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"testing"
"time"
)

func runGit(t *testing.T, repo string, arguments ...string) string {
Expand Down Expand Up @@ -648,6 +649,101 @@ func TestRequiredPRVisualEvidenceBlocksPublicationBeforeMutation(t *testing.T) {
}
}

// savePassVisualManifest records PASS evidence for the fixture feature's
// approved scenario, bound to the given source commit and product diff.
func savePassVisualManifest(t *testing.T, repo, feature, sourceCommit, diffHash string) {
t.Helper()
pngPath := filepath.Join(t.TempDir(), "warning.png")
writeTestPNG(t, pngPath)
if _, err := SavePRVisualEvidence(repo, PRVisualEvidenceManifest{
Key: feature, Policy: "suggest", Relevance: "relevant", RelevanceSource: "managed-plan",
Status: "PASS", SourceCommit: sourceCommit, ProductDiffSHA256: diffHash,
Scenarios: []PRVisualScenario{{ID: "warning", Entry: "/onboarding", State: "picker open", Viewport: "1440x900", Expected: []string{"warning visible"}}},
Items: []PRVisualEvidenceItem{{
ScenarioID: "warning", Path: pngPath, Viewport: "1440x900",
CapturedAt: time.Now().UTC().Truncate(time.Second).Format(time.RFC3339),
Status: "captured", PrivacyStatus: "human-reviewed",
}},
Publication: PRVisualPublication{State: "pending"},
}); err != nil {
t.Fatal(err)
}
}

func TestCommittingPreviewNeverInvalidatesPassVisualEvidence(t *testing.T) {
repo := prTestRepoConfigured(t, func(config *ProjectConfig) {
config.Workflow.PRVisualEvidence = "suggest"
})
activateManagedFeature(t, repo, "reviewer-ready")
captureCommit := runGit(t, repo, "rev-parse", "HEAD")
context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
if err != nil {
t.Fatal(err)
}
savePassVisualManifest(t, repo, "reviewer-ready", captureCommit, context.ProductDiffSHA256)
fresh, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
if err != nil {
t.Fatal(err)
}
if fresh.PRVisualEvidenceStatus != "PASS" || fresh.PRVisualEvidenceCount != 1 {
t.Fatalf("recorded PASS evidence was not trusted: %#v", fresh)
}
previewPath := writePreview(t, repo, fresh, "Keep evidence trusted across the preview commit", visualEvidenceBody(managedPRBody(), fresh.PRVisualEvidenceStatus))
runGit(t, repo, "add", fresh.PreviewPath)
runGit(t, repo, "commit", "-m", "record exact PR preview")
committed, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
if err != nil {
t.Fatal(err)
}
if committed.PRVisualEvidenceStatus != "PASS" {
t.Fatalf("committing the reviewed pr.md invalidated PASS evidence: %s", committed.PRVisualEvidenceStatus)
}
if committed.PRVisualEvidenceFingerprint != fresh.PRVisualEvidenceFingerprint {
t.Fatalf("preview commit changed the visual evidence fingerprint")
}
if _, _, err := CheckPRPreview(repo, previewPath); err != nil {
t.Fatalf("committed preview no longer checks: %v", err)
}
if committed.PRVisualEvidence == nil || committed.PRVisualEvidence.SourceCommit != captureCommit {
t.Fatalf("evidence provenance lost its capture commit: %#v", committed.PRVisualEvidence)
}
if template := PRPreviewTemplate(committed); !strings.Contains(template, captureCommit) {
t.Fatalf("preview template does not name the capture commit")
}
}

func TestProductDiffChangeInvalidatesPassVisualEvidence(t *testing.T) {
repo := prTestRepoConfigured(t, func(config *ProjectConfig) {
config.Workflow.PRVisualEvidence = "suggest"
})
activateManagedFeature(t, repo, "reviewer-ready")
context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"})
if err != nil {
t.Fatal(err)
}
savePassVisualManifest(t, repo, "reviewer-ready", runGit(t, repo, "rev-parse", "HEAD"), context.ProductDiffSHA256)
config, _, err := LoadConfig(filepath.Join(repo, ".product-loop", "project.json"))
if err != nil {
t.Fatal(err)
}
changedDiff := strings.Repeat("c", 64)
_, status, _, _, _, _, _, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff)
if err != nil {
t.Fatal(err)
}
if status != "NOT_VERIFIED" {
t.Fatalf("product change did not stale the evidence: %s", status)
}
config.Workflow.PRVisualEvidence = "require"
_, status, _, _, _, _, _, err = resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff)
if err != nil {
t.Fatal(err)
}
if status != "BLOCKED" {
t.Fatalf("require did not coerce stale evidence to BLOCKED: %s", status)
}
}

func TestPublishPRRequiresExactConfirmationAndUsesBodyWithoutFrontmatter(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("fake gh fixture uses a POSIX shell; publication behavior is covered by cross-platform pure-Go checks")
Expand Down
2 changes: 1 addition & 1 deletion docs/evidence-engineered-coding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`90356bed91109de2eb131fc95872bdb74abac6a7`](https://github.com/operatorstack/intelligence-flow/tree/90356bed91109de2eb131fc95872bdb74abac6a7/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 [`d604f832578355bd5bc34eb66e8d54b062645629`](https://github.com/operatorstack/intelligence-flow/tree/d604f832578355bd5bc34eb66e8d54b062645629/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.
Loading
Loading