SCOPE: everything in progressdocs/precommit-and-ci.md · build 40bb41bdb460

Loading ticks…

Pre-commit checks and CI: what runs, where, and why

At a glance

Every change to the code is checked automatically. If a check fails, the change does not go live.

When What is checked, automatically
Every commit No passwords or keys in the code. Code is tidy and follows our rules. Data files are never edited by hand.
Before any change is accepted Five required checks: the code has no errors, 65 test suites pass, the app builds, no secret anywhere in the history, the sample data rebuilds identically.
Every night The whole database is rebuilt from scratch and tested signed in as each role: driver, supervisor, manager, executive.

What that gives us:

  • No secrets leak. Keys are blocked before they leave a laptop, then scanned again in the cloud and in the finished app.
  • Nothing broken goes live. The five checks are required on every change. No one can skip them; an emergency override is logged.
  • People see only what they are allowed to. The nightly tests try to break the access rules directly, as each role. Every attempt must fail.
  • The numbers are right. 1,828 checks confirm each dashboard figure matches the records behind it.
  • Security flaws are flagged. Every change is scanned for unsafe code and for packages with known vulnerabilities, and package updates arrive monthly.

The rest of this page is the full catalogue: every check, what it is, and what it protects against.

Background

Prepared 27 Oct 2026, ready to implement once the CI/CD workflow lands in the lucent-axis GitHub organization. Modelled on Matt's Evidence Lab (dividor/evidencelab): its .pre-commit-config.yaml and .github/workflows/ci.yml were read in full and every hook was weighed against this repository. Built 28 Oct 2026: .pre-commit-config.yaml, the three guards in scripts/guards/, the CI jobs, .github/dependabot.yml and the nightly database job (.github/workflows/nightly-db.yml, running scripts/ci/nightly_db.sh). This page stays the catalogue; "What was built" below says where the build departs from the plan.

The short version

  • Yes, GitHub Actions, in the lucent-axis organization. It is where the code is moving (SOC 2 OW-1), it already runs our CI on lucent-axis/scope in about three minutes, and it is what the SOC 2 change-management controls (SC-4, SC-5, SC-7) expect to see as evidence. Vercel keeps deploying; Actions decides whether a change is allowed in.
  • Three layers, the same checks, different speeds. On commit: only what finishes in a few seconds. On push: the slower code checks. In GitHub Actions: everything, on every pull request, and that is the one that counts.
  • Pre-commit is a courtesy, CI is the lock. Anyone can skip a local hook with --no-verify. The same rule the app follows (the tile is a courtesy, the database policy is the lock) applies here: the hooks save a wasted push, the required checks on main are what actually stop a bad change.
  • Use Matt's tool, the pre-commit framework, so both teams share one way of working. One .pre-commit-config.yaml drives the local hooks, and CI runs the same file with pre-commit run --all-files, exactly as Evidence Lab does. It handles our Python scripts and our TypeScript in one place, which Husky and lint-staged would not.
  • Two failures a hook would have stopped: on 26 Oct two pushes in a row went red on main (a lint error, then a missing package). A push-time hook running lint and the type-check catches both before they leave the laptop.

The three layers

Layer When it runs Time budget What belongs there
On commit (pre-commit) Every git commit, on the files being committed Under 10 seconds File hygiene, secrets, lint on changed files, the project's own guards
On push (pre-push stage) Every git push About 2 minutes Type-check, all 65 unit suites, the data suites when the generator changed
GitHub Actions Every pull request and every push to main About 5 minutes Everything above, plus the build, dependency scanning and the full-history secret scan
Database checks Nightly and on demand, in a throwaway database 15-20 minutes The verify_*.sql scripts and qa_watchlist.mts, which need a loaded database

The database layer is the equivalent of Evidence Lab's integration job, which starts its whole stack in Docker. It must never point at the shared database: verify_demo_reset.sql commits, and a reset reaches every table hanging off its foreign keys (see "ONE DATABASE" in CLAUDE.md).

What Evidence Lab does, and what we take from it

