Skip to content

Commit 74e754f

Browse files
Sync Boatstack from Intelligence Flow Labs @ 6f60e1b420c8 (#136)
Co-authored-by: operator-stack-publisher[bot] <operator-stack-publisher[bot]@users.noreply.github.com>
1 parent dd212c5 commit 74e754f

12 files changed

Lines changed: 763 additions & 105 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/7f71d4816931f34405b6c9a6bccca0eb4e29021f/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/6f60e1b420c8236479d1ed76d755372de52620fc/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 & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"files": {
1414
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
15-
"CONTRIBUTING.md": "c84e0cbd421edb15d688e7a0c5a75e00a96da9e0fd7ff736c864a26ede7408b8",
15+
"CONTRIBUTING.md": "581c585e4246b2ef56f1d875a306446d09dc5dedc507b3aee3ee7f22697e52e2",
1616
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
1717
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
1818
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
@@ -71,16 +71,17 @@
7171
"boatstack/export_test.go": "dce5aa3ab5499c82d05859cf86b46dfcee308482491366d83e10ca3fb8605bb6",
7272
"boatstack/flow_coding.go": "9fa53a0204f98a25f97775c3acf37392a591c14ce850b44aa587b5806e770bb9",
7373
"boatstack/flow_coding_test.go": "dddcd7a85892d4fa10af42739d4c1ff265721b0313e27b6e7a1bbb019d5c3b51",
74-
"boatstack/flow_control.go": "da5759a2588acc8a67b46b480572fe2f00c1a68769bdf9127a4aef7351dcd44a",
74+
"boatstack/flow_control.go": "d1cc9268fafd37a322222b26e041d89d8f95e7ca8c7ea0726679801c2836c536",
7575
"boatstack/flow_control_test.go": "02d788c83be55ebd79ffc73875bfd019de45325151eb1f70f506980eb8e77f29",
7676
"boatstack/flow_drive.go": "a501ceda390dfd3605e22cf7ecfa15f9d50240b3fac6ebb2bb2d80c615d0a9fc",
7777
"boatstack/flow_drive_conformance_test.go": "23edea926c271a1f5718fb9dae1da11e4bf03cceb1357290cd61cd8ffb73beda",
7878
"boatstack/flow_guard.go": "dd18524d95f4a220cfd3d11b11003dacc52120785ee0ccdbeceb2307fab55872",
7979
"boatstack/flow_guard_test.go": "8ba75f11ddd080427c15bd7e25f7d03c1b746a2f587c212cea0e710337d1c0e1",
8080
"boatstack/flow_planning_prescribe_conformance_test.go": "c2fa2566b0676f34a777764ade87a0670d41413d052a9a339e01bdb6a9a8699b",
81-
"boatstack/flow_prescribe_conformance_test.go": "e2287aa079b7aafaf822e1f752a1bb5017acda811363e576eeff793347d0d5c8",
81+
"boatstack/flow_prescribe_conformance_test.go": "a307e26c03df2ac530f6a120fa021bd971921df5fb3aec7228578489eee74611",
8282
"boatstack/flow_report.go": "9e58cec51c6c903847f3ebc79cd6bf25e2f91d14b81811e82e5f4e026a7b71a3",
8383
"boatstack/flow_report_test.go": "eae00f2b8ead4f1ec20e1f1bc47db4c53a36048bb84eca9bea4f1a2105bdcdde",
84+
"boatstack/flow_solutions.go": "78d639ebd1012326f3e7e67cb5a4068de24898148a29db9176a1237af932f6aa",
8485
"boatstack/flow_tasks.go": "690db05d345dabdb24965015c94198aa3f93d2d9691599ae8e6a2ac3aafb9d44",
8586
"boatstack/flow_tasks_conformance_test.go": "fbc4d672536051f8e20a2e6c07c5b10f78cd84fc04765eee11cbed7a459b8e4b",
8687
"boatstack/flow_trace.go": "1a69f9dd53db313235c74f7ae4f821a6aed023f780aea57210c721ea8f11f2fc",
@@ -126,7 +127,7 @@
126127
"boatstack/mutation_undo_test.go": "39540e717e3f2136bf975594043a3db9072b28ebe61c6cb0b982cea5e8b1e14e",
127128
"boatstack/next.go": "c133dbf907dc86ca5aacec154f6e4a63e7aa9f5f0ebdd76e1803375b5700675a",
128129
"boatstack/next_banner_test.go": "c431a6987ed1e479442fc9f5db4371632880b92aa790fa9dd0f5285293352c41",
129-
"boatstack/next_response.go": "62777e52556098ca8a40197a5d7d42fddd49d5e89f7b9e1884c23bf2cdf07599",
130+
"boatstack/next_response.go": "f63f9593cf4adb1217cc65337fe737c4e5ef07b9c6f311641a77269b2c264b6b",
130131
"boatstack/next_response_conformance_test.go": "be4f3bc7507abfb0ae9f86310eb29e34b166dcc40b6fa103e05babb81f2bd928",
131132
"boatstack/next_test.go": "6b5ec46ecf1a197d7644846cecbb6d99873a06b7c4e5562772b5016fa0a4cb11",
132133
"boatstack/operation.go": "073113e1e7b6349417e70b704bd1a342b460604cbd97a7fab06b1a6494604112",
@@ -167,12 +168,13 @@
167168
"boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8",
168169
"boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85",
169170
"boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc",
170-
"boatstack/safety.go": "405782eba91a1718a061faa65ff52c10d345cbf3bccf5ecd2cb8ed45d5df9c00",
171+
"boatstack/safety.go": "0d04805e834c9cf10ba59b823fa1dd194c226fdfcd0d67889953cb38b6305653",
171172
"boatstack/safety_corpus_test.go": "e7d8c493d8cee957e4590f2c9034d4aa4af08bdcbe02c9889d0d98a0168bbcf9",
172173
"boatstack/safety_test.go": "500ad53cd5e3a700553eb781d9eaf4028ae27478796759a76bfd57321fff5a7c",
173174
"boatstack/safety_update_publisher_test.go": "ed3f8187036623694dfe7c395cdae00fdae14609bab6124d1fdfc6fe73fa2196",
174175
"boatstack/skill_frontmatter.go": "73364df463ce828c2d005aab55f72bb92f7a34d99cf3f53d4e0cd5a4da9dbd0e",
175176
"boatstack/skill_frontmatter_test.go": "a3ec52e7df357a72265c95dd66db15d9c0effc7e5f90f14ce69c27792ce394eb",
177+
"boatstack/solution_closure_conformance_test.go": "f73e6748dac373e2a10bc9269c2f2e060bd220bb4113bd0d5ff66ca4c8e91a54",
176178
"boatstack/statemap.go": "39ff3a7a7254ec5fde8340551fa92a82aac3f00a48bea34c94192823dcb41337",
177179
"boatstack/statemap_conformance_test.go": "38950377d10b97f4223b79cdb16b17c29a6334f8f2a9a4d826ce12cbd292aa57",
178180
"boatstack/supervisory_control_test.go": "c7ea4bcd678e8ec211dac772c834981c4e21762914be2770a5e181bc24605e06",
@@ -197,10 +199,10 @@
197199
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
198200
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
199201
"docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0",
200-
"docs/evidence-engineered-coding.md": "8722b287bb91f5fd0209d61f0594672ab026ebe9f8f70b65f8df5214c4451d95",
202+
"docs/evidence-engineered-coding.md": "d8fce9c9a21e9ddb382811bfe397edc526360c1ebe4e8ec4f5e603436e1082b8",
201203
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
202204
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
203-
"docs/public-claims.json": "6e804e45d843599b44b7f7733064ebf1a7799afc5debe7b694b5a0d4b1298edd",
205+
"docs/public-claims.json": "4932a0b1c1a59b3d75fa3facc206b8ba94720079b7de8f1c01ab10c0458d3df4",
204206
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
205207
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
206208
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
@@ -214,7 +216,7 @@
214216
"labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d",
215217
"labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71",
216218
"labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39",
217-
"labs/diagram-json/plan.lock.json": "2c2fbf7ea885339dad90e362725a2ffe8b0edec513d90f4bb5de5c7feede47ea",
219+
"labs/diagram-json/plan.lock.json": "76555eec4fcf58509beb849db669a1a924d0c9f123a784b51876ed1f9a92fd34",
218220
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
219221
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
220222
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
@@ -333,6 +335,7 @@
333335
"release-notes/2026-07-27-discoverable-planning-errors.md": "d8099d1a6cd1805c3fcd446d9fa95739dec93e57ff421ecf4f99562b143c9966",
334336
"release-notes/2026-07-27-document-content-is-data.md": "c6a35c222bf53ba465fadf21e7e95e1764b52e12e41852e51584ce9cb6513f4a",
335337
"release-notes/2026-07-27-first-planning-write-owned-channel.md": "7a37e7abf7fd5f8612aca4323d55af1748c9e668bb294518619a1c39e195309f",
338+
"release-notes/2026-07-27-flow-solution-set.md": "e138c333d515f8d2b7003334353ade2203b752cf726f2ec3158c657310b00014",
336339
"release-notes/2026-07-27-guard-dual-reward-corpus.md": "6bec0385c6c553f00517259821e502796ca1b1907aeab718a287560e3e0fa0d6",
337340
"release-notes/2026-07-27-helper-rendered-next-response.md": "08f53d8ade77c0ed6ffe7b63330c67ec0b88a5c71a466ed103f3b66d0952e4e2",
338341
"release-notes/2026-07-27-invalid-delivery-block-actionable.md": "8fac8e3921e2285291703efa46e624b72cb5bac1b8492beca4c4b633abb5ba16",
@@ -344,7 +347,7 @@
344347
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
345348
"schema_version": 1,
346349
"source": {
347-
"commit": "7f71d4816931f34405b6c9a6bccca0eb4e29021f",
350+
"commit": "6f60e1b420c8236479d1ed76d755372de52620fc",
348351
"path": "labs/12-product-engineering-loop",
349352
"repository": "operatorstack/intelligence-flow"
350353
}

boatstack/flow_control.go

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ const (
9292
MarkerPlanningCheckPlan = deliverycontrol.TransitionID("planning.check_plan")
9393
MarkerPlanningActivate = deliverycontrol.TransitionID("planning.activate")
9494
MarkerPlanningWorkspace = deliverycontrol.TransitionID("planning.workspace_cut")
95+
MarkerPlanningWrite = deliverycontrol.TransitionID("planning.planning_write")
96+
MarkerPlanningApproval = deliverycontrol.TransitionID("planning.record_approval")
9597
MarkerRecoveryDoctor = deliverycontrol.TransitionID("recovery.doctor")
9698
MarkerRecoveryDiscard = deliverycontrol.TransitionID("recovery.discard_delivery")
9799
MarkerRecoveryRepair = deliverycontrol.TransitionID("recovery.repair_state")
@@ -130,6 +132,12 @@ type FlowNext struct {
130132
// completion state and prescribes no command — coding work is never a modeled
131133
// transition, only an ordered pointer.
132134
SubAction *FlowTask `json:"sub_action,omitempty"`
135+
// Alternatives are the other admissible next commands from this position —
136+
// the computed solution set minus the single Prescribed primary. They let a
137+
// caller PICK a legal move instead of deriving one from the law's prose.
138+
// Advisory, never a second primary: the rendering keeps exactly one Run line.
139+
// control-law: solution-set-derives-from-guard-declarations
140+
Alternatives []PrescribedCommand `json:"alternatives,omitempty"`
133141
}
134142

135143
// PrescribedCommand is the exact next command that makes the oracle's lowest-cost
@@ -188,9 +196,30 @@ func prescribeCommand(repo, feature string, status NextStatus, transition delive
188196
preview := filepath.Join(WorkspaceFor(repo).GeneratedRoot(), "features", feature, "pr.md")
189197
cmd.Args = append(repoArgs, "--preview", preview, "--action", "open")
190198
cmd.RequiresHumanInput = []string{"--preview-fingerprint"}
199+
case deliverycontrol.TransitionID("delivery.record_change"):
200+
if feature == "" {
201+
return nil, false
202+
}
203+
// Rework: the correction facts (what changed, where it was observed, and
204+
// its classification) are human knowledge; owe them, never fabricate them.
205+
cmd.Args = append(repoArgs, "--feature", feature)
206+
if status.ActiveSlice != "" {
207+
cmd.Args = append(cmd.Args, "--slice", status.ActiveSlice)
208+
}
209+
cmd.RequiresHumanInput = []string{"--message", "--source-stage", "--classification"}
210+
case deliverycontrol.TransitionID("delivery.undo"):
211+
// The mutation id names WHICH receipt to reverse — a human decision.
212+
cmd.Args = repoArgs
213+
cmd.RequiresHumanInput = []string{"--mutation"}
214+
case deliverycontrol.TransitionID("delivery.discard_delivery"):
215+
if feature == "" {
216+
return nil, false
217+
}
218+
cmd.Args = append(repoArgs, "--feature", feature)
191219
default:
192-
// Recovery/observe/rework transitions are not prescribed as a forward move;
193-
// emit nothing rather than a command whose arguments we cannot derive.
220+
// Recovery/observe transitions outside the set above are not prescribed as
221+
// a forward move; emit nothing rather than a command whose arguments we
222+
// cannot derive.
194223
return nil, false
195224
}
196225
cmd.AutoDerivable = len(cmd.RequiresHumanInput) == 0
@@ -209,12 +238,18 @@ func prescribeCommand(repo, feature string, status NextStatus, transition delive
209238
// state"), not an execution grant: markers are off the auto-drive allowlist
210239
// and have no executor, so the driver always prescribes-and-stops on them.
211240
// control-law: prescriptive-closure-every-stage-names-a-runnable-command
241+
// planningFeatureDir is the single joined form of a feature's planning
242+
// directory used by the prescription layer and the solution-set enumerator.
243+
func planningFeatureDir(repo, feature string) string {
244+
return filepath.Join(repo, ".product-loop", "features", feature)
245+
}
246+
212247
func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, string) {
213248
var repoArgs []string
214249
if repo != "" && repo != "." {
215250
repoArgs = []string{"--repo", repo}
216251
}
217-
featureDir := filepath.Join(repo, ".product-loop", "features", status.Feature)
252+
featureDir := planningFeatureDir(repo, status.Feature)
218253
finish := func(cmd *PrescribedCommand, followUp string) (*PrescribedCommand, string) {
219254
cmd.AutoDerivable = len(cmd.RequiresHumanInput) == 0
220255
return cmd, followUp
@@ -233,32 +268,19 @@ func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, stri
233268
}, "Then run auto-plan with the validated SOURCE_PLAN path; author every feature artifact through `boatstack-helper planning-write` (document on stdin).")
234269
case "DRAFT_PLAN":
235270
return finish(&PrescribedCommand{
236-
Verb: "check-plan",
237-
Args: []string{"--plan", filepath.Join(featureDir, "plan.md")},
271+
Verb: "check-plan",
272+
Args: []string{"--plan", filepath.Join(featureDir, "plan.md")},
238273
Transition: MarkerPlanningCheckPlan,
239274
}, "After the check passes, present the plan for approval and record it with `record-approval` using the exact PLAN_FINGERPRINT it printed.")
240275
case "APPROVED", "POLICY_READY":
241276
// ResolveNext already ordered the move: a fresh workspace cut when one is
242277
// needed, otherwise activation. "build" is an operation name, not a verb;
243278
// activate-plan is the build operation's first concrete command.
244279
if status.NextOperation == "workspace-cut" {
245-
return finish(&PrescribedCommand{
246-
Verb: "workspace-cut",
247-
Args: append(repoArgs, "--feature", status.Feature),
248-
Transition: MarkerPlanningWorkspace,
249-
}, "Then activate the plan from the fresh workspace with `activate-plan`.")
280+
return finish(buildWorkspaceCut(repoArgs, status.Feature),
281+
"Then activate the plan from the fresh workspace with `activate-plan`.")
250282
}
251-
args := []string{
252-
"--plan", filepath.Join(featureDir, "plan.md"),
253-
"--out-dir", filepath.Join(featureDir, "compiled"),
254-
"--output", filepath.Join(featureDir, "plan.lock.json"),
255-
}
256-
if status.ObservedStage == "APPROVED" {
257-
args = append(args, "--approval", filepath.Join(featureDir, "approval.md"))
258-
}
259-
return finish(&PrescribedCommand{
260-
Verb: "activate-plan", Args: args, Transition: MarkerPlanningActivate,
261-
}, "")
283+
return finish(buildActivatePlan(featureDir, status.ObservedStage), "")
262284
case "INVALID_STATE":
263285
switch status.NextOperation {
264286
case "doctor":
@@ -296,6 +318,30 @@ func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, stri
296318
}
297319
}
298320

321+
// buildWorkspaceCut and buildActivatePlan are the single assembly points for
322+
// their commands, shared by prescribePlanning (the primary) and the solution-set
323+
// enumerator (the alternatives) so the two can never drift apart.
324+
// control-law: solution-set-derives-from-guard-declarations
325+
func buildWorkspaceCut(repoArgs []string, feature string) *PrescribedCommand {
326+
return &PrescribedCommand{
327+
Verb: "workspace-cut",
328+
Args: append(append([]string{}, repoArgs...), "--feature", feature),
329+
Transition: MarkerPlanningWorkspace,
330+
}
331+
}
332+
333+
func buildActivatePlan(featureDir, stage string) *PrescribedCommand {
334+
args := []string{
335+
"--plan", filepath.Join(featureDir, "plan.md"),
336+
"--out-dir", filepath.Join(featureDir, "compiled"),
337+
"--output", filepath.Join(featureDir, "plan.lock.json"),
338+
}
339+
if stage == "APPROVED" {
340+
args = append(args, "--approval", filepath.Join(featureDir, "approval.md"))
341+
}
342+
return &PrescribedCommand{Verb: "activate-plan", Args: args, Transition: MarkerPlanningActivate}
343+
}
344+
299345
// NextControl composes the authoritative read-only recommendation (ResolveNext)
300346
// with the deterministic oracle to advise the lowest-cost next move toward a
301347
// published delivery. It performs no mutation and is safe to call at any time.
@@ -330,6 +376,7 @@ func nextControlFromStatus(repo string, status NextStatus) (FlowNext, error) {
330376
out.Prescribed = cmd
331377
out.FollowUp = followUp
332378
}
379+
out.Alternatives = alternativesFor(repo, status, out)
333380
return out, nil
334381
}
335382
out.State = state
@@ -354,6 +401,7 @@ func nextControlFromStatus(repo string, status NextStatus) (FlowNext, error) {
354401
}
355402
}
356403
}
404+
out.Alternatives = alternativesFor(repo, status, out)
357405
return out, nil
358406
}
359407

