Skip to content
Closed
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
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,19 @@ jobs:
- name: Run lint
run: make lint-backend

version-consistency:
name: "P1 · Version Consistency"
needs: changes
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Assert version-bearing artifacts agree with VERSION
# Helm chart tag/appVersion and frontend package.json must equal VERSION,
# or the release (which publishes only :${VERSION}) yields ImagePullBackOff
# / silent drift (#177 Blocker 2).
run: bash scripts/sync-version-artifacts.sh --check

lint-frontend:
name: "P1 · Lint Frontend"
needs: changes
Expand Down Expand Up @@ -1063,6 +1076,7 @@ jobs:
- changes
# Phase 1
- lint-backend
- version-consistency
- lint-frontend
- typecheck-backend
- openapi-check
Expand Down Expand Up @@ -1096,6 +1110,7 @@ jobs:
failed=false
for job in \
"lint-backend:${{ needs.lint-backend.result }}" \
"version-consistency:${{ needs.version-consistency.result }}" \
"lint-frontend:${{ needs.lint-frontend.result }}" \
"typecheck-backend:${{ needs.typecheck-backend.result }}" \
"openapi-check:${{ needs.openapi-check.result }}" \
Expand Down
54 changes: 53 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ jobs:
if git ls-files --error-unmatch dist/VERSION 2>/dev/null; then
echo "$NEW" > dist/VERSION
fi
# Keep the Helm chart tag/appVersion and frontend package.json in
# lockstep so the chart never pins an image tag the release doesn't
# publish (#177 Blocker 2).
bash scripts/sync-version-artifacts.sh --write "$NEW"

- name: Update CHANGELOG.md
run: |
Expand Down Expand Up @@ -413,8 +417,32 @@ jobs:
NEW="${{ needs.preflight.outputs.new_version }}"
BUMP="${{ needs.preflight.outputs.bump_type }}"

# Stage VERSION, dist/VERSION (if tracked), CHANGELOG
# Stage VERSION, dist/VERSION (if tracked), CHANGELOG, and the
# version-bearing artifacts synced above (#177 Blocker 2).
git add VERSION CHANGELOG.md
# Stage EXACTLY the artifacts sync-version-artifacts.sh owns, from its
# own --list, so a newly-synced file can never be left unstaged and die
# with the runner (bonnyr-f5 #180 r3, BLOCKER 1: --write rewrote five
# files, the hard-coded `git add` staged three).
staged=0
while IFS= read -r f; do git add "$f"; staged=$((staged + 1)); done < <(bash scripts/sync-version-artifacts.sh --list)
# Vacuity floor mirroring the script's own `--check` `total < 5` guard:
# if --list ever yields fewer paths (script broke / was truncated) the
# add + verify loops both go silent and we would commit a bare VERSION
# bump with every image pin left unsynced — the exact BLOCKER-1 failure
# the staging logic exists to prevent (bonnyr-f5 #180 r5, F2). The
# per-file "not fully staged" check below cannot catch this: it runs the
# same possibly-empty --list, so an empty list makes it vacuously pass.
if [ "$staged" -lt 5 ]; then
echo "::error::sync-version-artifacts.sh --list yielded only $staged path(s) (expected >=5) — refusing to commit an unsynced release"; exit 1
fi
# Verify the INDEX, not the files: --write's post-write check re-reads
# the files (correct on disk even when unstaged), so assert each synced
# artifact has no unstaged residue — i.e. the sync is actually in the
# commit we are about to make.
while IFS= read -r f; do
git diff --quiet -- "$f" || { echo "::error::$f was synced but is not fully staged"; exit 1; }
done < <(bash scripts/sync-version-artifacts.sh --list)
git ls-files --error-unmatch dist/VERSION 2>/dev/null && git add dist/VERSION || true

git commit -m "release: v${NEW} [skip ci]
Expand Down Expand Up @@ -515,6 +543,7 @@ jobs:
if git ls-files --error-unmatch dist/VERSION 2>/dev/null; then
echo "${{ needs.preflight.outputs.new_version }}" > dist/VERSION
fi
bash scripts/sync-version-artifacts.sh --write "${{ needs.preflight.outputs.new_version }}"

- name: Update changelog
run: |
Expand Down Expand Up @@ -548,6 +577,29 @@ jobs:
- name: Commit and tag
run: |
git add VERSION CHANGELOG.md
# Stage EXACTLY the artifacts sync-version-artifacts.sh owns, from its
# own --list, so a newly-synced file can never be left unstaged and die
# with the runner (bonnyr-f5 #180 r3, BLOCKER 1: --write rewrote five
# files, the hard-coded `git add` staged three).
staged=0
while IFS= read -r f; do git add "$f"; staged=$((staged + 1)); done < <(bash scripts/sync-version-artifacts.sh --list)
# Vacuity floor mirroring the script's own `--check` `total < 5` guard:
# if --list ever yields fewer paths (script broke / was truncated) the
# add + verify loops both go silent and we would commit a bare VERSION
# bump with every image pin left unsynced — the exact BLOCKER-1 failure
# the staging logic exists to prevent (bonnyr-f5 #180 r5, F2). The
# per-file "not fully staged" check below cannot catch this: it runs the
# same possibly-empty --list, so an empty list makes it vacuously pass.
if [ "$staged" -lt 5 ]; then
echo "::error::sync-version-artifacts.sh --list yielded only $staged path(s) (expected >=5) — refusing to commit an unsynced release"; exit 1
fi
# Verify the INDEX, not the files: --write's post-write check re-reads
# the files (correct on disk even when unstaged), so assert each synced
# artifact has no unstaged residue — i.e. the sync is actually in the
# commit we are about to make.
while IFS= read -r f; do
git diff --quiet -- "$f" || { echo "::error::$f was synced but is not fully staged"; exit 1; }
done < <(bash scripts/sync-version-artifacts.sh --list)
git ls-files --error-unmatch dist/VERSION 2>/dev/null && git add dist/VERSION || true

