From b2e8bf2cd212f79c4c4e510a4f0d7f43f70fc537 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Sat, 25 Jul 2026 12:08:13 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 49a684f6752d --- CONTRIBUTING.md | 2 +- UPSTREAM.json | 17 ++- boatstack/deliverycontrol_parity_test.go | 65 ++++++++++ boatstack/internal/deliverycontrol/cost.go | 26 ++++ .../internal/deliverycontrol/registry.go | 116 ++++++++++++++++++ .../internal/deliverycontrol/registry_test.go | 86 +++++++++++++ boatstack/internal/deliverycontrol/state.go | 38 ++++++ .../internal/deliverycontrol/transition.go | 84 +++++++++++++ docs/evidence-engineered-coding.md | 2 +- docs/public-claims.json | 24 ++-- labs/diagram-json/plan.lock.json | 2 +- ...6-07-25-deliverycontrol-shadow-registry.md | 8 ++ 12 files changed, 450 insertions(+), 20 deletions(-) create mode 100644 boatstack/deliverycontrol_parity_test.go create mode 100644 boatstack/internal/deliverycontrol/cost.go create mode 100644 boatstack/internal/deliverycontrol/registry.go create mode 100644 boatstack/internal/deliverycontrol/registry_test.go create mode 100644 boatstack/internal/deliverycontrol/state.go create mode 100644 boatstack/internal/deliverycontrol/transition.go create mode 100644 release-notes/2026-07-25-deliverycontrol-shadow-registry.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f039ce..6ad29db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/UPSTREAM.json b/UPSTREAM.json index acafb3c..3b3611a 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -237,6 +243,7 @@ "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" @@ -244,7 +251,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "c25c282a5d9b5f952caf16e99e22749b70a92d78", + "commit": "49a684f6752d1f017281d7bbb8b17b856904df15", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/deliverycontrol_parity_test.go b/boatstack/deliverycontrol_parity_test.go new file mode 100644 index 0000000..2dc26df --- /dev/null +++ b/boatstack/deliverycontrol_parity_test.go @@ -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) + } + } +} diff --git a/boatstack/internal/deliverycontrol/cost.go b/boatstack/internal/deliverycontrol/cost.go new file mode 100644 index 0000000..4fb400a --- /dev/null +++ b/boatstack/internal/deliverycontrol/cost.go @@ -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 +} diff --git a/boatstack/internal/deliverycontrol/registry.go b/boatstack/internal/deliverycontrol/registry.go new file mode 100644 index 0000000..4cd5f2c --- /dev/null +++ b/boatstack/internal/deliverycontrol/registry.go @@ -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 +} diff --git a/boatstack/internal/deliverycontrol/registry_test.go b/boatstack/internal/deliverycontrol/registry_test.go new file mode 100644 index 0000000..7519cf7 --- /dev/null +++ b/boatstack/internal/deliverycontrol/registry_test.go @@ -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) + } + } +} diff --git a/boatstack/internal/deliverycontrol/state.go b/boatstack/internal/deliverycontrol/state.go new file mode 100644 index 0000000..35fd4b6 --- /dev/null +++ b/boatstack/internal/deliverycontrol/state.go @@ -0,0 +1,38 @@ +package deliverycontrol + +const ( + // Slice-status states — these string values mirror DeliverySlice.Status + // literals in the real state machine (delivery.go, next.go). Kept in sync by + // the parity conformance test (control-law: registry-mirrors-real-transitions). + StatePending StateID = "PENDING" + StateBuild StateID = "BUILD" + StateTestPassed StateID = "TEST_PASSED" + StateReviewPassed StateID = "REVIEW_PASSED" + StatePublished StateID = "PUBLISHED" + + // Boundary states — needed to describe transitions faithfully; not stored as + // a slice Status. + StateUninitialized StateID = "UNINITIALIZED" // no managed delivery yet + StateFeatureComplete StateID = "FEATURE_COMPLETE" // published + merged (ActiveIndex >= len(Slices)) + StateInvalid StateID = "INVALID" // malformed / blocked delivery + StateDiscarded StateID = "DISCARDED" // archived via discard-delivery + + // StateUnresolved is the sentinel an oracle returns for unknown/invalid + // state. A controller must never fabricate a path; it returns UNRESOLVED. + StateUnresolved StateID = "UNRESOLVED" +) + +// SliceStatusStates returns the states that mirror real DeliverySlice.Status +// literals, in lifecycle order. The parity test pins this set to the strings the +// real state machine actually uses. +func SliceStatusStates() []StateID { + return []StateID{StatePending, StateBuild, StateTestPassed, StateReviewPassed, StatePublished} +} + +// States returns every declared state. +func States() []StateID { + return []StateID{ + StatePending, StateBuild, StateTestPassed, StateReviewPassed, StatePublished, + StateUninitialized, StateFeatureComplete, StateInvalid, StateDiscarded, StateUnresolved, + } +} diff --git a/boatstack/internal/deliverycontrol/transition.go b/boatstack/internal/deliverycontrol/transition.go new file mode 100644 index 0000000..27265eb --- /dev/null +++ b/boatstack/internal/deliverycontrol/transition.go @@ -0,0 +1,84 @@ +// Package deliverycontrol is the shadow declaration of Boatstack's delivery +// state machine: the states an owned delivery moves through and the transitions +// (the moves an agent makes) between them. +// +// It exists to make J_flow — the cost of navigating the deterministic delivery +// workflow the tool already owns — computable, following the split +// J = J_flow + J_coding established in +// ../../notes/delivery-flow-navigation-model.md. This is the "one declaration": +// a single, source-cited registry that later projects two ways that can never +// disagree — a conformance view (every transition names a real handler, every +// state mirrors a real literal) and an optimization view (a weighted graph a +// deterministic oracle scores). +// +// This package is shadow-only. Nothing imports it at runtime; it changes no +// command, gate, authority, verification, evidence, or recovery behavior, and +// the mutation boundary (mutation.go) is preserved unchanged. Later, opt-in +// phases add tracing, an oracle, and an advisory controller on top of this +// declaration. +package deliverycontrol + +// StateID is a delivery-flow state. Slice-status states (see SliceStatusStates) +// mirror the string literals stored in DeliverySlice.Status by the real state +// machine; the remaining states name boundary conditions the registry needs to +// describe transitions faithfully (uninitialized, feature-complete, invalid, +// discarded), plus the UNRESOLVED sentinel an oracle must return for unknown +// state rather than fabricate a path. +type StateID string + +// TransitionID is the stable, semantic identifier of a delivery transition, +// independent of the CLI verb or Go handler that currently implements it. +type TransitionID string + +// TransitionKind classifies what a transition does to delivery state. +type TransitionKind string + +const ( + KindObserve TransitionKind = "observe" // read-only; no state change + KindInspect TransitionKind = "inspect" // read-only projection/derivation + KindQuery TransitionKind = "query" // read-only decision (may hit the network) + KindReversibleMutation TransitionKind = "reversible_mutation" // committed but closed under inversion / archived, not destroyed + KindCommittedMutation TransitionKind = "committed_mutation" // advances delivery state + KindRecovery TransitionKind = "recovery" // typed, bounded correction path +) + +// TransitionCostClass names the cost bucket a transition draws from. Weights +// live in FlowCostWeights; the cmg model +// (../../notes/delivery-flow-navigation-model.md) prices a normal +// move/observe/inspect at 1 and a denied/blocked committed mutation (friction) +// at 3. +type TransitionCostClass string + +const ( + CostObserve TransitionCostClass = "observe" + CostInspect TransitionCostClass = "inspect" + CostQuery TransitionCostClass = "query" + CostMutation TransitionCostClass = "mutation" + CostRecovery TransitionCostClass = "recovery" + CostFriction TransitionCostClass = "friction" +) + +// TransitionDescriptor is one row of the delivery-flow inventory +// (../../notes/delivery-control-inventory.md), declared once here. +type TransitionDescriptor struct { + ID TransitionID + From []StateID // source states; empty means "from an uninitialized/any delivery" (see Note) + To StateID // resulting delivery-flow state; "" when the transition changes no slice status + Kind TransitionKind + CostClass TransitionCostClass + Reversible bool + HandlerRef string // name of the real exported boatstack function that performs it + CLIVerb string // the boatstack-helper subcommand that invokes it ("" if none) + Note string // guard / authority / recovery summary, from the inventory +} + +// AllKinds enumerates the valid transition kinds so conformance checks can +// reject a descriptor that names an undeclared kind. +func AllKinds() []TransitionKind { + return []TransitionKind{KindObserve, KindInspect, KindQuery, KindReversibleMutation, KindCommittedMutation, KindRecovery} +} + +// AllCostClasses enumerates the valid cost classes for the same reason. +func AllCostClasses() []TransitionCostClass { + return []TransitionCostClass{CostObserve, CostInspect, CostQuery, CostMutation, CostRecovery, CostFriction} +} diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index de81573..5f7bbc6 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -146,6 +146,6 @@ Delivery and system improvement also remain separate. A failed task may suggest ## What is evidence-backed -The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`c25c282a5d9b5f952caf16e99e22749b70a92d78`](https://github.com/operatorstack/intelligence-flow/tree/c25c282a5d9b5f952caf16e99e22749b70a92d78/labs/12-product-engineering-loop). +The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`49a684f6752d1f017281d7bbb8b17b856904df15`](https://github.com/operatorstack/intelligence-flow/tree/49a684f6752d1f017281d7bbb8b17b856904df15/labs/12-product-engineering-loop). The evidence supports specific failure mechanisms and guardrails. It does not establish that Boatstack is optimal, that control-theory notation proves software quality, or that one workflow dominates every team. Those are evaluation questions, so the distribution preserves measurements, provenance, gaps, and negative results. diff --git a/docs/public-claims.json b/docs/public-claims.json index a21fbd4..46d8780 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "c25c282a5d9b5f952caf16e99e22749b70a92d78", + "source_commit": "49a684f6752d1f017281d7bbb8b17b856904df15", "statuses": ["verified", "observed", "still_being_evaluated"], "claims": [ { @@ -12,7 +12,7 @@ "readable_evidence": "why-these-steps.md#portable-workflow-and-state", "implementation": ["../boatstack/export.go", "../boatstack/references/artifacts.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "human-decisions", @@ -23,7 +23,7 @@ "readable_evidence": "why-these-steps.md#human-decisions", "implementation": ["../boatstack/references/workflow.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "validation-provenance", @@ -34,7 +34,7 @@ "readable_evidence": "why-these-steps.md#validation-provenance", "implementation": ["validation-and-evidence.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "irreversible-operations", @@ -46,7 +46,7 @@ "readable_evidence": "why-these-steps.md#irreversible-operations", "implementation": ["safety.md", "../boatstack/safety.go", "../boatstack/hooks.go"], "verification": ["../boatstack/safety_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "reviewer-ready-pr", @@ -57,7 +57,7 @@ "readable_evidence": "why-these-steps.md#reviewer-ready-pr", "implementation": ["../boatstack/pr.go", "getting-started.md"], "verification": ["../boatstack/pr_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "phase-scoped-delivery", @@ -68,7 +68,7 @@ "readable_evidence": "why-these-steps.md#phase-scoped-delivery", "implementation": ["../boatstack/delivery.go", "../boatstack/safety.go", "../boatstack/hooks.go", "../boatstack/references/workflow.md"], "verification": ["../boatstack/delivery_test.go", "../boatstack/pr_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "model-neutral-contract", @@ -79,7 +79,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "cross-model-failures", @@ -90,7 +90,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "lower-cost-outcomes", @@ -101,7 +101,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "git-worktree-activation", @@ -112,7 +112,7 @@ "readable_evidence": "why-these-steps.md#git-worktree-activation", "implementation": ["../boatstack/runtime_cache.go", "../boatstack/hooks.go"], "verification": ["../boatstack/runtime_cache_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" }, { "id": "visible-updates", @@ -123,7 +123,7 @@ "readable_evidence": "why-these-steps.md#visible-updates", "implementation": ["../boatstack/update.go", "../boatstack/init.go"], "verification": ["../boatstack/update_test.go", "../boatstack/init_test.go", "../boatstack/export_test.go"], - "last_verified_version": "source:c25c282a5d9b5f952caf16e99e22749b70a92d78" + "last_verified_version": "source:49a684f6752d1f017281d7bbb8b17b856904df15" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index b394239..8a601fa 100644 --- a/labs/diagram-json/plan.lock.json +++ b/labs/diagram-json/plan.lock.json @@ -6,7 +6,7 @@ "plan_path": "labs/diagram-json/plan.md", "plan_sha256": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "schema_version": 1, - "source_commit": "c25c282a5d9b5f952caf16e99e22749b70a92d78", + "source_commit": "49a684f6752d1f017281d7bbb8b17b856904df15", "source_plan_path": "labs/diagram-json/source-plan.md", "source_plan_sha256": "e10593ddaa7522ab80cc991d0a09399257139799e37f737794cd49d68a39985b", "spec_path": "labs/diagram-json/spec.md", diff --git a/release-notes/2026-07-25-deliverycontrol-shadow-registry.md b/release-notes/2026-07-25-deliverycontrol-shadow-registry.md new file mode 100644 index 0000000..f991575 --- /dev/null +++ b/release-notes/2026-07-25-deliverycontrol-shadow-registry.md @@ -0,0 +1,8 @@ +### Shadow transition registry for delivery-flow navigation (no runtime effect) + +A new internal package declares Boatstack's delivery workflow as a typed registry of transitions — +each state-changing operation with its states, kind, cost class, and the function that performs it — +as a single source of truth for future flow-navigation analysis. It is shadow-only: nothing consumes +it at runtime, and it changes no command, gate, authority, verification, evidence, or recovery +behavior. A conformance test keeps the registry faithful to the real state machine, so the declaration +and the code it mirrors can never silently diverge.