Skip to content

Commit 4f4a2e3

Browse files
Sync Boatstack from Intelligence Flow Labs @ 5f4dda1585d8 (#85)
Co-authored-by: operator-stack-publisher[bot] <operator-stack-publisher[bot]@users.noreply.github.com>
1 parent ba610e5 commit 4f4a2e3

17 files changed

Lines changed: 1233 additions & 31 deletions

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/2da3d0b8f8ca7baec350a8b803e5409721517e52/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/5f4dda1585d8032019dd0ed2c3b8ea264abfcf4d/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: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "715cf4044f2af36764254efffd0425135e300ce8d57c03d19eb089a7041cb1fd",
15+
"CONTRIBUTING.md": "ae6641051824b5bbc7588d7d70a2657f091e0579d5db4616a8aed5b4f18e1d69",
1616
"README.md": "125b47671a68556df382f19756fb61fa18925606cbbaf54d6bc9df8872b36870",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -34,9 +34,13 @@
3434
"boatstack/assets/templates/test-plan.md": "6db8a9f27dd171fb80222a501cae50eb051e7278c04703fa43b5ff86dd4d2df4",
3535
"boatstack/atomic_unix.go": "89f2723361591de2bb8bd22ce7e34ec529d3278509f0df78fd5c4a7d4140fbe9",
3636
"boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f",
37+
"boatstack/capability.go": "9d9a75086e88bb1d9d4170821436c686002fe3a125e6ee7d23eea5779aac5cfe",
38+
"boatstack/capability_test.go": "e8322903a843970d7f0317d2629466532cb05c3ffcb44b9423adf4219faa8021",
39+
"boatstack/capture.go": "e32dd7096ccc4844d37c9ec0aea8284adee58bc84cd5b6264516b6661b348bfc",
40+
"boatstack/capture_test.go": "63fa1177738081f1e862364d7a4257f5e259f8e9c36276ba1775b8085b277105",
3741
"boatstack/changelog.go": "c5e1f31440b44d61e6037ad27af0333540af3545d655e35819a0241cbbebd8ec",
3842
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
39-
"boatstack/cmd/boatstack-helper/main.go": "43f070dc71f924ea6466b4190c43811d7884cece7d9d44df433c9beeb6982053",
43+
"boatstack/cmd/boatstack-helper/main.go": "5aff755d60b2f5c06dab3ed5d30df026d28c6fb90d891b3be38e955aa200621d",
4044
"boatstack/cmd/boatstack-helper/main_test.go": "ff73003b6a5157202fa09ddf1129fb13c3d79702b2e05a8721ce5a11bf5ab779",
4145
"boatstack/command.go": "94d2117c6e390d5a644afc5cd90f7e712e3f8b1032f8c9e3b253cc134524c28a",
4246
"boatstack/command_test.go": "9f707abba3640add81c3e97ba7e72fedbf98f3394b1c060a9ca4b4a28e919968",
@@ -46,7 +50,7 @@
4650
"boatstack/delivery.go": "ea53af0e702ec3668a563a5f786dcac2e095362285ca6093b7ed71ec495a0a48",
4751
"boatstack/delivery_test.go": "564ad2029a8412de7953967b1acdf377e6c87b6f6e3465d1f8741a4813f2949f",
4852
"boatstack/evidence.go": "497a31e6ff632cb1d7c3adfc9f269af3f6aa84e948dd5d417c162767542a27df",
49-
"boatstack/export.go": "abccdfc04b7b49a17e8531c0332f80262e693273f7974b646528fe00af1d6ae6",
53+
"boatstack/export.go": "388c676e4fb25442a15e51e31bfc5931b6264d7a95a3422ecec0c1f78ab613d9",
5054
"boatstack/export_test.go": "67eb890728d20630925d6e4e90d2a97ec025195ba5c54994c1b098ab72721dca",
5155
"boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641",
5256
"boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017",
@@ -72,6 +76,8 @@
7276
"boatstack/planning_test.go": "c105a9c78c342be06614bf54d0bc1b661b0f7af64d63b79e43bd1fcc2769edd5",
7377
"boatstack/pr.go": "bdb066acb329b6b772cb880db2e590b381cae909f270085cfacb8ef2fbfda651",
7478
"boatstack/pr_test.go": "7f82954d94c1ceae848a581dda25e58af92251d78a5a94ed2d672bedf5a0349e",
79+
"boatstack/provision.go": "4882d49681f99b11ba9d182ca13772131b7f9a11a6c2b560800654ca14f5111e",
80+
"boatstack/provision_test.go": "214e9edb991a66d5bbb696a7c1b63876d2f799f2cab4e3f40785f4e8f1eac57b",
7581
"boatstack/recovery.go": "dd816b18b54a0085b8d8276a93ee98d2b1e90099059a0d85cf6e24edf6f37d5b",
7682
"boatstack/recovery_test.go": "29490e7477ba602491330036a491289dd9117b99ff862f66dae421ba17e04c9f",
7783
"boatstack/references/artifacts.md": "5fa888ac519085d65cee1d04df5902761651bcf2d7af81711fa0f8ecd1fc0f59",
@@ -99,7 +105,7 @@
99105
"boatstack/update_publication.go": "c8b7bd38019b1cbf8c523652e9e20e8c971c7e48b2f3972a0ac638648326fe63",
100106
"boatstack/update_publication_test.go": "c5f32578db53be65e35452d5e8b4520884354e4e80a370dcec19860ea644d091",
101107
"boatstack/update_test.go": "bf5f19f8499db6dd7356917d867b113b790548ab89bfdea59e2adf4999a82a6a",
102-
"boatstack/visual_evidence.go": "90a68d554e10ff4fb7afa45000912cdedd4cdf93b3d279055b50844401924f01",
108+
"boatstack/visual_evidence.go": "4d69f98adb6087d4830e6703273ae6e03b28ec8b3c496a6e432fd5e0e54d8a2f",
103109
"boatstack/visual_evidence_test.go": "0fe8f5154ef4398dfeba5e7f7b387b2d279ed75635ea35d93ff392269f2cc6d0",
104110
"boatstack/workspace.go": "91b343400b3506a6f516c28fabc3f1575f22024a5b19f934a020be660a20482e",
105111
"boatstack/workspace_sync.go": "0cc2f03fd1aa57c66b3d603d5ef30aa820f14ab60771a795cf10c46f3c9a71b8",
@@ -109,10 +115,10 @@
109115
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
110116
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
111117
"docs/configuration.md": "4d8f207b415a5a1e3b9b1698ee7bb1221aa0e5496a061bb8054294df2f347ad1",
112-
"docs/evidence-engineered-coding.md": "5326393482e5f97979f89c5b5714c9b8dda124cc16d398a962c9e5cf4fc8a528",
118+
"docs/evidence-engineered-coding.md": "58205ac01839ab8c29d76090e2744afa827a623d7be6de6b6e468f7b3730c047",
113119
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
114120
"docs/getting-started.md": "f314270c5ed1a55bbef5f3ddbcb5596693dbee9374e5f0d3df8838cefbd68052",
115-
"docs/public-claims.json": "650290515c86557a4316261f2313a3ddf07121e12ad76eca9072a4d696c1acaf",
121+
"docs/public-claims.json": "abbf9221262970d74e60e5cd54f69b1dad93ce453fa75e9516f86ee4ef12b2f8",
116122
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
117123
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
118124
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
@@ -126,7 +132,7 @@
126132
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
127133
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
128134
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
129-
"labs/diagram-json/plan.lock.json": "e416845baca59bf23f91f8417d074b63c96dfe65293a481039e24428c8d61fb4",
135+
"labs/diagram-json/plan.lock.json": "40ec1d86a0c3e7186546f6f26bc4023cbea0020b04553537515b3a85d5ae37fe",
130136
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
131137
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
132138
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -186,6 +192,9 @@
186192
"release-notes/2026-07-22-value-translation-boundary.md": "9cf168ff7caaf3906b78533935bdfb2c86e753984ed1e5ec8204cb373d083390",
187193
"release-notes/2026-07-23-bootstrap-safe-update-repair.md": "d8e66c46ae05e3d228dfea45879f4d1166d5e3a253ac24bababd4c3e396c214b",
188194
"release-notes/2026-07-23-canonical-update-ownership.md": "7f34f890b252493797519389b23f1b56ec7ec16db7547aac8ea196f75f3b8c2c",
195+
"release-notes/2026-07-23-capability-provisioning.md": "86fefcc4212212b0522b2a9b732663ee98cc41cec96b9842f19bb4433277a6a4",
196+
"release-notes/2026-07-23-capture-evidence-orchestration.md": "13e0dfa65898a4ba63a7866fc1db4d32df2f5610db0e8301734d47404f0e3da9",
197+
"release-notes/2026-07-23-evidence-capability-substrate.md": "a3e6e364536f826f299243932494096c27458e2621a5ce55465c5db280b53758",
189198
"release-notes/2026-07-23-explicit-source-plan.md": "ec1f97434f9263f6db4bc3b83ae83213053cd2bc6b7465e4fb2d67cbea5cf731",
190199
"release-notes/2026-07-23-ignore-ambiguous-deliveries.md": "9b1b9fd48db340b91fcced1c282297de0ecad8744723f2b1fdd94033927a88c4",
191200
"release-notes/2026-07-23-labkit-publishing-pipeline.md": "b3bfc5f28baf3961fb187380ff66e5383186c05a933bc6ca162416ce61149218",
@@ -197,7 +206,7 @@
197206
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
198207
"schema_version": 1,
199208
"source": {
200-
"commit": "2da3d0b8f8ca7baec350a8b803e5409721517e52",
209+
"commit": "5f4dda1585d8032019dd0ed2c3b8ea264abfcf4d",
201210
"path": "labs/12-product-engineering-loop",
202211
"repository": "operatorstack/intelligence-flow"
203212
}

boatstack/capability.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package boatstack
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// Capability describes a named producer of PR evidence. It is the generic spine
9+
// that concrete evidence types register on: detection ("can this repository
10+
// produce the evidence?"), capture orchestration ("what command runs it, and
11+
// when?"), and provisioning ("if it is unavailable, how do we help?") all read
12+
// this registry instead of hard-coding a single evidence type.
13+
//
14+
// Boatstack ships the contract in this registry, not the capture harness. The
15+
// harness is authored in the user's repository and invoked through the resolved
16+
// repository command; Boatstack only records what the command must satisfy.
17+
type Capability struct {
18+
// Name is the canonical capability identifier, e.g. "visual".
19+
Name string
20+
// CommandAliases are the project.commands keys that satisfy the capability,
21+
// in priority order. The first non-empty command wins.
22+
CommandAliases []string
23+
// AdmittedStages are the delivery stages in which a capture attempt may run.
24+
AdmittedStages []string
25+
// RetryClass is the operation retry class recorded for a capture attempt.
26+
RetryClass string
27+
}
28+
29+
// CapabilityResolution is the portable capability cut for one capability: it
30+
// reports whether the repository owns a command that produces this evidence.
31+
type CapabilityResolution struct {
32+
Name string `json:"name"`
33+
Kind string `json:"kind"` // repository-command | unavailable
34+
Command string `json:"command,omitempty"`
35+
}
36+
37+
// capabilityRegistry holds every evidence capability Boatstack knows about.
38+
// Adding a provider is a single entry here plus its tenant-specific manifest
39+
// contract (see visual_evidence.go for the first tenant).
40+
var capabilityRegistry = map[string]Capability{
41+
"visual": {
42+
Name: "visual",
43+
CommandAliases: []string{"visual", "screenshot", "e2e"},
44+
AdmittedStages: []string{"BUILD", "TEST_PASSED"},
45+
RetryClass: "IDEMPOTENT_EXTERNAL",
46+
},
47+
}
48+
49+
// LookupCapability returns the registered capability metadata for a name.
50+
func LookupCapability(name string) (Capability, bool) {
51+
capability, ok := capabilityRegistry[strings.TrimSpace(name)]
52+
return capability, ok
53+
}
54+
55+
// ResolveCapability performs the repository-owned capability cut: it selects the
56+
// first project command alias that is set. It is the generic detection primitive
57+
// shared by capture orchestration and provisioning. Kind is "repository-command"
58+
// when the repository owns a command, otherwise "unavailable".
59+
func ResolveCapability(name string, config ProjectConfig) (CapabilityResolution, error) {
60+
capability, ok := LookupCapability(name)
61+
if !ok {
62+
return CapabilityResolution{}, fmt.Errorf("unknown evidence capability %q", name)
63+
}
64+
for _, alias := range capability.CommandAliases {
65+
if command := strings.TrimSpace(config.Project.Commands[alias]); command != "" {
66+
return CapabilityResolution{Name: capability.Name, Kind: "repository-command", Command: command}, nil
67+
}
68+
}
69+
return CapabilityResolution{Name: capability.Name, Kind: "unavailable"}, nil
70+
}

boatstack/capability_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package boatstack
2+
3+
import "testing"
4+
5+
func TestResolveCapabilitySelectsRepositoryCommandByAliasPriority(t *testing.T) {
6+
config := testConfig()
7+
delete(config.Project.Commands, "visual")
8+
delete(config.Project.Commands, "screenshot")
9+
delete(config.Project.Commands, "e2e")
10+
11+
// Lowest-priority alias still resolves the repository-owned cut.
12+
config.Project.Commands["e2e"] = "npm run e2e"
13+
resolution, err := ResolveCapability("visual", config)
14+
if err != nil {
15+
t.Fatalf("resolve visual: %v", err)
16+
}
17+
if resolution.Kind != "repository-command" || resolution.Command != "npm run e2e" || resolution.Name != "visual" {
18+
t.Fatalf("alias fallback did not resolve: %#v", resolution)
19+
}
20+
21+
// A higher-priority alias wins over a lower one.
22+
config.Project.Commands["visual"] = "npm run capture:visual"
23+
resolution, err = ResolveCapability("visual", config)
24+
if err != nil {
25+
t.Fatalf("resolve visual: %v", err)
26+
}
27+
if resolution.Command != "npm run capture:visual" {
28+
t.Fatalf("higher-priority alias did not win: %#v", resolution)
29+
}
30+
}
31+
32+
func TestResolveCapabilityReportsUnavailableWithoutCommand(t *testing.T) {
33+
config := testConfig()
34+
delete(config.Project.Commands, "visual")
35+
delete(config.Project.Commands, "screenshot")
36+
delete(config.Project.Commands, "e2e")
37+
38+
resolution, err := ResolveCapability("visual", config)
39+
if err != nil {
40+
t.Fatalf("resolve visual: %v", err)
41+
}
42+
if resolution.Kind != "unavailable" || resolution.Command != "" {
43+
t.Fatalf("expected unavailable resolution: %#v", resolution)
44+
}
45+
}
46+
47+
func TestResolveCapabilityRejectsUnknownCapability(t *testing.T) {
48+
if _, err := ResolveCapability("does-not-exist", testConfig()); err == nil {
49+
t.Fatal("expected error for unknown capability")
50+
}
51+
}
52+
53+
func TestLookupCapabilityExposesRegisteredMetadata(t *testing.T) {
54+
capability, ok := LookupCapability("visual")
55+
if !ok {
56+
t.Fatal("visual capability is not registered")
57+
}
58+
if len(capability.CommandAliases) == 0 || capability.RetryClass == "" || len(capability.AdmittedStages) == 0 {
59+
t.Fatalf("visual capability metadata is incomplete: %#v", capability)
60+
}
61+
}

0 commit comments

Comments
 (0)