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
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ One verified runtime is cached under the clone's Git common directory and keyed

Independent clones do not share a Git common directory. Committed adapters survive a clone, but the ignored helper and repository-family cache do not; run the installer once in the new clone.

For an update, run `/boatstack-update` from a clean, current default branch. Boatstack creates `chore/update-boatstack-v<version>`, verifies the tagged release and checksum, preserves integrations, and stores a fingerprinted non-empty update-PR preview under Git-common Boatstack state before asking for `o`. `publish-update-pr` owns the exact commit, normal push, and single-PR reconciliation. Release-check state in `.product-loop/bin/update-state.json`, operation receipts under Git-common `boatstack/operations/v1`, the update preview, and the platform helper remain ignored; the adapters, generated lock, hook fragments, and merged host settings belong in the update PR.
For an update, run `/boatstack-update` from a current default branch with no product or user-owned edits. Boatstack creates `chore/update-boatstack-v<version>`, verifies the target helper before inspecting the installed runtime, preserves integrations, and stores a fingerprinted non-empty update-PR preview under Git-common Boatstack state before asking for `o`. Exact owned migrations are automatic. Explicit `--repair` backs up recoverable owned drift under Git-common `boatstack/repair-backups/<fingerprint>` and keeps the repaired files in the same update PR.

An update refuses feature branches, dirty worktrees, stale default branches, changed generated files, and user-owned collisions. It never merges its own PR.
`publish-update-pr` owns the exact commit, normal push, and single-PR reconciliation. Release-check state in `.product-loop/bin/update-state.json`, operation receipts under Git-common `boatstack/operations/v1`, repair backups, the update preview, and the platform helper remain ignored; adapters, generated locks, hook fragments, and merged host settings belong in the update PR.

An update refuses feature branches, stale default branches, product edits, user-owned collisions, mixed ownership, malformed host documents, and unsafe paths. `--repair` permits only fingerprinted Boatstack-owned drift. It never merges its own PR.

