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/cd4c03277c5812f8792cf6009e1a9bb8b0ec5f56/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/c550ef95f440e8b463ae0d436953e10ad91e1ab6/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
25 changes: 14 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": "be449f84d39fec8cd745cf5613ede48abcc853ca2ec20fff9951ef0a0a707e90",
"CONTRIBUTING.md": "370fe1191864a3a3b4acd5ca4bc22807f70da794743f376f992a140c534e0619",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -54,7 +54,7 @@
"boatstack/context.go": "02510af176d2d040c0080086f06d1235e76a1d47fef5176f96f740ad18d27660",
"boatstack/decision.go": "257ca328da6ae19ab252f10ee5d06bd7daf49dd8141d083ab1b32f106ea7a94c",
"boatstack/decision_test.go": "1a92ff832610f9559bd47ccac7fc1755a8b4f8261c35bc72a092830dff05f7c0",
"boatstack/delivery.go": "a9a266d0413b25276d9bcfeb8328ac3ca4b3020ada1f97912251f68b4a9b5019",
"boatstack/delivery.go": "f26db7386b94b551229fb90c17dfb1cd6967d3693155495387aaed299cb8ff88",
"boatstack/delivery_boundary_conformance_test.go": "53dde765046420b9119e82034d137742e600019938ed908c608f725d8a0c84c6",
"boatstack/delivery_migrate.go": "7566e49f9c1838d4d563866e941c7aacd61ac918c9e886222282398d287ca780",
"boatstack/delivery_migrate_conformance_test.go": "b8ba53681e1d0361ac62b06586c62b7763d55a65b5427976b5289e1fb1503bdc",
Expand All @@ -77,11 +77,11 @@
"boatstack/export_test.go": "dce5aa3ab5499c82d05859cf86b46dfcee308482491366d83e10ca3fb8605bb6",
"boatstack/flow_coding.go": "9fa53a0204f98a25f97775c3acf37392a591c14ce850b44aa587b5806e770bb9",
"boatstack/flow_coding_test.go": "dddcd7a85892d4fa10af42739d4c1ff265721b0313e27b6e7a1bbb019d5c3b51",
"boatstack/flow_control.go": "105556b619c83f5f68fe4d98cd4763745fa0dafadf4ba6569a6bab31896e58a4",
"boatstack/flow_control.go": "3105375ee3ca0e100aa61c5ccd74b7f49ae4aa73385e32c7f17b15e6dcc7f3fe",
"boatstack/flow_control_test.go": "02d788c83be55ebd79ffc73875bfd019de45325151eb1f70f506980eb8e77f29",
"boatstack/flow_drive.go": "90f57e178884aff017195a126954ac0aeb85f27707b9d42844323341dc1fefd9",
"boatstack/flow_drive_conformance_test.go": "23edea926c271a1f5718fb9dae1da11e4bf03cceb1357290cd61cd8ffb73beda",
"boatstack/flow_frontier.go": "64304ff8aa7f662d166ac3ae9d54cfd32caf35458101c3019cedd8b8326ca5b4",
"boatstack/flow_frontier.go": "57e860c1613f3d5cca2fde56fb6e7dad527a71ac148ee3617ceb4bccd9cf805a",
"boatstack/flow_frontier_conformance_test.go": "771838f06d6157547c1277eca1f7c1df609bb9f3ae630b2fa509f641b49aba86",
"boatstack/flow_guard.go": "dd18524d95f4a220cfd3d11b11003dacc52120785ee0ccdbeceb2307fab55872",
"boatstack/flow_guard_test.go": "8ba75f11ddd080427c15bd7e25f7d03c1b746a2f587c212cea0e710337d1c0e1",
Expand All @@ -94,10 +94,12 @@
"boatstack/flow_tasks_conformance_test.go": "fbc4d672536051f8e20a2e6c07c5b10f78cd84fc04765eee11cbed7a459b8e4b",
"boatstack/flow_trace.go": "1a69f9dd53db313235c74f7ae4f821a6aed023f780aea57210c721ea8f11f2fc",
"boatstack/flow_trace_test.go": "99f89a831e904f6a8ef710b6977ed3a808ce1c7ddfaba457b292d84f2ddca51b",
"boatstack/flow_watch.go": "3baca52f0e2a0e4e4ad90f5f6f30369fe28f382efbdf0e7f2153dafba2538051",
"boatstack/flow_watch.go": "3bf029c8a700636256f698dabd79c9ea20f89891d940e4698ea43e51522b8c41",
"boatstack/flow_watch_conformance_test.go": "aba71d94844a23bbd0be4bede038e006ae5f2eaaf5df662b8eb96fcc0572461b",
"boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641",
"boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017",
"boatstack/goal_escape.go": "394b9496f01d91c8158a9bdad235081363023b92ee90c1ca0e1f683a108e0ed3",
"boatstack/goal_escape_conformance_test.go": "ec340660251c892fc6b7b7f813e6b3fe321b0429f094ba9110a26a205cd1d554",
"boatstack/hooks.go": "a3881c69dd88025c914ebaaa43362a2b1c47565f0ec16074e078d16cb6cfb853",
"boatstack/hooks_hydrate_test.go": "7beeb26b2b1398741e8a28963a9686e974047016cc736f233024004add1afc32",
"boatstack/hooks_test.go": "fb75e3aabf2204871b3e6d16de98d26fb33b0ec19e41aae761cf1f34397c31f4",
Expand All @@ -119,7 +121,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": "d77c9ceea1feb576b857c3d1f4fc517afbf3f38cb43525a8a139206a6802980d",
"boatstack/internal/deliverycontrol/registry.go": "0c02a146b5590dad0e81a069717363a0d7ee9b4627d350fbe4656d191cb27219",
"boatstack/internal/deliverycontrol/registry_test.go": "473ab5e5d33f84d34c29a219db867abfc6eb3ad4489f3d5d0c7dc09b06d193f3",
"boatstack/internal/deliverycontrol/state.go": "2551624bbcbd8f9dd897a1e2240cef2cc1895d117a4030525d88f1d62f6e395e",
"boatstack/internal/deliverycontrol/trajectory.go": "4469a0c35b40f8e2a37060e9b26fcbda7d34d020088c31de367dd5cf6e7721dc",
Expand All @@ -135,7 +137,7 @@
"boatstack/mutation_test.go": "68d5049c7f96c1ac558e4c781151f67e8deee2f8d6b9bf293b90d44e769ef7c6",
"boatstack/mutation_undo.go": "697d11b600a276ddbcabe6a9f8040d4f7283e017a0e8fd689ef53a274638946c",
"boatstack/mutation_undo_test.go": "39540e717e3f2136bf975594043a3db9072b28ebe61c6cb0b982cea5e8b1e14e",
"boatstack/next.go": "e45b5e573616e6ac7616503d5b9b50ad6f9f9ee51b57f6bc90436b3375c5f338",
"boatstack/next.go": "d66e9303c52cac43a1ecc928b64eeedb5a596de271f0f214084b92a270b53dcc",
"boatstack/next_actor_conformance_test.go": "23c055bcc99d889344c3f86c7940eb9ef2ef6f4ddab34e91cf215c9d078f5d42",
"boatstack/next_banner_test.go": "c431a6987ed1e479442fc9f5db4371632880b92aa790fa9dd0f5285293352c41",
"boatstack/next_response.go": "11decf2e3b236cbaa183980946ec17ffbbbb1af9c08bd11a466a8487bf229d5f",
Expand Down Expand Up @@ -213,10 +215,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "221f979506a3a9de357e5277f1329c345bf175346ec8dfc8fdd1212fb100dea1",
"docs/evidence-engineered-coding.md": "1abb6b48dfa4bd0f8f4d736b3af9a757c20af6890bbc9b308510bb426f9af98a",
"docs/evidence-engineered-coding.md": "42c0efc8f5947b4b21f7f99ea8b86a303025bf3760b8fe17caf07ed47acfe609",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
"docs/public-claims.json": "b1a5d44604a96fa5bc5e1d465c2091261e1aa2d7f32156b33df3c4a58f60effc",
"docs/public-claims.json": "b47e678c1a830a0bd6ceea4fdc3fae7b55794099770df61f6819b9802212f561",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -230,7 +232,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": "01abc3857b88e21028c0ca68d4769369a0141830f5377cbf87041dee6bf8cb22",
"labs/diagram-json/plan.lock.json": "77ff19d1baf7a9872a2e0e052a1b02fdc804607eff4106bc677cf70c995e970e",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -359,6 +361,7 @@
"release-notes/2026-07-27-repeated-denials-escalate.md": "ee54b597e593ce74d8d9acbc67c74d2d8cb972ff206f618e08a047d4d962d7af",
"release-notes/2026-07-27-sandboxed-migration-grading.md": "03cebc372bbdfed37cc70d18f3b6374d1aa5e585bafefa073dbcced58bd0336a",
"release-notes/2026-07-27-state-ownership-map.md": "d032547aafc1a4acbeb520f6cbb59d7757de4f33fe824701d7b5ea8cd8c8b9e7",
"release-notes/2026-07-28-bounded-merge-pursuit.md": "a1f124b7cd5b4c495786d5bc1734f4477fa7ae5d9f992bb4ebc9e7e087e8b4f3",
"release-notes/2026-07-28-configurable-delivery-terminal.md": "54f42d8ee04f16429f7f6db484c12247a75dd365d9028ec6c24b2efb92f9e700",
"release-notes/2026-07-28-flow-frontier-dashboard.md": "9a768e0fd67f122bc0aef99899314f0a8530189164fb560b536ee03f20e29081",
"release-notes/2026-07-28-flow-watch-loop.md": "54833887e733c65626c3bb7f0e6430eca8028f0fe19aa2cd31cdbdcc6ff4d8ba",
Expand All @@ -372,7 +375,7 @@
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "cd4c03277c5812f8792cf6009e1a9bb8b0ec5f56",
"commit": "c550ef95f440e8b463ae0d436953e10ad91e1ab6",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
23 changes: 23 additions & 0 deletions boatstack/delivery.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ type DeliverySlice struct {
// updatable in place while non-terminal; once terminal, in-place correction
// is refused and a corrective child delivery is the bounded forward actuator.
PRState string `json:"pr_state,omitempty"`
// PostPublishFixAttempts counts the post-publish correction cycles
// recorded against this published slice, and GoalEscape caches a fired
// merged-goal demotion (sticky offline until the next recorded correction
// clears it). Both are written only under delivery.terminal "merged"; a
// default-terminal state file never carries them.
// control-law: goal-escape-demotes-to-operator-and-stops
PostPublishFixAttempts int `json:"post_publish_fix_attempts,omitempty"`
GoalEscape string `json:"goal_escape,omitempty"`
}

