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/7f71d4816931f34405b6c9a6bccca0eb4e29021f/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/6f60e1b420c8236479d1ed76d755372de52620fc/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: 12 additions & 9 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "c84e0cbd421edb15d688e7a0c5a75e00a96da9e0fd7ff736c864a26ede7408b8",
"CONTRIBUTING.md": "581c585e4246b2ef56f1d875a306446d09dc5dedc507b3aee3ee7f22697e52e2",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -71,16 +71,17 @@
"boatstack/export_test.go": "dce5aa3ab5499c82d05859cf86b46dfcee308482491366d83e10ca3fb8605bb6",
"boatstack/flow_coding.go": "9fa53a0204f98a25f97775c3acf37392a591c14ce850b44aa587b5806e770bb9",
"boatstack/flow_coding_test.go": "dddcd7a85892d4fa10af42739d4c1ff265721b0313e27b6e7a1bbb019d5c3b51",
"boatstack/flow_control.go": "da5759a2588acc8a67b46b480572fe2f00c1a68769bdf9127a4aef7351dcd44a",
"boatstack/flow_control.go": "d1cc9268fafd37a322222b26e041d89d8f95e7ca8c7ea0726679801c2836c536",
"boatstack/flow_control_test.go": "02d788c83be55ebd79ffc73875bfd019de45325151eb1f70f506980eb8e77f29",
"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_planning_prescribe_conformance_test.go": "c2fa2566b0676f34a777764ade87a0670d41413d052a9a339e01bdb6a9a8699b",
"boatstack/flow_prescribe_conformance_test.go": "e2287aa079b7aafaf822e1f752a1bb5017acda811363e576eeff793347d0d5c8",
"boatstack/flow_prescribe_conformance_test.go": "a307e26c03df2ac530f6a120fa021bd971921df5fb3aec7228578489eee74611",
"boatstack/flow_report.go": "9e58cec51c6c903847f3ebc79cd6bf25e2f91d14b81811e82e5f4e026a7b71a3",
"boatstack/flow_report_test.go": "eae00f2b8ead4f1ec20e1f1bc47db4c53a36048bb84eca9bea4f1a2105bdcdde",
"boatstack/flow_solutions.go": "78d639ebd1012326f3e7e67cb5a4068de24898148a29db9176a1237af932f6aa",
"boatstack/flow_tasks.go": "690db05d345dabdb24965015c94198aa3f93d2d9691599ae8e6a2ac3aafb9d44",
"boatstack/flow_tasks_conformance_test.go": "fbc4d672536051f8e20a2e6c07c5b10f78cd84fc04765eee11cbed7a459b8e4b",
"boatstack/flow_trace.go": "1a69f9dd53db313235c74f7ae4f821a6aed023f780aea57210c721ea8f11f2fc",
Expand Down Expand Up @@ -126,7 +127,7 @@
"boatstack/mutation_undo_test.go": "39540e717e3f2136bf975594043a3db9072b28ebe61c6cb0b982cea5e8b1e14e",
"boatstack/next.go": "c133dbf907dc86ca5aacec154f6e4a63e7aa9f5f0ebdd76e1803375b5700675a",
"boatstack/next_banner_test.go": "c431a6987ed1e479442fc9f5db4371632880b92aa790fa9dd0f5285293352c41",
"boatstack/next_response.go": "62777e52556098ca8a40197a5d7d42fddd49d5e89f7b9e1884c23bf2cdf07599",
"boatstack/next_response.go": "f63f9593cf4adb1217cc65337fe737c4e5ef07b9c6f311641a77269b2c264b6b",
"boatstack/next_response_conformance_test.go": "be4f3bc7507abfb0ae9f86310eb29e34b166dcc40b6fa103e05babb81f2bd928",
"boatstack/next_test.go": "6b5ec46ecf1a197d7644846cecbb6d99873a06b7c4e5562772b5016fa0a4cb11",
"boatstack/operation.go": "073113e1e7b6349417e70b704bd1a342b460604cbd97a7fab06b1a6494604112",
Expand Down Expand Up @@ -167,12 +168,13 @@
"boatstack/runtime_cache.go": "e026ffc1906f7e1e98b768bae63e6658164d2826c07169c9121ce0f23c73faf8",
"boatstack/runtime_cache_test.go": "b981467ddc9f0f562da6bff5de7a80a9fe5a433a0317541d1e48df268546ac85",
"boatstack/runtime_provenance_test.go": "1d52f1e6b0691cf4667729cc9b9f3c55c128f0aa3321f3a2843a9aa6fd0e73dc",
"boatstack/safety.go": "405782eba91a1718a061faa65ff52c10d345cbf3bccf5ecd2cb8ed45d5df9c00",
"boatstack/safety.go": "0d04805e834c9cf10ba59b823fa1dd194c226fdfcd0d67889953cb38b6305653",
"boatstack/safety_corpus_test.go": "e7d8c493d8cee957e4590f2c9034d4aa4af08bdcbe02c9889d0d98a0168bbcf9",
"boatstack/safety_test.go": "500ad53cd5e3a700553eb781d9eaf4028ae27478796759a76bfd57321fff5a7c",
"boatstack/safety_update_publisher_test.go": "ed3f8187036623694dfe7c395cdae00fdae14609bab6124d1fdfc6fe73fa2196",
"boatstack/skill_frontmatter.go": "73364df463ce828c2d005aab55f72bb92f7a34d99cf3f53d4e0cd5a4da9dbd0e",
"boatstack/skill_frontmatter_test.go": "a3ec52e7df357a72265c95dd66db15d9c0effc7e5f90f14ce69c27792ce394eb",
"boatstack/solution_closure_conformance_test.go": "f73e6748dac373e2a10bc9269c2f2e060bd220bb4113bd0d5ff66ca4c8e91a54",
"boatstack/statemap.go": "39ff3a7a7254ec5fde8340551fa92a82aac3f00a48bea34c94192823dcb41337",
"boatstack/statemap_conformance_test.go": "38950377d10b97f4223b79cdb16b17c29a6334f8f2a9a4d826ce12cbd292aa57",
"boatstack/supervisory_control_test.go": "c7ea4bcd678e8ec211dac772c834981c4e21762914be2770a5e181bc24605e06",
Expand All @@ -197,10 +199,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "060775c73431f28bd16066bdf9e0f89034d2855c7ca0f5544f660d24b91211d0",
"docs/evidence-engineered-coding.md": "8722b287bb91f5fd0209d61f0594672ab026ebe9f8f70b65f8df5214c4451d95",
"docs/evidence-engineered-coding.md": "d8fce9c9a21e9ddb382811bfe397edc526360c1ebe4e8ec4f5e603436e1082b8",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
"docs/public-claims.json": "6e804e45d843599b44b7f7733064ebf1a7799afc5debe7b694b5a0d4b1298edd",
"docs/public-claims.json": "4932a0b1c1a59b3d75fa3facc206b8ba94720079b7de8f1c01ab10c0458d3df4",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -214,7 +216,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": "2c2fbf7ea885339dad90e362725a2ffe8b0edec513d90f4bb5de5c7feede47ea",
"labs/diagram-json/plan.lock.json": "76555eec4fcf58509beb849db669a1a924d0c9f123a784b51876ed1f9a92fd34",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -333,6 +335,7 @@
"release-notes/2026-07-27-discoverable-planning-errors.md": "d8099d1a6cd1805c3fcd446d9fa95739dec93e57ff421ecf4f99562b143c9966",
"release-notes/2026-07-27-document-content-is-data.md": "c6a35c222bf53ba465fadf21e7e95e1764b52e12e41852e51584ce9cb6513f4a",
"release-notes/2026-07-27-first-planning-write-owned-channel.md": "7a37e7abf7fd5f8612aca4323d55af1748c9e668bb294518619a1c39e195309f",
"release-notes/2026-07-27-flow-solution-set.md": "e138c333d515f8d2b7003334353ade2203b752cf726f2ec3158c657310b00014",
"release-notes/2026-07-27-guard-dual-reward-corpus.md": "6bec0385c6c553f00517259821e502796ca1b1907aeab718a287560e3e0fa0d6",
"release-notes/2026-07-27-helper-rendered-next-response.md": "08f53d8ade77c0ed6ffe7b63330c67ec0b88a5c71a466ed103f3b66d0952e4e2",
"release-notes/2026-07-27-invalid-delivery-block-actionable.md": "8fac8e3921e2285291703efa46e624b72cb5bac1b8492beca4c4b633abb5ba16",
Expand All @@ -344,7 +347,7 @@
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "7f71d4816931f34405b6c9a6bccca0eb4e29021f",
"commit": "6f60e1b420c8236479d1ed76d755372de52620fc",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
113 changes: 92 additions & 21 deletions boatstack/flow_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ const (
MarkerPlanningCheckPlan = deliverycontrol.TransitionID("planning.check_plan")
MarkerPlanningActivate = deliverycontrol.TransitionID("planning.activate")
MarkerPlanningWorkspace = deliverycontrol.TransitionID("planning.workspace_cut")
MarkerPlanningWrite = deliverycontrol.TransitionID("planning.planning_write")
MarkerPlanningApproval = deliverycontrol.TransitionID("planning.record_approval")
MarkerRecoveryDoctor = deliverycontrol.TransitionID("recovery.doctor")
MarkerRecoveryDiscard = deliverycontrol.TransitionID("recovery.discard_delivery")
MarkerRecoveryRepair = deliverycontrol.TransitionID("recovery.repair_state")
Expand Down Expand Up @@ -130,6 +132,12 @@ type FlowNext struct {
// completion state and prescribes no command — coding work is never a modeled
// transition, only an ordered pointer.
SubAction *FlowTask `json:"sub_action,omitempty"`
// Alternatives are the other admissible next commands from this position —
// the computed solution set minus the single Prescribed primary. They let a
// caller PICK a legal move instead of deriving one from the law's prose.
// Advisory, never a second primary: the rendering keeps exactly one Run line.
// control-law: solution-set-derives-from-guard-declarations
Alternatives []PrescribedCommand `json:"alternatives,omitempty"`
}

// PrescribedCommand is the exact next command that makes the oracle's lowest-cost
Expand Down Expand Up @@ -188,9 +196,30 @@ func prescribeCommand(repo, feature string, status NextStatus, transition delive
preview := filepath.Join(WorkspaceFor(repo).GeneratedRoot(), "features", feature, "pr.md")
cmd.Args = append(repoArgs, "--preview", preview, "--action", "open")
cmd.RequiresHumanInput = []string{"--preview-fingerprint"}
case deliverycontrol.TransitionID("delivery.record_change"):
if feature == "" {
return nil, false
}
// Rework: the correction facts (what changed, where it was observed, and
// its classification) are human knowledge; owe them, never fabricate them.
cmd.Args = append(repoArgs, "--feature", feature)
if status.ActiveSlice != "" {
cmd.Args = append(cmd.Args, "--slice", status.ActiveSlice)
}
cmd.RequiresHumanInput = []string{"--message", "--source-stage", "--classification"}
case deliverycontrol.TransitionID("delivery.undo"):
// The mutation id names WHICH receipt to reverse — a human decision.
cmd.Args = repoArgs
cmd.RequiresHumanInput = []string{"--mutation"}
case deliverycontrol.TransitionID("delivery.discard_delivery"):
if feature == "" {
return nil, false
}
cmd.Args = append(repoArgs, "--feature", feature)
default:
// Recovery/observe/rework transitions are not prescribed as a forward move;
// emit nothing rather than a command whose arguments we cannot derive.
// Recovery/observe transitions outside the set above are not prescribed as
// a forward move; emit nothing rather than a command whose arguments we
// cannot derive.
return nil, false
}
cmd.AutoDerivable = len(cmd.RequiresHumanInput) == 0
Expand All @@ -209,12 +238,18 @@ func prescribeCommand(repo, feature string, status NextStatus, transition delive
// state"), not an execution grant: markers are off the auto-drive allowlist
// and have no executor, so the driver always prescribes-and-stops on them.
// control-law: prescriptive-closure-every-stage-names-a-runnable-command
// planningFeatureDir is the single joined form of a feature's planning
// directory used by the prescription layer and the solution-set enumerator.
func planningFeatureDir(repo, feature string) string {
return filepath.Join(repo, ".product-loop", "features", feature)
}

func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, string) {
var repoArgs []string
if repo != "" && repo != "." {
repoArgs = []string{"--repo", repo}
}
featureDir := filepath.Join(repo, ".product-loop", "features", status.Feature)
featureDir := planningFeatureDir(repo, status.Feature)
finish := func(cmd *PrescribedCommand, followUp string) (*PrescribedCommand, string) {
cmd.AutoDerivable = len(cmd.RequiresHumanInput) == 0
return cmd, followUp
Expand All @@ -233,32 +268,19 @@ func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, stri
}, "Then run auto-plan with the validated SOURCE_PLAN path; author every feature artifact through `boatstack-helper planning-write` (document on stdin).")
case "DRAFT_PLAN":
return finish(&PrescribedCommand{
Verb: "check-plan",
Args: []string{"--plan", filepath.Join(featureDir, "plan.md")},
Verb: "check-plan",
Args: []string{"--plan", filepath.Join(featureDir, "plan.md")},
Transition: MarkerPlanningCheckPlan,
}, "After the check passes, present the plan for approval and record it with `record-approval` using the exact PLAN_FINGERPRINT it printed.")
case "APPROVED", "POLICY_READY":
// ResolveNext already ordered the move: a fresh workspace cut when one is
// needed, otherwise activation. "build" is an operation name, not a verb;
// activate-plan is the build operation's first concrete command.
if status.NextOperation == "workspace-cut" {
return finish(&PrescribedCommand{
Verb: "workspace-cut",
Args: append(repoArgs, "--feature", status.Feature),
Transition: MarkerPlanningWorkspace,
}, "Then activate the plan from the fresh workspace with `activate-plan`.")
return finish(buildWorkspaceCut(repoArgs, status.Feature),
"Then activate the plan from the fresh workspace with `activate-plan`.")
}
args := []string{
"--plan", filepath.Join(featureDir, "plan.md"),
"--out-dir", filepath.Join(featureDir, "compiled"),
"--output", filepath.Join(featureDir, "plan.lock.json"),
}
if status.ObservedStage == "APPROVED" {
args = append(args, "--approval", filepath.Join(featureDir, "approval.md"))
}
return finish(&PrescribedCommand{
Verb: "activate-plan", Args: args, Transition: MarkerPlanningActivate,
}, "")
return finish(buildActivatePlan(featureDir, status.ObservedStage), "")
case "INVALID_STATE":
switch status.NextOperation {
case "doctor":
Expand Down Expand Up @@ -296,6 +318,30 @@ func prescribePlanning(repo string, status NextStatus) (*PrescribedCommand, stri
}
}

// buildWorkspaceCut and buildActivatePlan are the single assembly points for
// their commands, shared by prescribePlanning (the primary) and the solution-set
// enumerator (the alternatives) so the two can never drift apart.
// control-law: solution-set-derives-from-guard-declarations
func buildWorkspaceCut(repoArgs []string, feature string) *PrescribedCommand {
return &PrescribedCommand{
Verb: "workspace-cut",
Args: append(append([]string{}, repoArgs...), "--feature", feature),
Transition: MarkerPlanningWorkspace,
}
}

func buildActivatePlan(featureDir, stage string) *PrescribedCommand {
args := []string{
"--plan", filepath.Join(featureDir, "plan.md"),
"--out-dir", filepath.Join(featureDir, "compiled"),
"--output", filepath.Join(featureDir, "plan.lock.json"),
}
if stage == "APPROVED" {
args = append(args, "--approval", filepath.Join(featureDir, "approval.md"))
}
return &PrescribedCommand{Verb: "activate-plan", Args: args, Transition: MarkerPlanningActivate}
}

// NextControl composes the authoritative read-only recommendation (ResolveNext)
// with the deterministic oracle to advise the lowest-cost next move toward a
// published delivery. It performs no mutation and is safe to call at any time.
Expand Down Expand Up @@ -330,6 +376,7 @@ func nextControlFromStatus(repo string, status NextStatus) (FlowNext, error) {
out.Prescribed = cmd
out.FollowUp = followUp
}
out.Alternatives = alternativesFor(repo, status, out)
return out, nil
}
out.State = state
Expand All @@ -354,6 +401,7 @@ func nextControlFromStatus(repo string, status NextStatus) (FlowNext, error) {
}
}
}
out.Alternatives = alternativesFor(repo, status, out)
return out, nil
}

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

// writeAlternatives renders the solution set's other legal moves as ONE line of
// verbs with a short purpose gloss — never a second Run line, so the response
// contract's single primary action holds.
// control-law: solution-set-derives-from-guard-declarations
func writeAlternatives(b *strings.Builder, alternatives []PrescribedCommand) {
if len(alternatives) == 0 {
return
}
shown := alternatives
if len(shown) > solutionSetTextCap {
shown = shown[:solutionSetTextCap]
}
labels := make([]string, 0, len(shown))
for _, alt := range shown {
labels = append(labels, alt.Verb+" ("+solutionGloss(alt.Transition)+")")
}
fmt.Fprintf(b, "Also legal from here: %s\n", strings.Join(labels, ", "))
if len(alternatives) > len(shown) {
fmt.Fprintf(b, " (%d more in `flow next --json` under alternatives)\n", len(alternatives)-len(shown))
}
}

// writePrescribed renders the Run line and its owed-input annotation for a
// prescribed command, shared by the oracle and pre-activation branches.
func writePrescribed(b *strings.Builder, p *PrescribedCommand) {
Expand Down
Loading
Loading