Files
FabledScribe/ci-requirements.md
T
bvandeusen dd878bc498
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Failing after 7s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / integration (push) Successful in 39s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 21s
ci(plugin): add shellcheck + jq, and the fail-open smoke test they unlock
Per-job installs, not an image change. CI-runner's docs/process.md decision
checkpoint is explicit: "If only one project needs the dep, prefer that
project installing it per-job in their workflow — at least until a second
consumer arrives." Scribe is the only consumer, and shellcheck is not a
natural extension of a Python image's purpose. Promotion into ci-python is
filed as an issue on CI-runner rather than assumed here — same doc, step 1:
the maintainer's call goes in the issue, then the PR.

This also corrects something I got wrong earlier in this work: I cited rule
#5 as blocking a per-job install. Rule #5 is about language TOOLCHAINS via
setup-* actions, not small lint utilities, and CI-runner's own process doc
positively recommends per-job installs in exactly this case.

jq is load-bearing rather than convenient. Every hook opens with
`command -v jq || exit 0`, so without it a "runs and stays silent" smoke
test passes while exercising nothing — a green tick proving less than no
test at all. That is why the smoke test didn't ship with the first cut.

The smoke test pins the fail-open contract: each hook, with no credentials
and then against a refused connection, must exit 0. Three must also stay
silent; scribe_session_context.sh must NOT, because its static behavioural
floor is meant to survive having no credentials and no network — asserting
silence there would encode the opposite of the design.

Verified it can actually fail, rather than assuming: injected a non-zero
exit and separately a stray stdout write, and confirmed each is caught.

shellcheck and jq are both optional at runtime — missing either SKIPs its
check loudly rather than passing. A check that quietly no-ops is the exact
failure mode this file exists to prevent.

ci-requirements.md updated: jq + shellcheck recorded under per-job installs
(the input CI-runner's maintainer uses for the next promotion decision),
plus two stale entries corrected — the sheet claimed four jobs when there
are six, and listed `uv` as a per-job install when it has been in the image
since the ci-python Dockerfile started pip-installing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-07-28 22:39:55 -04:00

2.2 KiB

CI Requirements — FabledScribe

Spec lives in docs/process.md in the CI-Runner repo.

Runtime image

git.fabledsword.com/bvandeusen/ci-python:3.14

Used by all six jobs in .forgejo/workflows/ci.yml: typecheck (Vue/TS), plugin (hook checks), lint (ruff), test (pytest), integration (pytest + real Postgres), build (docker buildx).

Image deps used

  • python 3.14
  • node 24 (used for npm ci + vue-tsc in the typecheck job, and as the frontend builder stage inside the production Dockerfile)
  • ruff (lint job runs ruff check src/ with zero install overhead)
  • uv (test + integration jobs run uv sync --locked; installed in the image since the ci-python Dockerfile started pip-installing it)
  • docker CLI + buildx (build job pushes the production image to the Forgejo registry)

Per-job tool installs

Anything CI installs at job time that isn't in the image. Promotion candidates if more than one project needs them.

  • jq + shellcheck — apt-installed in the plugin job, which lints the four Claude Code hook scripts and runs their fail-open smoke test. Per docs/process.md's decision checkpoint, single-consumer deps stay per-job until a second consumer wants them; Scribe is the only one so far. Both are small (jq ~1 MB, shellcheck ~20 MB) and would be promotion candidates the moment another project lints shell. jq is load-bearing for the smoke test specifically: every hook starts with command -v jq || exit 0, so without it the test passes while exercising nothing.

Notes

  • Production runtime image (Dockerfile) also tracks Python 3.14 — the CI image and runtime image stay aligned by design so test results are representative.
  • Build wall time: dominated by pytest (full async test suite). Cold ci-python pulls add ~30s; not a blocker.
  • Registry-backed BuildKit layer cache (type=registry,ref=…:cache,mode=max) gives ~80% speedup on warm builds — see the build job comment.
  • pyproject.toml pins requires-python = ">=3.14" to match the CI + runtime target; lockfile (uv.lock) is committed and resolves against Python 3.14.