Evidence Lab check What it does For us
pre-commit-hooks (whitespace, end of file, YAML, JSON, TOML, merge markers, line endings, large files) Basic file hygiene Take, with data/csv/ excluded: the CSVs are generated, committed, and the largest is 50 MB
Black, isort, flake8 Python formatting, import order, lint Take as Ruff, one fast tool doing all three, on scripts/**/*.py only
mypy Python type-checking Leave: our Python is offline data scripts; the type risk is in the TypeScript, which tsc covers
Bandit Python security scan Leave: the scripts never take user input or run on a server
Hadolint, Trivy Dockerfile lint, container image scan Leave: we have no containers; Vercel builds the app
detect-secrets and Gitleaks Secret scanning, twice Take Gitleaks only, pinned to the version CI already runs. One scanner, no baseline file to maintain
code-metrics (complexity, maintainability) Fails on overly complex files Take later, report-only: several dashboard components would fail on day one
pre-commit run in CI The local hooks re-run on the server Take: one config file, two places
npm audit, pip-audit Known-vulnerable packages Take npm audit, blocking on critical, reporting high
CycloneDX SBOM A list of every package shipped Take: it is the vendor evidence a SOC 2 auditor and an enterprise buyer ask for
Dependabot plus auto-merge Monthly update pull requests Take Dependabot for npm and GitHub Actions; auto-merge only after branch protection exists
Integration tests in Docker Whole stack started in CI Take the idea: Supabase started in the runner, migrations applied, data loaded, verify_*.sql run
permissions: {} at the top of the workflow Each job gets only the access it needs Take

Two things in Evidence Lab we would not copy: its security scans all run with continue-on-error, so they can never fail a change, and trivy-action@master follows a moving branch rather than a pinned version.

Layer 1: on commit

Check What it is Why it matters here What it protects against Status
Trailing whitespace, end-of-file newline Tidies changed text files Clean diffs in review Whitespace noise hiding the real change in a pull request New
Mixed line endings (fix to LF) Forces Unix line endings The CSVs and SQL are LF by rule; load.sh reads them as such A Windows editor turning a file into CRLF and breaking a load or a byte-identical regeneration New
Check YAML, JSON Parses each changed config file Workflows, brand.json, tenant files A broken workflow or tenant file found only when the build fails New
Merge-conflict markers Refuses <<<<<<< left in a file Two sessions often share one worktree A half-resolved merge reaching main New
Large files (2 MB, data/csv/ excluded) Refuses big binaries Photos, exports and dumps get dropped in by accident A 50 MB export committed, permanently in history New
Private key detection Refuses a committed private key Supabase and AWS keys are in .env.local A key in git history, which means rotating it (SOC 2 SC-1) New
Gitleaks on staged changes Secret scan of what is about to be committed CI scans after the push; this scans before A secret leaving the laptop at all New locally, in CI already
ESLint on changed .ts/.tsx The same rules CI runs Lint failures turned main red on 26 Oct A push that fails CI for a missing Link or an unused import New locally, in CI already
Ruff on changed scripts/**/*.py Python lint (and later, formatting) The generator and suites are the data set's source of truth Undefined names and dead code in the generator New
Generated CSV guard Refuses a commit that changes data/csv/ unless the generator, scenarios.py or window.py changes too HARD CONSTRAINT 5: never hand-edit the CSVs A hand edit that the next regeneration silently undoes New, project script to write
Demo date guard Checks that the four places holding "today" agree: scripts/window.py, app.today() in the latest migration that defines it, TODAY in packages/core/src/config.ts, FROM/TO in packages/core/src/cofi/facts.ts HARD CONSTRAINT 1: three languages, nothing can import from the others The dashboard reading as historical, or one screen counting a different window from the rest New, project script to write
New table guard A migration that creates a table must also enable row-level security and call app.add_audit_columns() RLS is the access boundary; every table carries the audit columns A table exposed through the API with no policy (SOC 2 SC-4); verify_audit_columns.sql failing weeks later New, project script to write
Brand check apply-tenant.mjs --check-all when anything under tenants/ changes Every client build binds a tenant A tenant with an unreadable colour or a missing icon found at a client's deploy Exists as a command, new as a hook

Layer 2: on push

Check What it is Why it matters here What it protects against Status
Apply the default tenant node scripts/brand/apply-tenant.mjs The app imports gitignored generated files palette.test.mts failing on a fresh checkout (it does today until this runs) In CI already
Route types and type-check next typegen, then tsc --noEmit in apps/web Business rules live in packages/core and are shared by every screen A changed function signature breaking a screen nobody opened In CI already
Unit suites npm test: 65 suites, about 85 seconds The rules are the product; see the catalogue below A rule changing without anyone deciding it should In CI already
Data suites, when the generator changed audit.py, verify.py, coherence.py, precheck_ddl.py: 20 seconds together The data set is the demo A generator change producing broken foreign keys, wrong vocabulary or incoherent records Run by hand today

