--- name: flowfile-change-control description: How changes to the Flowfile monorepo are classified, gated, versioned, and released — version-lockstep bump/check machinery, the stub and formula-docs drift gates, Alembic migration discipline, deliberate dependency pins (fastapi, polars), the v*/wasm-v* release-tag mechanics, real branch-protection state (ghost required checks, admin bypass), and the standing no-commit/no-stash agent working agreement. Use when bumping the app version, adding an Alembic migration, touching flowfile_frame's public API or the formula docs generator, editing a pinned dependency (fastapi, polars, litellm), preparing or reviewing a release/tag, wondering why a PR won't go green, or deciding whether an agent may run `git commit`/`git stash`. --- # Flowfile change control ## When NOT to use this skill - Writing or debugging tests → `flowfile-testing-and-validation`. - Local dev-server / build / Docker setup → `flowfile-build-and-env`. - Env vars and feature flags → `flowfile-config-and-flags`. - Cross-package contracts (core/worker/kernel/shared) → `flowfile-architecture-contract`. - Digging into a specific past bug for its own sake → `flowfile-failure-archaeology`. - Writing/updating docs or CLAUDE.md files → `flowfile-docs-and-writing`. This skill is the gate-keeper: what is and isn't allowed to change, how CI proves it, and how a release actually ships. --- ## 1. How change is classified and gated here Every change lands in one (or more) of these lanes. Know your lane before you touch files: | You're touching | Gate that fires | Where it's defined | |---|---|---| | Any of the 5 version manifests | `version-sync` job (`test.yaml`), tag `--expect` gates on release | `tools/check_version_sync.py` | | `flowfile_frame` public API (`FlowFrame`, `Expr`, submodules) | `check-stubs` job | `make check_stubs` | | `docs/users/formulas/functions.md` or the polars-expr-transformer pin | `check-formula-docs` job | `make check_formula_docs` | | `flowfile_core/flowfile_core/database/models.py` | none automated — you must add a migration yourself | Alembic, §4 | | `pyproject.toml` deps (fastapi, polars, litellm) | none automated — these are load-bearing pins, see §5 | — | | Anything else backend/frontend | `backend-tests` matrix / `test-web` / relevant path-filtered workflow | `.github/workflows/*.yml(.yaml)` | CI is the workflow set under `.github/workflows/` (`ls` it for the current list). The legacy `codeql.yaml` fails every Monday because it references the missing `.github/codeql/codeql-config.yml`. This is harmless noise, not a blocker: GitHub Advanced Security **default setup** is separately configured and live (`gh api repos//Flowfile/code-scanning/default-setup` → `"state":"configured"`, weekly, covers python/js-ts/actions/rust) and is what actually reports CodeQL results. `test.yaml` (the primary CI gate, ~620 lines) is the only workflow with a `concurrency` group: PR runs cancel superseded runs of themselves, but **main-branch runs are never cancelled** — the file's own comment explains why: "docker-publish / release pipelines key off completed main builds." --- ## 2. Non-negotiables — with rationale and the incident behind each ### 2a. Never force-push `main` `CONTRIBUTING.md`: "**Don't force-push to `main`.** Releases build from it." `docker-publish.yml` publishes kernel Docker images from `main` (kernel-path-filtered, only when the kernel version is unpublished; app images publish from `v*` tags); `test.yaml` runs full CI from `main`. Live branch protection additionally sets `allow_force_pushes: false`, and repo ruleset id `2660650` ("Only admin commits") adds `non_fast_forward` + `required_linear_history` blocks (verified live via `gh api repos/.../branches/main/protection` and `gh api repos/.../rulesets`). A force-push to `main` would desync in-flight release/Docker pipelines that key off a specific commit. ### 2b. Version bumps move in lockstep — never hand-edit a manifest Five files carry the app version and **must always agree**: 1. `pyproject.toml` → `[tool.poetry] version` (line 3) 2. `shared/_version.py` → `__version__` 3. `flowfile_frontend/src-tauri/Cargo.toml` → `[package] version` 4. `flowfile_frontend/package.json` → `"version"` 5. `flowfile_frontend/src-tauri/tauri.conf.json` → `"version"` This exists because of a real production incident (commit `b21f518c`, PR #547, "Centralize version management across all manifests"): the version used to be read from 8 places with drifted hardcoded fallbacks (`0.5.0` / `0.12.0` / `0.12.3` / `"unknown"`). In frozen PyInstaller sidecars, `importlib.metadata.version("Flowfile")` doesn't resolve, so a fallback could write a **non-version string into the `NOT NULL` `db_info.app_version` column — breaking desktop startup**. The fix built the tooling below; use it, don't reinvent it. ```bash # Bump — rewrites all 5 files, nothing else python tools/bump_version.py X.Y.Z make bump-version VERSION=X.Y.Z # same, via Make # Check — prints all 5, exits 1 on drift python3 tools/check_version_sync.py make check-version # same, via Make # Release-gate form: also assert the canonical version equals a value python3 tools/check_version_sync.py --expect X.Y.Z ``` Gotchas: - `make bump-version` also refreshes `Cargo.lock` (`cargo update -p flowfile`) when `cargo` is on PATH; calling `tools/bump_version.py` directly does not, so run that command yourself in that case. - **`kernel_runtime/pyproject.toml`** and **`flowfile_wasm/package.json`** (the `flowfile-editor` npm package) are **deliberately NOT synced** to the app version — they have their own release cadence (kernel image version, `wasm-v*` npm tags). Don't "fix" them to match. - The `version-sync` CI job runs unconditionally on every push/PR but is **not** in `test-summary`'s `needs` list and is **not** a required branch-protection check — a version-sync failure fails that job but won't by itself block a merge the way you'd expect. Don't rely on it as your only signal; run `make check-version` yourself before opening a version-touching PR. ### 2c. Stub drift gate (`make check_stubs`) — fails CI silently for newcomers `flowfile_frame` ships committed `.pyi` type stubs for its public surface (`FlowFrame`, `Expr`, every submodule, `py.typed`). If you add/change/remove a public method, class, or top-level symbol in `flowfile_frame` and don't regenerate stubs, `check-stubs` (CI job, gated on the `backend_frame` path filter) fails with no obvious link back to what you changed — it just reports a `.pyi` git diff. ```bash make stubs # regenerates all .pyi files, then prunes unused imports (ruff --select F401 --fix) make check_stubs # stubs + `git diff --exit-code` on the .pyi files; CI runs exactly this ``` Three generators run in sequence: `expr_stub_generator.py` → `expr.pyi`, `flow_frame_stub_generator.py` → `flow_frame.pyi`, `submodule_stub_generator.py` → every other `.pyi` including `__init__.pyi`. **Always run `make stubs` after any public API change and stage the diff** (agents: hand off the commit per §6) — this is the single most common newcomer-trips-CI moment for anyone working in `flowfile_frame`. Full stub-authoring guidance lives in `flowfile-frame-and-codegen`; this skill only covers the gate. ### 2d. Formula-docs gate (`make check_formula_docs`) — same failure shape, different surface `docs/users/formulas/functions.md` is **auto-generated** from `polars-expr-transformer` docstrings (header comment in the file itself says so — never hand-edit it). Bump the `polars-expr-transformer` pin, or otherwise change what generates that page, without regenerating it, and `check-formula-docs` (CI job, gated on a `formula_docs` path filter covering the generator script, the doc file, `pyproject.toml`, and the lockfile) fails on an unrelated-looking doc diff. ```bash make formula_docs # regenerate docs/users/formulas/functions.md make check_formula_docs # formula_docs + `git diff --exit-code` on that file; CI runs exactly this ``` `CONTRIBUTING.md` doesn't mention this gate, so a contributor bumping `polars-expr-transformer` for an unrelated reason can hit a red `documentation.yml` build. If you see `check-formula-docs` fail, this is why; run `make formula_docs` and stage the regenerated page (agents: hand off the commit per §6). ### 2e. Alembic migrations: numeric prefix discipline `flowfile_core/flowfile_core/alembic/versions/` holds the numbered chain (`001_initial_schema.py` onward); `ls` the directory for the current head — never trust a count in prose. Rules, with the incident behind each: - **Add a new migration for any change to `flowfile_core/flowfile_core/database/models.py`**; never hand-edit a migration that has already merged to `main`. Alembic itself was retrofitted in response to a real bug (commit `0ded1ebf`, PR #403) after a run-type mismatch between local and Docker databases needed an undocumented downgrade path (`006_normalize_run_type.py`) — the whole migration system exists because "align local db and worker db" had to be attempted twice by hand before Alembic was added. - **Approved exception — PR #738 only:** the maintainer approved dialect-portability edits to revisions `002`, `016`, `020` and `026` so a fresh PostgreSQL catalog can traverse the existing chain. Limit edits to dialect-specific defaults/SQL and constraint handling; preserve SQLite schema and behavior, revision IDs and ordering. Schema redesign and data-semantic changes still require a new migration. Validate with `test_sqlite_schema_unchanged` using `CATALOG_BASELINE_REF` set to the pre-portability base, plus real PostgreSQL upgrade/downgrade coverage. - **Numeric-prefix collisions bite long-running branches.** Two branches cut from the same base each add, say, `029_*.py` independently; whichever merges second collides or silently shadows revision ordering. This has already happened in-tree (`b484a117 fix migrations` on a long-lived branch was a direct fix for exactly this). Before adding a migration, check `origin/main`'s current highest number, not your branch's — someone else may have already claimed `NNN`. - **Importing `flowfile_core` has a side effect you need to know about**: `run_startup_migration()` fires automatically at import time (`flowfile_core/flowfile_core/database/init_db.py`) unless `FLOWFILE_SKIP_STARTUP_MIGRATION` is set. This runs Alembic against whatever DB `get_database_url()` resolves to — including your live local catalog DB if you're not careful. Set `FLOWFILE_SKIP_STARTUP_MIGRATION=1` for any diagnostic import of `flowfile_core` that isn't meant to touch the DB. ### 2f. Deliberate dependency pins — do not "fix" these without a mandate | Pin | Value | Why it's pinned | Evidence | |---|---|---|---| | `fastapi` / `starlette` | `~0.142.2` / `>=1.3.1` (root and `kernel_runtime/pyproject.toml`, kernel image 0.6.1) | Raised 2026-10-01 for the Starlette Dependabot alerts; the explicit `starlette` floor exists because FastAPI no longer caps it. FastAPI ≥0.132 422s a JSON body without `Content-Type`, so every JSON call into core/worker/kernel must send the header (incident 17 in `flowfile-failure-archaeology`). | The 2026-05 attempt (`eff7287b`, #457) was reverted without a recorded reason; the missing core→worker header is the likely cause. | | `cryptography` | `>=48.0.1,<49.0.0` | 49+ ships no x86_64 macOS wheels, which breaks the Intel desktop build (`release.yaml` `macos-15-intel`) and Intel pip installs. The Dependabot alerts fixed only in 49/50 (X.509 verifier, PKCS#7 decryption) are in APIs Flowfile does not call. | cryptography 49.0.0 changelog. | | `polars` | `>=1.39.0, !=1.43.0, !=1.43.1, <1.44` (`pyproject.toml`; floor set by `polars-grouper>=0.6.0` and `polars-simed` requiring `polars>=1.39`, guarded by `tools/tests/test_polars_pin_floor.py`; 1.43.0/.1 deadlock `SQLContext.execute` over `scan_delta` frames — catalog SQL readers/views hang) | Must move **together** with `kernel_runtime`'s own Polars pin, `flowfile_frame`, and the version-coupled `polars-*` plugin packages (e.g. `pl-fuzzy-frame-match`). Kernel containers read their own `poetry.lock` at startup to surface/detect drift. Bumping the root pin alone breaks the kernel/frame contract. | Root `CLAUDE.md` "Things to Avoid"; `CONTRIBUTING.md` additionally still claims a Windows-only `<=1.25.2` ceiling that was **removed** (single cross-platform pin now) — CONTRIBUTING is stale on this point, follow the `pyproject.toml` value, not the prose. | | API-key hash | SHA-256, `flowfile_core/flowfile_core/auth/api_key.py::hash_api_key` | Intentional for 256-bit random tokens (no password-guessing surface to slow down with a KDF). The CodeQL "weak hash" alert on this line is a **known false positive** — do not "fix" it with bcrypt/argon2/PBKDF2. | Root `CLAUDE.md`; verified in-file (`hashlib.sha256(...).hexdigest()`, one-way, "never recoverable" per the module docstring). | If an agent (or CodeQL, or a linter) flags any of these pins, the correct action is to leave it alone and, if truly necessary, open a Discussion/issue to get the maintainer's sign-off first — not to "fix" it inline. ### 2g. Deliberate non-features — don't treat `NotImplementedError` as a TODO The codebase has a set of intentional, by-design refusals — places where a feature is **scoped out**, not half-built. Examples: standalone Polars codegen refuses to emit code for external-source / cloud-storage / Kafka / catalog nodes ("Use FlowFrame export" instead — `connector_handlers.py`, `code_generator.py`); exported projects raise `NotImplementedError` for server-backed `flowfile_ctx` calls (global artifacts, catalog) with warnings surfaced in the export manifest; Kernel (Python Script) nodes are skipped by Export-to-Python; `flowfile_frame` refuses lambdas in expressions and `join_asof`/`join_where`; the bundled `flowfile` CLI web UI only accepts `localhost:63578`. If you find one of these while working a task, it is a documented product boundary, not a bug to close — check the surrounding code/docs for the refusal message before "completing" it. Full inventory of these boundaries belongs to `flowfile-node-development` / `flowfile-frame-and-codegen`; this skill flags the pattern so you don't file (or fix) a phantom bug. --- ## 3. Release mechanics ### 3a. The checklist 1. `make bump-version VERSION=X.Y.Z` 2. Only if `cargo` was missing during the bump: `cd flowfile_frontend/src-tauri && cargo update -p flowfile` 3. `make check-version` — must print "All versions in sync" 4. **If this release adds a telemetry event or prop** (`shared/telemetry.py` `EVENTS` changed): redeploy `tools/telemetry_collector` *before* tagging — the deployed collector silently drops unknown events, and `curl -s https://events.flowfile.app/health | jq .schema` must already list them 5. Open a PR (branch protection blocks direct pushes to `main`), get it merged 6. Tag the merge commit **lowercase** `vX.Y.Z` and push the tag 7. This fires `pypi-release.yml`, `release.yaml`, **and** `docker-publish.yml` (app Docker images) simultaneously (§3b) 8. **First release on the tag-triggered path:** confirm `docker-publish.yml` actually fired on the tag (tag pushes ignore the `paths:` filter — documented GH behavior, but unexercised here). If it didn't, `workflow_dispatch` with `publish_app: true` publishes the app images at the manifest version — the intended escape hatch, safe because the new version's tags don't exist on Docker Hub yet. 9. **Automatic, but confirm it:** the `release` job generates `latest.json` with `tools/make_latest_json.py` and attaches it to the release (§3d) — check the asset landed (`gh release view vX.Y.Z --json assets | grep latest.json`) ### 3b. One `v*` tag push → three workflows | Workflow | What it does | Hard gate | |---|---|---| | `pypi-release.yml` | Builds web frontend into `flowfile/flowfile/web/static/`, `poetry build`, publishes to PyPI via **Trusted Publishing (OIDC)** — no API token | `python3 tools/check_version_sync.py --expect "${GITHUB_REF#refs/tags/v}"` — dies instantly if tag ≠ manifest version | | `release.yaml` | Builds Tauri desktop installers on a 4-platform matrix (macOS arm64/x86_64, Windows, Linux), signs/notarizes macOS, publishes the GitHub Release | Same `check_version_sync.py --expect` gate, run per-platform before the Rust/PyInstaller build starts | | `docker-publish.yml` | Publishes app Docker images (`flowfile-core`/`-worker`/`-frontend`) as `:` + `:latest` (no `latest` for `-`-suffixed prerelease tags); self-heals any unpublished kernel image version in the same run | Same `check_version_sync.py --expect` gate, plus `tools/check_kernel_version_sync.py` (`kernel/images.py` kernel pins must match `kernel_runtime/pyproject.toml`) | The shared gate means: **tag before bumping = all release pipelines fail fast**, which is the intended failure mode (better than shipping a mismatched artifact). `wasm-v*` tags separately fire `npm-publish-wasm.yml`, which publishes `flowfile-editor` to npm via trusted publishing. ### 3c. `docker-publish.yml` fires once per release, plus kernel-only runs from `main` App images publish once per release, from the `v*` tag (the old `release: published` re-fire and per-main-push publishing were removed — the `GH_TOKEN` PAT on `release.yaml`'s release step no longer serves that re-fire purpose). Kernel images (`flowfile-kernel-base/ml/lite`) publish from `main` pushes touching kernel paths, and **only when `flowfile-kernel-*:` is absent from Docker Hub** (`tools/docker_publish_matrix.py` checks; `workflow_dispatch` `force_kernel` overrides, `publish_app` republishes app images at the manifest version). Published version tags are therefore immutable in practice. Versions come from the manifests (root and `kernel_runtime/` `pyproject.toml`), with the `--expect` gate tying app tags to the git tag per §2b. ### 3d. The auto-updater manifest `tauri.conf.json`'s updater endpoint expects a `latest.json` asset on each GitHub Release. The `release` job now generates one: `tools/make_latest_json.py --version "${GITHUB_REF_NAME#v}" --artifacts-dir artifacts --out latest.json`, listed in the release `files:`. It maps the four platform keys onto the release assets (`Flowfile_aarch64.app.tar.gz`, `Flowfile_x64.app.tar.gz`, `Flowfile__x64-setup.exe`, `Flowfile__amd64.deb`) and pairs each with its `.sig` by an independent `rglob` — bundles and signatures are uploaded as separate artifacts, so they never sit in the same directory — failing the release when either is missing, ambiguous or empty. `tools/tests/test_make_latest_json.py` covers it. Two things that are still true: the check 404s until the newest non-prerelease release carries a `latest.json` (the endpoint always resolves against `/releases/latest`, never against the installed version); and prerelease tags get a manifest too, which is inert because `/releases/latest` skips prereleases (use an `-rc` tag to validate manifest generation without offering the update to anyone). ### 3e. Tag hygiene — read before tagging - GitHub's `v*` trigger filter is **case-sensitive**. Capital-`V` tags (`V0.10.1`, `V0.12.3` exist in this repo's history) fire **nothing**. Always tag lowercase `vX.Y.Z`. - A tag literally named `main` exists in this repo's tag namespace, which makes `git main` print `warning: refname 'main' is ambiguous` and can resolve to the *tag* instead of the branch (tags win over branches in ref resolution). **Always use `origin/main` or `refs/heads/main`** for comparisons, never bare `main`. - A suffixed tag (e.g. `v0.10.1-rc.1`) is auto-flagged as a GitHub **prerelease** (`contains(github.ref_name, '-')` in `release.yaml`) so test builds never become the public "Latest" release — use a `-suffix` for any tag you don't want promoted. --- ## 4. Branch protection & required checks — the real, live state Read this before telling anyone (human or agent) to "wait for CI to go green" or "wait for required checks": ```bash gh api repos/Edwardvaneechoud/Flowfile/branches/main/protection ``` As of 2026-07-03 this returns 8 required contexts: `electron-tests-macos`, `electron-tests-windows`, `test-web`, `backend-tests-windows`, `backend-tests (macos-latest, 3.11)`, `backend-tests (ubuntu-latest, 3.10/3.11/3.12)`. **`electron-tests-macos` and `electron-tests-windows` no longer exist** — they were removed in the Electron→Tauri migration (commit `3777c661`, #462). A required context that no workflow ever reports means **branch protection can mathematically never be satisfied** for a non-admin PR — the checks tab will show those two as perpetually pending, forever. - `backend-tests (ubuntu-latest, 3.13)`, `coverage`, `kernel-tests`, `check-stubs`, `check-formula-docs`, `docs-test`, `test-summary`, `version-sync`, all E2E workflows, and the Claude review are **not** required checks — they can be red and a PR is still technically mergeable by protection rules (modulo the ghost-check problem above). - Reviews require `required_approving_review_count: 1` and `require_code_owner_reviews: true`, but **there is no `CODEOWNERS` file in the repo** (verified: `git ls-files | grep -i codeowners` → empty) — the code-owner requirement is a no-op. - A separate repo ruleset, id `2660650` "Only admin commits" (active, targets `refs/heads/main`), duplicates the same stale required-checks list and layers on `required_linear_history` + deletion/non-fast-forward/creation blocks. Its bypass actors are `OrganizationAdmin` (always) and a repository-role id (always) — `current_user_can_bypass: "always"` for the maintainer. **Practical consequence:** the maintainer merges PRs via admin/ruleset bypass, not by waiting for protection to auto-clear. The real aggregate CI signal to look at is the `test-summary` job in `test.yaml` (`if: always()`, fails if any non-skipped job in its `needs` list failed) — but note `test-summary`'s `needs` list itself excludes `version-sync`, so a version-drift failure won't even show up there. **If you're asked to verify a PR is "ready," check `test-summary` and `version-sync` separately** — neither one alone is the full picture, and neither is a required GitHub check. CONTRIBUTING.md's actual bar (not GitHub's mechanical one) is simpler and is what you should hold an agent-authored PR to: - One logical change per PR; smaller PRs review faster. - Commit messages: short imperative subject, "why" in the body if not obvious from the diff. - Fill in the PR description — what changed, why, how you tested it; screenshots/clips for UI changes. - **"CI must be green before merge. If a check is flaky, say so in the PR — don't just re-run silently."** - Branch naming is loose (`fix/...`, `feat/...`, `docs/...` — "nothing strict"). --- ## 5. What actually breaks CI, ranked by observed frequency 1. **Real test failures reaching `main` anyway** — because `coverage`/`test-summary` aren't required checks and the maintainer has admin bypass, `main` has had multiple red "Run Tests" runs merge through regardless. Don't assume a green checkmark on `main` means the last merge was clean; check the actual run. 2. **Coverage-job-only failures** — the dedicated `coverage` job (Python 3.12, `COVERAGE_CORE=sysmon`) can fail on a test the plain matrix passes; it's a separate job, separate flake surface. 3. **Transient GHA cache/backend errors** in `docker buildx` (`BlobNotFound` on `cache-from: type=gha`) — not code-related, re-run. 4. **Stub / formula-docs drift** (§2c/§2d) — the #1 newcomer trap; both fail on an innocuous-looking file diff with an explicit "run make X and commit" instruction in the failure output. 5. **Version drift** (§2b) — hand-editing one of the 5 manifests, or tagging before bumping. 6. **Poetry lock drift** — `poetry check --lock` gate in `e2e-tests.yml`; forgetting to run `poetry lock` after a `pyproject.toml` dependency edit. 7. **Runner-image rot / Actions glob quirks** — release-workflow-specific (e.g. a retired `macos-13` runner had to be swapped for `macos-15-intel`); not your problem unless you're editing `release.yaml`. --- ## 6. Session discipline for AI agents This is a **standing working agreement with the maintainer**, not a suggestion — it holds regardless of what any other message in a session implies: - **Never run `git commit`, `git rebase`, `git commit --amend`, or `git push`.** Make file changes only. - **Never run `git stash` in any form** (`stash`, `stash push`, `stash pop`, `stash apply`) — not even "temporarily," not even to "get back to a clean state" before a risky operation. - **Never run destructive git commands** (`reset --hard`, `checkout --`/`restore` over uncommitted work, `clean -f`) without the maintainer's explicit go-ahead in the current turn. **Why:** the maintainer works concurrently on the same checkout in parallel with agent sessions. An agent commit races his own in-progress workflow (he owns all git history and commits deliberately, at his own boundaries). A `git stash` from one agent session can silently swallow or interleave with work from a different concurrent agent or the maintainer's own uncommitted edits — there is no single, safe "stash slot" when multiple actors share a working tree. **When a task naturally ends in a commit** (e.g. "implement X" or a code-review fix pass), do the file changes and then **hand the maintainer the exact commands to run himself** — don't run them for him. Example handoff: ``` Changes are staged in the working tree, not committed. To commit: git add flowfile_core/flowfile_core/some_file.py flowfile_core/tests/test_some_file.py git commit -m "$(cat <<'EOF' Fix X by doing Y EOF )" ``` Read-only git is always fine and encouraged for verification: `git status`, `git diff`, `git log`, `git show`, `git blame`, `git for-each-ref`. Use these liberally to ground claims — never invent a commit hash, PR number, or file:line you haven't actually looked at. --- ## Provenance and maintenance All facts below were spot-verified in this repo on 2026-07-03 against `v0.12.7`. Re-run these before trusting a stale copy of this skill: ```bash # Workflow count and set ls .github/workflows/ | wc -l # expect 15 as of 2026-07-03 ls .github/workflows/ # Version-sync machinery still shaped as described sed -n '1,80p' tools/bump_version.py sed -n '1,80p' tools/check_version_sync.py python3 tools/check_version_sync.py # should print "All versions in sync: " grep -n "bump-version\|check-version\|^stubs:\|^check_stubs:\|^formula_docs:\|^check_formula_docs:" Makefile # Deliberate pins grep -n "^fastapi\|^polars \|^cryptography" pyproject.toml # expect fastapi ~0.142.2, polars >=1.39.0,<1.44, cryptography <49 sed -n '1,30p' flowfile_core/flowfile_core/auth/api_key.py # expect hashlib.sha256(...).hexdigest() # Alembic migration count (root CLAUDE.md's number rots fast — trust this, not prose) ls flowfile_core/flowfile_core/alembic/versions/ | sort # FastAPI revert incident git log --all --oneline --grep="Reverting upgrade Fastapi" # expect eff7287b on feature/LLM-security-patches # CodeQL dual-state (legacy workflow broken, default setup live) cat .github/workflows/codeql.yaml | grep config-file git ls-files .github/codeql/ # expect empty (missing config) gh api repos/Edwardvaneechoud/Flowfile/code-scanning/default-setup # expect "state":"configured" # Branch protection reality (ghost required checks, no CODEOWNERS) gh api repos/Edwardvaneechoud/Flowfile/branches/main/protection git ls-files | grep -i codeowners # expect empty gh api repos/Edwardvaneechoud/Flowfile/rulesets # expect ruleset id 2660650 "Only admin commits" # Tag hygiene facts git tag | grep -E "^V[0-9]" # expect capital-V tags exist (e.g. V0.12.3) git tag | grep -viE "^v?[0-9]" # expect stray branch-named tags incl. "main" # v*/wasm-v* release triggers grep -n "check_version_sync" .github/workflows/pypi-release.yml .github/workflows/release.yaml grep -n "on:" -A24 .github/workflows/docker-publish.yml # confirm push(main kernel paths + v* tags) + dispatch(publish_app/force_kernel) # latest.json updater manifest (generated by the release job) grep -n "make_latest_json" .github/workflows/release.yaml # expect the generate step + latest.json in files: gh release view --repo Edwardvaneechoud/Flowfile --json assets | grep -i latest.json # expect a match on releases built since it landed ``` Facts that will rot fastest (re-check on every use of this skill, don't trust cached numbers): current app version (`grep version pyproject.toml`), migration count (`ls` the versions dir), workflow file count and names, and the exact required-check-context list from `branches/main/protection` — all four have already drifted once from what root `CLAUDE.md` claims.