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
6 changes: 3 additions & 3 deletions .github/tests/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,8 @@ def test_general_kernel_is_domain_neutral_and_owns_shared_control_laws(self) ->
REPO / "boatstack" / "internal" / "softwaredelivery" /
"protocol" / "prescription.go"
).read_text()
self.assertIn("Relate(RelationInput", runtime)
self.assertIn("general.Relate(general.RelationInput", software_relation)
self.assertIn("RelateWithTrace(RelationInput", runtime)
self.assertIn("general.RelateWithTrace(general.RelationInput", software_relation)
self.assertIn("general.Freshness", software_prescription)
self.assertIn("general.NewFreshness", software_prescription)

Expand Down Expand Up @@ -652,7 +652,7 @@ def test_documented_cli_verbs_are_registered_v2_surfaces(self) -> None:
*sorted((REPO / "boatstack" / "references").glob("*.md")),
]
registered = {
"status", "next", "next-status", "apply", "recover", "doctor",
"status", "next", "next-status", "explain", "apply", "recover", "doctor",
"events", "catalog", "guard", "rpc", "retro", "version", "init",
"update", "attach", "detach", "hydrate-runtime", "configure",
"reconcile-update",
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ control graph. The complete list is generated from the registry in the
| **Transactions** | Prescriptions bind the exact control instance, state revision, program, objective binding, observation, transition, and authority. Apply rechecks that boundary before execution. |
| **Verification and receipts** | Fresh postcondition verification, atomic state-and-receipt commits, and immutable transition facts. |
| **Recovery** | A durable effect attempt precedes execution. Interrupted or uncertain outcomes enter explicit recovery instead of blindly repeating an effect. |
| **Control debugging** | Read-only decision traces explain why a transition was selected, rejected, blocked, ambiguous, or waiting on authority without reconstructing lifecycle logic in the host. |
| **Conformance** | A reusable, domain-neutral suite verifies objective handling, authority, freshness, recovery, atomic commit, replay isolation, concurrency, and marked-state reachability against any explicitly mapped domain fixture. |

### Software delivery
Expand Down Expand Up @@ -152,6 +153,9 @@ boatstack next --repo . --objective-id <objective> --target-id <kind> \
# Resolve one repository-owned entry.
boatstack next --repo . --flow product-delivery --entry run --format json

# Explain the current decision without executing an effect.
boatstack explain --repo . --flow product-delivery --entry run

# Inspect the exact program and transition surface.
boatstack doctor --repo . --format text
boatstack catalog --format json
Expand All @@ -165,7 +169,11 @@ boatstack apply --repo . --transition <stable-id> --run-id <run> \
--expected-snapshot-fingerprint <sha256> --format json
```

`status`, `next`, `doctor`, `catalog`, and `events` are read-only. Friendly
`status`, `next`, `explain`, `doctor`, `catalog`, and `events` are read-only.
The three Flow surfaces answer different questions: `flow check` verifies that
the artifact is a valid executable Control Program; `next` and `flow run`
resolve or execute the controller; `explain` reports why the current controller
decision occurred. It does not grant authority or recommend a fix. Friendly
commands such as `plan-create`, `workspace-cut`, `record-test`, and `publish-pr`
resolve and consume one exact prescription in the same invocation.

Expand Down
25 changes: 17 additions & 8 deletions boatstack/cmd/boatstack-helper/delegation_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,19 @@ func prepareDelegation(ctx context.Context, request *surfaces.Request) (ports.Lo
releaseOnError()
return nil, nil, err
}
filtered := request.Authority.Receipts[:0]
for _, receipt := range request.Authority.Receipts {
if len(receipt.ID) < len("delegation-") || receipt.ID[:len("delegation-")] != "delegation-" {
filtered = append(filtered, receipt)
}
}
request.Authority.Receipts = filtered
record, err := delegation.Load(recordPath)
if os.IsNotExist(err) {
releaseOnError()
if request.Operation == surfaces.OperationExplain {
return nil, nil, nil
}
return nil, &surfaces.Response{
SchemaVersion: surfaces.SchemaVersion, Operation: request.Operation, ProgramID: request.ProgramID, EntryID: request.EntryID, RunID: request.FlowID, Objective: request.Objective,
Delegation: &surfaces.DelegationRequired{Code: "DELEGATION_REQUIRED", RunID: request.FlowID, RequestFingerprint: request.DelegationRequestFingerprint, Authorities: append([]catalog.AuthorityClass(nil), request.DelegatedAuthorities...), Description: "Explicitly authorize " + request.ProgramID + "/" + request.EntryID + " for this exact run"},
Expand All @@ -82,24 +92,23 @@ func prepareDelegation(ctx context.Context, request *surfaces.Request) (ports.Lo
releaseOnError()
return nil, nil, fmt.Errorf("DELEGATION_CONTEXT_UNAUTHORIZED: current worktree is not in the verified run lineage")
}
filtered := request.Authority.Receipts[:0]
for _, receipt := range request.Authority.Receipts {
if len(receipt.ID) < len("delegation-") || receipt.ID[:len("delegation-")] != "delegation-" {
filtered = append(filtered, receipt)
}
}
request.Authority.Receipts = filtered
if record.Status == "completed" && request.Operation == surfaces.OperationResolve {
if record.Status == "completed" && (request.Operation == surfaces.OperationResolve || request.Operation == surfaces.OperationExplain) {
// A completed delegation carries no authority, but resolving the exact
// bound run remains safe and lets restarts replay its terminal state.
return nil, nil, nil
}
if record.Status != "active" {
releaseOnError()
if request.Operation == surfaces.OperationExplain {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("DELEGATION_REVOKED: run authorization is %s", record.Status)
}
if !record.ExpiresAt.IsZero() && !time.Now().UTC().Before(record.ExpiresAt) {
releaseOnError()
if request.Operation == surfaces.OperationExplain {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("DELEGATION_EXPIRED: run authorization expired")
}
for _, authority := range request.DelegatedAuthorities {
Expand Down
Loading
Loading