Layer 3: GitHub Actions

Job What it is Why it matters here What it protects against Status
pre-commit pre-commit run --all-files Same file as the laptops Someone who never installed the hooks, or skipped them New
check Type-check, lint, unit suites The main gate today Broken code reaching main In place (.github/workflows/ci.yml)
build next build with placeholder environment values Vercel builds the app; this builds it first A build that only fails on Vercel, like the missing @scope/spotter dependency on 26 Oct New
data Regenerate, run the four suites, then git diff --exit-code data/ Regeneration must be byte-identical (determinism broke once, silently, through hash()) A generator that no longer reproduces the committed data set New
add-on removable check-removable.mjs --build: delete every add-on in a scratch copy, then build Client repos start without Spotter Core code quietly depending on an add-on Exists as a command, new in CI (only when addons/ or packages/spotter change)
secrets Gitleaks over the whole history Catches anything a laptop missed A secret already pushed In place
dependencies npm audit (critical blocks, high reported) and a CycloneDX SBOM kept with the run SOC 2 SC-7 and vendor questionnaires Shipping a package with a known exploit; having no list of what we ship New
Dependabot Monthly update pull requests for npm and GitHub Actions, majors ignored Keeps pinned versions current Old, vulnerable packages and actions New (.github/dependabot.yml)
CodeQL GitHub's code scanning for TypeScript SOC 2 SC-7 Injection and unsafe patterns in the app code Not bought: Semgrep CE instead, see "CodeQL: the decision" below

Layer 4: database checks

Run in a throwaway database inside the runner (supabase start, all migrations, load.sh --reset, the seeds in the order CLAUDE.md gives), nightly and before any client demo. Never against the shared database.

Check What it protects against
verify.sql The planted storylines drifting (7 LTI cases in 2026, the Castleton decline), and any public table without RLS (SOC 2 SC-4)
verify_raw_writes.sql A raw API token doing what the app's functions refuse: signing its own sign-off, closing without a verifier, bypassing the cert gate
verify_requests.sql, verify_complete_action.sql, verify_operator_gate.sql The request-for-input, action sign-off and DVIR cert-gate rules failing for a real login
verify_training.sql, verify_lms.sql, verify_lms_review.sql, verify_rule_timing.sql Training assignment, sessions, outside training and renewal dates going wrong at the database
verify_dq_documents.sql, verify_site_documents.sql Who may file and approve a document, and the dates that drive reminders
verify_incident_changelog.sql, verify_audit_columns.sql The audit trail missing a change, or a table without who-and-when columns
verify_lost_time.sql, verify_restricted_duty.sql Lost-time and restricted-duty counts, which feed DART
verify_rule_mapping.sql and the three rule-book scripts Who may decide a rule mapping, and the trail being append-only
verify_addon_switch.sql An add-on switched off for a tenant still answering a raw token
verify_demo_reset.sql A demo reset failing to put every row back (this one commits, hence the throwaway database)
qa_watchlist.mts A watchlist panel promising four people and opening onto three: 1,828 assertions as each persona's own token
qa_ask.mts Spotter's answers disagreeing with the Reports screen

The unit suites, one line each

All 65 run in npm test today, in CI on every push. Each is a plain script of assertions, one process per file.

Incidents and investigations

Suite What it checks What it protects against
incident-kinds Ticked kinds on the one incident form and the incident types they file as A report filed under the wrong type, skewing every count by type
incident-filter The status rail: pills, what each asks the database for, the counts, the query string A pill counting one population and opening another
incident-table The incidents table's columns, populations and reconciling caption A table that does not add up to the tile that sent the reader there
incident-history The investigation's history windows across month and year ends Off-by-one dates at a month or year boundary
investigation Three states, what each requires, what closing does not require An incident closing without a sign-off, or blocked by something irrelevant
investigation-queue Search and slicers on the queue A case the owner cannot find
investigation-request Who owes the next move on a request for input, and what blocks a sign-off Signing off with an open request still waiting on somebody
changelog The audit trail's order, reconstructed rows, and what an export writes An exported history that misorders or drops changes
attachment File types, the size limit and the storage path the policies rely on A photo stored where its access policy cannot see it
capture-draft When an unfinished form is kept and how it reads on Tasks Losing typed work, or Tasks filling with empty drafts