@@ -389,9 +437,32 @@ func FormatFlowNext(next FlowNext) string {
389437
} else {
390438
fmt.Fprintf(&b, "Flow state: unresolved (no oracle advisory; follow the recommended operation above)\n")
391439
}
440+
writeAlternatives(&b, next.Alternatives)
392441
return b.String()
393442
}
394443

444+
// writeAlternatives renders the solution set's other legal moves as ONE line of
445+
// verbs with a short purpose gloss — never a second Run line, so the response
446+
// contract's single primary action holds.
447+
// control-law: solution-set-derives-from-guard-declarations
448+
func writeAlternatives(b *strings.Builder, alternatives []PrescribedCommand) {
449+
if len(alternatives) == 0 {
450+
return
451+
}
452+
shown := alternatives
453+
if len(shown) > solutionSetTextCap {
454+
shown = shown[:solutionSetTextCap]
455+
}
456+
labels := make([]string, 0, len(shown))
457+
for _, alt := range shown {
458+
labels = append(labels, alt.Verb+" ("+solutionGloss(alt.Transition)+")")
459+
}
460+
fmt.Fprintf(b, "Also legal from here: %s\n", strings.Join(labels, ", "))
461+
if len(alternatives) > len(shown) {
462+
fmt.Fprintf(b, " (%d more in `flow next --json` under alternatives)\n", len(alternatives)-len(shown))
463+
}
464+
}
465+
395466
// writePrescribed renders the Run line and its owed-input annotation for a
396467
// prescribed command, shared by the oracle and pre-activation branches.
397468
func writePrescribed(b *strings.Builder, p *PrescribedCommand) {

0 commit comments

Comments
 (0)