With fsync-off the whole integration suite runs in ~45s (was ~13min across
shards), so the 3-way split only triplicated the ~2min fixed overhead
(container + install + migrate) and consumed 3 of 6 runner slots for no
wall-clock gain. Merge intapi/intimp/intcore into one `integration` job:
spin up once, install once, migrate once, run `pytest -m integration` over
the whole suite. Frees 2 runner slots (6 jobs -> 4) and drops ~140 lines of
near-duplicate YAML.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Option 1 (pooling the teardown connection) left teardowns at ~1.5-2s/test, so
the cost is the per-test TRUNCATE's commit forcing an fsync, not the connect
handshake. Each shard now ALTER SYSTEM SETs fsync/synchronous_commit/
full_page_writes off + pg_reload_conf() right after deps install, before
alembic — sighup/user-context GUCs apply with no restart. The DB is ephemeral
(rebuilt per run) so fsync-off is safe; the step is non-fatal so a perms
surprise can't red a shard. Speeds up every test's commit (setup inserts +
the teardown TRUNCATE), stacking on the pooled engine from the prior commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`${GITHUB_SHA:0:7}` substring expansion is bash-only; the runner
executes the step via dash/BusyBox sh and errored with
`Bad substitution` (act_runner workflow shell, observed on the
65386f0 main-push run). Switched to
`SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7)` which works in
both shells. Both build-web and build-ml tag-determination steps
updated.
Adds an immutable per-commit docker tag to every main-push build:
`git.fabledsword.com/bvandeusen/fabledcurator{,-ml}:c-<short_sha>`,
alongside the existing floating `:main` + `:latest`. Implements the
new family release-posture rule "Tags are milestones, not gates —
commit-SHA images are the rollback unit" so rollback to any commit
on main is `docker pull …:c-<sha>` with no release ceremony required.
Behavior change summary:
- main-push: was {:main, :latest} → now {:main, :latest, :c-<short_sha>}
- tag-push (opt-in vYY.MM.DD only, no .N): unchanged
- safety-net dev: unchanged
No code changes; the rule is about how the tag list is constructed.
Tag-push workflows stay as-is — vYY.MM.DD milestone cuts can still
fire them when the operator wants a labeled checkpoint.
ruff is pre-installed in the ci-python image, so a new `lint` job runs it
with no dependency install and fails in seconds — surfacing the common lint
bounce class without waiting on the backend job's ~30-60s wheel install.
Dropped the now-redundant ruff step from backend-lint-and-test (same job
name, required-checks unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cutting a release fires BOTH the push-to-main workflow AND the push-to-tag
workflow in parallel. main-push runs sign-extension (AMO round-trip 1-5min)
then publishes the ext-<version> Forgejo release; tag-push skips
sign-extension (gated to main) and races straight to build-web's Download
XPI step. Tag-push lost every time — got 404 from
releases/tags/ext-<version> before main-push had finished signing.
v26.05.27.0 hit this: tag-push build-web died on exit 22 because the
ext-1.0.4 release wasn't published yet (it arrived ~4min later).
Fix: wrap the release lookup in a 20-iteration sleep+retry loop, 30s
between attempts (10min total upper bound, generous for AMO). main-push's
signing eventually publishes the release; tag-push picks it up on a later
poll. No more manual rerun of the failed job after every release cut.
Banked the trap as reference_tag_push_main_push_race.md — same shape will
recur any time a tag-push workflow consumes a main-push-produced artifact.
ci.yml: drop pull_request: trigger — push: branches: [dev, main] already covers it; pull_request was duplicating ci.yml runs on every dev push with an open PR. (No fork PRs in this repo.)
build.yml: drop dev from push triggers — operator doesn't use the :dev image. Add tags: ['v*'] trigger + tag-push branch in the Determine-tag logic so cutting a release tag publishes an immutable :v26.05.26.X image (rollback story) without re-publishing :latest. Extend the XPI-download step to fire on tag pushes too so the versioned image carries the signed extension.
Net per hotfix cycle: 5 runs → 3 (no tag) / 4 (with tag).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
uv falls back to pip install on runners without uv binary, so this
change is forward-compatible with the current ci-python image. When
the runner image gets uv pre-installed in a future bump, the warm
install path drops from ~2 min to ~10 seconds.
pytest-xdist parallelization is OUT OF SCOPE for this commit:
tests/conftest.py uses a TRUNCATE ALL TABLES RESTART IDENTITY CASCADE
fixture after every integration test against a single shared
database; xdist workers running in parallel would nuke each other's
mid-test state. A future refactor to per-worker databases or
per-worker schema isolation is the prerequisite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI failure resolving 'postgres' hostname was the symptom; the cause is
that the workflow violated FabledRulebook/forgejo.md's "CI philosophy —
lint + short unit tests only" rule. Integration tests against a real
Postgres are supposed to run locally via docker-compose, not in CI.
Changes:
- Marked 8 DB-dependent test files with @pytest.mark.integration:
test_tag_service, test_importer, test_gallery_service, test_api_gallery,
test_api_tags, test_api_settings, test_api_import_admin, test_maintenance.
- CI workflow drops the postgres/redis service containers and the alembic
upgrade smoke step entirely.
- Pytest invocation in CI changes to `pytest -v -m "not integration"`.
- Added pytest marker registration to pyproject.toml.
- DB_PASSWORD and SECRET_KEY env vars retained because config.py reads
them at import time even though unit tests don't actually use them
(set to placeholder values).
What CI now runs:
- ruff check
- pytest on the 6 unit test files: test_slug, test_paths,
test_migration_0002, test_thumbnailer, test_celery_smoke,
test_tasks_register.
- npm install + npm run build
What CI no longer runs:
- alembic upgrade (no live DB)
- the 8 integration test files (these run locally via docker-compose)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ruff:
The remaining I001 errors came from ruff treating `alembic` as a first-
party module (because the alembic/ directory exists in the repo root)
rather than third-party. Ran `ruff check --fix` locally — auto-sorted
import groupings to put alembic/sqlalchemy alongside backend.* as first-
party, and trimmed redundant blank lines after a few import blocks.
Frontend:
`npm run check` (vue-tsc --noEmit) was failing because vue-tsc has no
tsconfig.json to read against, and the frontend is pure JS without
JSDoc annotations — vue-tsc had nothing to do. Skipping the step until
we add a tsconfig + convert to TS or add JSDoc annotations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per feedback-no-local-runs we don't run npm locally, so no
package-lock.json is tracked. npm ci fails without a lockfile; npm
install works fine. We lose strict reproducibility, which is acceptable
for a pre-v1 project — if we want it later, commit a package-lock.json
and flip back to npm ci.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per FabledRulebook forgejo.md, toolchain versions live on the runner
image, not in the workflow. Two changes here, paired with the runner
image bump (RUFF_VERSION 0.9.7 -> 0.15.13 in CI-Runner/CI-python):
- Drop "ruff>=0.9,<1.0" from ci.yml's pip install — the runner image's
pre-installed ruff is authoritative now. Previously this constraint
matched the existing 0.9.7 in the image so pip never upgraded.
- Flip ruff.toml's target-version back to py314 now that the runner
has a ruff new enough to know about it.
Next bump path: edit RUFF_VERSION in CI-Runner/CI-python/Dockerfile,
'make push', done. No workflow churn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generic python-ci runner is reusable across the family (FabledScribe,
FabledSteward, NhenArchiver, StashHandler, etc.) rather than scoped to
just this project. Runner image lives at CI-Runner/CI-python/ in the
operator's workspace; pattern mirrors CI-Runner/CI-go and CI-Runner/CI-flutter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pushes :dev on every dev push; pushes :main and :latest on main.
Uses RELEASE_TOKEN (a broader-scoped Forgejo PAT covering write:package,
read:package, write:release, write:issue) so the same secret can serve
future release-cutting and issue-management workflows.
README now documents the fabledcurator-ci runner label and the required
RELEASE_TOKEN scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend job spins up Postgres+pgvector and Redis as services, runs ruff,
applies the initial migration to confirm it's clean, and runs pytest.
Frontend job runs vue-tsc and vite build.
Requires a runner labeled "fabledcurator-ci" with Python 3.14, ruff,
and Node 22 pre-installed. Integration tests run locally via
docker-compose with testing.Short() gating per FabledRulebook
verification.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>