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/eec4b62c152cc2da37579f891706640bff9cb1a6/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/719220d52b8ac1237c9169099b53a024dc583cc6/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
34 changes: 23 additions & 11 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "ff655c8e8bb64b7c06dfd123aba96cabe03f30541e5d33c338933bf3c6baf45e",
"CONTRIBUTING.md": "d036445ff05dfc40abb8a74d9bab61411474599a66c8e6678b72987e2ad9df2d",
"README.md": "6b7402c5cef5b3b9b739281d3d4d576cdc995796ff127fc6aefb97c5743e0bac",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -40,7 +40,8 @@
"boatstack/capture_test.go": "63fa1177738081f1e862364d7a4257f5e259f8e9c36276ba1775b8085b277105",
"boatstack/changelog.go": "6b06be7cd9738de29ba6e87aa2569f3b027a2e618b04524f5abd7abaa17945bf",
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
"boatstack/cmd/boatstack-helper/flow.go": "795e6129351600cd81ffa86db5e1016dfae894c2d0e9b75615f37eb5fdf5f939",
"boatstack/cmd/boatstack-helper/coverage_conformance_test.go": "8ece156fa5c341e5ceb41d0a7abe4b7da378a0db9244bdb25b348b02e2c39b0a",
"boatstack/cmd/boatstack-helper/flow.go": "5ab24541d3f85c2f442730d3122d18eb6676600bc11fde4805e803a25a8300c7",
"boatstack/cmd/boatstack-helper/main.go": "a4a29e53d803cd1d8ec35e105bab17000e2855f978393031f1516aab26c732b6",
"boatstack/cmd/boatstack-helper/main_test.go": "b36c52d6d5c9dd2428730de10ff18194b7e32a98722e41341c301c6f7a04cad5",
"boatstack/command.go": "4726ac515dedab4947be7eb48f88c6cb8b53d674124504b69f03e6396b080ee8",
Expand All @@ -49,22 +50,29 @@
"boatstack/config_documentation_test.go": "0632366edc5e88145bb080083ea03c6515da07b0162ce404d63e51bb5bc0774e",
"boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c",
"boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0",
"boatstack/delivery.go": "3d1580512c1922ae4ce349a790acfb3356e3c10105af7871457e37fffda39416",
"boatstack/delivery.go": "772793e493b97348c198692a282035a40257b7ca8e21bdea66a881a5c805c22a",
"boatstack/delivery_boundary_conformance_test.go": "800cd722d8d2a696a0529e8343d3523e453bb052f0917c8a2cad2990296ac1b3",
"boatstack/delivery_migrate.go": "7566e49f9c1838d4d563866e941c7aacd61ac918c9e886222282398d287ca780",
"boatstack/delivery_migrate_conformance_test.go": "b8ba53681e1d0361ac62b06586c62b7763d55a65b5427976b5289e1fb1503bdc",
"boatstack/delivery_reactivation_test.go": "573a2dba0034bc4290478414e3bdd8670b06a326128eb0295d77e748ecc8689e",
"boatstack/delivery_test.go": "45c48ff7581c911bcaf821c3e4241d4ae2a9bb4aa682485cc58b6ad8fe1c85bf",
"boatstack/deliverycontrol_parity_test.go": "027c04471c6037fc585a8af8646171addcc548e43147c5c6dbbe76bad9f5a10c",
"boatstack/deliverycontrol_parity_test.go": "f8662cfc35043395a0e1eef8a87051c2120752f38b09c56b78f82896008f1b65",
"boatstack/evidence.go": "497a31e6ff632cb1d7c3adfc9f269af3f6aa84e948dd5d417c162767542a27df",
"boatstack/export.go": "9cb23234e6cd79441ff6f39f88ed66d6d47ef7c27901404439a3572b03fdf881",
"boatstack/export_test.go": "dce5aa3ab5499c82d05859cf86b46dfcee308482491366d83e10ca3fb8605bb6",
"boatstack/flow_coding.go": "9fa53a0204f98a25f97775c3acf37392a591c14ce850b44aa587b5806e770bb9",
"boatstack/flow_coding_test.go": "dddcd7a85892d4fa10af42739d4c1ff265721b0313e27b6e7a1bbb019d5c3b51",
"boatstack/flow_control.go": "9eaa1188f86c307bf0f31a5ca465637e5fd6792f42206dce10d0272a1d1ea36b",
"boatstack/flow_control.go": "58e721c4704d260511eaf05b190b8fd97914931e3b74ccd896c88fc2564564a3",
"boatstack/flow_control_test.go": "d52e095f2e0f18067abe03e3b5f7c98bc30f8b1c8f5230103797c599aec95013",
"boatstack/flow_drive.go": "a501ceda390dfd3605e22cf7ecfa15f9d50240b3fac6ebb2bb2d80c615d0a9fc",
"boatstack/flow_drive_conformance_test.go": "23edea926c271a1f5718fb9dae1da11e4bf03cceb1357290cd61cd8ffb73beda",
"boatstack/flow_guard.go": "dd18524d95f4a220cfd3d11b11003dacc52120785ee0ccdbeceb2307fab55872",
"boatstack/flow_guard_test.go": "8ba75f11ddd080427c15bd7e25f7d03c1b746a2f587c212cea0e710337d1c0e1",
"boatstack/flow_prescribe_conformance_test.go": "e2287aa079b7aafaf822e1f752a1bb5017acda811363e576eeff793347d0d5c8",
"boatstack/flow_report.go": "9e58cec51c6c903847f3ebc79cd6bf25e2f91d14b81811e82e5f4e026a7b71a3",
"boatstack/flow_report_test.go": "eae00f2b8ead4f1ec20e1f1bc47db4c53a36048bb84eca9bea4f1a2105bdcdde",
"boatstack/flow_tasks.go": "690db05d345dabdb24965015c94198aa3f93d2d9691599ae8e6a2ac3aafb9d44",
"boatstack/flow_tasks_conformance_test.go": "fbc4d672536051f8e20a2e6c07c5b10f78cd84fc04765eee11cbed7a459b8e4b",
"boatstack/flow_trace.go": "95b5a99f5f557a27a3eca7152de9ec18459f2a88d9749924432463031536a96c",
"boatstack/flow_trace_test.go": "99f89a831e904f6a8ef710b6977ed3a808ce1c7ddfaba457b292d84f2ddca51b",
"boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641",
Expand All @@ -90,7 +98,7 @@
"boatstack/internal/deliverycontrol/liveness_test.go": "7a148075d9d2c5df468fecb710bdd38226c4cd4b37584a810b6848909a0f3292",
"boatstack/internal/deliverycontrol/oracle.go": "80765b1946d6c863f0e635a99b68d3ccafa7ff235360fba774811b5b0de791da",
"boatstack/internal/deliverycontrol/oracle_test.go": "ce320a71f0c9440c5a7bc1b742d0f74c6a46759845e919ab36bde5ff8fabb311",
"boatstack/internal/deliverycontrol/registry.go": "aa89cef9eec8d715c06d2f61a472df20bb9334c950a6d751a1e15d67b567c433",
"boatstack/internal/deliverycontrol/registry.go": "d77c9ceea1feb576b857c3d1f4fc517afbf3f38cb43525a8a139206a6802980d",
"boatstack/internal/deliverycontrol/registry_test.go": "473ab5e5d33f84d34c29a219db867abfc6eb3ad4489f3d5d0c7dc09b06d193f3",
"boatstack/internal/deliverycontrol/state.go": "2551624bbcbd8f9dd897a1e2240cef2cc1895d117a4030525d88f1d62f6e395e",
"boatstack/internal/deliverycontrol/trajectory.go": "4469a0c35b40f8e2a37060e9b26fcbda7d34d020088c31de367dd5cf6e7721dc",
Expand All @@ -104,7 +112,7 @@
"boatstack/mutation_test.go": "68d5049c7f96c1ac558e4c781151f67e8deee2f8d6b9bf293b90d44e769ef7c6",
"boatstack/mutation_undo.go": "697d11b600a276ddbcabe6a9f8040d4f7283e017a0e8fd689ef53a274638946c",
"boatstack/mutation_undo_test.go": "39540e717e3f2136bf975594043a3db9072b28ebe61c6cb0b982cea5e8b1e14e",
"boatstack/next.go": "d45f4e5b3ab7072e700725cab5b6eac83b1a460cd08f07b55370de5ee4d8ee59",
"boatstack/next.go": "39d813c96a6a5119efcabda0f59ce7c6faf91e5e76c77c7be999bf85f43de284",
"boatstack/next_banner_test.go": "c431a6987ed1e479442fc9f5db4371632880b92aa790fa9dd0f5285293352c41",
"boatstack/next_test.go": "d442d22023831ba39fcfbbf73f1a2da4170a83fc2aab5ce1b44f10cf9d88e173",
"boatstack/operation.go": "62f97bf2091f33eb2ca91915bf08bee73d53387611b673e849355bfd516ca467",
Expand Down Expand Up @@ -167,10 +175,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "df054f49d532c8b1b7d94184810d1b3b5bf18cdc30eb985b4b6d0639162e341a",
"docs/evidence-engineered-coding.md": "e42fd1e704cc9d60d7fcb79c53e248944caf4984b02df1c496aceec5a5e51072",
"docs/evidence-engineered-coding.md": "f8abc34bc426482e0fa0f7e30fc0c5b6cdbfc03b25fdc81963b1e718e8736922",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "1dd4f4e2e636cc5adfc2f79939629701e171087c3d5e558cf919548b9224adfd",
"docs/public-claims.json": "52529747f11523647ca794a4d992008537e51d473935cf644144f95a4207603d",
"docs/public-claims.json": "178cdeb2738ef09101e056591760ecf450666ad039931134987cb225512ffd81",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -184,7 +192,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": "30ac25c13348433f4be8c4f3df06b51431a434b427ddd6d1f4f26f14ad550f6c",
"labs/diagram-json/plan.lock.json": "f3e4094f2ac5731cf8774986c3e8233d1dbc843d5684efc9b2ab7ba245cea340",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -268,11 +276,15 @@
"release-notes/2026-07-25-boatstack-banner.md": "28e83f294de606211cfdc91b2586aa834e004dee76d5c4bee08859986ae86b5b",
"release-notes/2026-07-25-delivery-control-inventory.md": "1f359bcf4071dd47bd1011c877db573bd26309d28683abea8389c353f1c6c88d",
"release-notes/2026-07-25-delivery-flow-navigation-model.md": "b2d805fae30100a7de4e76760341247237cc2476fdcc57d99074825bf47d6450",
"release-notes/2026-07-25-deliverycontrol-change-safety-net.md": "1cdf220b40d3577cb6e2b98b9c3dcd41e54fdf905746814b074f4e1aa9b751d1",
"release-notes/2026-07-25-deliverycontrol-coding-telemetry.md": "a229c737bfc9cc5ceddf109c246c41f00619e8a3fef0e0e78870e4255131741b",
"release-notes/2026-07-25-deliverycontrol-flow-check-advisory.md": "80ffb0fb64958d41f0b05fb3ef25ad464e63d1bc8021561c7360827859b6da77",
"release-notes/2026-07-25-deliverycontrol-flow-control.md": "4aeb013d9f0c54370a364b5fc9ee3aa30ec305d00ab77575b873bc1fd8da6ebf",
"release-notes/2026-07-25-deliverycontrol-flow-next-execute.md": "3dd4c2997c5fef0750f5a14cdfccfbdd535ddd618424bbdba8f9974e57b570c1",
"release-notes/2026-07-25-deliverycontrol-flow-next-prescribes-command.md": "63c6949c2955bbc80950a6716df15851282f5b74c460513032bd55a5c861c1bb",
"release-notes/2026-07-25-deliverycontrol-flow-oracle.md": "04e64e27638b32a90a132f615f896bf20ee805f3c75bf5e3ab088f3c55e641a7",
"release-notes/2026-07-25-deliverycontrol-flow-report.md": "86c519e7debd72362f8ef6ef21ec443912aa705669f396259ba771303925bbb5",
"release-notes/2026-07-25-deliverycontrol-flow-tasks-subaction.md": "f5fb40312e4f3084d352492805a1a9d2f23ee1a0a38057d2c696a995044101b4",
"release-notes/2026-07-25-deliverycontrol-shadow-registry.md": "e7f8ca4e4f188eda3088e46cba77369d8ff0d903f29e43846103d986e79a2273",
"release-notes/2026-07-25-evidence-path-resolution.md": "b32cb8a6e69f397f751c3a7fb62be254a7407a28bed25ae9108d6d773c863d11",
"release-notes/2026-07-25-published-slice-correction-routing.md": "129cdd62c80c8b93060726027d68ba3abdb0bca1a1ce9e64d6053271af3fd082",
Expand All @@ -281,7 +293,7 @@
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "eec4b62c152cc2da37579f891706640bff9cb1a6",
"commit": "719220d52b8ac1237c9169099b53a024dc583cc6",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
218 changes: 218 additions & 0 deletions boatstack/cmd/boatstack-helper/coverage_conformance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package main