Actions, tasks and the dashboards

Suite What it checks What it protects against
action-detail What a corrective action panel says for each of the four things that can raise one An action that opens onto nothing
tasks What lands on the Tasks panel, in what order, with whose name Somebody's work not reaching their landing screen
dashboard Rates against the exposure basis, roll-ups recomputed rather than averaged, month handling, money format A TRIR or DART that is wrong because rates were averaged
benchmark Comparison bands, with and without one A tile coloured against a band that does not apply
watchlist Signals, the band rule, ranking, the site thresholds, on real rows A person in danger ranked below one who is not
person-detail Every watchlist signal has a section to land on A red flag that opens onto a page saying nothing is wrong
crew-chase Renewals to chase as a reading, not as Tasks rows Tasks rows nobody can clear
vehicle-record Which defects are open and whether the truck may roll The record and the watchlist disagreeing about one truck
vehicle-tree Yard, kind of truck, truck, on the real shapes The vehicle hierarchy drifting from the data
shop The shop supervisor's queue, grouped by who holds each repair Repairs nobody is holding
report-finder Every report's cells line up with its columns A blank or shifted column in a CSV a client exports
search The order the people pickers sort into The right person sinking down the list as a name is typed
persona-swap A demo's own persona names applied to database replies A demo showing the wrong name, or changing the real identity

Drivers, the DQ file and equipment

Suite What it checks What it protects against
driver-home DQ file status, licence grouping, to-do order, on-duty window A driver told they are fine when a card has lapsed
dq-document Which buttons a screen offers on the DQ file before it asks the database A screen offering an action the database will refuse
dq-file The file as a document: summary, "not qualified today", coverage An auditor's summary that disagrees with the lines beneath it
coverage Licence and company cert read across the workforce The equipment coverage report overstating who may drive what
operator-gate The DVIR cert gate on the real catalogue and two real cases A driver filing a DVIR on a machine they are not certified for

Training

Suite What it checks What it protects against
training-assignment Assigning a renewal, and the two-step that finishes it A renewal that never closes, or closes without its second step
training-course Course relevance; a training names a course and the unsafe line it answers Remedial training unconnected to its cause
training-library Search, filters, counts, the catalogue editor, who may do what A course edited by somebody who may not
training-records Wording and rows on the older training screens Two screens describing the same record differently
training-rule Standing assignment rules and automatic renewal People missed by a rule, or assigned twice
training-session A class's state machine, roster and single signature A signed session being changed afterwards
external-training The outside-training form and who may approve it A learner approving their own certificate
renewal-plan Who needs a renewal, which month, and whether a class has them The 12-month plan showing red for people already booked
classes-panel Classes ahead and who is due with none A supervisor not seeing who still needs a class
site-documents Document states, grouping, validation, path A permit's renewal reminder not firing

Rule book and rule mapping

Suite What it checks What it protects against
policy Which rule-book section a checklist line opens, and search A supervisor pointed at the wrong paragraph
rulebook The Word reader: structure, chapter numbers, redaction A new edition read wrongly, or the operator's name surviving redaction
rule-mapping Hazards link the book and the catalogue; decisions override suggestions A suggested action with no reason behind it
rule-publish What publishing includes, what blocks it, the diff Publishing a mapping with undecided links
rule-vocabulary A client's own terms over our library A client's word silently matching nothing
catalog-import A client's course catalogue from CSV or Excel Rows dropped or courses retired without a word

Hub, add-ons and the rest

Suite What it checks What it protects against
business-rules The rules register, and that every rule still points at a real file and line The register drifting from the code it describes
compliance-plan The SOC 2 readiness file: parser, ticks, sort orders, malformed rows A SOC 2 gap silently dropping off the hub page
questions, data-checklist, hub-activity The hub's question board, data checklist form and "new since you last looked" A question or checklist row not appearing
sales Leads vocabulary matches the database's CHECKs A screen offering a value the database refuses
addon-boundary Core code never imports an add-on, except through the registry A client repo failing to build once Spotter is removed
sync (packages/db) The offline outbox: retry or park, order, stop on network loss A DVIR captured offline being lost or sent twice
Spotter: ask-log, blocks, model, saved, talk, words The question log, the building blocks, what is sent to a model, saved questions, the exchange, and how a VP actually asks A plausible wrong number; a name or site leaving in a question to the model
COFI: gaps, keyboard, palette The needs-data registry, chart keyboard rules, every colour a Lucent Axis colour COFI misreporting what it needs, inaccessible charts, off-brand colours
COFI: model The replica's arithmetic against the dashboard's own Two screens computing one fact two ways. Needs a database dump, so it is skipped in CI by name

