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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

## Install

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

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

## Coding-agent skills

Expand Down
6 changes: 3 additions & 3 deletions boatstack/cmd/boatstack-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,10 @@ func renderResponse(response surfaces.Response, format string) error {
return nil
}
if response.Doctor != nil {
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",
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",
response.Doctor.Healthy, response.Doctor.KernelVersion, response.Doctor.CoreSystemID, response.Doctor.CoreSystemVersion,
response.Doctor.PrimaryFlowID, response.Doctor.PrimaryFlowVersion, response.Doctor.CoreTransitionCount,
response.Doctor.FlowTransitionCount, response.Doctor.ExtensionTransitionCount, response.Doctor.TransitionCount,
response.Doctor.ProgramID, response.Doctor.ProgramVersion, response.Doctor.CoreTransitionCount,
response.Doctor.RuntimeTransitionCount, response.Doctor.ExtensionTransitionCount, response.Doctor.TransitionCount,
response.Doctor.ProgramFingerprint, response.Doctor.UnresolvedProgramDrift, response.Doctor.RuntimeHealthy, response.Doctor.UpdateReady, response.Doctor.RecoveryRequired, response.Doctor.Snapshot, response.Doctor.Detail)
return nil
}
Expand Down
151 changes: 78 additions & 73 deletions boatstack/control/control.go

Large diffs are not rendered by default.

48 changes: 26 additions & 22 deletions boatstack/control/control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (
"github.com/operatorstack/boatstack/boatstack/flow/standard"
)

type staticFlowDefinition struct{ manifest control.PrimaryFlowManifest }
type staticProgramRuntimeDefinition struct {
manifest control.ProgramRuntimeManifest
}

func (f staticFlowDefinition) FlowManifest(context.Context) (control.PrimaryFlowManifest, error) {
func (f staticProgramRuntimeDefinition) RuntimeManifest(context.Context) (control.ProgramRuntimeManifest, error) {
return f.manifest, nil
}

Expand All @@ -34,7 +36,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
t.Fatalf("identical compilation drifted: %s != %s", one.Fingerprint(), two.Fingerprint())
}
summary := one.Summary()
if summary.CoreTransitionCount != 33 || summary.FlowTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 63 {
if summary.CoreTransitionCount != 33 || summary.RuntimeTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 63 {
t.Fatalf("compiled counts = %+v", summary)
}
counts := map[string]int{}
Expand All @@ -44,7 +46,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
t.Fatalf("transition lost compiled ownership: %+v", transition)
}
}
if counts["core-system"] != 33 || counts["primary-flow"] != 30 || counts["extension"] != 0 {
if counts["core-system"] != 33 || counts["control-program"] != 30 || counts["extension"] != 0 {
t.Fatalf("origin counts = %#v", counts)
}
}
Expand Down Expand Up @@ -116,11 +118,11 @@ func TestProgramFingerprintBindsCompositionAndPolicyInputs(t *testing.T) {
})
}

policyOne, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: standard.Definition(), Settings: map[string]any{"policy": "one"}})
policyOne, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: standard.Definition(), Settings: map[string]any{"policy": "one"}})
if err != nil {
t.Fatal(err)
}
policyTwo, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: standard.Definition(), Settings: map[string]any{"policy": "two"}})
policyTwo, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: standard.Definition(), Settings: map[string]any{"policy": "two"}})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -158,43 +160,43 @@ func TestCompileEnforcesDeclaredComponentSchemas(t *testing.T) {
}
})
}
flow, err := standard.Definition().FlowManifest(context.Background())
flow, err := standard.Definition().RuntimeManifest(context.Background())
if err != nil {
t.Fatal(err)
}
flow.ConfigurationSchema = json.RawMessage(`{"type":"object","required":["mode"],"additionalProperties":false}`)
flow.Settings = json.RawMessage(`{}`)
if _, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Flow: staticFlowDefinition{manifest: flow}}); err == nil {
t.Fatal("PrimaryFlow settings that violate ConfigurationSchema compiled")
if _, err := control.Compile(context.Background(), control.CompileRequest{KernelVersion: "kernel", Core: core.System(), Runtime: staticProgramRuntimeDefinition{manifest: flow}}); err == nil {
t.Fatal("ProgramRuntime settings that violate ConfigurationSchema compiled")
}
}

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

flow, err = standard.Definition().FlowManifest(context.Background())
flow, err = standard.Definition().RuntimeManifest(context.Background())
if err != nil {
t.Fatal(err)
}
flow.Transitions[0].SelectionClass = control.SelectionSystemRecovery
if _, err := control.Compile(context.Background(), control.CompileRequest{
KernelVersion: "kernel", Core: core.System(), Flow: staticFlow{manifest: flow},
KernelVersion: "kernel", Core: core.System(), Runtime: staticFlow{manifest: flow},
}); err == nil || !strings.Contains(err.Error(), "SYSTEM_RECOVERY") {
t.Fatalf("flow claimed CoreSystem recovery precedence: %v", err)
}
}

func TestPrimaryFlowCannotClaimCoreSystemResources(t *testing.T) {
func TestProgramRuntimeCannotClaimCoreSystemResources(t *testing.T) {
// control-law: every-resource-has-exactly-one-component-owner
coreManifest, err := core.System().CoreManifest(context.Background())
if err != nil {
Expand All @@ -210,16 +212,16 @@ func TestPrimaryFlowCannotClaimCoreSystemResources(t *testing.T) {
if coreResource == "" {
t.Fatal("CoreSystem fixture declares no owned resource")
}
flow, err := standard.Definition().FlowManifest(context.Background())
flow, err := standard.Definition().RuntimeManifest(context.Background())
if err != nil {
t.Fatal(err)
}
flow.OwnedResources = append(flow.OwnedResources, coreResource)
flow.Transitions[0].OwnedResources = append(flow.Transitions[0].OwnedResources, coreResource)
if _, err := control.Compile(context.Background(), control.CompileRequest{
KernelVersion: "kernel", Core: core.System(), Flow: staticFlow{manifest: flow},
KernelVersion: "kernel", Core: core.System(), Runtime: staticFlow{manifest: flow},
}); err == nil || !strings.Contains(err.Error(), "overlapping owners") {
t.Fatalf("PrimaryFlow claimed CoreSystem resource %q: %v", coreResource, err)
t.Fatalf("ProgramRuntime claimed CoreSystem resource %q: %v", coreResource, err)
}
}

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

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

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

type staticFlow struct{ manifest control.PrimaryFlowManifest }
type staticFlow struct {
manifest control.ProgramRuntimeManifest
}

func (s staticFlow) FlowManifest(context.Context) (control.PrimaryFlowManifest, error) {
func (s staticFlow) RuntimeManifest(context.Context) (control.ProgramRuntimeManifest, error) {
return s.manifest, nil
}

Expand Down
106 changes: 0 additions & 106 deletions boatstack/control/flow_runtime.go

This file was deleted.

Loading