Skip to content

Commit 02cf817

Browse files
authored
feat: add canonical control program ABI (#195)
1 parent e4fa27a commit 02cf817

49 files changed

Lines changed: 1413 additions & 450 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Boatstack is a programmable supervisory control runtime for software delivery,
44
with a first-party standard delivery flow. It compiles one CoreSystem, one
5-
explicit primary flow, and optional conservative extensions into an immutable
5+
explicit program runtime, and optional conservative extensions into an immutable
66
ControlProgram before it observes a repository, resolves one legal transition,
77
binds exact authority, executes owned effects, verifies the result, and records
88
a receipt.
@@ -33,6 +33,9 @@ explicit invocation
3333
The immediate value is simple: every host consumes one executable delivery law.
3434
The [technical specification](docs/architecture/boatstack-v2-kernel.md) records
3535
the complete contract and the historical failure synthesis.
36+
The [Control Program ABI](docs/architecture/control-program-abi.md) defines the
37+
strict repository source, canonical fingerprint, compatibility gate, and
38+
program-qualified transition identity used by complete user-facing Flows.
3639

3740
## Install
3841

@@ -90,14 +93,14 @@ The generated [transition catalog](docs/architecture/boatstack-v2-transition-cat
9093
and [Mermaid inventory](docs/architecture/boatstack-v2-transition-catalog.mmd)
9194
come directly from the runtime registry. The generated
9295
[StandardFlow graph](docs/architecture/boatstack-standard-flow.mmd) filters the
93-
same compiled registry by primary-flow origin; it is not a second graph.
96+
same compiled registry by control-program origin; it is not a second graph.
9497
The [replacement closure report](docs/architecture/boatstack-v2-closure-report.md)
9598
records the deleted V1 authority and its V2 evidence.
9699

97100
The Go SDK keeps the standard distribution ergonomic with `sdk.New(...)`.
98-
Custom applications use `sdk.NewKernel(..., sdk.WithFlow(flow),
101+
Custom applications use `sdk.NewKernel(..., sdk.WithProgramRuntime(runtime),
99102
sdk.WithExtension(extension))`; the lower-level constructor requires an
100-
explicit trusted in-process primary flow and never inserts StandardFlow.
103+
explicit trusted in-process program runtime and never inserts StandardFlow.
101104

102105
## Coding-agent skills
103106

boatstack/cmd/boatstack-helper/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -555,10 +555,10 @@ func renderResponse(response surfaces.Response, format string) error {
555555
return nil
556556
}
557557
if response.Doctor != nil {
558-
fmt.Printf("healthy=%t kernel=%s core=%s@%s flow=%s@%s core_transitions=%d flow_transitions=%d extension_transitions=%d transitions=%d program=%s drift=%t runtime_healthy=%t update_ready=%t recovery_required=%t snapshot=%s\n%s\n",
558+
fmt.Printf("healthy=%t kernel=%s core=%s@%s program=%s@%s core_transitions=%d runtime_transitions=%d extension_transitions=%d transitions=%d fingerprint=%s drift=%t runtime_healthy=%t update_ready=%t recovery_required=%t snapshot=%s\n%s\n",
559559
response.Doctor.Healthy, response.Doctor.KernelVersion, response.Doctor.CoreSystemID, response.Doctor.CoreSystemVersion,
560-
response.Doctor.PrimaryFlowID, response.Doctor.PrimaryFlowVersion, response.Doctor.CoreTransitionCount,
561-
response.Doctor.FlowTransitionCount, response.Doctor.ExtensionTransitionCount, response.Doctor.TransitionCount,
560+
response.Doctor.ProgramID, response.Doctor.ProgramVersion, response.Doctor.CoreTransitionCount,
561+
response.Doctor.RuntimeTransitionCount, response.Doctor.ExtensionTransitionCount, response.Doctor.TransitionCount,
562562
response.Doctor.ProgramFingerprint, response.Doctor.UnresolvedProgramDrift, response.Doctor.RuntimeHealthy, response.Doctor.UpdateReady, response.Doctor.RecoveryRequired, response.Doctor.Snapshot, response.Doctor.Detail)
563563
return nil
564564
}

boatstack/control/control.go

Lines changed: 78 additions & 73 deletions
Large diffs are not rendered by default.

boatstack/control/control_test.go

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
"github.com/operatorstack/boatstack/boatstack/flow/standard"
1515
)
1616

17-
type staticFlowDefinition struct{ manifest control.PrimaryFlowManifest }
17+
type staticProgramRuntimeDefinition struct {
18+
manifest control.ProgramRuntimeManifest
19+
}
1820

19-
func (f staticFlowDefinition) FlowManifest(context.Context) (control.PrimaryFlowManifest, error) {
21+
func (f staticProgramRuntimeDefinition) RuntimeManifest(context.Context) (control.ProgramRuntimeManifest, error) {
2022
return f.manifest, nil
2123
}
2224

@@ -34,7 +36,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
3436
t.Fatalf("identical compilation drifted: %s != %s", one.Fingerprint(), two.Fingerprint())
3537
}
3638
summary := one.Summary()
37-
if summary.CoreTransitionCount != 33 || summary.FlowTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 63 {
39+
if summary.CoreTransitionCount != 33 || summary.RuntimeTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 63 {
3840
t.Fatalf("compiled counts = %+v", summary)
3941
}
4042
counts := map[string]int{}
@@ -44,7 +46,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
4446
t.Fatalf("transition lost compiled ownership: %+v", transition)
4547
}
4648
}
47-
if counts["core-system"] != 33 || counts["primary-flow"] != 30 || counts["extension"] != 0 {
49+
if counts["core-system"] != 33 || counts["control-program"] != 30 || counts["extension"] != 0 {
4850
t.Fatalf("origin counts = %#v", counts)
4951
}
5052
}
@@ -116,11 +118,11 @@ func TestProgramFingerprintBindsCompositionAndPolicyInputs(t *testing.T) {
116118
})
117119
}
118120

119-
policyOne, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: standard.Definition(), Settings: map[string]any{"policy": "one"}})
121+
policyOne, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: standard.Definition(), Settings: map[string]any{"policy": "one"}})
120122
if err != nil {
121123
t.Fatal(err)
122124
}
123-
policyTwo, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: standard.Definition(), Settings: map[string]any{"policy": "two"}})
125+
policyTwo, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: standard.Definition(), Settings: map[string]any{"policy": "two"}})
124126
if err != nil {
125127
t.Fatal(err)
126128
}
@@ -158,43 +160,43 @@ func TestCompileEnforcesDeclaredComponentSchemas(t *testing.T) {
158160
}
159161
})
160162
}
161-
flow, err := standard.Definition().FlowManifest(context.Background())
163+
flow, err := standard.Definition().RuntimeManifest(context.Background())
162164
if err != nil {
163165
t.Fatal(err)
164166
}
165167
flow.ConfigurationSchema = json.RawMessage(`{"type":"object","required":["mode"],"additionalProperties":false}`)
166168
flow.Settings = json.RawMessage(`{}`)
167-
if _, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: staticFlowDefinition{manifest: flow}}); err == nil {
168-
t.Fatal("PrimaryFlow settings that violate ConfigurationSchema compiled")
169+
if _, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: staticProgramRuntimeDefinition{manifest: flow}}); err == nil {
170+
t.Fatal("ProgramRuntime settings that violate ConfigurationSchema compiled")
169171
}
170172
}
171173

172174
func TestComponentsMustDeclareTheirOwnSelectionSemantics(t *testing.T) {
173175
// control-law: generic-compiler-never-infers-flow-order-from-transition-ids
174-
flow, err := standard.Definition().FlowManifest(context.Background())
176+
flow, err := standard.Definition().RuntimeManifest(context.Background())
175177
if err != nil {
176178
t.Fatal(err)
177179
}
178180
flow.Transitions[0].SelectionClass = ""
179181
if _, err := control.Compile(context.Background(), control.CompileRequest{
180-
KernelVersion: "kernel", Core: core.System(), Flow: staticFlow{manifest: flow},
182+
KernelVersion: "kernel", Core: core.System(), Runtime: staticFlow{manifest: flow},
181183
}); err == nil || !strings.Contains(err.Error(), "selection class") {
182184
t.Fatalf("flow without an explicit selection class was accepted: %v", err)
183185
}
184186

185-
flow, err = standard.Definition().FlowManifest(context.Background())
187+
flow, err = standard.Definition().RuntimeManifest(context.Background())
186188
if err != nil {
187189
t.Fatal(err)
188190
}
189191
flow.Transitions[0].SelectionClass = control.SelectionSystemRecovery
190192
if _, err := control.Compile(context.Background(), control.CompileRequest{
191-
KernelVersion: "kernel", Core: core.System(), Flow: staticFlow{manifest: flow},
193+
KernelVersion: "kernel", Core: core.System(), Runtime: staticFlow{manifest: flow},
192194
}); err == nil || !strings.Contains(err.Error(), "SYSTEM_RECOVERY") {
193195
t.Fatalf("flow claimed CoreSystem recovery precedence: %v", err)
194196
}
195197
}
196198

197-
func TestPrimaryFlowCannotClaimCoreSystemResources(t *testing.T) {
199+
func TestProgramRuntimeCannotClaimCoreSystemResources(t *testing.T) {
198200
// control-law: every-resource-has-exactly-one-component-owner
199201
coreManifest, err := core.System().CoreManifest(context.Background())
200202
if err != nil {
@@ -210,16 +212,16 @@ func TestPrimaryFlowCannotClaimCoreSystemResources(t *testing.T) {
210212
if coreResource == "" {
211213
t.Fatal("CoreSystem fixture declares no owned resource")
212214
}
213-
flow, err := standard.Definition().FlowManifest(context.Background())
215+
flow, err := standard.Definition().RuntimeManifest(context.Background())
214216
if err != nil {
215217
t.Fatal(err)
216218
}
217219
flow.OwnedResources = append(flow.OwnedResources, coreResource)
218220
flow.Transitions[0].OwnedResources = append(flow.Transitions[0].OwnedResources, coreResource)
219221
if _, err := control.Compile(context.Background(), control.CompileRequest{
220-
KernelVersion: "kernel", Core: core.System(), Flow: staticFlow{manifest: flow},
222+
KernelVersion: "kernel", Core: core.System(), Runtime: staticFlow{manifest: flow},
221223
}); err == nil || !strings.Contains(err.Error(), "overlapping owners") {
222-
t.Fatalf("PrimaryFlow claimed CoreSystem resource %q: %v", coreResource, err)
224+
t.Fatalf("ProgramRuntime claimed CoreSystem resource %q: %v", coreResource, err)
223225
}
224226
}
225227

@@ -298,17 +300,17 @@ func TestControlProgramAccessorsCannotMutateCompiledBytes(t *testing.T) {
298300
extensions := program.Extensions()
299301
extensions[0].Manifest.Facts[0] = "mutated.fact.id"
300302
extensions[0].Manifest.Settings = json.RawMessage(`{"mutated":true}`)
301-
flow := program.Flow()
303+
flow := program.ProgramRuntime()
302304
flow.Manifest.GoalContracts[0].Conditions[0].Values = []string{"mutated"}
303305

304306
if program.Fingerprint() != originalFingerprint || program.Transitions()[0].SourcePhases[0] == control.PhaseAbandoned ||
305-
program.Extensions()[0].Manifest.Facts[0] == "mutated.fact.id" || program.Flow().Manifest.GoalContracts[0].Conditions[0].Values[0] == "mutated" {
307+
program.Extensions()[0].Manifest.Facts[0] == "mutated.fact.id" || program.ProgramRuntime().Manifest.GoalContracts[0].Conditions[0].Values[0] == "mutated" {
306308
t.Fatal("public accessor mutated the compiled ControlProgram")
307309
}
308310
}
309311

310312
func TestExtensionGoalConditionsAreConjunctive(t *testing.T) {
311-
// control-law: extension-terminal-set-is-a-subset-of-primary-flow-terminal-set
313+
// control-law: extension-terminal-set-is-a-subset-of-control-program-terminal-set
312314
base, err := distribution.StandardProgram(context.Background())
313315
if err != nil {
314316
t.Fatal(err)
@@ -369,9 +371,11 @@ type declarationOnlyExtension struct {
369371
goalConditions []control.FacetCondition
370372
}
371373

372-
type staticFlow struct{ manifest control.PrimaryFlowManifest }
374+
type staticFlow struct {
375+
manifest control.ProgramRuntimeManifest
376+
}
373377

374-
func (s staticFlow) FlowManifest(context.Context) (control.PrimaryFlowManifest, error) {
378+
func (s staticFlow) RuntimeManifest(context.Context) (control.ProgramRuntimeManifest, error) {
375379
return s.manifest, nil
376380
}
377381

boatstack/control/flow_runtime.go

Lines changed: 0 additions & 106 deletions
This file was deleted.

0 commit comments

Comments
 (0)