elektrine/AGENTS.md
maxfield e956bcfae1 docs: align AGENTS commit policy and agent defaults
Share Conventional Commits rules and agent hygiene across repos.
2026-08-01 05:28:03 -04:00

7.3 KiB

Working in this repo

Elektrine is an Elixir umbrella app. See README.md for the app layout and what each umbrella app under apps/ owns.

Git commits

Shared agent commit policy. Keep this section identical in every repo that uses it.

  • Do not add AI attribution of any kind: no Co-Authored-By: Claude ..., no Claude-Session: / Codex-Session: / similar trailers, no "🤖 Generated with ..." lines, in commit messages or PR bodies.
  • Use Conventional Commits: type(scope): subject.
    • Types: feat, fix, refactor, docs, test, chore, ci, build, perf.
    • Scope (optional): short area name for what changed (app, package, or feature). Omit when the change is repo-wide.
  • Subject in the imperative mood, no trailing period, ~72 chars or less. Examples: fix(auth): treat blank env as unset, feat(ui): full-size image modal lightbox, ci: wire multi-arch release artifacts.
  • Body (when needed) explains why, not a restatement of the diff. Use complete sentences.
  • Prefer focused commits. Each commit should leave the tree buildable and tests passable on its own.
  • Do not commit or push unless the user asks.

Agent defaults

Shared agent hygiene. Keep this section identical in every repo that uses it.

  • Prefer existing project tools and lockfiles; do not invent new package managers, frameworks, or large dependency upgrades unless asked.
  • Do not commit, push, force-push, amend published history, or deploy unless the user explicitly asks.
  • Never invent, print, or commit secrets (.env, API keys, private keys, tokens, production passwords). Use example env files only.
  • Do not run destructive git or filesystem commands (reset --hard, clean -fdx, rm -rf of real data) unless the user clearly requests it.
  • Prefer absolute paths in tool calls; do not create files the user did not ask for (especially drive-by markdown docs).
  • Match surrounding code style. Keep changes scoped to the request.
  • When fixing a bug, prefer a regression test if the repo already has a test harness and the failure is easy to pin.

Before you push

Pushing main to the github remote deploys to production (.github/workflows/docker-deploy.yml runs on push to main).

  • allow_format_failure: false — unformatted code blocks the deploy.
  • allow_test_failure: true — failing tests do not block it. Run mix test yourself; CI will not stop a broken suite from shipping.
  • main tracks origin/main, a local forge at ssh://localhost:2222. The github remote is the one that deploys. A bare git push is not a deploy.

Verifying changes

Always run the checks that CI runs before considering a change done — do not report work as finished until format, compile, and credo pass.

After editing a file, run the fast per-file checks first:

  • mix format <file> — format the file.
  • mix credo --strict <file> — lint just that file (fast).

Then, before finishing, run the broader gate:

  • mix format --check-formatted — CI fails if anything is unformatted.
  • mix compile --warnings-as-errors — warnings fail the build; fix them.
  • mix credo --strict — style/consistency must pass (whole project).
  • mix check — the full CI gate (all of the above plus generated-artifact + legacy-marker checks, asset check, dep/hex audits, and test). Run this for anything non-trivial.

Three budget guards fail in ways that are easy to misread:

  • scripts/check_maintainability_budgets.sh errors when a budgeted path no longer exists, so moving or deleting a budgeted file must update the budget in the same commit.
  • scripts/check_design_consistency.sh caps raw btn/card markup, arbitrary type sizes, off-convention radii, raw hex, and inline styles — per app, for every *_web tree. Lower the budget when you remove instances, so the surface cannot grow back. A new *_web app fails the check until it gets a check_app line.
  • scripts/check_legacy_marker_budget.sh caps legacy|backward|compat| deprecated markers across apps, config, scripts, and deploy.

Tests

  • Run the whole suite from the umbrella root: mix test.
  • Run one app's tests from its dir: cd apps/elektrine_web && mix test.
  • Run a single file/line: cd apps/<app> && mix test test/path/to_test.exs:42.
  • Prefer adding a regression test with any bug fix.
  • Running one app's tests can poison _build/test for the umbrella; if the root suite then behaves oddly, rm -rf _build/test/lib/elektrine.

Conventions

  • Match the style and idioms of the surrounding code in each app.
  • Keep domain logic in apps/elektrine; keep web/LiveView code in apps/elektrine_web. Don't reach across app boundaries casually.
  • Two exceptions, both because apps/elektrine is the only app every other app depends on: UI components shared by more than one product app live in apps/elektrine/lib/elektrine/components (avatar, skeleton, badges), and the frontend assets live in apps/elektrine/assets. Import those modules directly — do not add a re-export shim in apps/elektrine_web.
  • The ElektrineWeb.Components.UI kit is the design system and stays in apps/elektrine_web. Buttons and cards come from it, not hand-written markup; use the text-3xs/text-2xs and radius tokens rather than arbitrary values.
  • Shared text formatting lives in Elektrine.TextHelperstruncate/2, truncate_to/2, abbreviate_count/1, time_ago_compact/1. Call those rather than re-deriving them privately, so the same value renders the same way in every app.

Federation security

  • Elektrine.ActivityPub.SignatureVerification is the only inbound HTTP signature verifier. Every entrance — the inbox plug and the signature retry queue — goes through verify/2; callers differ only in the options they pass. Never add a second verifier or a path that checks the signature without also checking required signed headers, timestamp freshness, digest against the raw body, and replay. A partial verifier reachable from anywhere becomes the way in.
  • Elektrine.ActivityPub.HTTPSignature signs outbound requests only.
  • Only key-resolution failures are retryable (SignatureVerification.retryable_error?/1). A signature that verified but broke a constraint has been rejected on the merits — do not queue it.
  • Retried requests store the raw body and the arrival time. The activity is re-parsed from those bytes after the digest check, so what gets processed is what the signature covered, and freshness is judged against arrival rather than when the job happens to run.
  • Elektrine.ActivityPub.InboundDeliveryPolicy.permit/4 is the one audience gate. Any new inbox entrance applies it — it is what stops a peer pushing content at an inbox it has no relationship with.

Mail and Haraka

Haraka is a separate deployment (elektrine-haraka) that owns inbound SMTP on port 25 and outbound delivery; Elektrine owns mailboxes, JMAP, submission, and the webhooks Haraka calls. Two layouts are supported and each has its own TLS entry point, which write the same compose.override.yml — use exactly one per Haraka deployment:

  • same host: scripts/deploy/configure_haraka_wildcard_tls.sh (the default; docker_deploy.sh keeps it configured)
  • separate host: scripts/acme/setup_haraka_mail_tls.sh

See docs/self-hosting/mail.md before changing either.