diff --git a/boatstack/delivery/program_manifest_test.go b/boatstack/delivery/program_manifest_test.go index 56fab49..e880b1b 100644 --- a/boatstack/delivery/program_manifest_test.go +++ b/boatstack/delivery/program_manifest_test.go @@ -316,6 +316,7 @@ func programFixture() delivery.ProgramManifest { advance.TargetPhases = []delivery.ProtocolPhase{delivery.PhaseTerminal} advance.ObjectiveKinds = []delivery.ObjectiveKind{delivery.ObjectiveVerified} advance.Authority = []delivery.AuthorityClass{delivery.AuthorityHuman, delivery.AuthorityRepository} + advance.RequiredCapabilities = []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityProductMutate} advance.Effect = "program.advance" advance.LocalEffects = []delivery.EffectID{"program.advance"} advance.Prescription = delivery.Prescription{Operation: "advance", Arguments: []string{"--exact"}, ExpectedPostcondition: "terminal"} @@ -331,7 +332,7 @@ func programFixture() delivery.ProgramManifest { SchemaVersion: delivery.ProgramSchemaVersion, ProgramID: "test-program", ProgramVersion: "1", RequiresRuntime: ">=1.0.0", Capabilities: delivery.ProgramCapabilities{ Effects: []string{"program.advance", "program.recover"}, Verifiers: []string{"program.current", "program.terminal"}, - CapabilitySurface: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute}, + CapabilitySurface: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute, delivery.CapabilityProductMutate}, }, OwnedResources: []string{"program.state"}, ObjectiveContracts: []delivery.ObjectiveContract{{ObjectiveKind: delivery.ObjectiveVerified, Conditions: []delivery.FacetCondition{delivery.KnownCondition(delivery.FacetDelivery, "terminal")}}}, Transitions: []delivery.ProgramTransition{advance, recovery}, @@ -339,7 +340,7 @@ func programFixture() delivery.ProgramManifest { } func runtimeFixture() delivery.RuntimeCompatibility { - return delivery.RuntimeCompatibility{Version: "v1.2.3", Effects: []string{"program.advance", "program.recover", "alternate.effect"}, Verifiers: []string{"program.current", "program.terminal", "alternate.verifier"}, Capabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute, delivery.CapabilityHumanApprove}} + return delivery.RuntimeCompatibility{Version: "v1.2.3", Effects: []string{"program.advance", "program.recover", "alternate.effect"}, Verifiers: []string{"program.current", "program.terminal", "alternate.verifier"}, Capabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute, delivery.CapabilityProductMutate, delivery.CapabilityHumanApprove}} } func loadManifest(t *testing.T, manifest delivery.ProgramManifest) delivery.ControlProgram { diff --git a/boatstack/internal/softwaredelivery/catalog/capability.go b/boatstack/internal/softwaredelivery/catalog/capability.go index 473d1fd..e7671de 100644 --- a/boatstack/internal/softwaredelivery/catalog/capability.go +++ b/boatstack/internal/softwaredelivery/catalog/capability.go @@ -3,7 +3,8 @@ package catalog import ( "fmt" "sort" - "strings" + + "github.com/operatorstack/boatstack/boatstack/internal/softwaredelivery/model" ) // Capability names a kernel-enforced class of effect. A declaration narrows @@ -131,17 +132,17 @@ func KernelEffectCapabilities(transition Transition) []Capability { if transition.RuntimeExecution { required[CapabilityCommandExecute] = true } + for _, facet := range transition.OwnedFacets { + if facet == model.StateFacetProduct { + required[CapabilityProductMutate] = true + } + } id := string(transition.Effect) switch id { case "gate.build.record", "gate.test.record", "workspace.cut", "workspace.sync", "workspace.cleanup", "workspace.reap", "publication.observe", "publication.reconcile", "publication.execute", "publication.correct": required[CapabilityCommandExecute] = true } - if strings.HasPrefix(id, "objective.") || strings.HasPrefix(id, "plan.") || strings.HasPrefix(id, "workspace.") || - strings.HasPrefix(id, "gate.") || strings.HasPrefix(id, "evidence.") || strings.HasPrefix(id, "delivery.") || - strings.HasPrefix(id, "publication.") { - required[CapabilityProductMutate] = true - } if id == "publication.preview" { required[CapabilityPublicationPrepare] = true } diff --git a/boatstack/internal/softwaredelivery/catalog/capability_test.go b/boatstack/internal/softwaredelivery/catalog/capability_test.go index a2eecac..11072e1 100644 --- a/boatstack/internal/softwaredelivery/catalog/capability_test.go +++ b/boatstack/internal/softwaredelivery/catalog/capability_test.go @@ -1,6 +1,10 @@ package catalog -import "testing" +import ( + "testing" + + "github.com/operatorstack/boatstack/boatstack/internal/softwaredelivery/model" +) func TestCapabilityVocabularyFailsClosed(t *testing.T) { if _, err := NormalizeCapabilities("test", []Capability{"production.nuke"}); err == nil { @@ -15,6 +19,7 @@ func TestKernelEffectClassificationCannotBeWeakenedByTransitionDeclaration(t *te // control-law: repository-authored requirements cannot under-classify a kernel effect transition := Transition{ ID: "program/publish", Class: EventOwnedExternal, Effect: "publication.execute", + OwnedFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, RequiredCapabilities: []Capability{CapabilityRepositoryWrite}, DeclaredCapabilities: []Capability{CapabilityRepositoryWrite, CapabilityCommandExecute, CapabilityProductMutate, CapabilityPublicationPublish}, } @@ -26,6 +31,18 @@ func TestKernelEffectClassificationCannotBeWeakenedByTransitionDeclaration(t *te } } +func TestProductOwnershipRequiresProductMutationForArbitraryEffect(t *testing.T) { + transition := Transition{ + ID: "program/advance", Class: EventOwnedLocal, Effect: "acme.advance", + OwnedFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, + RequiredCapabilities: []Capability{CapabilityRepositoryWrite}, + } + required := NewCapabilitySet(RequiredCapabilities(transition)...) + if !required[CapabilityProductMutate] { + t.Fatalf("product-owning arbitrary effect was under-classified: %v", required.Sorted()) + } +} + func TestCapabilityClassesHaveNoImplicitHierarchy(t *testing.T) { granted := AuthorityCapabilities(AuthoritySet{AuthorityProvider: true}) if !granted[CapabilityPublicationPublish] || granted[CapabilityPublicationPrepare] || granted[CapabilityRepositoryWrite] { diff --git a/boatstack/internal/softwaredelivery/catalog/state_effect_closure_test.go b/boatstack/internal/softwaredelivery/catalog/state_effect_closure_test.go index c6267e7..3962f56 100644 --- a/boatstack/internal/softwaredelivery/catalog/state_effect_closure_test.go +++ b/boatstack/internal/softwaredelivery/catalog/state_effect_closure_test.go @@ -126,6 +126,19 @@ func TestDeclarativeAssignmentsRejectApplyTimeOnlyValueConstraints(t *testing.T) } } +func TestStateAssignmentMustSatisfyEveryTargetCondition(t *testing.T) { + assignment := literalAssignment("delivery", string(model.DeliveryPublished)) + transition := Transition{ + TargetConditions: []FacetCondition{ + {Facet: model.FacetDelivery, Statuses: []model.FactStatus{model.FactKnown}, Values: []string{string(model.DeliveryPublished)}}, + {Facet: model.FacetDelivery, Statuses: []model.FactStatus{model.FactKnown}, Values: []string{string(model.DeliveryTerminal)}}, + }, + } + if stateAssignmentMatchesTarget(transition, assignment) { + t.Fatal("assignment matched only the first of two target conditions") + } +} + func closureTransition(source FacetCondition, assignments ...StateAssignment) Transition { transition := Transition{ ID: "test.transition", SourcePhases: []model.ProtocolPhase{model.PhaseActive}, TargetPhases: []model.ProtocolPhase{model.PhaseActive}, diff --git a/boatstack/internal/softwaredelivery/catalog/state_facet.go b/boatstack/internal/softwaredelivery/catalog/state_facet.go index 3044c52..2944321 100644 --- a/boatstack/internal/softwaredelivery/catalog/state_facet.go +++ b/boatstack/internal/softwaredelivery/catalog/state_facet.go @@ -157,6 +157,26 @@ func DurableStateFacetPolicy(transition Transition) (StateFacetPolicy, error) { return StateFacetPolicy{Reads: append([]model.StateFacet(nil), allStateFacets...), Writes: writes}, nil } +// RecoveryStateFacets reconstructs the interrupted transition's write envelope +// from admission-bound capability identity. Privileged core facets remain +// closed over the fixed transition IDs that own them; repository-authored +// programs can recover only control and admitted product mutations. +func RecoveryStateFacets(id TransitionID, required []Capability) []model.StateFacet { + switch id { + case "runtime.hydrate", "runtime.replace", "runtime.reconcile", "installation.update": + return []model.StateFacet{model.StateFacetControl, model.StateFacetInstallation} + case "installation.initialize", "installation.reconcile-update": + return []model.StateFacet{model.StateFacetControl, model.StateFacetInstallation, model.StateFacetProgram} + case "repository.attach", "catalog.reconcile": + return []model.StateFacet{model.StateFacetControl, model.StateFacetProgram} + } + writes := []model.StateFacet{model.StateFacetControl} + if NewCapabilitySet(required...).ContainsAll([]Capability{CapabilityProductMutate}) { + writes = append(writes, model.StateFacetProduct) + } + return writes +} + func containsStateFacet(values []model.StateFacet, wanted model.StateFacet) bool { for _, value := range values { if value == wanted { diff --git a/boatstack/internal/softwaredelivery/catalog/transition.go b/boatstack/internal/softwaredelivery/catalog/transition.go index 507b538..aefb78c 100644 --- a/boatstack/internal/softwaredelivery/catalog/transition.go +++ b/boatstack/internal/softwaredelivery/catalog/transition.go @@ -858,22 +858,35 @@ func stateAssignmentMatchesTarget(t Transition, assignment StateAssignment) bool if target.Facet != facet { continue } - if len(target.Statuses) != 1 || target.Statuses[0] != model.FactKnown { + if !containsFactStatus(target.Statuses, model.FactKnown) { return false } if len(target.Values) == 0 { - return true + continue } + matched := false for _, value := range target.Values { if value == *assignment.Value { - return true + matched = true + break } } - return false + if !matched { + return false + } } return true } +func containsFactStatus(values []model.FactStatus, wanted model.FactStatus) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +} + func cloneConditions(values []FacetCondition) []FacetCondition { result := make([]FacetCondition, len(values)) for index, value := range values { diff --git a/boatstack/internal/softwaredelivery/effects/cas_integration_test.go b/boatstack/internal/softwaredelivery/effects/cas_integration_test.go index f0817f6..f90f7b8 100644 --- a/boatstack/internal/softwaredelivery/effects/cas_integration_test.go +++ b/boatstack/internal/softwaredelivery/effects/cas_integration_test.go @@ -141,7 +141,8 @@ func TestConcurrentApplyConsumesOneRevisionExactlyOnce(t *testing.T) { t.Fatalf("canonical committed journal count=%d err=%v", len(committedJournals), err) } committedRaw, err := os.ReadFile(committedJournals[0]) - if err != nil || !bytes.Contains(committedRaw, []byte(committed.Receipt.ID)) || !bytes.Contains(committedRaw, []byte("committed_effects")) { + if err != nil || !bytes.Contains(committedRaw, []byte(committed.Receipt.ID)) || !bytes.Contains(committedRaw, []byte("committed_effects")) || + !bytes.Contains(committedRaw, []byte(`"schema_version": 8`)) || !bytes.Contains(committedRaw, []byte(`"allowed_state_facets"`)) { t.Fatalf("committed journal lacks its complete transition fact: %v %q", err, committedRaw) } // Simulate a crash after canonical commit but before the passive receipt diff --git a/boatstack/internal/softwaredelivery/effects/journal.go b/boatstack/internal/softwaredelivery/effects/journal.go index f64d8cf..94dc0bd 100644 --- a/boatstack/internal/softwaredelivery/effects/journal.go +++ b/boatstack/internal/softwaredelivery/effects/journal.go @@ -33,10 +33,12 @@ func NewJournal(resolver ports.InvocationResolver, clock ports.Clock) (*Journal, } type journalRecord struct { - SchemaVersion int `json:"schema_version"` - Admission protocol.Admission `json:"admission"` - TransitionID catalog.TransitionID `json:"transition_id"` - TransitionClass catalog.EventClass `json:"transition_class"` + SchemaVersion int `json:"schema_version"` + Admission protocol.Admission `json:"admission"` + TransitionID catalog.TransitionID `json:"transition_id"` + TransitionClass catalog.EventClass `json:"transition_class"` + // AllowedStateFacets preserves the current schema-8 record shape. Recovery + // authority is reconstructed from Admission.RequiredCapabilities instead. AllowedStateFacets []model.StateFacet `json:"allowed_state_facets"` ReconcilesProgram bool `json:"reconciles_program,omitempty"` Status string `json:"status"` diff --git a/boatstack/internal/softwaredelivery/effects/journal_schema_test.go b/boatstack/internal/softwaredelivery/effects/journal_schema_test.go index 0b86489..1faac72 100644 --- a/boatstack/internal/softwaredelivery/effects/journal_schema_test.go +++ b/boatstack/internal/softwaredelivery/effects/journal_schema_test.go @@ -1,27 +1,13 @@ package effects import ( - "os" - "path/filepath" - "strings" "testing" -) -func TestPriorJournalSchemaRequiresExplicitReset(t *testing.T) { - path := filepath.Join(t.TempDir(), "adm-prior.pending") - raw := []byte(`{"schema_version":7,"admission":{"id":"adm-prior"},"transition_id":"plan.create","transition_class":"owned-local","status":"begun"}`) - if err := os.WriteFile(path, raw, 0o600); err != nil { - t.Fatal(err) - } + "github.com/operatorstack/boatstack/boatstack/internal/softwaredelivery/protocol" +) - if _, err := readJournal(path); err == nil || !strings.Contains(err.Error(), "invalid transaction journal") { - t.Fatalf("read prior journal schema error = %v, want explicit invalid journal refusal", err) - } - got, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if string(got) != string(raw) { - t.Fatalf("prior journal changed during refusal:\n got %s\nwant %s", got, raw) +func TestInstallationUpdateKeepsCurrentJournalSchema(t *testing.T) { + if protocol.JournalSchemaVersion != 8 { + t.Fatalf("journal schema = %d, want current schema 8 for in-flight installation updates", protocol.JournalSchemaVersion) } } diff --git a/boatstack/internal/softwaredelivery/effects/recovery.go b/boatstack/internal/softwaredelivery/effects/recovery.go index f8f5074..2a1da05 100644 --- a/boatstack/internal/softwaredelivery/effects/recovery.go +++ b/boatstack/internal/softwaredelivery/effects/recovery.go @@ -147,7 +147,7 @@ func recoveryStateFacets(record journalRecord, recovery catalog.TransitionID, in if err != nil { return nil, err } - allowed := model.UnionStateFacets(record.AllowedStateFacets, []model.StateFacet{model.StateFacetControl}) + allowed := catalog.RecoveryStateFacets(record.TransitionID, record.Admission.RequiredCapabilities) if _, err := validateAllowedStateFacets(recovery, staged, allowed); err != nil { return nil, err } diff --git a/boatstack/internal/softwaredelivery/effects/recovery_test.go b/boatstack/internal/softwaredelivery/effects/recovery_test.go index c5bb5be..6785292 100644 --- a/boatstack/internal/softwaredelivery/effects/recovery_test.go +++ b/boatstack/internal/softwaredelivery/effects/recovery_test.go @@ -1,6 +1,7 @@ package effects import ( + "bytes" "context" "os" "os/exec" @@ -152,6 +153,17 @@ func TestRestartRecoveryRestoresPriorStateAndCommitsRecoveryRevision(t *testing. if err := journalBeforeRestart.RequireRecovery(ctx, admission.ID, "simulated process loss after effect"); err != nil { t.Fatal(err) } + basePendingPath, err := journalBeforeRestart.pendingPath(ctx, admission) + if err != nil { + t.Fatal(err) + } + basePending, err := os.ReadFile(basePendingPath) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(basePending, []byte(`"schema_version": 8`)) || !bytes.Contains(basePending, []byte(`"allowed_state_facets"`)) { + t.Fatalf("pending update journal is not current-main schema 8: %s", basePending) + } restartedInvocation, err := resolver.ResolveInvocation(ctx, repository, "cli", "after-restart") if err != nil { diff --git a/boatstack/internal/softwaredelivery/effects/state_facet_test.go b/boatstack/internal/softwaredelivery/effects/state_facet_test.go index df563e7..00208bf 100644 --- a/boatstack/internal/softwaredelivery/effects/state_facet_test.go +++ b/boatstack/internal/softwaredelivery/effects/state_facet_test.go @@ -131,7 +131,7 @@ func TestRecoveryReportsActualStateDeltaInsteadOfInterruptedEnvelope(t *testing. staged.UpdatedAt = time.Unix(101, 0).UTC() prior, _ := durable.EncodeState(before) target, _ := durable.EncodeState(staged) - record := journalRecord{TransitionID: "plan.create", AllowedStateFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, Mutations: []ports.ResourceMutation{{ + record := journalRecord{TransitionID: "plan.create", AllowedStateFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, Admission: protocol.Admission{RequiredCapabilities: []catalog.Capability{catalog.CapabilityProductMutate}}, Mutations: []ports.ResourceMutation{{ Path: "/controller/state.json", PriorExists: true, Prior: prior, Target: target, StateFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, }}} @@ -151,6 +151,28 @@ func TestRecoveryReportsActualStateDeltaInsteadOfInterruptedEnvelope(t *testing. } } +func TestRecoveryCannotWidenFacetsOutsideAdmissionCapabilities(t *testing.T) { + before := ownershipState() + after := before + after.Plan = model.PlanApproved + prior, _ := durable.EncodeState(before) + target, _ := durable.EncodeState(after) + record := journalRecord{ + TransitionID: "repository-program/advance", + AllowedStateFacets: []model.StateFacet{model.StateFacetControl, model.StateFacetProduct}, + Admission: protocol.Admission{RequiredCapabilities: []catalog.Capability{ + catalog.CapabilityRepositoryWrite, + }}, + Mutations: []ports.ResourceMutation{{ + Path: "/controller/state.json", PriorExists: true, Prior: prior, Target: target, + StateFacets: []model.StateFacet{model.StateFacetProduct}, + }}, + } + if _, err := recoveryStateFacets(record, "recovery.resume", model.InvocationContext{}, nil); err == nil || !strings.Contains(err.Error(), "FACET_OWNERSHIP_VIOLATION") { + t.Fatalf("recovery widened admission-bound facets: %v", err) + } +} + func TestJournalRejectsReceiptFacetMismatch(t *testing.T) { err := validateCommittedMutationFacts(catalog.EventOwnedLocal, []ports.ResourceMutation{{StateFacets: []model.StateFacet{model.StateFacetControl}}}, []model.StateFacet{model.StateFacetProduct}, nil) if err == nil || !strings.Contains(err.Error(), "do not match staged mutation facets") { diff --git a/boatstack/program_effects_test.go b/boatstack/program_effects_test.go index 3a0efbc..7e27b2e 100644 --- a/boatstack/program_effects_test.go +++ b/boatstack/program_effects_test.go @@ -90,7 +90,7 @@ func (protocolStateRuntime) RuntimeManifest(context.Context) (delivery.ProgramRu SourcePhases: []delivery.ProtocolPhase{delivery.PhaseActive}, TargetPhases: []delivery.ProtocolPhase{delivery.PhaseActive}, ObjectiveKinds: []delivery.ObjectiveKind{delivery.ObjectiveVerified}, RequiredIdentity: []string{"repository-id", "git-common-id", "worktree-id"}, Authority: []delivery.AuthorityClass{delivery.AuthorityHuman, delivery.AuthorityRepository}, - RequiredCapabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute}, RequiredEvidence: []string{"snapshot", "objective"}, + RequiredCapabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute, delivery.CapabilityProductMutate}, RequiredEvidence: []string{"snapshot", "objective"}, OwnedResources: []string{resource}, OwnedFacets: []delivery.StateFacet{delivery.StateFacetControl, delivery.StateFacetProduct}, StateEffect: delivery.StateEffect{Kind: delivery.StateEffectAssignments, Assignments: []delivery.StateAssignment{{Facet: "delivery", Value: &published}}}, Effect: publishEffect, LocalEffects: []delivery.EffectID{publishEffect}, Idempotent: true, @@ -109,7 +109,7 @@ func (protocolStateRuntime) RuntimeManifest(context.Context) (delivery.ProgramRu ObjectiveContracts: []delivery.ObjectiveContract{{ObjectiveKind: delivery.ObjectiveVerified, Conditions: []delivery.FacetCondition{delivery.KnownCondition(delivery.FacetDelivery, string(model.DeliveryPublished))}}}, Transitions: []delivery.Transition{publish, recover}, OwnedResources: []string{resource}, Effects: []string{string(publishEffect), string(recoverEffect)}, Verifiers: []string{"fixture.state.published", "fixture.state.recovered"}, - Capabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute}, + Capabilities: []delivery.Capability{delivery.CapabilityRepositoryWrite, delivery.CapabilityCommandExecute, delivery.CapabilityProductMutate}, ConfigurationSchema: json.RawMessage(`{"type":"object"}`), PrivacyClassification: "metadata-only", TelemetryClassification: "transition-receipt", }, nil } diff --git a/docs/architecture/boatstack-v2-transition-catalog.md b/docs/architecture/boatstack-v2-transition-catalog.md index 92ae93f..c29513f 100644 --- a/docs/architecture/boatstack-v2-transition-catalog.md +++ b/docs/architecture/boatstack-v2-transition-catalog.md @@ -7,39 +7,39 @@ Controlling facets: `phase`, `program`, `topology`, `engagement`, `delivery`, `w | Transition | Origin | Owner | Selection | Class | Source phases | Target phases | Authority | Required capabilities | Parameters | Owned resources | Verifier | Recovery | Cost | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| -| `catalog.reconcile` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED / TERMINAL / ABANDONED | human | `repository.write` | `prior_program_fingerprint*`, `accept_obligation_change*` | `catalog-identity` | `verifier:fresh-observation:catalog.reconcile` | `recovery.resume` | `declared-neutral` | -| `configuration.initialize` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | OBSERVED | OBSERVED / TERMINAL | human/repository-policy | `repository.write` | `config_path*`, `config_sha256*` | `configuration` | `verifier:fresh-observation:configuration.initialize` | `configuration.reconcile` | `declared-neutral` | -| `configuration.mutate` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / ACTIVE / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | human/autonomy | `repository.write` | `config_path*`, `config_sha256*` | `configuration` | `verifier:fresh-observation:configuration.mutate` | `configuration.reconcile` | `declared-neutral` | -| `configuration.reconcile` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | OBSERVED / FRONTIER / TERMINAL | human/repository-policy | `repository.write` | `transaction_id*` | `configuration` | `verifier:fresh-observation:configuration.reconcile` | `recovery.escalate` | `declared-neutral` | +| `catalog.reconcile` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED / TERMINAL / ABANDONED | human | `repository.write` | `prior_program_fingerprint*`, `accept_obligation_change*` | `catalog-identity` | `verifier:fresh-observation:catalog.reconcile` | `recovery.resume` | `declared-neutral` | +| `configuration.initialize` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | OBSERVED | OBSERVED / TERMINAL | human/repository-policy | `repository.write` | `config_path*`, `config_sha256*` | `configuration` | `verifier:fresh-observation:configuration.initialize` | `configuration.reconcile` | `declared-neutral` | +| `configuration.mutate` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / ACTIVE / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | human/autonomy | `repository.write` | `config_path*`, `config_sha256*` | `configuration` | `verifier:fresh-observation:configuration.mutate` | `configuration.reconcile` | `declared-neutral` | +| `configuration.reconcile` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | OBSERVED / FRONTIER / TERMINAL | human/repository-policy | `repository.write` | `transaction_id*` | `configuration` | `verifier:fresh-observation:configuration.reconcile` | `recovery.escalate` | `declared-neutral` | | `delivery.slice.advance` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | ACTIVE | ACTIVE / TERMINAL | human/autonomy | `product.mutate`, `repository.write` | `slice_id*`, `source_revision*` | `delivery-state` | `verifier:fresh-observation:delivery.slice.advance` | `recovery.resume` | `declared-neutral` | -| `engagement.begin` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBJECTIVE_REQUIRED | authority | DORMANT / OBSERVED | OBSERVED / ACTIVE | repository-policy | `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.begin` | `recovery.resume` | `declared-neutral` | -| `engagement.release` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | authority | ACTIVE / FRONTIER | DORMANT | repository-policy | `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.release` | `recovery.resume` | `declared-neutral` | -| `engagement.renew` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | authority | ACTIVE | ACTIVE | repository-policy/autonomy | `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.renew` | `recovery.resume` | `declared-neutral` | +| `engagement.begin` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBJECTIVE_REQUIRED | authority | DORMANT / OBSERVED | OBSERVED / ACTIVE | repository-policy | `product.mutate`, `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.begin` | `recovery.resume` | `declared-neutral` | +| `engagement.release` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | authority | ACTIVE / FRONTIER | DORMANT | repository-policy | `product.mutate`, `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.release` | `recovery.resume` | `declared-neutral` | +| `engagement.renew` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | authority | ACTIVE | ACTIVE | repository-policy/autonomy | `product.mutate`, `repository.write` | - | `engagement` | `verifier:fresh-observation:engagement.renew` | `recovery.resume` | `declared-neutral` | | `evidence.approval.revoke` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | authority | ACTIVE / FRONTIER | FRONTIER | human | `product.mutate`, `repository.write` | - | `approval` | `verifier:fresh-observation:evidence.approval.revoke` | `recovery.resume` | `declared-neutral` | | `evidence.visual.attach` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | ACTIVE | ACTIVE / TERMINAL | human/repository-policy | `product.mutate`, `repository.write` | `manifest_path*`, `privacy_receipt*`, `source_revision*` | `evidence` | `verifier:fresh-observation:evidence.visual.attach` | `recovery.resume` | `declared-neutral` | -| `external.branch-changed` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.branch-changed` | `-` | `declared-neutral` | -| `external.ci-completed` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.ci-completed` | `-` | `declared-neutral` | -| `external.configuration-drifted` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / UNRESOLVED | none | - | - | - | `verifier:fresh-observation:external.configuration-drifted` | `-` | `declared-neutral` | -| `external.files-changed` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.files-changed` | `-` | `declared-neutral` | -| `external.head-changed` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.head-changed` | `-` | `declared-neutral` | -| `external.host-interrupted` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | RECOVERY | none | - | - | - | `verifier:fresh-observation:external.host-interrupted` | `-` | `declared-neutral` | -| `external.lease-expired` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | DORMANT / FRONTIER | none | - | - | - | `verifier:fresh-observation:external.lease-expired` | `-` | `declared-neutral` | -| `external.pr-closed` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / FRONTIER | none | - | - | - | `verifier:fresh-observation:external.pr-closed` | `-` | `declared-neutral` | -| `external.pr-merged` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-merged` | `-` | `declared-neutral` | -| `external.pr-opened` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-opened` | `-` | `declared-neutral` | -| `external.pr-updated` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-updated` | `-` | `declared-neutral` | -| `external.provider-unavailable` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | UNRESOLVED / RECOVERY | none | - | - | - | `verifier:fresh-observation:external.provider-unavailable` | `-` | `declared-neutral` | -| `external.runtime-disappeared` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / RECOVERY | none | - | - | - | `verifier:fresh-observation:external.runtime-disappeared` | `-` | `declared-neutral` | +| `external.branch-changed` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.branch-changed` | `-` | `declared-neutral` | +| `external.ci-completed` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.ci-completed` | `-` | `declared-neutral` | +| `external.configuration-drifted` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / UNRESOLVED | none | - | - | - | `verifier:fresh-observation:external.configuration-drifted` | `-` | `declared-neutral` | +| `external.files-changed` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.files-changed` | `-` | `declared-neutral` | +| `external.head-changed` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED | none | - | - | - | `verifier:fresh-observation:external.head-changed` | `-` | `declared-neutral` | +| `external.host-interrupted` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | RECOVERY | none | - | - | - | `verifier:fresh-observation:external.host-interrupted` | `-` | `declared-neutral` | +| `external.lease-expired` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | DORMANT / FRONTIER | none | - | - | - | `verifier:fresh-observation:external.lease-expired` | `-` | `declared-neutral` | +| `external.pr-closed` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / FRONTIER | none | - | - | - | `verifier:fresh-observation:external.pr-closed` | `-` | `declared-neutral` | +| `external.pr-merged` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-merged` | `-` | `declared-neutral` | +| `external.pr-opened` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-opened` | `-` | `declared-neutral` | +| `external.pr-updated` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | none | - | - | - | `verifier:fresh-observation:external.pr-updated` | `-` | `declared-neutral` | +| `external.provider-unavailable` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | UNRESOLVED / RECOVERY | none | - | - | - | `verifier:fresh-observation:external.provider-unavailable` | `-` | `declared-neutral` | +| `external.runtime-disappeared` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBSERVED_EXTERNAL | observed-external | DORMANT / OBSERVED / ACTIVE / RECOVERY / FRONTIER / UNRESOLVED | OBSERVED / RECOVERY | none | - | - | - | `verifier:fresh-observation:external.runtime-disappeared` | `-` | `declared-neutral` | | `gate.build.record` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | ACTIVE | ACTIVE | repository-policy | `command.execute`, `product.mutate`, `repository.write` | `source_revision*`, `evidence_path*`, `evidence_fingerprint*` | `gate-evidence` | `verifier:fresh-observation:gate.build.record` | `recovery.resume` | `declared-neutral` | | `gate.change.record` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | ACTIVE | ACTIVE | repository-policy | `product.mutate`, `repository.write` | `source_revision*`, `evidence_path*`, `evidence_fingerprint*` | `gate-evidence` | `verifier:fresh-observation:gate.change.record` | `recovery.resume` | `declared-neutral` | | `gate.journey.record` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | ACTIVE | ACTIVE | repository-policy | `product.mutate`, `repository.write` | `source_revision*`, `evidence_path*`, `evidence_fingerprint*` | `gate-evidence` | `verifier:fresh-observation:gate.journey.record` | `recovery.resume` | `declared-neutral` | | `gate.review.record` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | ACTIVE | ACTIVE / TERMINAL | human/repository-policy | `product.mutate`, `repository.write` | `source_revision*`, `evidence_path*`, `evidence_fingerprint*` | `gate-evidence` | `verifier:fresh-observation:gate.review.record` | `recovery.resume` | `declared-neutral` | | `gate.test.record` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | ACTIVE | ACTIVE / TERMINAL | repository-policy | `command.execute`, `product.mutate`, `repository.write` | `source_revision*`, `evidence_path*`, `evidence_fingerprint*` | `gate-evidence` | `verifier:fresh-observation:gate.test.record` | `recovery.resume` | `declared-neutral` | -| `installation.initialize` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | DORMANT / OBSERVED | OBSERVED | human | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `config_path*`, `config_sha256*` | `installation` | `verifier:fresh-observation:installation.initialize` | `runtime.reconcile` | `declared-neutral` | -| `installation.reconcile-update` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `accept_obligation_change*` | `installation` | `verifier:fresh-observation:installation.reconcile-update` | `recovery.rollback` | `declared-neutral` | -| `installation.update` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/autonomy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `installation` | `verifier:fresh-observation:installation.update` | `runtime.reconcile` | `declared-neutral` | -| `invocation.rebind` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / UNRESOLVED | OBSERVED | repository-policy | `repository.write` | - | `identity-binding` | `verifier:fresh-observation:invocation.rebind` | `recovery.resume` | `declared-neutral` | -| `objective.bind` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBJECTIVE_REQUIRED | authority | OBSERVED / DORMANT / ACTIVE / FRONTIER / TERMINAL / ABANDONED | OBSERVED / ACTIVE / FRONTIER | human/autonomy | `product.mutate`, `repository.write` | `objective_kind*`, `delivery_id*` | `objective` | `verifier:fresh-observation:objective.bind` | `recovery.resume` | `declared-neutral` | +| `installation.initialize` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | DORMANT / OBSERVED | OBSERVED | human | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `config_path*`, `config_sha256*` | `installation` | `verifier:fresh-observation:installation.initialize` | `runtime.reconcile` | `declared-neutral` | +| `installation.reconcile-update` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `accept_obligation_change*` | `installation` | `verifier:fresh-observation:installation.reconcile-update` | `recovery.rollback` | `declared-neutral` | +| `installation.update` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/autonomy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `installation` | `verifier:fresh-observation:installation.update` | `runtime.reconcile` | `declared-neutral` | +| `invocation.rebind` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / UNRESOLVED | OBSERVED | repository-policy | `repository.write` | - | `identity-binding` | `verifier:fresh-observation:invocation.rebind` | `recovery.resume` | `declared-neutral` | +| `objective.bind` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBJECTIVE_REQUIRED | authority | OBSERVED / DORMANT / ACTIVE / FRONTIER / TERMINAL / ABANDONED | OBSERVED / ACTIVE / FRONTIER | human/autonomy | `product.mutate`, `repository.write` | `objective_kind*`, `delivery_id*` | `objective` | `verifier:fresh-observation:objective.bind` | `recovery.resume` | `declared-neutral` | | `plan.abandon` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | authority | OBSERVED / ACTIVE / FRONTIER | ABANDONED | human | `product.mutate`, `repository.write` | - | `plan` | `verifier:fresh-observation:plan.abandon` | `recovery.resume` | `declared-neutral` | | `plan.activate` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | OBSERVED / ACTIVE | ACTIVE | human/autonomy | `product.mutate`, `repository.write` | - | `delivery-state` | `verifier:fresh-observation:plan.activate` | `recovery.resume` | `declared-neutral` | | `plan.amend` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | ACTIVE / FRONTIER | ACTIVE | human/autonomy | `product.mutate`, `repository.write` | `source_path*`, `delivery_id*` | `plan` | `verifier:fresh-observation:plan.amend` | `recovery.resume` | `declared-neutral` | @@ -54,14 +54,14 @@ Controlling facets: `phase`, `program`, `topology`, `engagement`, `delivery`, `w | `publication.observe` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | OBSERVED / ACTIVE / RECOVERY / UNRESOLVED | ACTIVE / TERMINAL / FRONTIER / UNRESOLVED | repository-policy | `command.execute`, `product.mutate`, `repository.write` | `publication_id*` | `publication-evidence` | `verifier:fresh-observation:publication.observe` | `recovery.resume` | `declared-neutral` | | `publication.preview` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | ACTIVE | ACTIVE | repository-policy | `product.mutate`, `publication.prepare`, `repository.write` | `base_ref*`, `head_ref*`, `body_path*` | `publication-preview` | `verifier:fresh-observation:publication.preview` | `recovery.resume` | `declared-neutral` | | `publication.reconcile` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_RECOVERY | recovery | RECOVERY / UNRESOLVED | ACTIVE / TERMINAL / FRONTIER / UNRESOLVED | human/external-provider | `command.execute`, `product.mutate`, `repository.write` | `publication_id*`, `transaction_id*` | `publication` | `verifier:fresh-observation:publication.reconcile` | `recovery.escalate` | `declared-neutral` | -| `recovery.escalate` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | FRONTIER | repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.escalate` | `recovery.escalate` | `declared-neutral` | -| `recovery.resume` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/autonomy/repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.resume` | `recovery.escalate` | `declared-neutral` | -| `recovery.rollback` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.rollback` | `recovery.escalate` | `declared-neutral` | -| `repository.attach` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED | OBSERVED | human | `repository.write` | `topology*`, `config_authority*` | `repository-binding` | `verifier:fresh-observation:repository.attach` | `recovery.resume` | `declared-neutral` | -| `repository.detach` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / FRONTIER | DORMANT | human | `repository.write` | - | `repository-binding` | `verifier:fresh-observation:repository.detach` | `recovery.resume` | `declared-neutral` | -| `runtime.hydrate` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | OBSERVED / RECOVERY / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `runtime` | `verifier:fresh-observation:runtime.hydrate` | `runtime.reconcile` | `declared-neutral` | -| `runtime.reconcile` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | OBSERVED / FRONTIER / TERMINAL | repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `transaction_id*` | `runtime` | `verifier:fresh-observation:runtime.reconcile` | `recovery.escalate` | `declared-neutral` | -| `runtime.replace` | core-system:`boatstack.core@1.0.0`
`85ee13e3604eee43bde7cbdfa48de4a5e15b47ea2987f915f0a9463be5312ab6` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / RECOVERY | OBSERVED / TERMINAL | human/repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `runtime` | `verifier:fresh-observation:runtime.replace` | `runtime.reconcile` | `declared-neutral` | +| `recovery.escalate` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | FRONTIER | repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.escalate` | `recovery.escalate` | `declared-neutral` | +| `recovery.resume` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/autonomy/repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.resume` | `recovery.escalate` | `declared-neutral` | +| `recovery.rollback` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY | DORMANT / OBSERVED / ACTIVE / FRONTIER / TERMINAL / ABANDONED | human/repository-policy | `repository.write` | `transaction_id*` | `recovery-journal` | `verifier:fresh-observation:recovery.rollback` | `recovery.escalate` | `declared-neutral` | +| `repository.attach` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED | OBSERVED | human | `repository.write` | `topology*`, `config_authority*` | `repository-binding` | `verifier:fresh-observation:repository.attach` | `recovery.resume` | `declared-neutral` | +| `repository.detach` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | DORMANT / OBSERVED / FRONTIER | DORMANT | human | `product.mutate`, `repository.write` | - | `repository-binding` | `verifier:fresh-observation:repository.detach` | `recovery.resume` | `declared-neutral` | +| `runtime.hydrate` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | OBJECTIVE_REQUIRED | owned-local | OBSERVED / RECOVERY / UNRESOLVED | OBSERVED / ACTIVE / TERMINAL | repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `runtime` | `verifier:fresh-observation:runtime.hydrate` | `runtime.reconcile` | `declared-neutral` | +| `runtime.reconcile` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | SYSTEM_RECOVERY | recovery | RECOVERY / UNRESOLVED | OBSERVED / FRONTIER / TERMINAL | repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*`, `transaction_id*` | `runtime` | `verifier:fresh-observation:runtime.reconcile` | `recovery.escalate` | `declared-neutral` | +| `runtime.replace` | core-system:`boatstack.core@1.0.0`
`888b71d9c3d401472edfb234dc78f48eeca0308d915e7bf7e2a52b418164b463` | `boatstack.core` | EXPLICIT_ONLY | owned-local | OBSERVED / RECOVERY | OBSERVED / TERMINAL | human/repository-policy | `repository.write` | `source_revision*`, `runtime_version*`, `runtime_sha256*` | `runtime` | `verifier:fresh-observation:runtime.replace` | `runtime.reconcile` | `declared-neutral` | | `workspace.abandon` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | ACTIVE / FRONTIER | ABANDONED | human | `product.mutate`, `repository.write` | `branch*` | `workspace` | `verifier:fresh-observation:workspace.abandon` | `recovery.resume` | `declared-neutral` | | `workspace.activate` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | PROGRAM_PROGRESS | owned-local | OBSERVED / ACTIVE | ACTIVE | repository-policy | `product.mutate`, `repository.write` | `branch*` | `workspace` | `verifier:fresh-observation:workspace.activate` | `recovery.resume` | `declared-neutral` | | `workspace.cleanup` | control-program:`boatstack.standard@1.0.0`
`43b24be10c7c809c0198207dc0fecb6551669b0a15568e0362a074ee118f83f7` | `boatstack.standard` | EXPLICIT_ONLY | owned-local | OBSERVED / ACTIVE / TERMINAL / ABANDONED | OBSERVED / TERMINAL / ABANDONED | human/autonomy | `command.execute`, `product.mutate`, `repository.write` | `branch*` | `workspace` | `verifier:fresh-observation:workspace.cleanup` | `recovery.escalate` | `declared-neutral` | diff --git a/release-notes/2026-08-12-declarative-state-effect-boundaries.md b/release-notes/2026-08-12-declarative-state-effect-boundaries.md new file mode 100644 index 0000000..ba87bc3 --- /dev/null +++ b/release-notes/2026-08-12-declarative-state-effect-boundaries.md @@ -0,0 +1,10 @@ +### Bind declarative effects to admitted control law + +Product-state effects now require product mutation authority based on their +owned facets. Declarative assignments must satisfy every target condition for +the affected facet, and recovery reconstructs its write boundary from the +admitted transition instead of mutable journal data. + +The declarative state-effect change keeps the existing journal schema, so an +installation update can resume its pending transaction after runtime +activation.