The data suites

They need no database and run in about 20 seconds; regeneration takes another 45. All are standard-library Python.

Suite What it checks What it protects against
generate_sample_data.py, then git diff --exit-code data/ The committed data set is exactly what the generator produces Non-determinism, and generator changes nobody regenerated for
audit.py 48 foreign-key checks, 122 logic checks, realism benchmarks (TRIR, DART) Orphaned rows, impossible combinations, rates outside industry bands
verify.py File hygiene, vocabulary, business-key uniqueness, org structure A duplicate IN- number, a stray status word, a BOM in a file
coherence.py Each record reads as one event An incident whose equipment, place and cause contradict each other
precheck_ddl.py Every DDL constraint asserted against the CSVs, and every named CHECK has a test A load that fails on a constraint, or a constraint nobody tests

Best practice for the lucent-axis workflow

  1. Protect main with a ruleset, admins included. Pull request required, one approving review, the CI jobs above as required checks, no force-push, and a written emergency path (SOC 2 SC-5). This ends committing straight to main, which is how the project works today: that is Dan's call, and the SOC 2 Type I needs it.
  2. permissions: {} at the top of every workflow, and each job asks only for what it uses.
  3. Pin every third-party action to a full commit SHA, not a tag or @master, and let Dependabot move the pins. A tag can be re-pointed by whoever owns the action.
  4. Cancel superseded runs with a concurrency group per branch, so a quick second push does not queue behind the first.
  5. Keep required check names stable. A renamed job silently stops being required.
  6. Never make a flaky or external check required. Evidence Lab runs its translation tests on every pull request but cannot fail on them, because a third-party outage must not block a merge.
  7. Secrets at the organization level, and per environment for anything that touches a database, with required reviewers on that environment. Fork pull requests receive no secrets, so jobs that need them are skipped there, as Evidence Lab does. For AWS (Bedrock), use GitHub's OIDC login rather than a stored key.
  8. Actions gates, Vercel deploys. Keep Vercel's git integration for previews and production, and make the CI checks required before a merge. Later, if releases need to wait on CI, deploy from Actions with vercel build then vercel deploy --prebuilt.
  9. Previews must not reach real data. Today every preview reads the one shared database (SOC 2 VE-1, VE-3). The database layer above needs its own throwaway database for the same reason.
  10. Gitleaks as a pinned binary, not the action. The action needs a paid licence on an organization account; our CI already downloads the binary and checks its SHA-256.
  11. Cost. GitHub-hosted runners on a private repo draw on the plan's included minutes (3,000 a month on Team). At about 5 minutes a run plus a nightly database job, that is comfortable.

