mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-03 02:24:33 +00:00
Freeze the engine half of fabro-workflow to bug fixes
The integration plan's F4.1: once Fabro runs on Petri, the engine half of fabro-workflow (handler/, lifecycle/, pipeline/execute, graph/routing, node_handler, retry, condition, context, model_fallback) takes bug fixes only, and new engine behaviour goes to Petri. scripts/check-engine-freeze.sh holds the frozen path list, diffs the branch against a base ref and exits 1 when any frozen file gained lines; scripts/check-engine-freeze-test.sh proves that on a synthetic repository. The Engine freeze workflow runs both on every pull request that touches the crate's src, re-runs on label changes, and fails unless the pull request carries the `bugfix` label. AGENTS.md and the fabro-petri README name the freeze, the label and the script. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
6e38ad27fb
commit
806ac90c98
5 changed files with 214 additions and 0 deletions
53
.github/workflows/engine-freeze.yml
vendored
Normal file
53
.github/workflows/engine-freeze.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Engine freeze
|
||||
|
||||
# The engine half of fabro-workflow takes bug fixes only; new engine behaviour
|
||||
# goes to Petri. A pull request that adds lines under the frozen paths fails
|
||||
# here unless it carries the `bugfix` label. The frozen paths live in
|
||||
# `scripts/check-engine-freeze.sh`, which runs locally the same way.
|
||||
# Labeling re-runs the check so a label added after a failure clears it.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
paths:
|
||||
- "lib/components/fabro-workflow/src/**"
|
||||
- "scripts/check-engine-freeze.sh"
|
||||
- "scripts/check-engine-freeze-test.sh"
|
||||
- ".github/workflows/engine-freeze.yml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
freeze:
|
||||
name: Engine half takes bug fixes only
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The check diffs against the merge base with the base branch.
|
||||
fetch-depth: 0
|
||||
- name: Self-test the check
|
||||
run: scripts/check-engine-freeze-test.sh
|
||||
- name: Check the frozen paths
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
HAS_BUGFIX_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'bugfix') }}
|
||||
run: |
|
||||
git fetch --no-tags origin "$BASE_REF"
|
||||
if scripts/check-engine-freeze.sh "origin/$BASE_REF"; then
|
||||
exit 0
|
||||
fi
|
||||
if [ "$HAS_BUGFIX_LABEL" = "true" ]; then
|
||||
echo "The 'bugfix' label waives the engine freeze for this pull request."
|
||||
exit 0
|
||||
fi
|
||||
echo "::error::This pull request adds lines to the frozen engine half of fabro-workflow (see the log for the paths). New engine behaviour goes to Petri (lib/components/fabro-petri). A bug fix needs the 'bugfix' label."
|
||||
exit 1
|
||||
|
|
@ -136,6 +136,10 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
|
|||
- **fabro-telemetry** — CLI analytics (Segment) and crash reporting (Sentry), with anonymous IDs, command sanitization, and detached subprocess delivery
|
||||
- **fabro-util** — Shared utilities (redaction, terminal formatting)
|
||||
|
||||
### Engine freeze
|
||||
|
||||
The engine half of `fabro-workflow` takes bug fixes only: `handler/`, `lifecycle/`, `pipeline/execute` (the file and the directory), `graph/routing.rs`, `node_handler.rs`, `retry.rs`, `condition.rs`, `context.rs` and `model_fallback.rs` under `lib/components/fabro-workflow/src/`. New engine behaviour goes to Petri through `fabro-petri`. The `Engine freeze` CI check (`.github/workflows/engine-freeze.yml`) fails a pull request that adds lines under those paths unless it carries the `bugfix` label. The path list lives in `scripts/check-engine-freeze.sh`, which runs locally as `scripts/check-engine-freeze.sh origin/main` and reports the added lines; `scripts/check-engine-freeze-test.sh` is its self-test.
|
||||
|
||||
### TypeScript (`apps/` and `lib/packages/`)
|
||||
- **apps/fabro-web** — React 19 + React Router + Tailwind CSS frontend, bundled by a custom Bun script (`apps/fabro-web/scripts/build.ts`), not Vite
|
||||
- **lib/packages/fabro-api-client** — Auto-generated TypeScript Axios client from OpenAPI spec
|
||||
|
|
|
|||
|
|
@ -10,6 +10,18 @@ member that lists them as dependencies. Every other Fabro crate reaches the
|
|||
engine through what this crate exports. A Petri pin move is therefore a change
|
||||
to this crate and the lockfile, nothing else.
|
||||
|
||||
## Engine freeze
|
||||
|
||||
The engine half of `fabro-workflow` (`handler/`, `lifecycle/`,
|
||||
`pipeline/execute`, `graph/routing.rs`, `node_handler.rs`, `retry.rs`,
|
||||
`condition.rs`, `context.rs` and `model_fallback.rs` under its `src/`) takes
|
||||
bug fixes only. New engine behaviour goes to Petri and reaches Fabro through
|
||||
this crate. The `Engine freeze` CI check
|
||||
(`.github/workflows/engine-freeze.yml`) fails a pull request that adds lines
|
||||
under those paths unless it carries the `bugfix` label. The path list is in
|
||||
`scripts/check-engine-freeze.sh`; run it locally as
|
||||
`scripts/check-engine-freeze.sh origin/main` to see what a branch adds there.
|
||||
|
||||
## What it holds
|
||||
|
||||
Every adapter the integration plan describes lands here.
|
||||
|
|
|
|||
65
scripts/check-engine-freeze-test.sh
Executable file
65
scripts/check-engine-freeze-test.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env bash
|
||||
# Self-test for scripts/check-engine-freeze.sh against a synthetic repository:
|
||||
# an added line under a frozen path exits 1, and deletions or additions
|
||||
# elsewhere exit 0.
|
||||
set -euo pipefail
|
||||
|
||||
CHECK="$(cd "$(dirname "$0")" && pwd)/check-engine-freeze.sh"
|
||||
SRC="lib/components/fabro-workflow/src"
|
||||
|
||||
export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.com
|
||||
export GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.com
|
||||
|
||||
repo="$(mktemp -d)"
|
||||
trap 'rm -rf "$repo"' EXIT
|
||||
cd "$repo"
|
||||
git init -q -b main
|
||||
mkdir -p "$SRC/handler" "$SRC/pipeline/execute" "$SRC/transforms"
|
||||
printf 'a\nb\nc\n' > "$SRC/handler/agent.rs"
|
||||
printf 'a\nb\n' > "$SRC/pipeline/execute/tests.rs"
|
||||
printf 'a\n' > "$SRC/retry.rs"
|
||||
printf 'a\n' > "$SRC/transforms/preamble.rs"
|
||||
printf 'a\n' > "$SRC/pipeline/finalize.rs"
|
||||
git add -A
|
||||
git commit -q -m base
|
||||
|
||||
failures=0
|
||||
expect() {
|
||||
local name="$1" want="$2"
|
||||
git checkout -q -b "$name" main
|
||||
"case_$name"
|
||||
git add -A
|
||||
git commit -q --allow-empty -m "$name"
|
||||
local got=0
|
||||
"$CHECK" main > /dev/null || got=$?
|
||||
if [ "$got" -eq "$want" ]; then
|
||||
echo "ok $name (exit $got)"
|
||||
else
|
||||
echo "FAIL $name: expected exit $want, got $got"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
git checkout -q main
|
||||
}
|
||||
|
||||
case_adds_to_frozen_file() { echo d >> "$SRC/handler/agent.rs"; }
|
||||
case_adds_to_frozen_dir() { echo c >> "$SRC/pipeline/execute/tests.rs"; }
|
||||
case_rewrites_frozen_line() { printf 'a\nB\nc\n' > "$SRC/handler/agent.rs"; }
|
||||
case_deletes_from_frozen_file() { printf 'a\n' > "$SRC/handler/agent.rs"; }
|
||||
case_adds_outside_freeze() {
|
||||
echo b >> "$SRC/transforms/preamble.rs"
|
||||
echo b >> "$SRC/pipeline/finalize.rs"
|
||||
}
|
||||
case_no_change() { :; }
|
||||
|
||||
expect adds_to_frozen_file 1
|
||||
expect adds_to_frozen_dir 1
|
||||
expect rewrites_frozen_line 1
|
||||
expect deletes_from_frozen_file 0
|
||||
expect adds_outside_freeze 0
|
||||
expect no_change 0
|
||||
|
||||
if [ "$failures" -ne 0 ]; then
|
||||
echo "$failures case(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "all cases passed"
|
||||
80
scripts/check-engine-freeze.sh
Executable file
80
scripts/check-engine-freeze.sh
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env bash
|
||||
# Report added lines under the frozen engine half of fabro-workflow.
|
||||
#
|
||||
# The engine half of `lib/components/fabro-workflow` takes bug fixes only;
|
||||
# new engine behaviour goes to Petri (`lib/components/fabro-petri`). This
|
||||
# script lists every frozen file the current branch adds lines to, compared
|
||||
# with the base ref, and exits 1 when there is at least one. It knows nothing
|
||||
# about pull request labels: the CI job (`.github/workflows/engine-freeze.yml`)
|
||||
# waives a failure when the pull request carries the `bugfix` label.
|
||||
#
|
||||
# Usage: scripts/check-engine-freeze.sh [<base-ref>] (default: origin/main)
|
||||
set -euo pipefail
|
||||
|
||||
BASE_REF="${1:-origin/main}"
|
||||
LABEL="bugfix"
|
||||
|
||||
# The frozen paths, relative to the fabro-workflow crate's `src/`. A directory
|
||||
# freezes everything under it. `preamble` in the integration plan is
|
||||
# `handler/llm/preamble.rs`, which `handler/` covers; `transforms/preamble.rs`
|
||||
# is a graph transform and is not frozen.
|
||||
FROZEN=(
|
||||
handler
|
||||
lifecycle
|
||||
pipeline/execute.rs
|
||||
pipeline/execute
|
||||
graph/routing.rs
|
||||
node_handler.rs
|
||||
retry.rs
|
||||
condition.rs
|
||||
context.rs
|
||||
model_fallback.rs
|
||||
)
|
||||
|
||||
if [ "${FREEZE_LIST_ONLY:-}" = "1" ]; then
|
||||
printf '%s\n' "${FROZEN[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
CRATE_SRC="lib/components/fabro-workflow/src"
|
||||
|
||||
paths=()
|
||||
for entry in "${FROZEN[@]}"; do
|
||||
paths+=("$CRATE_SRC/$entry")
|
||||
done
|
||||
|
||||
# Three dots: the changes since the merge base, which is what a pull request
|
||||
# adds to its base branch.
|
||||
numstat="$(git -C "$ROOT" diff --numstat "$BASE_REF...HEAD" -- "${paths[@]}")"
|
||||
|
||||
offenders=()
|
||||
while IFS=$'\t' read -r added _deleted path; do
|
||||
[ -n "${path:-}" ] || continue
|
||||
case "$added" in
|
||||
''|*[!0-9]*) continue ;; # binary files report '-'
|
||||
esac
|
||||
if [ "$added" -gt 0 ]; then
|
||||
offenders+=("$added $path")
|
||||
fi
|
||||
done <<< "$numstat"
|
||||
|
||||
if [ "${#offenders[@]}" -eq 0 ]; then
|
||||
echo "engine freeze: no lines added under the frozen paths of fabro-workflow since $BASE_REF"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "engine freeze: this branch adds lines to the frozen engine half of fabro-workflow (since $BASE_REF):"
|
||||
for line in "${offenders[@]}"; do
|
||||
echo " +${line%% *} ${line#* }"
|
||||
done
|
||||
echo
|
||||
echo "The engine half of lib/components/fabro-workflow takes bug fixes only."
|
||||
echo "New engine behaviour goes to Petri (lib/components/fabro-petri)."
|
||||
echo "Frozen paths under $CRATE_SRC/:"
|
||||
for entry in "${FROZEN[@]}"; do
|
||||
echo " $entry"
|
||||
done
|
||||
echo
|
||||
echo "A bug fix passes CI when the pull request carries the '$LABEL' label."
|
||||
exit 1
|
||||
Loading…
Add table
Reference in a new issue