Appearance
Architecture
Rails, following Basecamp's conventions closely: thin controllers, fat models, concerns over inheritance, Hotwire for interactivity, and no service-object layer for its own sake.
Layout
app/
controllers/ thin — set ivars, call models, authorise
api/v1/ the JSON and Markdown API
concerns/ authentication, request context
models/ domain logic
concerns/ cross-model behaviour
<model>/ model-specific concerns, e.g. entity/check_posture.rb
lib/
risk/ the risk engine: assessment, gates, tiers, provenance, observations
providers/ one namespace per provider, adapters and clients
provider_fetch_runs/ config resolution, projection, payload sanitising
person_resolution/ identity linking and deduplication
screening/ disposition resolution
policies/ Pundit policies, one per resource
views/ ERB, Turbo Streams, jbuilder for the API
jobs/ Solid QueueKey seams
Risk::Assessment — the only writer of entities.risk_level, and it always writes risk_level_updated_at with it.
Risk::Tier — the single score-to-band function, used for the overall score and for individual factor values, which is what makes per-factor labels and the overall band agree by construction rather than by two sets of thresholds drifting apart.
ProviderFetchRuns::ConfigResolver — the one place that decides which provider, identifier and adapter serve a given check.
ProviderFetchRun.capture! — the one place a provider call is recorded, success or failure.
Screening::Disposition — the one resolver the approval guard and the screening panel share, so what the panel shows and what the guard enforces cannot disagree.
Policies — every action authorised through Pundit, with account scoping in the policy scope rather than trusted from params.
Multi-tenancy
Everything is scoped to an account. Policy scopes filter by account_id; API lookups go through the policy scope so a cross-account record is not found rather than forbidden.
Request-scoped state
Current carries the session, user, account and request details. Nothing passes current_user through layers.
Frontend
Importmap, Propshaft, Turbo and Stimulus. No bundler, no Node in the app's asset path. Interactivity is Turbo Frames and Streams; Stimulus is reserved for genuinely browser-side behaviour.
The design system lives in app/assets/stylesheets/verity.css with one rule worth repeating: strong colour means risk, never decoration and never a count. A number nobody measured is rendered in slate.
Background work
Solid Queue for jobs, Solid Cache and Solid Cable on their own databases. Asynchronous checks (IBAN verification) and webhook deliveries run as jobs.