What was built, and where it departs from the plan

  • Ruff checks only E9 and F (syntax errors, undefined names, unused imports and variables), set in ruff.toml. Its default set reported 378 findings across the scripts, almost all house style; the six real ones (dead imports, an unused variable in the generator) were fixed, and the regenerated data set came out byte-identical. Widen select one rule family at a time.
  • The hygiene hooks skip vendor/ (Power BI's own files, CRLF as shipped) and the files scripts write (data/csv/, the suites' data/*.txt, docs/dba/). The one-off whitespace pass they made over the rest is its own commit.
  • The new-table guard holds new migrations only, those sorting after 20261027130000_lms_switch.sql (and Spotter's 0004). The older ones enable RLS in loops and backfill audit columns in bulk, which a text check cannot follow; verify.sql and verify_audit_columns.sql already cover them at the database.
  • The CI pre-commit job skips ESLint, because check lints the whole app; everything else in the file runs.
  • npm audit was not clean on day one: one critical (Next.js 16.0 to 16.4.0, remote code execution in next/og) and four high. Next moved to 16.4.3 and zod to 4.6.7, both inside their major, so the job blocks on critical from its first run.
  • The data job runs Python 3.11: regeneration was confirmed byte-identical on 3.9 and 3.15.
  • The first nightly run found that a database could not be built from empty. Four fixes, none changing what a migration did to an already-loaded database: two migrations that seed against CSV rows (20260901120000, 20260909110000) now skip that work on an empty database, where reseed_reference.sql does it after the load; load.sh --reset truncates even a database with no people yet (migrations had already seeded five requirement rows the CSV also carries); gen_demo_user.py writes the restriction dates its planted case got on the shared database from a backfill; and 20261028140000 gives site_document and site_document_category the audit columns they never had, on the shared database too, which is not yet applied there (re-take the demo baseline after applying it).
  • Semgrep CE is the code-scan job (SC-7): the registry's TypeScript, JavaScript, React and Next.js rules, any finding fails it, not required because the rules are fetched at run time. Its first pass found one finding, the hub's own markdown, silenced inline with the reason.
  • Not built yet: the add-on removable job (path-filtered) and code metrics.

Shipping once main is protected (ORG-07, SOC 2 SC-5)

The ruleset is a file, .github/rulesets/main.json, so what is enforced can be read and diffed: pull request required, 0 approvals (one developer; Kyle reviews merged changes monthly), merge commits only, no force-push or deletion, no bypass for admins, and five required checks: check, secrets, pre-commit, build, data. Not required, because something outside the change can turn them red: dependencies (a new advisory) and code-scan (new registry rules). Applied by a repository admin with:

gh api -X POST repos/lucent-axis/scope/rulesets --input .github/rulesets/main.json

From then on nothing pushes to main on lucent-axis/scope. scripts/git/ship.sh is the routine: push HEAD to the personal repo's main as before and to personal-main on lucent-axis/scope, open the standing PR into main if none is open, wait for the required checks, merge it. Dependabot's PRs merge on lucent-axis/scope only, so each is brought home with git merge origin/main before the next ship; the script refuses to ship until it is. Emergency path: an admin switches the ruleset to Disabled, which GitHub's audit log records, and back afterwards.

Applied 28 Oct 2026 by Dan's go: ruleset main, id 26059181, active. The first change shipped through the PR (this paragraph) is the proof that the routine works.

CodeQL: the decision

The question was: pay for GitHub's code scanning on lucent-axis/scope, or not.

  • What it adds: static analysis of our own TypeScript (injection, unsafe data handling), as one more CI job with findings in GitHub's Security tab.
  • What it costs: the repo is private on the Team plan, so it needs GitHub Code Security, a paid add-on, currently off. About $30 per active committer a month, roughly $60 for two (check the current price before buying).
  • Same catch for secret scanning: "turn on secret scanning and push protection" below is also paid on a private repo (Secret Protection, about $19 per committer a month), also off. Gitleaks already covers it for free, on commit and over the whole history in CI.
  • Decided (Dan, 28 Oct, "free first", docs/compliance/README.md): Semgrep Community Edition as a CI job beside ESLint is the SC-7 route. CodeQL is the upgrade, bought only if an auditor or a buyer wants findings tracked in one place. Gitleaks in CI plus the pre-commit hook is the recorded ORG-03 control, so Secret Protection is not bought either. The Semgrep job is code-scan in ci.yml.

Ready to implement

Each line is one piece of work once the CI/CD workflow is in place.

  • Install pre-commit (Python 3.11, to match Evidence Lab) and add .pre-commit-config.yaml with the Layer 1 hooks, data/csv/ excluded
  • Write the three project guards: generated CSVs, the demo date, new tables
  • Add the pre-push stage: tenant, type-check, unit suites, data suites when the generator changed
  • Add the pre-commit, build and data jobs to .github/workflows/ci.yml
  • Add npm audit and the CycloneDX SBOM to CI
  • Add .github/dependabot.yml for npm and GitHub Actions
  • Pin every action in the workflows to a commit SHA
  • Turn on secret scanning and push protection on the lucent-axis repo (SOC 2 SC-3)
  • Decide on CodeQL (GitHub Code Security is a paid add-on for private repos)
  • Build the nightly database job on a throwaway Supabase in the runner
  • Protect main with a ruleset and make the CI jobs required checks (SOC 2 SC-5)
  • Add a "Getting started" line to README.md: pre-commit install --hook-type pre-commit --hook-type pre-push
Back to top