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
14 changes: 7 additions & 7 deletions .github/tests/test_detached_supervision.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def porcelain(self, repository: Path | None = None) -> str:
@staticmethod
def objective_flags() -> tuple[str, ...]:
return (
"--objective-id", "bootstrap", "--objective-kind", "approved-plan",
"--objective-id", "bootstrap", "--target-id", "approved-plan",
"--delivery", "bootstrap",
)

Expand Down Expand Up @@ -207,7 +207,7 @@ def test_detached_installation_and_engaged_guard_use_the_same_kernel(self) -> No
self.apply_prescribed(
"objective.bind", "--repo", self.repo,
*self.objective_flags(), "--human", "contract",
"--param", "objective_kind=approved-plan", "--param", "delivery_id=bootstrap",
"--param", "target_id=approved-plan", "--param", "delivery_id=bootstrap",
)
self.apply_prescribed(
"engagement.begin", "--repo", self.repo,
Expand All @@ -232,7 +232,7 @@ def test_authority_free_frontier_does_not_block_authorized_plan_creation(self) -
# control-law: codex-mode-authority-survives-observation-and-effects
objective = (
"--objective-id", "codex-driver-authority-triggers",
"--objective-kind", "open-or-updated-pr",
"--target-id", "open-or-updated-pr",
"--delivery", "codex-driver-authority-triggers",
)
flow = ("--run-id", "flow-codex-driver-authority-triggers")
Expand All @@ -258,7 +258,7 @@ def test_authority_free_frontier_does_not_block_authorized_plan_creation(self) -
self.apply_prescribed(
"objective.bind", "--repo", self.repo,
*objective, *flow, "--human", "contract",
"--param", "objective_kind=open-or-updated-pr",
"--param", "target_id=open-or-updated-pr",
"--param", "delivery_id=codex-driver-authority-triggers",
)
self.apply_prescribed(
Expand Down Expand Up @@ -322,7 +322,7 @@ def test_one_delivery_context_rematerializes_repository_authority_after_initiali
# control-law: retained-repository-source-crosses-maintenance-receipt-once
objective = (
"--objective-id", "preserve-repository-authority-context",
"--objective-kind", "open-or-updated-pr",
"--target-id", "open-or-updated-pr",
"--delivery", "preserve-repository-authority-context",
)
flow = ("--run-id", "flow-preserve-repository-authority-context")
Expand Down Expand Up @@ -391,7 +391,7 @@ def test_one_delivery_context_rematerializes_repository_authority_after_initiali
configured = self.apply_prescribed(
"objective.bind", "--repo", self.repo,
*objective, *flow, *actor,
"--param", "objective_kind=open-or-updated-pr",
"--param", "target_id=open-or-updated-pr",
"--param", "delivery_id=preserve-repository-authority-context",
)
self.assertEqual(configured["receipt"]["transition_id"], "objective.bind")
Expand Down Expand Up @@ -449,7 +449,7 @@ def test_repository_authority_rematerialization_fails_closed_without_verified_co
result = self.run_helper(
"next", "--repo", root,
"--objective-id", "unverified-authority",
"--objective-kind", "open-or-updated-pr",
"--target-id", "open-or-updated-pr",
"--delivery", "unverified-authority",
"--run-id", "flow-unverified-authority",
"--human", "contract", "--repository-authority",
Expand Down
6 changes: 3 additions & 3 deletions .github/tests/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,12 +839,12 @@ def test_offline_installer_initializes_updates_and_guards_through_kernel(self) -
self.assertEqual(doctor["snapshot"]["runtime"]["value"], "verified")

objective = (
"--objective-id", "bootstrap", "--objective-kind", "approved-plan",
"--objective-id", "bootstrap", "--target-id", "approved-plan",
"--delivery", "bootstrap",
)
self.apply_prescribed(
launcher, "objective.bind", "--repo", repository, *objective,
"--human", "contract", "--param", "objective_kind=approved-plan",
"--human", "contract", "--param", "target_id=approved-plan",
"--param", "delivery_id=bootstrap", env=env,
)
self.apply_prescribed(
Expand Down Expand Up @@ -956,7 +956,7 @@ def test_program_changing_update_is_explicit_atomic_and_dormant_safe(self) -> No
"contract",
"--objective-id",
"bootstrap",
"--objective-kind",
"--target-id",
"approved-plan",
"--delivery",
"bootstrap",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ The public protocol is deliberately small:
```sh
# Observe or resolve. These commands do not mutate managed state.
boatstack status --repo . --format json
boatstack next --repo . --objective-id <objective> --objective-kind <kind> \
boatstack next --repo . --objective-id <objective> --target-id <kind> \
--delivery <delivery> --format json

# Resolve one repository-owned entry.
Expand Down
32 changes: 18 additions & 14 deletions boatstack/cmd/boatstack-helper/flow_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ func bindFlowEntry(ctx context.Context, options commandOptions) (commandOptions,
return commandOptions{}, fmt.Errorf("FLOW_ACTIVE_RUN_INVALID: active abandonment has no committed run identity")
}
options.repository = repository
if options.objectiveKind == "" {
options.objectiveKind = string(objective)
if options.targetID == "" {
options.targetID = string(objective.TargetID)
}
if options.trustedObjectiveClass == "" {
options.trustedObjectiveClass = string(objective.TrustedClass)
}
if options.deliveryID == "" {
options.deliveryID = deliveryID
Expand All @@ -110,15 +113,15 @@ func bindFlowEntry(ctx context.Context, options commandOptions) (commandOptions,
if options.objectiveID == "" {
options.objectiveID = expectedObjectiveID
}
if options.objectiveKind != string(objective) || options.deliveryID != deliveryID || options.objectiveID != expectedObjectiveID {
if options.targetID != string(objective.TargetID) || options.trustedObjectiveClass != string(objective.TrustedClass) || options.deliveryID != deliveryID || options.objectiveID != expectedObjectiveID {
return commandOptions{}, fmt.Errorf("FLOW_CONTEXT_MISMATCH: objective or delivery changed across the run")
}
parameters, err := parseParameters(options.parameters)
if err != nil {
return commandOptions{}, err
}
for name, expected := range map[string]string{
"objective_kind": string(objective),
"target_id": string(objective.TargetID),
"delivery_id": deliveryID,
"source_path": plan,
"source_fingerprint": planFingerprint,
Expand All @@ -129,7 +132,7 @@ func bindFlowEntry(ctx context.Context, options commandOptions) (commandOptions,
}
switch options.transitionID {
case "objective.bind":
if err := bindResolvedParameter(&options, parameters, "objective_kind", string(objective)); err != nil {
if err := bindResolvedParameter(&options, parameters, "target_id", string(objective.TargetID)); err != nil {
return commandOptions{}, err
}
if err := bindResolvedParameter(&options, parameters, "delivery_id", deliveryID); err != nil {
Expand All @@ -149,8 +152,8 @@ func bindFlowEntry(ctx context.Context, options commandOptions) (commandOptions,
return options, nil
}

func bindActiveFlowContext(ctx context.Context, repository string, options commandOptions, entryObjective model.ObjectiveKind) (commandOptions, error) {
if options.runID != "" && entryObjective != model.ObjectiveAbandoned {
func bindActiveFlowContext(ctx context.Context, repository string, options commandOptions, entryObjective softwareflow.EntryObjective) (commandOptions, error) {
if options.runID != "" && entryObjective.TrustedClass != model.ObjectiveAbandoned {
return options, nil
}
resolver, err := plant.NewResolver("")
Expand Down Expand Up @@ -201,13 +204,13 @@ func bindActiveFlowContext(ctx context.Context, repository string, options comma
if !found || !strings.HasPrefix(receipt.FlowID, "run-") {
return commandOptions{}, fmt.Errorf("FLOW_ACTIVE_RUN_INVALID: active objective has no committed run identity")
}
if active.Kind == entryObjective && strings.HasPrefix(active.ID, prefix) {
if active.TargetID == entryObjective.TargetID && strings.HasPrefix(active.ID, prefix) {
options.runID, options.deliveryID = receipt.FlowID, active.DeliveryID
options.objectiveID, options.objectiveKind = active.ID, string(active.Kind)
options.objectiveID, options.targetID, options.trustedObjectiveClass = active.ID, string(active.TargetID), string(active.TrustedObjectiveClass())
options.activeFlowBound = true
return options, nil
}
if entryObjective == model.ObjectiveAbandoned {
if entryObjective.TrustedClass == model.ObjectiveAbandoned {
repositoryIdentity, identityErr := flowRepositoryIdentity(repository)
if identityErr != nil {
return commandOptions{}, identityErr
Expand Down Expand Up @@ -252,7 +255,7 @@ func bindRPCFlowEntry(ctx context.Context, request surfaces.Request) (surfaces.R
bound, err := bindFlowEntry(ctx, commandOptions{
repository: request.Repository, host: request.Host, programID: request.ProgramID, entryID: request.EntryID,
flowProgramFingerprint: request.ProgramFingerprint,
runID: request.FlowID, objectiveID: request.Objective.ID, objectiveKind: string(request.Objective.Kind), deliveryID: request.Objective.DeliveryID,
runID: request.FlowID, objectiveID: request.Objective.ID, targetID: string(request.Objective.TargetID), trustedObjectiveClass: string(request.Objective.TrustedObjectiveClass()), deliveryID: request.Objective.DeliveryID,
transitionID: string(request.TransitionID), parameters: parameterFlags,
})
if err != nil {
Expand All @@ -266,14 +269,15 @@ func bindRPCFlowEntry(ctx context.Context, request surfaces.Request) (surfaces.R
request.ProgramFingerprint = bound.flowProgramFingerprint
request.FlowID = bound.runID
request.Objective.ID = bound.objectiveID
request.Objective.Kind = model.ObjectiveKind(bound.objectiveKind)
request.Objective.TargetID = model.TargetID(bound.targetID)
request.Objective.TrustedClass = model.TargetID(bound.trustedObjectiveClass)
request.Objective.DeliveryID = bound.deliveryID
request.Parameters = parameters
return request, nil
}

func resolveBoundPlan(repository string, entry controlprogram.Entry, entryObjective model.ObjectiveKind, options commandOptions) (string, string, error) {
if options.activeFlowBound && entryObjective == model.ObjectiveAbandoned {
func resolveBoundPlan(repository string, entry controlprogram.Entry, entryObjective softwareflow.EntryObjective, options commandOptions) (string, string, error) {
if options.activeFlowBound && entryObjective.TrustedClass == model.ObjectiveAbandoned {
return "", options.deliveryID, nil
}
if options.runID == "" && options.deliveryID == "" {
Expand Down
22 changes: 12 additions & 10 deletions boatstack/cmd/boatstack-helper/flow_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func TestFlowRunIdentitySurvivesWorkspaceTransfer(t *testing.T) {
resumed, err := bindFlowEntry(context.Background(), commandOptions{
repository: destination, programID: "product-delivery", entryID: "run", host: "codex",
flowProgramFingerprint: initial.flowProgramFingerprint, runID: initial.runID,
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID,
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID,
transitionID: "plan.create",
})
if err != nil {
Expand Down Expand Up @@ -777,13 +777,13 @@ func TestFlowEntryBindsStableRunAndResumesManagedPlan(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(initial.runID, "run-") || initial.deliveryID != "delivery-one" || initial.objectiveKind != "open-or-updated-pr" || len(initial.parameters) != 0 {
if !strings.HasPrefix(initial.runID, "run-") || initial.deliveryID != "delivery-one" || initial.targetID != "published-pr" || initial.trustedObjectiveClass != "open-or-updated-pr" || len(initial.parameters) != 0 {
t.Fatalf("initial Flow context = %#v", initial)
}
for _, transitionID := range []string{"objective.bind", "plan.create"} {
preManaged, err := bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID, transitionID: transitionID,
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID, transitionID: transitionID,
})
if err != nil {
t.Fatalf("pre-materialization %s binding failed: %v", transitionID, err)
Expand All @@ -803,7 +803,7 @@ func TestFlowEntryBindsStableRunAndResumesManagedPlan(t *testing.T) {
writeFixture(t, repository, ".boatstack/plans/inbox/unrelated.md", []byte("other plan"))
resumed, err := bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID, transitionID: "plan.create",
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID, transitionID: "plan.create",
})
if err != nil {
t.Fatal(err)
Expand All @@ -822,7 +822,9 @@ func TestFlowEntryBindsStableRunAndResumesManagedPlan(t *testing.T) {

func TestRepositoryNamedAbandonmentEntryUsesCompiledObjective(t *testing.T) {
entry := controlprogram.Entry{ID: "cancel", Target: "safely-abandoned"}
plan, delivery, err := resolveBoundPlan(t.TempDir(), entry, model.ObjectiveAbandoned, commandOptions{
plan, delivery, err := resolveBoundPlan(t.TempDir(), entry, softwareflow.EntryObjective{
TargetID: model.TargetID("safely-abandoned"), TrustedClass: model.ObjectiveAbandoned,
}, commandOptions{
entryID: "cancel", activeFlowBound: true, deliveryID: "delivery-one",
})
if err != nil {
Expand All @@ -845,7 +847,7 @@ func TestFlowEntryRejectsSelectedPlanContentSubstitution(t *testing.T) {
writeFixture(t, repository, planPath, []byte("plan B"))
_, err = bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID, transitionID: "plan.create",
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID, transitionID: "plan.create",
})
if err == nil || !strings.Contains(err.Error(), "FLOW_RUN_MISMATCH") {
t.Fatalf("plan substitution result = %v", err)
Expand All @@ -865,7 +867,7 @@ func TestFlowEntryPreservesSelectedPlanFilenameBeforeMaterialization(t *testing.
}
resumed, err := bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID, transitionID: "plan.create",
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID, transitionID: "plan.create",
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -898,7 +900,7 @@ func TestFlowEntryRejectsAmbiguousPlanFilenameOnResume(t *testing.T) {
}
_, err = bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID, transitionID: "plan.create",
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID, transitionID: "plan.create",
})
if err == nil || !strings.Contains(err.Error(), "FLOW_INPUT_INVALID") {
t.Fatalf("ambiguous resume result = %v", err)
Expand All @@ -915,7 +917,7 @@ func TestFlowEntryRejectsObjectiveSubstitutionWithinRun(t *testing.T) {
}
_, err = bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", host: "codex",
runID: initial.runID, deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind,
runID: initial.runID, deliveryID: initial.deliveryID, targetID: initial.targetID,
objectiveID: "objective-substituted", transitionID: "objective.bind",
})
if err == nil || !strings.Contains(err.Error(), "FLOW_CONTEXT_MISMATCH") {
Expand Down Expand Up @@ -947,7 +949,7 @@ func TestFlowEntryRejectsManagedPlanSymlinkEscape(t *testing.T) {
}
_, err = bindFlowEntry(context.Background(), commandOptions{
repository: repository, programID: "product-delivery", entryID: "run", runID: initial.runID, host: "codex",
deliveryID: initial.deliveryID, objectiveKind: initial.objectiveKind, objectiveID: initial.objectiveID,
deliveryID: initial.deliveryID, targetID: initial.targetID, objectiveID: initial.objectiveID,
})
if err == nil || !strings.Contains(err.Error(), "regular non-symlink") {
t.Fatalf("managed symlink result = %v", err)
Expand Down
11 changes: 6 additions & 5 deletions boatstack/cmd/boatstack-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ type commandOptions struct {
repository string
format string
objectiveID string
objectiveKind string
targetID string
trustedObjectiveClass string
deliveryID string
programID string
flowProgramFingerprint string
Expand Down Expand Up @@ -272,12 +273,12 @@ func parseOptions(command string, arguments []string, transition catalog.Transit
flags.SetOutput(os.Stderr)
options := commandOptions{format: "json", transitionID: string(transition), host: "cli"}
if defaults != nil {
options.objectiveKind, options.deliveryID, options.objectiveID = defaults["objective-kind"], defaults["delivery"], defaults["objective-id"]
options.targetID, options.deliveryID, options.objectiveID = defaults["target-id"], defaults["delivery"], defaults["objective-id"]
}
flags.StringVar(&options.repository, "repo", ".", "explicit invoking repository or worktree")
flags.StringVar(&options.format, "format", options.format, "json, text, or jsonl")
flags.StringVar(&options.objectiveID, "objective-id", options.objectiveID, "configured objective identity")
flags.StringVar(&options.objectiveKind, "objective-kind", options.objectiveKind, "approved-plan, verified-implementation, open-or-updated-pr, merged-delivery, or safely-abandoned")
flags.StringVar(&options.targetID, "target-id", options.targetID, "program-scoped marked target identity")
flags.StringVar(&options.deliveryID, "delivery", options.deliveryID, "delivery identity")
flags.StringVar(&options.programID, "flow", "", "repository Control Program identity")
flags.StringVar(&options.entryID, "entry", "", "named Flow entry")
Expand Down Expand Up @@ -495,8 +496,8 @@ func buildRequest(operation surfaces.Operation, options commandOptions) (surface
correlation = fmt.Sprintf("cli-%d-%d", os.Getpid(), now.UnixNano())
}
objective := model.Objective{}
if options.objectiveKind != "" || options.objectiveID != "" || options.deliveryID != "" {
objective = model.Objective{ID: options.objectiveID, Kind: model.ObjectiveKind(options.objectiveKind), DeliveryID: options.deliveryID}
if options.targetID != "" || options.objectiveID != "" || options.deliveryID != "" {
objective = model.Objective{ID: options.objectiveID, TargetID: model.TargetID(options.targetID), TrustedClass: model.TargetID(options.trustedObjectiveClass), DeliveryID: options.deliveryID}
if err := objective.Validate(); err != nil {
return surfaces.Request{}, err
}
Expand Down
2 changes: 1 addition & 1 deletion boatstack/cmd/boatstack-helper/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TestHumanPublicationConfirmationBindsExactPreviewFingerprint(t *testing.T)
// control-law: publication-authority-confirms-exact-preview-bytes
now := time.Now().UTC()
options := commandOptions{humanActor: "reviewer", transitionID: "publication.execute"}
objective := model.Objective{ID: "publish", Kind: model.ObjectiveOpenPR, DeliveryID: "delivery"}
objective := model.Objective{ID: "publish", TargetID: model.ObjectiveOpenPR, DeliveryID: "delivery"}
one, err := loadAuthority(options, "correlation", objective, protocol.Parameters{{Name: "preview_fingerprint", Value: strings.Repeat("a", 64)}}, now)
if err != nil {
t.Fatal(err)
Expand Down
Loading
Loading