Skip to content

Development

Developer-facing notes for working on Kerbside itself. See AGENTS.md for build commands, conventions, and common tasks, and testing.md for the test harnesses and CI lanes.

Database migrations

Kerbside uses Alembic for database schema migrations. The migration files are located in the alembic/versions/ directory.

Creating a new migration

cd /path/to/shakenfist/kerbside
alembic revision -m "description_of_your_changes"

This will create a new migration file in alembic/versions/. Edit the generated file to add your schema changes in the upgrade() and downgrade() functions.

Example:

def upgrade() -> None:
    op.add_column('table_name', sa.Column('column_name', sa.Type()))

def downgrade() -> None:
    op.drop_column('table_name', 'column_name')

Applying migrations

To apply all pending migrations:

alembic upgrade head

To rollback one migration:

alembic downgrade -1

Note: Alembic automatically uses the database URL from the kerbside configuration, so ensure your kerbside config is properly set up before running migrations.

Review tracking

Kerbside receives periodic whole-file human review in addition to the usual review of changes at pull request time; the current state is in REVIEWS.md. Which files count is set by .vscode/review-scope.toml: Python, Rust, shell, and Markdown, less the plan archive in docs/plans/ and the generated protobuf stubs.

The state (REVIEWS.md, .vscode/*.weaudit*) is maintained with tools/review-tracking.sh, a wrapper around the shared helper in the shakenfist/development repository. In a clone it is run by hand, not from git hooks: prune after a pull to discard reviews of files that have since changed, stamp before committing new review marks, regen to rebuild REVIEWS.md, next to pick an unreviewed file, and status to report effective coverage at HEAD. On develop itself the prune-reviews workflow runs prune automatically after every push, committing the result back as shakenfist-bot, and the daily consistency audit in shakenfist/development files an issue when five or more in-scope files need review.

Signing review marks

A review mark is an attestation, so the commit that introduces one must be signed -- that signature is what binds the reviewer to the exact content reviewed. Signing is configured per clone and is easy to forget in a fresh one; check it before stamping, because an unsigned review commit records a mark that nothing vouches for:

git config gpg.format x509
git config gpg.x509.program gitsign
git config commit.gpgsign true
git config tag.gpgsign true

Verify with git log --format='%h %G? %s'; review commits should report U (signed, with gitsign's Fulcio chain not in a local trust store) rather than N (unsigned). gitsign needs an interactive Sigstore login on first use, so run gitsign-credential-cache & to authenticate once per session instead of once per commit.

The bot's prune commits are deliberately unsigned: pruning only ever removes marks, so it cannot manufacture an attestation. Only the commits that add marks need signatures.

Vendored web assets

Bootstrap CSS

Kerbside uses bootstrap CSS for styling. This was constructed by downloading Bootstrap 5.3 and jQuery 3.7.0 and then installing to kerbside/api/static/js.

Axios

Kerbside's web administration API uses Axios for HTTP requests. Version 1.6.5 is cached at kerbside/api/static/js.

sfui

kerbside/api/static/sfui is a vendored copy of shakenfist/sfui, the Shaken Fist design system: design tokens (tokens.css), the shared page stylesheet (sf.css), a theme boot script, the brand logo, Lit-based web components such as sf-tabs, and the vendored Lit and morphdom libraries those pages need. It is copied in verbatim by sfui's own tools/vendor.sh, which also stamps the copy with its source commit in .sfui-commit.

The design system's own README.md is vendored along with it, so the full contract -- the token rules, what sf.css provides and how its cascade layer works, and the component contract -- is readable at kerbside/api/static/sfui/README.md without leaving the repository.

Never edit anything under kerbside/api/static/sfui/ in place: change the canonical sfui checkout instead and re-vendor, or the next sync will silently discard the local change. To re-vendor from a clean, up-to-date sfui checkout:

tools/vendor.sh <path-to-kerbside>/kerbside/api/static/sfui

To check whether the vendored copy has drifted from canonical sfui without copying anything:

tools/vendor.sh --check <path-to-kerbside>/kerbside/api/static/sfui

Both commands are run from the sfui checkout, not from kerbside.

Re-vendor from sfui's develop branch once the change you need has merged there, not from the branch you made it on. The sfui-vendor consistency audit compares .sfui-commit against canonical develop and reports a copy that is behind it, so a stamp naming a feature branch commit -- or an ancestor of a merge commit -- is flagged even when every vendored file is byte for byte correct.

Previewing templates

sfui has no CI of its own, and nothing in kerbside's tox lanes lints templates or CSS -- flake8 and the unit tests cover Python, and the HTML smoke tests deliberately assert on fixture data, never on markup. The only safety net for a converted page's chrome is a human looking at rendered pixels, in both palettes, without having to stand up a deployed kerbside first.

tools/preview-templates.py renders a converted page through kerbside.api's own Flask test client -- so routing, context and Jinja rendering are exactly what a real request would produce -- and writes it next to a symlink of the real static tree, because the templates reference their assets as root-relative absolute paths (/static/sfui/...). Only pages that have actually been converted onto base-sfui.html are supported; today that is login, which needs neither authentication nor the database, and consoles, whose fixtures render two consoles -- one with sessions and active tokens, one with neither -- so a single screenshot shows both terminate states (the two-step disclosure and the dim zero badge).

The script imports kerbside.api, so it needs an interpreter with kerbside's dependencies installed. The tox environment already has them, which makes .tox/py3/bin/python the interpreter to reach for after any tox -epy3 run; a virtualenv with pip install -e . works just as well. A bare system python3 will not.

tox -epy3  # only if .tox/py3 does not exist yet
.tox/py3/bin/python tools/preview-templates.py login /tmp/preview
(cd /tmp/preview && python3 -m http.server 8099) &

chromium --headless --disable-gpu --no-sandbox \
    --hide-scrollbars --window-size=1280,1000 \
    --virtual-time-budget=4000 \
    --screenshot=/tmp/login-dark.png \
    http://localhost:8099/login.html

chromium --headless --disable-gpu --no-sandbox \
    --hide-scrollbars --window-size=1280,1000 \
    --virtual-time-budget=4000 \
    --blink-settings=preferredColorScheme=2 \
    --screenshot=/tmp/login-light.png \
    http://localhost:8099/login.html

Three details are easy to get wrong:

  • Pick a port nothing else is using. 8099 is only an example, and a stale server left running from an earlier preview will happily serve 404s from a directory that no longer exists.
  • Headless Chromium reports prefers-color-scheme: dark by default, so the plain run above exercises the dark palette; --blink-settings=preferredColorScheme=2 is the only value that gives the light one.
  • Serve the directory over HTTP, not file:// -- the theme toggle is an ES module, and modules do not load from the file:// scheme.

Then actually look at both PNGs.

This only covers what renders. The interactive paths -- submitting a form, a wrong password, the theme toggle, logout -- still need a browser against a running kerbside, or a hand-check of the relevant fetch calls.

📝 Report an issue with this page