import (
"go/ast"
"go/parser"
"go/token"
"sort"
"testing"

"github.com/operatorstack/boatstack/boatstack/internal/deliverycontrol"
)

// control-law: registry-covers-real-delivery-machine
//
// The deliverycontrol registry is the authoritative projection of the real
// delivery state machine: the flow commands resolve the prescribed CLI command
// through it (Transition(id).CLIVerb). For that projection to be trustworthy, the
// registry must cover EXACTLY the real delivery machine — every registry CLIVerb
// must name a real dispatch verb, and every real delivery-mutation/observe
// dispatch verb must have a registry row. Nothing asserted this before, so a new
// delivery CLI verb could ship green with no registry row, invisible to the
// liveness/deadlock guarantee. These tests close that gap in both directions.
//
// The real dispatch inventory is read from the actual `run()` switch in main.go
// by parsing its AST, so it cannot drift from what the binary really accepts.
// Every dispatch verb must be classified: either it is a delivery-machine verb
// (a registry CLIVerb) or it is explicitly declared out of the delivery machine
// in nonDeliveryVerbs below. A new verb that is neither fails the suite — the
// author must consciously register it or declare it non-delivery (the Bypass
// guard). This list is behavior describing, not behavior defining: it names the
// dispatch verbs that are not transitions of the delivery state machine.
var nonDeliveryVerbs = map[string]bool{
// Update / release / distribution lifecycle (not the per-feature delivery machine).
"init": true,
"update": true,
"check-update": true,
"prepare-update-pr": true,
"publish-update-pr": true,
"release-classify": true,
"next-patch": true,
"export": true,
"migrate-config": true,
"hydrate-runtime": true,
"version": true,
// Planning phase, before a plan is activated into a delivery.
"check-source-plan": true,
"check-plan": true,
"planning-write": true,
"record-approval": true,
// Read-only status / diagnostics (observe helpers, not modeled transitions).
"repair-status": true,
"operation-status": true,
"mutation-status": true,
"run-preflight": true,
"check-safety": true,
"doctor": true,
"diagnose-hook": true,
"workspace-status": true,
// Evidence / capability substrate (a separate tenant, not the delivery graph).
"record-pr-visual-evidence": true,
"capture-evidence": true,
"provision-capability": true,
"capability-register": true,
"record-pr-visual-publication": true,
// PR construction / verification helpers reached around the ship gate.
"check-pr": true,
// Safety hooks and workspace management (guard/scaffold, not delivery moves).
"safety-hook": true,
"bootstrap-safety-hook": true,
"workspace-cut": true,
"workspace-cleanup": true,
"workspace-sync": true,
// Flow layer itself is read-only navigation over the machine, not a transition.
"flow": true,
}

