The release gate
This is the list to walk before a deployment takes real traffic. It assumes the threat model on The security model and the commands from Observability. Every item here was checked against the code rather than inherited from an earlier document.
code ──▶ config ──▶ identity ──▶ data ──▶ disk ──▶ edge │ │ │ │ │ │ └─ CI └─ preflight└─ Logto └─ RLS └─ backup└─ Cloudflare green gates policy clean tested rules- Ruff, import-linter, pyrefly, ty, mypy, and pytest are all green on the commit being deployed. CI runs the same gates against a real VectorChord database.
tests/store/test_rls.py::test_chunk_write_requires_the_parent_document_scopepasses. This is the cross-tenant child-write regression and it is the one test that proves the foreign-key loophole is closed.- The docs build passes, which includes the page gate in
docs/scripts/check-pages.mjs.
Configuration
Section titled “Configuration”- Grafana is the only host-published port and it binds
127.0.0.1. Nothing else insrc/deploy/docker-compose.ymlpublishes anything. serverandapicarry blankAIZK_ADMIN_PASSWORD,AIZK_ADMIN_DATABASE_URL,AIZK_BACKUP_DATABASE_URLandAIZK_DATABASE_URL, so neither can construct an owner connection.- Every external image resolves to its pinned tag, and
db,objects,clamavanddoclingalso match their pinned digests. - Database passwords are unique, random and absent from every tracked file.
AIZK_WEB_SESSION_SECRETis at least 32 bytes and differs from the web, Management API and OAuth client secrets.Settings.independent_session_secretenforces this, so a failure here shows up as a container that will not start.
Identity
Section titled “Identity”public-checkexits zero. It runsadmin auth check-publicwithAIZK_REQUIRE_AUTH=1, which constructsSettingsand therefore fails when Logto, the public URLs or either OAuth client is missing or partial.web-checkexits zero when the browser UI is enabled.AIZK_WEB_PUBLIC_URLandAIZK_API_PUBLIC_URLare both HTTPS. Settings validates the scheme but does not require the two to be equal, so confirm by hand that Caddy is routing them as one origin. This one is a convention, not a check.aizk admin auth auditreports clean, meaning the live Logto tenant matches the committed policy.logtoconnects as its own dedicated role owning only thelogtodatabase.- A suspended account and an account without the
aizk-userglobal role are both rejected by the browser path.LogtoClient._screen_accountand_screen_rolesare what do it. - Organization management adds only an existing account by exact email and refuses to demote or remove the last administrator.
- The
frontendimage contains no database password, Logto secret or session secret. Every one of those is runtime environment on the container, never a build argument.
- Alembic is at head and
aizk admin database check-rlsprintsok. The same verification shows up as an emptyrls_violationslist inaizk admin health. - Per-caller rate limiting and the MCP request size limits are active with their configured values.
SHOW data_checksumsreturnson.- The health report finishes inside its bounds and its real find returns candidates with no
error. The probe timeouts are 2 seconds per model endpoint and 3.5 for the find. aizk admin queue doctorexits zero, meaning no current blockers.
Disk and backups
Section titled “Disk and backups”- The device holding PostgreSQL has at least 20 percent free.
- SMART monitoring, temperature alerting and periodic TRIM are enabled on the host.
- A current aizk archive and a current Logto archive both exist off-host in encrypted storage. They are separate dumps and a deployment needs a matched pair.
- A matching generation of the SeaweedFS object data exists off-host. There is no automated job for this in the repository, so it is a manual step and it is easy to skip.
- A scratch restore has passed within the last month, all the way through PostgreSQL accepting the archive, the RLS check passing, Logto starting and an authenticated find returning evidence.
- Cloudflare rate and body-size rules protect
/authorize,/register,/tokenand/mcp. Application middleware only sees tool calls after FastMCP builds a request context, so it does not cover those routes. - Container egress is restricted so URI intake cannot reach loopback, link-local, private or metadata networks through DNS rebinding.
Accepted gaps
Section titled “Accepted gaps”Verify block encryption and its reboot recovery path. PostgreSQL and storage explains passphrase, TPM, and network-bound unlock designs.
Verify object-store backups separately. PostgreSQL archives preserve hashes, derivatives, and artifact metadata, but not original bytes. A restore without the matching object backup leaves file records whose originals cannot be opened.
What changed from the old checklist
Section titled “What changed from the old checklist”Two items in the previous version were wrong. There is no pgrls lint command here, and the real
check is aizk admin database check-rls backed by ops.scoped_rls_violations. And the browser
image was called static, which it is not, since frontend is an adapter-node server that renders
every page. Its real claim, that no secret is baked at build time, still holds.
- The security model explains why each item is on this list.
- Backups and recovery covers the restore drill in detail.
- Upgrades covers where this gate sits in a deployment.
- Observability has the commands the data section runs.