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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Contributing

Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/53f0f064a116c6d0edbacd2b07dba935481bba96/labs/12-product-engineering-loop).
Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/4f00d6d7338b12116fd5757238dd45a2ae344237/labs/12-product-engineering-loop).

The Boatstack repository receives product/runtime changes through a generated pull request. Review the PR's `UPSTREAM.json`, tests, adapter diff, and context-size change; do not hand-edit generated output on `main`. `.github/workflows` is the exception: it is Boatstack's executable control plane, excluded from scheduled projection and changed only through a separate manually reviewed Boatstack PR.

Expand Down
86 changes: 46 additions & 40 deletions UPSTREAM.json

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions boatstack/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
type AttachOptions struct {
Repo string
ConfigPath string
// BinaryPath is the already verified helper to install into detached
// controller state. The CLI leaves it empty and uses its running binary;
// tests and embedders may bind an equivalent verified helper explicitly.
BinaryPath string
Force bool
}

Expand Down Expand Up @@ -174,11 +178,19 @@ func AttachDetached(opts AttachOptions) (AttachResult, error) {
// Populate the external shared-runtime slot from the running helper so the
// developer-level ambient guard has a stable helper to invoke. The binding is
// written above, so WorkspaceFor now resolves detached and the slot is external.
if source, execErr := os.Executable(); execErr == nil {
if _, runtimeErr := installDetachedRuntime(root, source); runtimeErr != nil {
return blockedAttach("Boatstack could not install the external runtime: " + runtimeErr.Error()), nil
source := strings.TrimSpace(opts.BinaryPath)
if source == "" {
source, err = os.Executable()
if err != nil {
return blockedAttach("Boatstack could not locate its running helper: " + err.Error()), nil
}
}
if _, runtimeErr := installDetachedRuntime(root, source); runtimeErr != nil {
return blockedAttach("Boatstack could not install the external runtime: " + runtimeErr.Error()), nil
}
if _, _, runtimeErr := installControllerLocalRuntime(ctx.ExportRoot(), source, config.Integrations); runtimeErr != nil {
return blockedAttach("Boatstack could not install the detached controller helper: " + runtimeErr.Error()), nil
}

return AttachResult{
SchemaVersion: detachedSchemaVersion,
Expand Down
4 changes: 4 additions & 0 deletions boatstack/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ func CaptureEvidence(options CaptureEvidenceOptions) (PRVisualEvidenceManifest,
if base == "" {
base = defaultPRBase(repo)
}
base, err = canonicalPRBase(repo, base)
if err != nil {
return PRVisualEvidenceManifest{}, err
}
headCommit, diffHash, err := captureProductDiff(repo, base, feature, head)
if err != nil {
return PRVisualEvidenceManifest{}, err
Expand Down
107 changes: 107 additions & 0 deletions boatstack/cmd/boatstack-helper/command_trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"strings"
"time"

boatstack "github.com/operatorstack/boatstack/boatstack"
"github.com/operatorstack/boatstack/boatstack/internal/deliverycontrol"
)

type commandTracePolicy struct {
Category string
ExcludedReason string
}

// commandTracePolicies is the declared helper surface inventory. Safety hooks
// are intentionally excluded because telemetry must not add latency or writes
// to the enforcement path; every other dispatch is recorded once by run().
var commandTracePolicies = map[string]commandTracePolicy{
"attach": {Category: "supervision"}, "detach": {Category: "supervision"},
"detached-status": {Category: "supervision"}, "context": {Category: "supervision"},
"activate": {Category: "supervision"}, "deactivate": {Category: "supervision"},
"init": {Category: "installation"}, "update": {Category: "installation"},
"check-update": {Category: "installation"}, "repair-status": {Category: "installation"},
"prepare-update-pr": {Category: "update-publication"}, "publish-update-pr": {Category: "update-publication"},
"release-classify": {Category: "release"}, "next-patch": {Category: "release"},
"export": {Category: "installation"}, "migrate-config": {Category: "installation"},
"hydrate-runtime": {Category: "installation"}, "doctor": {Category: "readiness"},
"check-source-plan": {Category: "planning"}, "check-plan": {Category: "planning"},
"planning-write": {Category: "planning"}, "record-approval": {Category: "planning"},
"record-autonomy": {Category: "planning"}, "activate-plan": {Category: "delivery"},
"delivery-status": {Category: "delivery"}, "next-status": {Category: "delivery"},
"recovery-status": {Category: "recovery"}, "repair-state": {Category: "recovery"},
"mutation-status": {Category: "recovery"}, "undo": {Category: "recovery"},
"run-preflight": {Category: "readiness"}, "authority-context": {Category: "readiness"},
"record-change": {Category: "recovery"}, "record-journey-results": {Category: "evidence"},
"ignore-delivery": {Category: "delivery"}, "discard-delivery": {Category: "recovery"},
"record-delivery-gate": {Category: "delivery"}, "record-pr-visual-evidence": {Category: "visual-evidence"},
"review-pr-visual-evidence": {Category: "visual-evidence"}, "capture-evidence": {Category: "visual-evidence"},
"provision-capability": {Category: "capability"}, "capability-register": {Category: "capability"},
"record-pr-visual-publication": {Category: "visual-evidence"}, "attach-evidence": {Category: "visual-evidence"},
"pr-context": {Category: "publication"}, "check-pr": {Category: "publication"},
"publish-pr": {Category: "publication"}, "operation-status": {Category: "publication"},
"diagnose-hook": {Category: "diagnostic"}, "render-denial": {Category: "diagnostic"},
"check-safety": {Category: "readiness"}, "workspace-cut": {Category: "workspace"},
"workspace-cleanup": {Category: "workspace"}, "workspace-reap": {Category: "workspace"},
"workspace-status": {Category: "workspace"}, "workspace-sync": {Category: "workspace"},
"flow": {Category: "flow"}, "retro": {Category: "analysis"},
"insight": {Category: "insight"}, "version": {Category: "diagnostic"},
"safety-hook": {Category: "safety", ExcludedReason: "latency-sensitive enforcement path"},
"ambient-safety-hook": {Category: "safety", ExcludedReason: "latency-sensitive enforcement path"},
"bootstrap-safety-hook": {Category: "safety", ExcludedReason: "latency-sensitive enforcement path"},
}

func traceFlag(arguments []string, name string) string {
for index, argument := range arguments {
if argument == name && index+1 < len(arguments) {
return strings.TrimSpace(arguments[index+1])
}
if strings.HasPrefix(argument, name+"=") {
return strings.TrimSpace(strings.TrimPrefix(argument, name+"="))
}
}
return ""
}

func traceTransition(verb string, arguments []string) deliverycontrol.TransitionID {
if verb == "record-delivery-gate" {
switch strings.ToLower(traceFlag(arguments, "--gate")) {
case "test":
return "delivery.record_gate_test"
case "review":
return "delivery.record_gate_review"
}
}
for _, transition := range deliverycontrol.Transitions() {
if transition.CLIVerb == verb {
return transition.ID
}
}
return ""
}

func commandTraceCompletion(verb string, arguments []string) func(int) {
policy, ok := commandTracePolicies[verb]
if !ok || policy.ExcludedReason != "" {
return nil
}
started := time.Now()
recordedVerb := verb
if (verb == "flow" || verb == "retro" || verb == "insight") && len(arguments) > 0 && !strings.HasPrefix(arguments[0], "-") {
recordedVerb += "/" + arguments[0]
}
repo := traceFlag(arguments, "--repo")
if repo == "" {
repo = "."
}
feature := traceFlag(arguments, "--feature")
slice := traceFlag(arguments, "--slice")
transition := traceTransition(verb, arguments)
return func(exitCode int) {
boatstack.RecordCommandEvent(boatstack.CommandTraceInput{
Repo: repo, Verb: recordedVerb, Category: policy.Category, Feature: feature, Slice: slice,
Transition: transition, StartedAt: started, FinishedAt: time.Now(), ExitCode: exitCode,
})
}
}
29 changes: 29 additions & 0 deletions boatstack/cmd/boatstack-helper/coverage_conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"go/parser"
"go/token"
"sort"
"strings"
"testing"

"github.com/operatorstack/boatstack/boatstack/internal/deliverycontrol"
Expand Down Expand Up @@ -61,6 +62,7 @@ var nonDeliveryVerbs = map[string]bool{
"workspace-status": true,
// Evidence / capability substrate (a separate tenant, not the delivery graph).
"record-pr-visual-evidence": true,
"review-pr-visual-evidence": true,
"capture-evidence": true,
"provision-capability": true,
"capability-register": true,
Expand Down Expand Up @@ -136,6 +138,33 @@ func dispatchVerbs(t *testing.T) map[string]bool {
return verbs
}

// control-law: every-helper-dispatch-is-observed-or-explicitly-excluded
func TestCommandTraceInventoryCoversDispatchSurfaceExactly(t *testing.T) {
dispatch := dispatchVerbs(t)
for verb := range dispatch {
policy, ok := commandTracePolicies[verb]
if !ok {
t.Errorf("dispatch verb %q has no command trace policy", verb)
continue
}
_ = policy
}
for verb := range commandTracePolicies {
if !dispatch[verb] {
t.Errorf("command trace policy names stale verb %q", verb)
}
}
allowedExcluded := map[string]bool{"safety-hook": true, "ambient-safety-hook": true, "bootstrap-safety-hook": true}
for verb, policy := range commandTracePolicies {
if policy.ExcludedReason != "" && !allowedExcluded[verb] {
t.Errorf("only latency-sensitive safety hooks may be excluded; found %q", verb)
}
if allowedExcluded[verb] && strings.TrimSpace(policy.ExcludedReason) == "" {
t.Errorf("excluded safety hook %q has no reason", verb)
}
}
}

// switchesOnArgs reports whether a switch tag is an index into os.Args (the
// command dispatch), e.g. `os.Args[1]`.
func switchesOnArgs(tag ast.Expr) bool {
Expand Down
16 changes: 8 additions & 8 deletions boatstack/cmd/boatstack-helper/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,15 @@ func driveExecute(repo, feature string, next boatstack.FlowNext) int {

// executePrescribed is the driver's second, independent gate: even a move the pure
// decision blessed as auto-drivable runs only if an executor is explicitly
// registered here for its verb. Nothing is registered today — every real forward
// move owes human input and is refused by the decision before reaching here — so
// this defends against a future allowlist entry landing without a deliberate,
// reviewed executor. It never synthesizes arguments; it would only ever invoke the
// registered here for its verb. It never synthesizes arguments; it only invokes the
// same verb dispatch a human would run.
func executePrescribed(cmd *boatstack.PrescribedCommand) error {
switch cmd.Verb {
// No verbs are registered for auto-execution. Add a case here only together with
// an allowlist entry in flow_drive.go, and only for a verb whose arguments are
// fully state-derivable with nothing to fabricate.
case "publish-pr":
if code := publishPRCommand(cmd.Args); code != 0 {
return fmt.Errorf("publish-pr exited with status %d", code)
}
return nil
default:
return fmt.Errorf("no registered auto-executor for verb %q; run it by hand: %s", cmd.Verb, cmd.CommandLine())
}
Expand Down Expand Up @@ -249,11 +248,12 @@ func flowTasksCommand(arguments []string) int {
func flowReportCommand(arguments []string) int {
flags := flag.NewFlagSet("flow report", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository whose flow session should be reported")
feature := flags.String("feature", "", "optional managed feature used to filter command telemetry")
jsonOutput := flags.Bool("json", false, "print the structured report")
if err := flags.Parse(arguments); err != nil {
return 2
}
report, err := boatstack.FlowReport(*repo)
report, err := boatstack.FlowReportFor(*repo, *feature)
if err != nil {
return fail(err)
}
Expand Down
41 changes: 38 additions & 3 deletions boatstack/cmd/boatstack-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,30 @@ func recordPRVisualEvidenceCommand(arguments []string) int {
return 0
}

func reviewPRVisualEvidenceCommand(arguments []string) int {
flags := flag.NewFlagSet("review-pr-visual-evidence", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository whose Git-common state owns the evidence")
key := flags.String("key", "", "managed feature or ad-hoc visual evidence key")
evidenceFingerprint := flags.String("evidence-fingerprint", "", "exact capture manifest fingerprint reviewed")
reviewerIdentity := flags.String("reviewer-identity", "", "human reviewer identity")
if err := flags.Parse(arguments); err != nil {
return 2
}
if *key == "" || *evidenceFingerprint == "" || *reviewerIdentity == "" {
return fail(fmt.Errorf("review-pr-visual-evidence requires --key, --evidence-fingerprint, and --reviewer-identity"))
}
review, err := boatstack.RecordPRVisualPrivacyReview(*repo, *key, *evidenceFingerprint, *reviewerIdentity)
if err != nil {
return fail(err)
}
value, err := boatstack.MarshalJSON(review)
if err != nil {
return fail(err)
}
fmt.Print(string(value))
return 0
}

func captureEvidenceCommand(arguments []string) int {
flags := flag.NewFlagSet("capture-evidence", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository whose Git-common state owns the evidence")
Expand Down Expand Up @@ -954,11 +978,17 @@ func runPreflightCommand(arguments []string) int {
flags := flag.NewFlagSet("run-preflight", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository whose Git state should be verified before boatstack run")
feature := flags.String("feature", "", "optional specific managed feature to verify")
healthOnly := flags.Bool("health-only", false, "verify installation and generated state without Git network or branch checks")
jsonOutput := flags.Bool("json", false, "print the versioned structured preflight")
if err := flags.Parse(arguments); err != nil {
return 2
}
status := boatstack.CheckRunPreflight(*repo, *feature)
var status boatstack.RunPreflight
if *healthOnly {
status = boatstack.CheckInstallationPreflight(*repo)
} else {
status = boatstack.CheckRunPreflight(*repo, *feature)
}
if *jsonOutput {
value, err := boatstack.MarshalJSON(status)
if err != nil {
Expand Down Expand Up @@ -1563,11 +1593,14 @@ func workspaceSyncCommand(arguments []string) int {
return 0
}

func run() int {
func run() (result int) {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <attach|detach|detached-status|context|activate|deactivate|init|update|check-update|repair-status|operation-status|prepare-update-pr|publish-update-pr|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|record-autonomy|activate-plan|delivery-status|next-status|recovery-status|repair-state|mutation-status|undo|run-preflight|authority-context|record-change|record-journey-results|ignore-delivery|record-delivery-gate|record-pr-visual-evidence|capture-evidence|provision-capability|capability-register|record-pr-visual-publication|attach-evidence|check-safety|migrate-config|safety-hook|ambient-safety-hook|diagnose-hook|render-denial|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-reap|workspace-status|workspace-sync|flow|retro|insight|doctor|version>")
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <attach|detach|detached-status|context|activate|deactivate|init|update|check-update|repair-status|operation-status|prepare-update-pr|publish-update-pr|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|record-autonomy|activate-plan|delivery-status|next-status|recovery-status|repair-state|mutation-status|undo|run-preflight|authority-context|record-change|record-journey-results|ignore-delivery|record-delivery-gate|record-pr-visual-evidence|review-pr-visual-evidence|capture-evidence|provision-capability|capability-register|record-pr-visual-publication|attach-evidence|check-safety|migrate-config|safety-hook|ambient-safety-hook|diagnose-hook|render-denial|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-reap|workspace-status|workspace-sync|flow|retro|insight|doctor|version>")
return 2
}
if complete := commandTraceCompletion(os.Args[1], os.Args[2:]); complete != nil {
defer func() { complete(result) }()
}
switch os.Args[1] {
case "attach":
return attachCommand(os.Args[2:])
Expand Down Expand Up @@ -1641,6 +1674,8 @@ func run() int {
return recordDeliveryGateCommand(os.Args[2:])
case "record-pr-visual-evidence":
return recordPRVisualEvidenceCommand(os.Args[2:])
case "review-pr-visual-evidence":
return reviewPRVisualEvidenceCommand(os.Args[2:])
case "capture-evidence":
return captureEvidenceCommand(os.Args[2:])
case "provision-capability":
Expand Down
Loading
Loading