// dispatchVerbs parses main.go and returns the set of command verbs the run()
// switch actually dispatches. It fails loudly rather than returning an empty set,
// so the coverage guarantee can never silently pass by finding nothing.
func dispatchVerbs(t *testing.T) map[string]bool {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "main.go", nil, 0)
if err != nil {
t.Fatalf("parse main.go: %v", err)
}
verbs := map[string]bool{}
ast.Inspect(file, func(n ast.Node) bool {
fn, ok := n.(*ast.FuncDecl)
if !ok || fn.Name.Name != "run" {
return true
}
ast.Inspect(fn.Body, func(inner ast.Node) bool {
sw, ok := inner.(*ast.SwitchStmt)
if !ok || !switchesOnArgs(sw.Tag) {
return true
}
for _, stmt := range sw.Body.List {
clause, ok := stmt.(*ast.CaseClause)
if !ok {
continue
}
for _, expr := range clause.List {
if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
verbs[mustUnquote(t, lit.Value)] = true
}
}
}
return false
})
return false
})
if len(verbs) < 10 {
t.Fatalf("dispatch switch parse found only %d verbs; expected the full run() command set — the coverage guard would be vacuous", len(verbs))
}
return verbs
}

// switchesOnArgs reports whether a switch tag is an index into os.Args (the
// command dispatch), e.g. `os.Args[1]`.
func switchesOnArgs(tag ast.Expr) bool {
index, ok := tag.(*ast.IndexExpr)
if !ok {
return false
}
sel, ok := index.X.(*ast.SelectorExpr)
return ok && sel.Sel.Name == "Args"
}

