Imported from ministryofjustice/offender-management-allocation-manager (
AGENTS.md). Install upstream withnpx skills add ministryofjustice/offender-management-allocation-manager. Copyright stays with the author.
AGENTS.md
Big picture
- This is a Rails 8 app for allocating prisoners to Prison Offender Managers (POMs). The main web surface lives under
/prisons/:prison_id/...(config/routes.rb); public APIs live under/api; admin/support tools live under/manage,/sidekiq, and/debugging. - Request flow is usually: controller -> service/HMPPS API client -> local ActiveRecord model -> presenter/adapter for the view. The local DB stores allocation and handover state; prisoner/staff facts are mostly fetched live from HMPPS APIs.
- Key local records are
AllocationHistory,CaseInformation,CalculatedHandoverDate,PomDetail,Responsibility,EarlyAllocation, andLocalDeliveryUnit(app/models).OffenderServicewraps remote prison data intoMpcOffender, and view-facing adapters likeAllocatedOffender/OffenderWithAllocationPresentermerge remote offender data with local allocation state.
Architecture and boundaries
- All prison-scoped controllers inherit from
PrisonsApplicationController, which loads@prison,@current_user, service notifications, sorting helpers, and enforces the active caseload (app/controllers/prisons_application_controller.rb). - Staff/POM prison pages often go one level deeper and inherit from
PrisonStaffApplicationController, which centralises POM allocation filtering/sorting and handover summary helpers for screens likeCaseloadControllerandPomsController(app/controllers/prison_staff_application_controller.rb,app/controllers/caseload_controller.rb,app/controllers/poms_controller.rb). - SSO/auth is HMPPS OAuth via OmniAuth (
config/initializers/omniauth.rb). Role checks live inSsoIdentitywith project-specific roles:ROLE_ALLOC_CASE_MGR(POM),ROLE_ALLOC_MGR(SPO),ROLE_MOIC_ADMIN(admin) (app/controllers/concerns/sso_identity.rb). - External integrations should usually go through
app/services/hmpps_api/*.HmppsApi::Clientalready handles bearer auth, retries, Typhoeus, and response caching; disable cache explicitly for mutable endpoints (app/services/hmpps_api/client.rb). - POM onboarding/offboarding now crosses both NOMIS user-roles and prison APIs: use
NomisUserRolesService/HmppsApi::NomisUserRolesApifor staff search and role assignment/removal, but keep usingPrison#get_list_of_pomsfor prison POM lists because it de-duplicates NOMIS role results and merges localPomDetail(app/controllers/onboarding_controller.rb,app/services/nomis_user_roles_service.rb,app/models/prison.rb). MpcOffendernow pulls active alert labels fromHmppsApi::PrisonAlertsApi, and Delius imports attachLocalDeliveryUnitrows that are synced from Mailbox Register (app/models/mpc_offender.rb,app/services/hmpps_api/prison_alerts_api.rb,app/services/delius_data_import_service.rb,lib/import_local_delivery_units.rb).PrisonService.womens_prison?is the canonical branch for women’s-estate behaviour; routes and controllers rely on it (config/routes.rb,app/services/prison_service.rb).
Async and event flows
- ActiveJob uses Sidekiq unless
RUN_JOBS_INLINEis set (config/application.rb). Queue names are inconfig/sidekiq.yml(debounce,default,mailers). - Domain events are first-class here. Outbound events are built with
DomainEvents::Event/EventFactoryand published to SNS; inbound events are consumed from SQS byDomainEventsConsumervia Shoryuken (app/lib/domain_events/*,app/workers/domain_events_consumer.rb). - Event handlers are wired centrally in
config.application.domain_event_handlers(config/application.rb), not by convention scanning. - A common cross-system flow is: inbound probation event ->
ProbationChangeHandler-> debouncedDebouncedProcessDeliusDataJob->ProcessDeliusDataJob/DeliusDataImportService->CaseInformationupdate +AuditEvent(app/lib/domain_events/handlers/probation_change_handler.rb,app/jobs/*delius*,app/services/delius_data_import_service.rb). - Other inbound events also fan out through jobs/services rather than mutating state inline:
PrisonerUpdatedHandlerandPrisonerReleasedHandlerenqueue prisoner status/release processing jobs, whileTierChangeHandlerupdatesCaseInformation#tierand emits an audit event (app/lib/domain_events/handlers/*,app/jobs/process_prisoner_*_job.rb). - Another key flow is allocation/handover changes:
AllocationHistoryafter-commit hooks publish audit events, outboundallocation.changed, and flattened PaperTrail versions;RecalculateHandoverDateJobmay publishhandover.changed(app/models/allocation_history.rb,app/jobs/recalculate_handover_date_job.rb).
Project-specific conventions
- Do not put complex objects in session. Use
ApplicationController#save_to_sessionso only.attributeshashes are stored; this matters because dev/test use cache-backed sessions to match production (app/controllers/application_controller.rb,config/environments/development.rb,config/environments/test.rb). - Existing multi-step journeys often use
Wicked::Wizardplus smallActiveModelform objects rather than giant AR forms. SeeBuildAllocationsControllerandFemaleMissingInfosController;OnboardingControlleris a plain controller/action flow that still uses a smallPomOnboardingFormplus session-backed state inapp/forms/. - For new journeys, or when significantly refactoring an existing wizard, prefer a simpler native Rails design over adding more
Wicked::Wizardusage. Treat Wicked as legacy/project history, not the default pattern to extend. - Allocation and handover history rely on PaperTrail plus explicit audit rows. If you change tracked models, check both
has_paper_trailbehaviour andAuditEvent.publishside effects. - Structured logs are intentional: many jobs/handlers log
event=...key/value messages. Preserve that style when extending background flows. Prison#get_list_of_pomsintentionally de-duplicates NOMIS role results and merges localPomDetail; use it instead of calling NOMIS directly from controllers (app/models/prison.rb).
Developer workflows
- Initial setup follows
README.md:bundle install,yarn install,bundle exec rails db:setup. - Fast local web loop:
bin/devruns Puma + CSS watch (Procfile.dev,package.json). CSS is built by Sass intoapp/assets/builds/application.css. - The production image in
Dockerfileis a multi-stage Alpine build. Keepca-certificatesandtzdatain both builder and runtime: builder needs them for Bundler/Yarn plusrails assets:precompile, and runtime needs them for outbound HTTPS and Rails timezone data. - Runtime also needs
libcurlbecauseHmppsApi::Clientuses Faraday with the Typhoeus adapter. The image does not need the AWS CLI or legacy Bower assets; the RDS PostgreSQL trust bundle is downloaded during the build and copied to/home/appuser/.postgresql/root.crt. - Background processing is separate locally: start Sidekiq with
bundle exec sidekiq -C config/sidekiq.ymland Shoryuken withbin/rake shoryuken:startwhen testing domain events. - Local AWS/event testing uses Localstack and the SNS/SQS setup documented in
README.md; the important env vars areLOCALSTACK_URL,DOMAIN_EVENTS_TOPIC_ARN, andDOMAIN_EVENTS_SQS_QUEUE_NAME. - For Delius/LDU work,
bundle exec rake import:local_delivery_units:dry_runpreviews the Mailbox Register sync andbundle exec rake import:local_delivery_units:processpersists it (lib/tasks/import_local_delivery_units.rake). - Main test command is
bundle exec rspec. Feature specs expect Firefox + geckodriver. Tests run jobs inline, block external HTTP with WebMock, stub DPS header/footer by default, and commonly stub event publication unless metadata opts back in; checkspec/rails_helper.rbfor useful metadata hooks like:queueing,:enable_domain_event_publish,:skip_dps_header_footer_stubbing, and:skip_active_caseload_check_stubbing. - For local linting, prefer
bin/rubocopoverbundle exec rubocop; the wrapper uses RuboCop server mode to reduce repeated startup time. - API docs are generated with rswag; see request/API specs under
spec/apiand browse locally at/api-docs.
Documentation and writing style
AGENTS.mdis primarily for AI agents and other tooling, not general human-facing documentation, so keep it concise and instruction-first; apply these style rules where they help, but do not rewrite it to read like end-user docs.- For Markdown and other documentation, keep the tone friendly, professional, and concise; prefer clear, actionable wording with minimal jargon.
- Follow GOV.UK style guidance where practical, especially for structure, clarity, grammar, and punctuation.
- Use consistent capitalisation for product names and proper nouns, for example GitHub, macOS, Docker, and Ruby.
- Use British English spelling, for example organise, behaviour, centre, travelling, and labelled.
- Write naturally, including contractions where they help the tone, but avoid sounding too casual.