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/c25c282a5d9b5f952caf16e99e22749b70a92d78/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/49a684f6752d1f017281d7bbb8b17b856904df15/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
17 changes: 12 additions & 5 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "0a0dcec357890e5d6b853c1c15f7fb40f5f3902024c445a62f23ef0c48f02a00",
"CONTRIBUTING.md": "3fea013e59482851b40d6c70734457600b421a021ef679c630bbee0cda19c17d",
"README.md": "6b7402c5cef5b3b9b739281d3d4d576cdc995796ff127fc6aefb97c5743e0bac",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
Expand Down Expand Up @@ -52,6 +52,7 @@
"boatstack/delivery_boundary_conformance_test.go": "800cd722d8d2a696a0529e8343d3523e453bb052f0917c8a2cad2990296ac1b3",
"boatstack/delivery_reactivation_test.go": "573a2dba0034bc4290478414e3bdd8670b06a326128eb0295d77e748ecc8689e",
"boatstack/delivery_test.go": "45c48ff7581c911bcaf821c3e4241d4ae2a9bb4aa682485cc58b6ad8fe1c85bf",
"boatstack/deliverycontrol_parity_test.go": "027c04471c6037fc585a8af8646171addcc548e43147c5c6dbbe76bad9f5a10c",
"boatstack/evidence.go": "497a31e6ff632cb1d7c3adfc9f269af3f6aa84e948dd5d417c162767542a27df",
"boatstack/export.go": "9cb23234e6cd79441ff6f39f88ed66d6d47ef7c27901404439a3572b03fdf881",
"boatstack/export_test.go": "dce5aa3ab5499c82d05859cf86b46dfcee308482491366d83e10ca3fb8605bb6",
Expand All @@ -67,6 +68,11 @@
"boatstack/installation_repair.go": "6574f7133a9644843c9260b9b9daede641a14438f7357bae42fb8ec188890446",
"boatstack/installation_repair_test.go": "ae5a5ea1110836bd78cf20ade863a4d32cfd63d282559f92786f57b31869bd14",
"boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c",
"boatstack/internal/deliverycontrol/cost.go": "a0a22292b8ed55cbfce9808599449d5128ae5b67ef6adc4881e604db0897f3f0",
"boatstack/internal/deliverycontrol/registry.go": "aa89cef9eec8d715c06d2f61a472df20bb9334c950a6d751a1e15d67b567c433",
"boatstack/internal/deliverycontrol/registry_test.go": "473ab5e5d33f84d34c29a219db867abfc6eb3ad4489f3d5d0c7dc09b06d193f3",
"boatstack/internal/deliverycontrol/state.go": "2551624bbcbd8f9dd897a1e2240cef2cc1895d117a4030525d88f1d62f6e395e",
"boatstack/internal/deliverycontrol/transition.go": "b43abb0e99d29697b27b0bb8ee2e2f5f31f3471a2983f25d18ae3564ee246775",
"boatstack/migrate.go": "eaf589e2b266238068e42c6d78e01dc040266d28e342cb24f09e33e8541749b3",
"boatstack/migrate_test.go": "9f4bda2fb158c5e54bcc0242dace1da3c1965f9846a213c573956a35b7d1724e",
"boatstack/mutation.go": "59fc9e92105d8ec20f854af9898cde037ab0e3e46c453794838dbfc65fecdd6d",
Expand Down Expand Up @@ -136,10 +142,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "df054f49d532c8b1b7d94184810d1b3b5bf18cdc30eb985b4b6d0639162e341a",
"docs/evidence-engineered-coding.md": "2e21eeb6f2cc73fc667ded5163c3e6005f30993f72a181bcad5d205564686e0d",
"docs/evidence-engineered-coding.md": "b8d35dc38d3e9385972fdd81595eb5245367b4e7e55a89363b5f6b53cce73977",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "1dd4f4e2e636cc5adfc2f79939629701e171087c3d5e558cf919548b9224adfd",
"docs/public-claims.json": "04c1a4898228049b9e586f8db3af7c2ad13a01846c20c1b736e17005edeba12f",
"docs/public-claims.json": "57e7d04dd7141b62e89ff111ced83674b6e9ad9094fa71a96da3dcb723ffc8f5",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -153,7 +159,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": "5b2f60c8a1c7921f7513f25591cca82f42c5b1ca4e2380964f2dc989b969cafa",
"labs/diagram-json/plan.lock.json": "1ddebf9603a1058b81ea2f02a7314bbba1a46a82a32d59bf3f3721cf74a85228",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -237,14 +243,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-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",
"release-notes/2026-07-25-root-cause-operation.md": "5bf1f082e9123c5a7bcc8bc01b12e97b24b5ae15958577b4ff2a358994fca891"
},
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "c25c282a5d9b5f952caf16e99e22749b70a92d78",
"commit": "49a684f6752d1f017281d7bbb8b17b856904df15",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
65 changes: 65 additions & 0 deletions boatstack/deliverycontrol_parity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package boatstack