func mustUnquote(t *testing.T, quoted string) string {
t.Helper()
if len(quoted) < 2 {
t.Fatalf("malformed string literal %q in dispatch switch", quoted)
}
return quoted[1 : len(quoted)-1]
}

func registryVerbs() map[string]bool {
verbs := map[string]bool{}
for _, tr := range deliverycontrol.Transitions() {
if tr.CLIVerb != "" {
verbs[tr.CLIVerb] = true
}
}
return verbs
}

// Positive: every CLIVerb the registry declares names a real dispatch verb, so
// the prescribed command a resolver emits through Transition(id).CLIVerb is
// always a command the binary actually accepts.
func TestRegistryCLIVerbsAreRealDispatchVerbs(t *testing.T) {
dispatch := dispatchVerbs(t)
for verb := range registryVerbs() {
if !dispatch[verb] {
t.Errorf("registry declares CLIVerb %q that main.go does not dispatch (prescribed command would be unrunnable)", verb)
}
}
}

// Bypass / Negative: every dispatch verb must be classified — a delivery-machine
// verb (registry CLIVerb) or explicitly non-delivery. A new delivery CLI verb
// added to the dispatch switch without a registry row (or a conscious
// non-delivery declaration) fails here; it cannot ship invisibly to the machine.
func TestEveryDispatchVerbIsClassified(t *testing.T) {
dispatch := dispatchVerbs(t)
registry := registryVerbs()
for verb := range dispatch {
if registry[verb] {
continue
}
if nonDeliveryVerbs[verb] {
continue
}
t.Errorf("dispatch verb %q is neither a registry delivery transition nor declared in nonDeliveryVerbs; register it or classify it before shipping", verb)
}
}

