Integration checklist¶
What a host has to do for a JASIL deployment to be sound. The threat model explains the reasoning; this is the list to work through.
Deployment shape¶
- [ ] Set
profileto match reality.distributedrefuses to start when a capability URI is unset, which is the point — a silent fallback to a process-local backend across replicas is the failure the profile system exists to prevent. - [ ] Set
web_workersto the number of worker processes you actually run. Four workers under thelocalprofile are still four processes, and process-local state cannot be shared between them. - [ ] Set
job_worker_mode=standalonein every process — API and workers — when durable jobs run in dedicated worker processes. A worker is a process too: without the declaration the checks cannot see it, and a handler sends mail or touches state through a backend its API never shares. - [ ] Leave
enforce_deployment_consistencyon. It refuses to build a platform whose wiring contradicts its topology. Turning it off downgrades those to warnings — reasonable on a development machine, not in production. - [ ] Read the capability report JASIL logs at
INFOon startup and confirm each capability resolved to what you expected.
Database¶
- [ ] JASIL never creates the engine. Give
configure_sessionmakera factory bound to an engine whose credentials, TLS, and pool sizing you control. - [ ] Run the packaged migrations (
jasil.migrations.upgrade) rather thancreate_allin production, and keep them in your deploy pipeline. JASIL uses its ownjasil_alembic_versiontable and never touches yours. - [ ] On MySQL, note that
DATETIMEstores whole seconds and rounds. Do not compare a timestamp JASIL persisted for equality with the value you passed. - [ ] Upgrade through JASIL's packaged revisions before rolling out queue-aware
code. The queue column is non-null and has a database-side
defaultdefault so previous-release writers remain valid during the rollout. Selective new workers adopt legacydefaultrows only for subscribers assigned to their queues, and a selectivedefaultworker excludes subscribers assigned to named queues.
Secrets and event payloads¶
- [ ] Audit what your producers put in
payloadandmetadata. Both are stored verbatim in three tables, serialized onto the Redis stream, andmetadatais logged in full when a best-effort subscriber fails. Carry identifiers. - [ ] Never put a token, password, key, or personal data in either.
- [ ] Apply the same rule to worker metadata. It appears in operator summaries and is capped at 16 KiB of serialized JSON.
- [ ] Remember that a dead-lettered job keeps its payload indefinitely — those rows are deliberately never pruned.
Outbound egress¶
- [ ] Leave reverse geocoding unconfigured unless you need it. Unset means the capability is a no-op backend and no outbound call is ever made.
- [ ] If you allow-list for a self-hosted instance, prefer a CIDR over a hostname. A hostname entry exempts every address that name resolves to, including a cloud metadata endpoint.
- [ ] Alert on the
SSRF allowlist hitlog line. Every exemption taken is logged, and the hostname form logs atWARNING. - [ ] Treat the host check as start-up-time only. If the upstream is somewhere an attacker could later repoint DNS at, put egress policy in the network too.
Outbound mail¶
- [ ] Use
smtp://(STARTTLS) orsmtps://. Cleartext (mail.secure = False) is for a relay on the same host or a private network only; it is refused with credentials. - [ ] Put credentials in
MailSettings.username/password, never in the URI. - [ ] Set
from_addressto an identity your relay has verified. - [ ] Remember the SMTP host is not SSRF-checked — it is expected to be an internal relay. Keep that setting out of attacker-influenced configuration.
- [ ] Do not put a rendered body or secrets in an event payload that drives a mail job; build the message in the handler (see Durable jobs).
Storage¶
- [ ] Keep keys server-generated. Traversal is rejected either way, but a key derived from a filename is a category of problem you do not need.
- [ ] For
local://, restrict the URL prefix in the web server that serves it. Those URLs cannot expire —expires_inis ignored and the link is permanent. - [ ] If you need genuinely time-limited links, use
s3://, whereexpires_inproduces a real presigned URL. - [ ] Serve uploaded content from a separate origin, or with
Content-Disposition: attachmentand a restrictiveContent-Security-Policy.
Durable jobs¶
- [ ] Make every subscriber handler idempotent. Delivery is at-least-once.
- [ ] Give every durable subscriber a reconciliation net — a scheduled backfill,
or a documented exemption — and hold the registry to it with
assert_nets_completein a test. Delivery is at-least-once but not guaranteed. - [ ] Alert on
dead_letterjob count, on theReaped N expired job lease(s)warning (a lease only expires because a worker died or went silent), and onexceeded its maximum runtimeerrors (a handler is hung or too slow). - [ ] Alert on the oldest pending job age from
jasil.admin.get_jobs_summary(). - [ ] Alert on
supersededattempts (a handler finished after its job was taken: it ran twice) and onclaimed_by_stale_workersfromjasil.admin.get_workers_summary()(jobs waiting out a lease behind a silent worker). Read a job's whole history withjasil.admin.get_job. - [ ] Assign queue names at subscriber registration. Keep them lowercase ASCII, stable, and domain-neutral from JASIL's point of view; renaming a queue while old rows remain requires workers for both names during the drain.
- [ ] For SQLite, run exactly one API process and one in-process worker
(
job_worker_mode=in_process, the default). For PostgreSQL, do not start handlers in API replicas; setjob_worker_mode=standaloneand run standalone workers with explicit non-empty allowlists, sizing each queue's process group. - [ ] Remember that durable jobs do not require Redis. Do not introduce a second
queue as a source of truth beside
processing_jobs. - [ ] Set
max_runtime_secondsabove the longest handler you expect, and keeplease_secondsat least threeheartbeat_interval_seconds. The heartbeat renews a running job's lease, solease_secondsbounds how long a worker may go silent, not how long a handler may run. A handler pastmax_runtime_secondsis abandoned and may be run again, and a stale worker cannot finalize over a replacement claim, but both may already have performed external side effects, so idempotency still matters. - [ ] In handlers that loop or perform several side effects, check
jasil.jobs.lease_lost()between units of work and return early.
The admin surface¶
- [ ] Put authentication and authorization in front of every
jasil.adminroute. It exposes operational data andreplay_dead_letter_jobchanges state. JASIL has no notion of who is calling. - [ ] Do not reflect the summaries to end users.
recent_failurescarries error messages andevent_metadata. - [ ] Expose worker and queue summaries through host-owned authenticated routes. JASIL supplies typed reads, not FastAPI routes or UI.
- [ ] Treat running/stale/stopped as operator telemetry. Decide alert and container-health policy in the host rather than mapping status to health automatically.
Logging¶
- [ ] JASIL logs through
logging.getLogger(__name__)and configures nothing. Route thejasil.*loggers wherever your application logs go. - [ ] Do not run production at
DEBUG. The debug lines name every event published and every job batch claimed. - [ ] Do not parse or alert on log wording — only levels and structured fields are stable. See API stability.
- [ ] Wire
jasil.correlation.configure_providerto your request-id middleware so events carry it.
Shutdown¶
- [ ] Call
jasil.lifecycle.shutdown()on the way down. It stops the durable-job worker before releasing the bus its subscribers publish through. - [ ] Standalone workers must pass a stop event and set it from their signal handler so JASIL can record a graceful stop.
- [ ] Stop your own scheduler and dispose your own engine — JASIL created neither and does not touch them.
Dependencies¶
- [ ] Install only the extras you use. A core install pulls
sqlalchemyandpydanticand nothing else, and every backend client is imported lazily. - [ ] Pin JASIL. While the version is
0.x, a minor release may break the API. - [ ] Verify the release artifact if you care about provenance — see Verifying a release.