import (
"testing"

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

// control-law: registry-mirrors-real-transitions
// The deliverycontrol registry is a second projection of the real delivery
// state machine in this package. These tests are the boundary that keeps the two
// from drifting: every HandlerRef must name a real exported function here, and
// the registry's slice-status states must equal the DeliverySlice.Status
// literals the real machine uses.

// realDeliveryHandlers maps the exported functions the registry may reference to
// their values. Referencing the values makes this fail to COMPILE if any handler
// is renamed or removed, so a registry HandlerRef can never point at a function
// that no longer exists.
var realDeliveryHandlers = map[string]any{
"ActivatePlan": ActivatePlan,
"RecordDeliveryGate": RecordDeliveryGate,
"RecordChangeObservation": RecordChangeObservation,
"PublishPR": PublishPR,
"UndoManagedMutation": UndoManagedMutation,
"DiscardDelivery": DiscardDelivery,
"RepairState": RepairState,
"IgnoreDelivery": IgnoreDelivery,
"CurrentDeliveryState": CurrentDeliveryState,
"CheckDeliveryReadyForShip": CheckDeliveryReadyForShip,
"ResolveNext": ResolveNext,
"ResolveRecovery": ResolveRecovery,
}

func TestRegistryHandlerRefsAreRealFunctions(t *testing.T) {
for _, tr := range deliverycontrol.Transitions() {
if _, ok := realDeliveryHandlers[tr.HandlerRef]; !ok {
t.Errorf("transition %s references handler %q which is not a known real delivery function", tr.ID, tr.HandlerRef)
}
}
}

func TestRegistrySliceStatusMatchesRealLiterals(t *testing.T) {
// The real slice lifecycle literals, from DeliverySlice.Status assignments in
// delivery.go and the nextForDelivery switch in next.go. If the real machine
// gains or renames a slice status, update both the machine and the registry.
realLiterals := map[string]bool{
"PENDING": true, "BUILD": true, "TEST_PASSED": true,
"REVIEW_PASSED": true, "PUBLISHED": true,
}
registryStatus := map[string]bool{}
for _, s := range deliverycontrol.SliceStatusStates() {
registryStatus[string(s)] = true
}
for lit := range realLiterals {
if !registryStatus[lit] {
t.Errorf("registry SliceStatusStates is missing real literal %q", lit)
}
}
for s := range registryStatus {
if !realLiterals[s] {
t.Errorf("registry declares slice-status %q that the real machine does not use", s)
}
}
}
26 changes: 26 additions & 0 deletions boatstack/internal/deliverycontrol/cost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package deliverycontrol

// FlowCostWeights maps a TransitionCostClass to its J_flow cost. Per the cmg
// model (../../notes/delivery-flow-navigation-model.md): a normal
// move/observe/inspect costs 1; a denied/blocked committed mutation is friction
// and costs 3 (it burns a turn and returns nothing).
type FlowCostWeights map[TransitionCostClass]int

// DefaultFlowCostWeights is the cost model the cmg prototype pins
// (composable-model-graph:python/examples/12-agent-trajectory/main.py).
func DefaultFlowCostWeights() FlowCostWeights {
return FlowCostWeights{
CostObserve: 1,
CostInspect: 1,
CostQuery: 1,
CostMutation: 1,
CostRecovery: 1,
CostFriction: 3,
}
}

// Cost returns the weight for a cost class and whether it is defined.
func (w FlowCostWeights) Cost(class TransitionCostClass) (int, bool) {
v, ok := w[class]
return v, ok
}
116 changes: 116 additions & 0 deletions boatstack/internal/deliverycontrol/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package deliverycontrol

// registry is the single declaration of Boatstack's delivery transitions,
// mirroring ../../notes/delivery-control-inventory.md. Each HandlerRef names a
// real exported function in package boatstack; the parity conformance test keeps
// this faithful. This is a shadow catalog — nothing consumes it at runtime yet.
var registry = []TransitionDescriptor{
{
ID: "delivery.activate", From: []StateID{StateUninitialized}, To: StateBuild,
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "ActivatePlan", CLIVerb: "activate-plan",
Note: "Requires CheckPlan + repository-safety PASS and a human/policy approval receipt; writes the plan lock via the mutation boundary. Reversible via delivery.undo while no gate receipt exists.",
},
{
ID: "delivery.record_gate_test", From: []StateID{StateBuild}, To: StateTestPassed,
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "RecordDeliveryGate", CLIVerb: "record-delivery-gate",
Note: "Records the test gate (PASS/PASS_WITH_GAPS) against a validated plan lock and a matching evidence ledger. Reversible via record-change re-gate.",
},
{
ID: "delivery.record_gate_review", From: []StateID{StateTestPassed}, To: StateReviewPassed,
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "RecordDeliveryGate", CLIVerb: "record-delivery-gate",
Note: "Records the review gate; requires prior TEST_PASSED with a matching diff and reviewer identity/method on high-risk paths. Clears rework mode.",
},
{
ID: "delivery.record_change", From: []StateID{StateTestPassed, StateReviewPassed, StatePublished}, To: StateBuild,
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "RecordChangeObservation", CLIVerb: "record-change",
Note: "Rework resets the addressable slice to BUILD (bounded by RepairAttempt<3); amendment/plan-invalid set Mode; a fully-published delivery emits a corrective child with no state mutation.",
},
{
ID: "delivery.publish", From: []StateID{StateReviewPassed, StatePublished}, To: StatePublished,
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: false,
HandlerRef: "PublishPR", CLIVerb: "publish-pr",
Note: "Publishes the reviewed slice behind a human-confirmed preview fingerprint and advances ActiveIndex to the next slice. Re-publishing a PUBLISHED-open slice is idempotent and does not advance the pointer.",
},
{
ID: "delivery.undo", From: []StateID{StateBuild}, To: StateUninitialized,
Kind: KindReversibleMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "UndoManagedMutation", CLIVerb: "undo",
Note: "Reverses a plan-activation/compiled-plan mutation through the boundary (closed under inversion, so redo is undo of the returned receipt). Refused once any gate receipt exists (would strand delivery state).",
},
{
ID: "delivery.discard_delivery", From: []StateID{StatePending, StateBuild, StateTestPassed, StateReviewPassed, StatePublished}, To: StateDiscarded,
Kind: KindReversibleMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "DiscardDelivery", CLIVerb: "discard-delivery",
Note: "Archives (never deletes) the delivery state directory. Refuses a slice with a set PRState unless --force; preserves published authority and git/lock/merged history.",
},
{
ID: "delivery.repair_state", From: []StateID{StateInvalid}, To: StateUninitialized,
Kind: KindRecovery, CostClass: CostRecovery, Reversible: true,
HandlerRef: "RepairState", CLIVerb: "repair-state",
Note: "Quarantines a malformed unregistered feature draft by moving it aside; refuses when a plan lock, pr.md, managed state, tracked files, or an active/published delivery is present. Typed and bounded — never a generic bypass.",
},
{
ID: "delivery.ignore_delivery", From: []StateID{StatePending, StateBuild, StateTestPassed, StateReviewPassed, StatePublished}, To: "",
Kind: KindCommittedMutation, CostClass: CostMutation, Reversible: true,
HandlerRef: "IgnoreDelivery", CLIVerb: "ignore-delivery",
Note: "Appends the feature to project.json workflow.ignored_deliveries, filtering it from ResolveNext and publication authority. Changes no slice status; reversible by removing the entry.",
},
{
ID: "delivery.status", From: []StateID{StateBuild, StateTestPassed, StateReviewPassed, StatePublished}, To: "",
Kind: KindObserve, CostClass: CostObserve, Reversible: false,
HandlerRef: "CurrentDeliveryState", CLIVerb: "delivery-status",
Note: "Reads delivery state and validates the plan lock. No state change.",
},
{
ID: "delivery.check_ship", From: []StateID{StateReviewPassed, StatePublished}, To: "",
Kind: KindQuery, CostClass: CostQuery, Reversible: false,
HandlerRef: "CheckDeliveryReadyForShip", CLIVerb: "ship-gate",
Note: "Re-checks receipt freshness and gate policy for the addressable slice and returns its PR sources. No state change.",
},
{
ID: "delivery.next", From: nil, To: "",
Kind: KindObserve, CostClass: CostObserve, Reversible: false,
HandlerRef: "ResolveNext", CLIVerb: "next-status",
Note: "Derives the recommended next move. Read-only, except that the published branch caches an observed terminal PRState as a best-effort side effect (a known bypass, modeled not fixed).",
},
{
ID: "delivery.recovery_status", From: []StateID{StateBuild, StateTestPassed, StateReviewPassed, StatePublished}, To: "",
Kind: KindObserve, CostClass: CostObserve, Reversible: false,
HandlerRef: "ResolveRecovery", CLIVerb: "recovery-status",
Note: "Derives a correction decision; carries no edit/approve/publish authority. Read-only, except the same best-effort terminal-PRState cache.",
},
}

// Transitions returns a copy of the declared transition set.
func Transitions() []TransitionDescriptor {
out := make([]TransitionDescriptor, len(registry))
copy(out, registry)
return out
}

// Transition returns the descriptor with the given ID.
func Transition(id TransitionID) (TransitionDescriptor, bool) {
for _, t := range registry {
if t.ID == id {
return t, true
}
}
return TransitionDescriptor{}, false
}

// HandlerRefs returns the distinct real function names the registry declares.
func HandlerRefs() []string {
seen := map[string]bool{}
var out []string
for _, t := range registry {
if !seen[t.HandlerRef] {
seen[t.HandlerRef] = true
out = append(out, t.HandlerRef)
}
}
return out
}
86 changes: 86 additions & 0 deletions boatstack/internal/deliverycontrol/registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package deliverycontrol

import "testing"

// control-law: deliverycontrol-registry-well-formed
// The single declaration must be internally consistent: unique ids, declared
// states on every edge, valid kinds/cost classes, and a defined cost weight for
// every class a transition uses.
func TestRegistryWellFormed(t *testing.T) {
states := map[StateID]bool{}
for _, s := range States() {
states[s] = true
}
kinds := map[TransitionKind]bool{}
for _, k := range AllKinds() {
kinds[k] = true
}
classes := map[TransitionCostClass]bool{}
for _, c := range AllCostClasses() {
classes[c] = true
}
weights := DefaultFlowCostWeights()

seen := map[TransitionID]bool{}
for _, tr := range Transitions() {
if tr.ID == "" {
t.Errorf("transition with empty ID: %+v", tr)
}
if seen[tr.ID] {
t.Errorf("duplicate transition ID %q", tr.ID)
}
seen[tr.ID] = true
if !kinds[tr.Kind] {
t.Errorf("%s: undeclared kind %q", tr.ID, tr.Kind)
}
if !classes[tr.CostClass] {
t.Errorf("%s: undeclared cost class %q", tr.ID, tr.CostClass)
}
if _, ok := weights.Cost(tr.CostClass); !ok {
t.Errorf("%s: cost class %q has no weight", tr.ID, tr.CostClass)
}
for _, from := range tr.From {
if !states[from] {
t.Errorf("%s: undeclared From state %q", tr.ID, from)
}
}
if tr.To != "" && !states[tr.To] {
t.Errorf("%s: undeclared To state %q", tr.ID, tr.To)
}
if tr.HandlerRef == "" {
t.Errorf("%s: empty HandlerRef", tr.ID)
}
}
if len(seen) == 0 {
t.Fatal("registry is empty")
}
}

// The cmg model only makes friction expensive; if friction ever costs no more
// than a move, regret vanishes and the whole model is meaningless.
func TestFrictionCostsMoreThanAMove(t *testing.T) {
w := DefaultFlowCostWeights()
move, ok := w.Cost(CostObserve)
if !ok {
t.Fatal("observe cost undefined")
}
friction, ok := w.Cost(CostFriction)
if !ok {
t.Fatal("friction cost undefined")
}
if friction <= move {
t.Errorf("friction (%d) must cost more than a move (%d)", friction, move)
}
}

func TestSliceStatusStatesAreDeclared(t *testing.T) {
declared := map[StateID]bool{}
for _, s := range States() {
declared[s] = true
}
for _, s := range SliceStatusStates() {
if !declared[s] {
t.Errorf("slice-status state %q is not in States()", s)
}
}
}
Loading
Loading