// Relation: the delivery-machine dispatch verbs (all dispatch verbs minus the
// declared non-delivery ones) equal the registry CLIVerb set exactly — the two
// inventories agree with no orphan on either side.
func TestDeliveryDispatchVerbsEqualRegistry(t *testing.T) {
dispatch := dispatchVerbs(t)
registry := registryVerbs()

deliveryDispatch := map[string]bool{}
for verb := range dispatch {
if !nonDeliveryVerbs[verb] {
deliveryDispatch[verb] = true
}
}
if diff := symmetricDiff(deliveryDispatch, registry); len(diff) != 0 {
sort.Strings(diff)
t.Errorf("delivery dispatch verbs and registry CLIVerbs disagree: %v", diff)
}

// A non-delivery declaration must name a verb that is actually dispatched;
// a stale entry (verb renamed/removed) is drift and must be cleaned up.
for verb := range nonDeliveryVerbs {
if !dispatch[verb] {
t.Errorf("nonDeliveryVerbs names %q which main.go no longer dispatches (stale allowlist entry)", verb)
}
}
}

func symmetricDiff(a, b map[string]bool) []string {
var diff []string
for k := range a {
if !b[k] {
diff = append(diff, "only-in-dispatch:"+k)
}
}
for k := range b {
if !a[k] {
diff = append(diff, "only-in-registry:"+k)
}
}
return diff
}
Loading
Loading