type DeliveryState struct {
Expand Down Expand Up @@ -654,7 +662,19 @@ func RecordChangeObservation(options ChangeObservationOptions) (ChangeObservatio
if err := appendChangeObservation(repo, observation); err != nil {
return ChangeObservation{}, DeliveryState{}, err
}
// Under the merged terminal, a recorded post-publish correction advances
// the targeted published slice's fix-cycle bookkeeping (and, after an
// escape, is the operator's explicit reset for a fresh cycle). The
// published default records nothing — its state files stay byte-stable.
// control-law: goal-escape-demotes-to-operator-and-stops
trackPostPublishCycle := resolveDeliveryTerminal(repo, options.Feature) == TerminalMerged
if published {
if trackPostPublishCycle && len(state.Slices) > 0 {
bumpPostPublishFixCycle(&state.Slices[len(state.Slices)-1])
if err := saveDeliveryState(repo, state); err != nil {
return ChangeObservation{}, DeliveryState{}, err
}
}
return observation, state, nil
}
if publishedOpen {
Expand Down Expand Up @@ -683,6 +703,9 @@ func RecordChangeObservation(options ChangeObservationOptions) (ChangeObservatio
} else {
slice.Status = StatusBuild
}
if trackPostPublishCycle {
bumpPostPublishFixCycle(slice)
}
if err := saveDeliveryState(repo, state); err != nil {
return ChangeObservation{}, DeliveryState{}, err
}
Expand Down
10 changes: 9 additions & 1 deletion boatstack/flow_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ func classifyNextActor(status NextStatus, next FlowNext) NextActor {
// unknown position — stays the operator's. Fail-closed: the zero
// Terminal behaves as published.
// control-law: turn-ends-only-at-the-operator-frontier
if next.Terminal == TerminalMerged {
// A fired goal escape demotes unconditionally: the pursuit contract
// ended, so no phase can hand the step back to the agent.
// control-law: goal-escape-demotes-to-operator-and-stops
if next.Terminal == TerminalMerged && status.GoalEscape == "" {
switch PRPhase(status.PRPhase) {
case PRPhaseChecksPending, PRPhaseChecksFailing, PRPhaseMergeEligible:
return NextActorAgent
Expand Down Expand Up @@ -457,6 +460,11 @@ func prescribePostPublish(repo string, status NextStatus, terminal DeliveryTermi
if terminal != TerminalMerged || status.ObservedStage != "PUBLISHED" || status.Lifecycle == "PUBLISHED_MERGED" {
return nil, ""
}
// A fired escape prescribes nothing: demote-and-stop, never
// demote-and-suggest. control-law: goal-escape-demotes-to-operator-and-stops
if status.GoalEscape != "" {
return nil, ""
}
var repoArgs []string
if repo != "" && repo != "." {
repoArgs = []string{"--repo", repo}
Expand Down
9 changes: 7 additions & 2 deletions boatstack/flow_frontier.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type FrontierRow struct {
TotalSlices int `json:"total_slices,omitempty"`
Stage string `json:"stage"`
Lifecycle string `json:"lifecycle,omitempty"`
GoalEscape string `json:"goal_escape,omitempty"`
PRPhase string `json:"pr_phase,omitempty"`
PRFailingChecks []string `json:"pr_failing_checks,omitempty"`
PRURL string `json:"pr_url,omitempty"`
Expand Down Expand Up @@ -85,7 +86,7 @@ func ResolveFrontier(repoPath string) (FlowFrontier, error) {
continue
}
branch, _, prURL := deliveryBranchAndSlice(state)
status := publishedNextStatus(state, observePRTarget(repo, prURL, branch))
status := publishedNextStatus(state, observePRTarget(repo, prURL, branch), resolveDeliveryTerminal(repo, state.Feature))
frontier.Rows = append(frontier.Rows, frontierRowFromStatus(repo, status))
}
for _, row := range frontier.Rows {
Expand Down Expand Up @@ -139,6 +140,9 @@ func activeDeliveryRows(repo string, state DeliveryState) []FrontierRow {
PRMergeState: observation.MergeState, PRFailingChecks: observation.FailingChecks,
Reason: fmt.Sprintf("Slice %q is published with an open pull request while a later slice is active.", slice.ID),
}
if resolveDeliveryTerminal(repo, state.Feature) == TerminalMerged && observation.Lifecycle != "PUBLISHED_MERGED" {
sliceStatus.GoalEscape = evaluateGoalEscape(slice, observation)
}
rows = append(rows, frontierRowFromStatus(repo, sliceStatus))
}
return rows
Expand All @@ -153,7 +157,8 @@ func frontierRowFromStatus(repo string, status NextStatus) FrontierRow {
Feature: status.Feature, Slice: status.ActiveSlice,
SliceIndex: status.SliceIndex, TotalSlices: status.TotalSlices,
Stage: status.ObservedStage, Lifecycle: status.Lifecycle,
PRPhase: status.PRPhase, PRFailingChecks: status.PRFailingChecks,
GoalEscape: status.GoalEscape,
PRPhase: status.PRPhase, PRFailingChecks: status.PRFailingChecks,
PRURL: status.PRURL, NextOperation: status.NextOperation,
Reason: status.Reason,
Blocked: status.VerificationStatus == "BLOCKED",
Expand Down
1 change: 1 addition & 0 deletions boatstack/flow_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func frontierSignatures(frontier FlowFrontier) map[string]string {
key := row.Feature + "/" + row.Slice
signatures[key] = strings.Join([]string{
row.Stage, row.Lifecycle, row.PRPhase, row.Actor, row.NextOperation,
row.GoalEscape,
fmt.Sprintf("blocked=%t", row.Blocked),
strings.Join(row.PRFailingChecks, "|"),
}, "·")
Expand Down
Loading
Loading