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
4 changes: 4 additions & 0 deletions .github/reviews/objective-lifecycle-relations.receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"reviewed_tree": "0b9386e235424a81b116adc466e70491bd333644",
"program_fingerprint": "3ca3397ff275d89bdb6d5c934b86b51d3cbdfab0ee628c47fe94d1d4f5767155"
}
8 changes: 4 additions & 4 deletions .github/tests/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,8 +728,8 @@ def test_catalog_and_generated_artifacts_match_the_executable_registry(self) ->

response = json.loads(self.run_helper("catalog").stdout)
transitions = response["catalog"]
self.assertEqual(len(transitions), 63)
self.assertEqual(len({item["id"] for item in transitions}), 63)
self.assertEqual(len(transitions), 64)
self.assertEqual(len({item["id"] for item in transitions}), 64)
self.assertEqual(
{item["class"] for item in transitions},
{"authority", "owned-local", "owned-external", "recovery", "observed-external"},
Expand Down Expand Up @@ -765,7 +765,7 @@ def test_catalog_and_generated_artifacts_match_the_executable_registry(self) ->
checked = (REPO / "docs" / "architecture" / name).read_text()
self.assertEqual(rendered, checked)
model = json.loads(checked)
self.assertEqual(len(model["events"]), 63)
self.assertEqual(len(model["events"]), 64)
self.assertEqual(
{event["id"] for event in model["events"]},
{item["id"] for item in transitions},
Expand Down Expand Up @@ -890,7 +890,7 @@ def test_offline_installer_initializes_updates_and_guards_through_kernel(self) -
self.run_command(launcher, "doctor", "--repo", repository, env=env).stdout
)
self.assertTrue(doctor["doctor"]["healthy"])
self.assertEqual(doctor["doctor"]["transition_count"], 63)
self.assertEqual(doctor["doctor"]["transition_count"], 64)
self.assertEqual(doctor["snapshot"]["runtime"]["value"], "verified")

objective = (
Expand Down
2 changes: 1 addition & 1 deletion boatstack/core/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestManifestOwnsOnlyOperationalCapabilities(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if manifest.ID != core.ID || manifest.Version != core.Version || len(manifest.Transitions) != 33 {
if manifest.ID != core.ID || manifest.Version != core.Version || len(manifest.Transitions) != 34 {
t.Fatalf("CoreSystem identity/count = %s@%s/%d", manifest.ID, manifest.Version, len(manifest.Transitions))
}
for _, transition := range manifest.Transitions {
Expand Down
147 changes: 145 additions & 2 deletions boatstack/core/transitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3196,7 +3196,6 @@
{
"facet": "objective",
"statuses": [
"known",
"absent"
]
},
Expand Down Expand Up @@ -3249,7 +3248,7 @@
"telemetry_classification": "transition-receipt",
"cost_class": "declared-neutral",
"policy": {
"binds_requested_objective": true,
"objective_mutation": "bind-initial",
"objective_scope": "none"
},
"priority": 30,
Expand All @@ -3262,6 +3261,150 @@
"native_handler": "objective-bind"
}
},
{
"id": "objective.replace",
"version": 1,
"origin": {
"kind": "",
"id": "",
"version": "",
"manifest_fingerprint": ""
},
"owner": "",
"selection_class": "OBJECTIVE_REQUIRED",
"class": "authority",
"source_phases": [
"OBSERVED",
"DORMANT",
"ACTIVE",
"FRONTIER",
"TERMINAL",
"ABANDONED"
],
"target_phases": [
"OBSERVED",
"ACTIVE",
"FRONTIER"
],
"required_identity": [
"repository-id",
"git-common-id",
"worktree-id",
"ref",
"controller-id",
"invoking-path",
"runtime-version",
"runtime-path",
"runtime-fingerprint",
"topology",
"host",
"correlation-id"
],
"authority": [
"human",
"autonomy"
],
"required_evidence": [
"invocation-context",
"snapshot-fingerprint",
"objective",
"facet:objective",
"facet:program"
],
"owned_resources": [
"objective"
],
"effect": "objective.replace",
"local_effects": [
"objective.replace"
],
"idempotent": true,
"parameters": [
{
"name": "target_id",
"required": true,
"secret": false
},
{
"name": "delivery_id",
"required": true,
"secret": false
}
],
"prescription": {
"operation": "objective.replace",
"expected_postcondition": "predicate:target-phase:objective.replace"
},
"source_predicate": "predicate:source-phase:objective.replace",
"source_conditions": [
{
"facet": "objective",
"statuses": [
"known"
]
},
{
"facet": "program",
"statuses": [
"known"
],
"values": [
"unbound",
"current"
]
}
],
"admission_predicate": "predicate:exact-admission:objective.replace",
"target_predicate": "predicate:target-phase:objective.replace",
"target_conditions": [
{
"facet": "objective",
"statuses": [
"known"
]
}
],
"verifier": "verifier:fresh-observation:objective.replace",
"interruption": {
"points": [
"after-lock",
"after-stage",
"after-effect",
"before-receipt"
],
"partial_state": [
"journal-begun",
"effect-staged",
"effect-possibly-installed",
"postcondition-unreceipted"
],
"detection": "pending-journal-plus-fresh-canonical-observation",
"resume_contract": "journal-target-replay-when-permitted",
"rollback_contract": "exact-prior-byte-replay-when-permitted",
"compensation_contract": "not-required-for-owned-local-effects",
"recovery": "recovery.resume",
"recovery_authority": "declared-by:recovery.resume",
"resumption_predicate": "recovery-contract-for:objective.replace"
},
"reversibility": "reversible",
"terminal_effect": "none",
"privacy_classification": "metadata-only",
"telemetry_classification": "transition-receipt",
"cost_class": "declared-neutral",
"policy": {
"objective_mutation": "replace",
"objective_scope": "none"
},
"priority": 31,
"owned_facets": [
"control",
"product"
],
"state_effect": {
"kind": "native",
"native_handler": "objective-replace"
}
},
{
"id": "recovery.resume",
"version": 1,
Expand Down
16 changes: 13 additions & 3 deletions boatstack/delivery/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type FacetCondition = catalog.FacetCondition
type SelectionClass = catalog.SelectionClass
type ObjectiveContract = catalog.ObjectiveContract
type ObjectiveScope = catalog.ObjectiveScope
type ObjectiveMutation = catalog.ObjectiveMutation
type EffectID = catalog.EffectID
type Prescription = catalog.Prescription
type ParameterSpec = catalog.ParameterSpec
Expand Down Expand Up @@ -81,6 +82,10 @@ const (
ObjectiveScopeOptionalPreserve = catalog.ObjectiveScopeOptionalPreserve
ObjectiveScopeBoundExact = catalog.ObjectiveScopeBoundExact

ObjectiveMutationPreserve = catalog.ObjectiveMutationPreserve
ObjectiveMutationBindInitial = catalog.ObjectiveMutationBindInitial
ObjectiveMutationReplace = catalog.ObjectiveMutationReplace

ObjectiveApprovedPlan = model.ObjectiveApprovedPlan
ObjectiveVerified = model.ObjectiveVerified
ObjectiveOpenPR = model.ObjectiveOpenPR
Expand Down Expand Up @@ -605,9 +610,14 @@ func compileSupervisoryProgram(runtime ProgramRuntimeManifest, compatibility, do
facets = append(facets, string(facet))
}
mutation := general.PreserveObjective
if transition.Policy.BindsRequestedObjective {
mutation = general.BindObjectiveMutation
capabilities = append(capabilities, general.Capability("objective.bind"))
switch transition.Policy.Mutation() {
case catalog.ObjectiveMutationBindInitial:
mutation = general.BindInitialObjective
case catalog.ObjectiveMutationReplace:
mutation = general.ReplaceObjective
}
if capability, mutates := mutation.Capability(); mutates {
capabilities = append(capabilities, capability)
facets = append(facets, "supervisor.objective")
}
selection := general.SelectionExplicitOnly
Expand Down
9 changes: 6 additions & 3 deletions boatstack/delivery/control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
if len(supervisory.Transitions) != controllable {
t.Fatalf("kernel program transition count = %d, want %d controllable software transitions", len(supervisory.Transitions), controllable)
}
if summary.CoreTransitionCount != 33 || summary.RuntimeTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 63 {
if summary.CoreTransitionCount != 34 || summary.RuntimeTransitionCount != 30 || summary.ExtensionTransitionCount != 0 || summary.TotalTransitionCount != 64 {
t.Fatalf("compiled counts = %+v", summary)
}
counts := map[string]int{}
Expand All @@ -59,7 +59,7 @@ func TestStandardProgramHasExplicitStableComposition(t *testing.T) {
t.Fatalf("transition lost compiled ownership: %+v", transition)
}
}
if counts["core-system"] != 33 || counts["control-program"] != 30 || counts["extension"] != 0 {
if counts["core-system"] != 34 || counts["control-program"] != 30 || counts["extension"] != 0 {
t.Fatalf("origin counts = %#v", counts)
}
}
Expand Down Expand Up @@ -272,7 +272,10 @@ func TestExtensionCompilationRejectsBoundaryViolations(t *testing.T) {
value.Transitions[0].Policy.ReconcilesProgram = true
},
"requested-objective-binding-claim": func(value *delivery.ExtensionManifest) {
value.Transitions[0].Policy.BindsRequestedObjective = true
value.Transitions[0].Policy.ObjectiveMutation = delivery.ObjectiveMutationBindInitial
},
"objective-replacement-claim": func(value *delivery.ExtensionManifest) {
value.Transitions[0].Policy.ObjectiveMutation = delivery.ObjectiveMutationReplace
},
"foreign-target": func(value *delivery.ExtensionManifest) {
value.Transitions[0].TargetConditions = []delivery.FacetCondition{delivery.KnownCondition(delivery.FacetPlan, "approved")}
Expand Down
2 changes: 1 addition & 1 deletion boatstack/delivery/program_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func ValidateProgram(manifest ProgramManifest, runtime RuntimeCompatibility) (Co
}
source.Interruption.Recovery = TransitionID(manifest.ProgramID + "/" + string(source.Interruption.Recovery))
}
if source.Policy.BindsRequestedObjective || source.Policy.ReconcilesProgram {
if source.Policy.MutatesObjectiveBinding() || source.Policy.ReconcilesProgram {
return ControlProgram{}, invalidProgram(fmt.Sprintf("transitions[%d].policy", index), "runtime-reserved program mutation policy is not repository-declarable")
}
if source.Controllable() && !containsDeclaration(effects, string(source.Effect)) {
Expand Down
2 changes: 1 addition & 1 deletion boatstack/examples/control_program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Example_standardFlowWithReleaseNoteExtension() {
summary := program.Summary()
fmt.Printf("%s + %s + %s: %d transitions\n", summary.Core.ID, summary.Runtime.ID, summary.Extensions[0].ID, summary.TotalTransitionCount)
// Output:
// boatstack.core + boatstack.standard + boatstack.release-note: 64 transitions
// boatstack.core + boatstack.standard + boatstack.release-note: 65 transitions
}

func Example_sdkCustomKernel() {
Expand Down
7 changes: 5 additions & 2 deletions boatstack/flow/standard/completeness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestEveryControllingFacetAndEventIsClassifiedByTheRuntimeCatalog(t *testing
t.Errorf("controlling facet %s is absent from executable predicates", facet)
}
}
want := map[string]int{"invocation-engagement": 6, "installation-runtime-configuration": 9, "catalog": 1, "objective-plan": 9, "workspace": 8, "gate-evidence-delivery": 8, "publication": 6, "recovery": 3, "external": 13}
want := map[string]int{"invocation-engagement": 6, "installation-runtime-configuration": 9, "catalog": 1, "objective-plan": 10, "workspace": 8, "gate-evidence-delivery": 8, "publication": 6, "recovery": 3, "external": 13}
for family, count := range want {
if families[family] != count {
t.Errorf("family %s=%d, want %d", family, families[family], count)
Expand Down Expand Up @@ -335,9 +335,12 @@ func TestNativeStateHandlersAreBoundToAuthorizedSemantics(t *testing.T) {
{"mismatched-effect", "plan.approve", func(value *catalog.Transition) { value.Effect = "plan.create" }},
{"mismatched-facets", "plan.approve", func(value *catalog.Transition) { value.OwnedFacets = []model.StateFacet{model.StateFacetControl} }},
{"objective-bind-policy", "objective.bind", func(value *catalog.Transition) {
value.Policy.BindsRequestedObjective = false
value.Policy.ObjectiveMutation = catalog.ObjectiveMutationPreserve
value.Policy.ObjectiveScope = catalog.ObjectiveScopeBoundExact
}},
{"objective-replace-policy", "objective.replace", func(value *catalog.Transition) {
value.Policy.ObjectiveMutation = catalog.ObjectiveMutationBindInitial
}},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions boatstack/flow/standard/supervisor_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ func TestTerminalEvidenceForOldObjectiveDoesNotTerminateNewObjective(t *testing.
snapshot = recanonicalize(t, snapshot)
newObjective := model.Objective{ID: "next-objective", TargetID: model.ObjectiveOpenPR, DeliveryID: "next-delivery"}
decision := s.Resolve(snapshot, newObjective, catalog.AuthoritySet{catalog.AuthorityHuman: true}, "")
if decision.Kind != DecisionPrescribed || decision.Transition == nil || decision.Transition.ID != "objective.bind" {
t.Fatalf("untargeted terminal replacement decision=%#v, want exact new-objective configuration", decision)
if decision.Kind != DecisionPrescribed || decision.Transition == nil || decision.Transition.ID != "objective.replace" {
t.Fatalf("untargeted terminal replacement decision=%#v, want exact new-objective replacement", decision)
}
}

Expand All @@ -150,8 +150,8 @@ func TestUntargetedResolutionReconfiguresDifferentObjectiveAndSkipsSatisfiedObje

newObjective := model.Objective{ID: "new-objective", TargetID: model.ObjectiveOpenPR, DeliveryID: "delivery"}
decision := New(testprogram.StandardRegistry(), testObjectiveContracts()).Resolve(snapshot, newObjective, authority, "")
if decision.Kind != DecisionPrescribed || decision.Transition == nil || decision.Transition.ID != "objective.bind" {
t.Fatalf("different-objective decision = %#v, want objective.bind", decision)
if decision.Kind != DecisionPrescribed || decision.Transition == nil || decision.Transition.ID != "objective.replace" {
t.Fatalf("different-objective decision = %#v, want objective.replace", decision)
}

snapshot.Plan = model.Known(model.PlanValid, snapshot.Plan.Evidence[0])
Expand All @@ -169,16 +169,16 @@ func TestDormantBootstrapObjectiveReconfiguresBeforeEngagement(t *testing.T) {
authority := catalog.AuthoritySet{catalog.AuthorityHuman: true, catalog.AuthorityRepository: true}

untargeted := New(testprogram.StandardRegistry(), testObjectiveContracts()).Resolve(snapshot, requested, authority, "")
if untargeted.Kind != DecisionPrescribed || untargeted.Transition == nil || untargeted.Transition.ID != "objective.bind" {
t.Fatalf("untargeted decision = %#v, want objective.bind", untargeted)
if untargeted.Kind != DecisionPrescribed || untargeted.Transition == nil || untargeted.Transition.ID != "objective.replace" {
t.Fatalf("untargeted decision = %#v, want objective.replace", untargeted)
}
targeted := New(testprogram.StandardRegistry(), testObjectiveContracts()).Resolve(snapshot, requested, authority, untargeted.Transition.ID)
if targeted.Kind != DecisionPrescribed || targeted.Transition == nil || targeted.Transition.ID != untargeted.Transition.ID {
t.Fatalf("targeted decision = %#v, want parity with %#v", targeted, untargeted)
}
engagement := New(testprogram.StandardRegistry(), testObjectiveContracts()).Resolve(snapshot, requested, authority, "engagement.begin")
if engagement.Kind != DecisionRefused {
t.Fatalf("engagement decision = %#v, want refusal until objective.bind", engagement)
t.Fatalf("engagement decision = %#v, want refusal until the objective is reconfigured", engagement)
}
}

Expand Down
Loading
Loading