git commit -m "release: v${{ needs.preflight.outputs.new_version }} [skip ci]
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,7 @@ next-session-prompt
agent-selection
handoffs/
*.code-workspace

# Transient sed backup files from scripts/sync-version-artifacts.sh --write
# (removed on success; gitignored so an interrupted run leaves no tracked litter)
*.syncbak
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,21 @@ Strong success criteria let you loop independently. Weak criteria ("make it work

**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

## Commit conventions

Conventional Commits (`type: subject`, optional body, `BREAKING CHANGE:` footer for a
major). One repo-specific trap worth stating outright:

- **Never write a CI-control marker as literal text anywhere in a commit message —
subject *or* body — even when quoting it in prose.** GitHub scans the whole message,
so a `[skip ci]` / `[ci skip]` sitting in a sentence suppresses the run for that
commit. This has bitten us twice, most recently on a shell-script change where the
gates that got skipped (ShellCheck, Script Self-Tests, Secret Scan) were exactly the
ones that mattered. Refer to it indirectly instead: "CI suppressed", "the skip-CI
marker", or split it across backticks. The release job's *deliberate* skip is the
only legitimate use — and the release loop's own skip-detection grep is
line-oriented over the whole message (`^\[skip ci\]` / `\[skip ci\]$` anchored
per line), so it matches the marker on any line, not just the subject.

---

14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ AWSBNKCTL_STAMP := bin/.awsbnkctl-$(AWSBNKCTL_VERSION).stamp
test test-backend test-backend-unit test-backend-component test-backend-legacy test-frontend \
test-proxy test-operator test-db test-contracts test-e2e test-e2e-tier1 test-e2e-tier2 \
test-integration test-integration-full build-frontend-check smoke-mcp-live mcp-readiness mcp-recreate \
lint lint-backend lint-frontend shellcheck coverage quick-check pre-push push install-hooks setup-hooks \
lint lint-backend lint-frontend shellcheck coverage quick-check version-check pre-push push install-hooks setup-hooks \
dev-setup security-audit docker-check docker-verify docker-validate \
openapi openapi-types openapi-check openapi-types-check typecheck-backend typecheck-frontend \
build build-retry build-backend build-frontend build-worker build-agent build-all \
Expand Down Expand Up @@ -684,9 +684,19 @@ check-migrations:
@echo "=== Migration Chain Validator ==="
@python3 scripts/check-migrations.py

# ── Version-artifact consistency ─────────────────────────────────────────────
# Mirror of CI's "P1 · Version Consistency" job. ci.yml promises `make pre-push`
# ≡ CI, so the gate must be reachable from the documented local target or drift
# is undetectable until the release job dies (bonnyr-f5 #180 r5, F3). Pulled in
# by quick-check (a pre-push prerequisite).
version-check:
@echo ""
@echo "=== Version Artifact Consistency (Helm tag/appVersion, frontend, operator) ==="
@bash scripts/sync-version-artifacts.sh --check

# ── Quick check (~15s): lint + types + contracts ────────────────────────────
# Run before every commit. Catches most CI failures instantly.
quick-check: lint typecheck-backend openapi-types-check check-migrations
quick-check: lint typecheck-backend openapi-types-check check-migrations version-check
@echo ""
@echo "========================================="
@echo " Quick check passed (~15s)"
Expand Down
2 changes: 1 addition & 1 deletion bnk-operator/charts/bnk-operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: bnk-operator
description: BNK Operator — lightweight agent that connects K8s clusters to BNK-Forge
type: application
version: 1.1.0
appVersion: "1.1.0"
appVersion: "3.1.6"
keywords:
- f5
- bnk
Expand Down
4 changes: 2 additions & 2 deletions bnk-operator/charts/bnk-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ cwc:

# Operator image
image:
repository: f5/bnk-operator
tag: "1.2.0"
repository: ghcr.io/f5devcentral/bnk-forge-operator
tag: "3.1.6"
pullPolicy: IfNotPresent

# Image pull secrets (if using private registry)
Expand Down
2 changes: 1 addition & 1 deletion frontend-v2/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend-v2",
"private": true,
"version": "2.12.0",
"version": "3.1.6",
"type": "module",
"sideEffects": [
"*.css"
Expand Down
2 changes: 1 addition & 1 deletion helm/bnk-forge/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: bnk-forge
description: BNK-Forge — F5 BNK lifecycle / deployment platform (api, workers, beat, frontend, proxy, mcp)
type: application
version: 0.1.0
appVersion: "3.0.1"
appVersion: "3.1.6"
home: https://github.com/f5devcentral/bnk-forge
maintainers:
- name: BNK Forge Maintainers
Expand Down
2 changes: 1 addition & 1 deletion helm/bnk-forge/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ global:

image:
pullPolicy: IfNotPresent
tag: "3.0.1"
tag: "3.1.6"

# Generated/explicit secrets. If left empty, helm generates random values on
# first install and reuses them on upgrade (lookup-based).
Expand Down
Loading
Loading