If generated state looks wrong, run:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ After the feature PR is merged, switch to a clean, current default branch and ru
/boatstack-update
```

You may also ask, “Update Boatstack.” Boatstack checks the latest stable release, creates `chore/update-boatstack-v<version>`, preserves the current configuration and integrations, runs `doctor`, and shows the exact infrastructure diff. Product files are outside the allowed update scope.
You may also ask, “Update Boatstack.” Boatstack checks the latest stable release, creates `chore/update-boatstack-v<version>`, preserves the current configuration and integrations, and shows the exact infrastructure diff. If the installed helper cannot perform release discovery, Boatstack uses the official GitHub release endpoint and proceeds through the checksum-verified target installer. The old helper is never required to certify its own repair.

Exact stale Boatstack state migrates automatically. If Boatstack finds recoverable owned drift, an interactive update shows the affected paths and asks whether to continue with `--repair`; pressing Enter declines. Noninteractive runs stop with one copyable `--repair` retry. The repair is backed up outside the worktree and remains visible in the same update PR. User-owned changes are never overwritten. Downgrades require the separate `--allow-downgrade` flag as well.

When the preview is correct, reply:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,13 @@ Release discovery uses a short, unauthenticated request to GitHub and a 24-hour

## The update reports generated drift

Boatstack found an installed generated file that no longer matches its previous lock. Review the named path and move durable project-owned content into `.boatstack-project.json` or repository documentation. Do not overwrite the drift merely to make the update pass.
Boatstack classifies the named path before writing. Exact installed state migrates automatically. If the path is provably Boatstack-owned but drifted, an interactive update shows the fingerprinted repair and asks whether to continue; a noninteractive update returns one retry using `--repair`. The repair is backed up outside the worktree and included in the same update PR.

Do not use `--repair` for user-owned or mixed changes. Move durable project content into `.boatstack-project.json` or repository documentation first. A downgrade additionally requires `--allow-downgrade`; repair authority alone never removes newer behavior.

## The installed helper or hook prevents updating

Use the installer for the target release in update mode. It downloads and verifies the target helper before treating the installed helper's `doctor` result as diagnostic, so a missing helper or stale owned hook cannot disable recovery. Run `repair-status --repo . --json` to inspect the secret-free classification. Malformed host JSON, partial interceptor markers, symlinks, and unverifiable user content remain blocking and are never overwritten.

## A tool call repeats or publication appears stuck

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ The paired product evaluation will use the same feature, lower-cost model, budge

**Status:** release notification and update preparation behavior are verified in automated tests. This is not a claim that updates install themselves or may be merged without review.

**Repair boundary.** The checksum-verified target helper classifies installed control state instead of requiring the old helper to certify itself. Exact owned migrations are automatic; recoverable owned drift receives a fingerprinted `--repair` preview and Git-common backup in the update PR. User-owned changes and downgrades retain separate explicit boundaries.

## Git worktree activation

**What happened.** A Claude Code worktree contained the committed fail-closed hook but not `.product-loop/bin/`, which Git intentionally ignores. Every shell call was denied because the helper was absent, including the installer command that could have repaired it.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
# Generated from operatorstack/intelligence-flow.
[CmdletBinding()]
param(
[switch]$Repair,
[switch]$AllowDowngrade
)
$ErrorActionPreference = "Stop"

$repository = "operatorstack/boatstack"
$version = if ($env:BOATSTACK_VERSION) { $env:BOATSTACK_VERSION } else { "latest" }
$targetRepo = if ($env:BOATSTACK_REPO) { $env:BOATSTACK_REPO } else { (Get-Location).Path }
$mode = if ($env:BOATSTACK_MODE) { $env:BOATSTACK_MODE } else { "install" }
$repairRequested = $Repair -or $env:BOATSTACK_REPAIR -eq "1"
$downgradeRequested = $AllowDowngrade -or $env:BOATSTACK_ALLOW_DOWNGRADE -eq "1"
if ($mode -notin @("install", "update")) {
throw "BLOCKED: BOATSTACK_MODE must be install or update"
}

$existingGeneratedLock = Test-Path -PathType Leaf (Join-Path $targetRepo ".product-loop/generated.lock.json")
$existingHelper = (Test-Path -PathType Leaf (Join-Path $targetRepo ".product-loop/bin/boatstack-helper")) -or (Test-Path -PathType Leaf (Join-Path $targetRepo ".product-loop/bin/boatstack-helper.exe"))
if ($mode -eq "install" -and ($existingGeneratedLock -or $existingHelper)) {
if ($repairRequested) {
$mode = "update"
Write-Host "Existing Boatstack installation detected; preserving its configuration and using update repair semantics."
} else {
throw "BLOCKED: Boatstack is already installed; use BOATSTACK_MODE=update, or add -Repair when owned control state prevents updating"
}
}

if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "BLOCKED: Git is required because Boatstack operates on reviewable repository state"
}
Expand All @@ -21,16 +39,6 @@ $arch = switch ($architecture) {
}

$asset = "boatstack-helper_windows_${arch}.exe"
if ($mode -eq "update") {
$currentHelper = Join-Path $targetRepo ".product-loop/bin/boatstack-helper.exe"
if (-not (Test-Path -PathType Leaf $currentHelper)) {
throw "BLOCKED: current Boatstack helper is missing; repair the installation before updating"
}
& $currentHelper doctor --repo $targetRepo
if ($LASTEXITCODE -ne 0) {
throw "Current Boatstack installation must pass doctor before updating"
}
}
$base = if ($version -eq "latest") {
"https://github.com/$repository/releases/latest/download"
} else {
Expand All @@ -51,6 +59,22 @@ try {
throw "BLOCKED: Boatstack binary checksum mismatch"
}

if ($mode -eq "update") {
$currentHelper = Join-Path $targetRepo ".product-loop/bin/boatstack-helper.exe"
if (Test-Path -PathType Leaf $currentHelper) {
try {
& $currentHelper doctor --repo $targetRepo
if ($LASTEXITCODE -ne 0) {
Write-Warning "Current Boatstack doctor reported drift; the verified target helper will classify whether it is safely repairable."
}
} catch {
Write-Warning "Current Boatstack doctor reported drift; the verified target helper will classify whether it is safely repairable."
}
} else {
Write-Warning "Current Boatstack helper is missing; the verified target helper will classify whether it is safely repairable."
}
}

$commandName = if ($mode -eq "update") { "update" } else { "init" }
$arguments = @($commandName, "--repo", $targetRepo, "--binary", $binary)
if ($mode -eq "install" -and $env:BOATSTACK_INTEGRATIONS) {
Expand All @@ -59,6 +83,12 @@ try {
if ($env:BOATSTACK_YES -eq "1") {
$arguments += "--yes"
}
if ($repairRequested) {
$arguments += "--repair"
}
if ($downgradeRequested) {
$arguments += "--allow-downgrade"
}
& $binary @arguments
if ($LASTEXITCODE -ne 0) {
throw "Boatstack initialization failed with exit code $LASTEXITCODE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@ repository="operatorstack/boatstack"
version="${BOATSTACK_VERSION:-latest}"
target_repo="${BOATSTACK_REPO:-$PWD}"
mode="${BOATSTACK_MODE:-install}"
repair="${BOATSTACK_REPAIR:-0}"
allow_downgrade="${BOATSTACK_ALLOW_DOWNGRADE:-0}"

while [ "$#" -gt 0 ]; do
case "$1" in
--repair) repair=1 ;;
--allow-downgrade) allow_downgrade=1 ;;
*) echo "BLOCKED: unsupported installer argument: $1" >&2; exit 1 ;;
esac
shift
done

case "$mode" in
install|update) ;;
*) echo "BLOCKED: BOATSTACK_MODE must be install or update" >&2; exit 1 ;;
esac

if [ "$mode" = "install" ] && { [ -f "$target_repo/.product-loop/generated.lock.json" ] || [ -f "$target_repo/.product-loop/bin/boatstack-helper" ] || [ -f "$target_repo/.product-loop/bin/boatstack-helper.exe" ]; }; then
if [ "$repair" = "1" ]; then
mode="update"
echo "Existing Boatstack installation detected; preserving its configuration and using update repair semantics."
else
echo "BLOCKED: Boatstack is already installed; use BOATSTACK_MODE=update, or add --repair when owned control state prevents updating" >&2
exit 1
fi
fi

case "$(uname -s)" in
Darwin) os_name="darwin" ;;
Linux) os_name="linux" ;;
Expand All @@ -31,11 +52,6 @@ command -v git >/dev/null 2>&1 || { echo "BLOCKED: Git is required because Boats
extension=""
[ "$os_name" = "windows" ] && extension=".exe"
asset="boatstack-helper_${os_name}_${arch}${extension}"
if [ "$mode" = "update" ]; then
current_helper="$target_repo/.product-loop/bin/boatstack-helper${extension}"
[ -x "$current_helper" ] || { echo "BLOCKED: current Boatstack helper is missing; repair the installation before updating" >&2; exit 1; }
"$current_helper" doctor --repo "$target_repo"
fi
if [ "$version" = "latest" ]; then
base="https://github.com/${repository}/releases/latest/download"
else
Expand All @@ -62,6 +78,17 @@ fi
[ "$expected" = "$actual" ] || { echo "BLOCKED: Boatstack binary checksum mismatch" >&2; exit 1; }
chmod +x "$binary"

if [ "$mode" = "update" ]; then
current_helper="$target_repo/.product-loop/bin/boatstack-helper${extension}"
if [ -x "$current_helper" ]; then
if ! "$current_helper" doctor --repo "$target_repo"; then
echo "Current Boatstack doctor reported drift; the verified target helper will classify whether it is safely repairable." >&2
fi
else
echo "Current Boatstack helper is missing; the verified target helper will classify whether it is safely repairable." >&2
fi
fi

command_name="init"
[ "$mode" = "update" ] && command_name="update"
arguments=("$command_name" --repo "$target_repo" --binary "$binary")
Expand All @@ -71,5 +98,11 @@ fi
if [ "${BOATSTACK_YES:-0}" = "1" ]; then
arguments+=(--yes)
fi
if [ "$repair" = "1" ]; then
arguments+=(--repair)
fi
if [ "$allow_downgrade" = "1" ]; then
arguments+=(--allow-downgrade)
fi

exec "$binary" "${arguments[@]}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Repair an update without trusting the broken installation

Boatstack updates now download and verify the target helper before diagnosing the installed runtime. Exact stale hook and generated-state migrations repair automatically. Recoverable Boatstack-owned drift receives a fingerprinted `--repair` preview, a Git-common backup, and remains visible in the same update PR. User-owned state is preserved, and downgrades require separate `--allow-downgrade` authority.
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,49 @@ func updateCommand(arguments []string) int {
repo := flags.String("repo", ".", "repository to update")
binary := flags.String("binary", "", "verified replacement helper binary")
yes := flags.Bool("yes", false, "accept the generated-file preview")
repair := flags.Bool("repair", false, "repair only fingerprinted Boatstack-owned control state")
allowDowngrade := flags.Bool("allow-downgrade", false, "permit an explicitly repaired downgrade")
if err := flags.Parse(arguments); err != nil {
return 2
}
err := boatstack.RunUpdate(boatstack.InitOptions{Repo: *repo, BinaryPath: *binary, Yes: *yes})
err := boatstack.RunUpdate(boatstack.InitOptions{Repo: *repo, BinaryPath: *binary, Yes: *yes, Repair: *repair, AllowDowngrade: *allowDowngrade})
if err != nil {
return fail(err)
}
return 0
}

func repairStatusCommand(arguments []string) int {
flags := flag.NewFlagSet("repair-status", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository installation to inspect")
allowDowngrade := flags.Bool("allow-downgrade", false, "include explicit downgrade authority in the projection")
jsonOutput := flags.Bool("json", false, "emit the versioned JSON projection")
if err := flags.Parse(arguments); err != nil {
return 2
}
config, _, err := boatstack.LoadConfig(filepath.Join(*repo, ".boatstack-project.json"))
if err != nil {
return fail(err)
}
result, err := boatstack.ClassifyInstallationRepair(*repo, config.Adapters, *allowDowngrade)
if err != nil {
return fail(err)
}
value, err := boatstack.MarshalJSON(result)
if err != nil {
return fail(err)
}
if *jsonOutput {
fmt.Print(string(value))
} else {
fmt.Printf("REPAIR_STATUS=%s\nDIRECTION=%s\nPACKAGE_FINGERPRINT=%s\nNEXT_OPERATION=%s\n", result.VerificationStatus, result.Direction, result.PackageFingerprint, result.NextOperation)
}
if result.VerificationStatus == "BLOCKED" {
return 1
}
return 0
}

func checkUpdateCommand(arguments []string) int {
flags := flag.NewFlagSet("check-update", flag.ContinueOnError)
repo := flags.String("repo", ".", "repository whose Boatstack release should be checked")
Expand Down Expand Up @@ -896,7 +929,7 @@ func workspaceStatusCommand(arguments []string) int {

func run() int {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <init|update|check-update|operation-status|prepare-update-pr|publish-update-pr|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|activate-plan|delivery-status|next-status|recovery-status|run-preflight|record-change|record-delivery-gate|record-pr-visual-evidence|record-pr-visual-publication|check-safety|migrate-config|safety-hook|diagnose-hook|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-status|doctor|version>")
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <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|activate-plan|delivery-status|next-status|recovery-status|run-preflight|record-change|record-delivery-gate|record-pr-visual-evidence|record-pr-visual-publication|check-safety|migrate-config|safety-hook|diagnose-hook|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-status|doctor|version>")
return 2
}
switch os.Args[1] {
Expand All @@ -906,6 +939,8 @@ func run() int {
return updateCommand(os.Args[2:])
case "check-update":
return checkUpdateCommand(os.Args[2:])
case "repair-status":
return repairStatusCommand(os.Args[2:])
case "operation-status":
return operationStatusCommand(os.Args[2:])
case "prepare-update-pr":
Expand Down
Loading