| SQLite via `dataset`, sync by design, no ORM/migrations | MET | `molodetz/database/core.py`: `dataset.connect` with NullPool, exact `connect_args` and PRAGMA list; schema sync in `database/init_db.py`; no threadpool around DB calls (backup DB writes moved out of `to_thread`) |
| passlib PBKDF2-SHA256, no JWT | MET | `molodetz/utils/auth.py``hash_password`/`verify_password`; sessions in `sessions` table |
| httpx (HTTP/2) only through the stealth factory; curl_cffi impersonation | MET | `molodetz/stealth.py` (only file that builds httpx clients) + `molodetz/curl_transport.py`; `httpx[http2]`; SSRF guard `molodetz/net_guard.py`; used by `link_check.py` (admin repo-link check) |
| mistune (GFM) server-side, emoji as shortcode source | MET | `molodetz/rendering.py`; `molodetz/emoji_builder.py` writes `static/js/generated/EmojiMap.js` from the `emoji` library |
| Pillow, imagehash | MET | `molodetz/gallery.py`: webp thumbnails as blobs in `data/uploads`, perceptual-hash dedupe |
| pypdf | MET | `molodetz/cli/commands.py``posts import` reads `.pdf`; unit test `tests/unit/cli/commands.py` |
| sqlglot | MET | `molodetz/database/sql_lint.py` parses every raw SQL literal; `molodetz system sql-lint`; `tests/unit/database/sql_lint.py` |
| defusedxml | MET | `routers/seo.py` sitemap self-check, `link_check.py` XML titles; tests parse sitemap with it |
| playwright (direct API) + Chromium | MET | `tests/conftest.py` fixtures, no pytest-playwright plugin |
| pytest, no xdist, no pytest-playwright | MET | `pyproject.toml` addopts `-p no:xdist` |
## Section 1.2 Frontend choices
| Choice | Status | Where / how |
|---|---|---|
| Pure ES6 modules, one class per file, OO, relative imports, no NPM/bundler | MET | `molodetz/static/js/**`; `Application.js` imports `components/index.js` first; `window.app` |
| marked (live content only) | MET | `static/vendor/marked.esm.js`, used only by `dp-content` in the editor preview |
| DOMPurify | MET | `static/vendor/purify.es.mjs`; fail-closed in `utils/ContentRenderer.js`; e2e asserts script/javascript:/onclick are stripped |
| highlight.js | MET | `static/vendor/highlight.es.min.js` via `dp-code` |
| Scripts loaded with `type="module"` | MET | `templates/base.html` and page blocks |
| Optional libs chromadb, weasyprint, cairosvg, faker | MET (not used) | their modules are not selected and the libs are not dependencies |
## Section 1.3 Bans
| Ban | Status | Evidence |
|---|---|---|
| No JWT, OAuth/social login, payments, ads, tracking | MET | session cookie + API key only; `swagger_ui_oauth2_redirect_url=None`; no external scripts (CSP in `main.py`); test `tests/unit/main.py` |
| No JS framework, bundler, transpiler | MET | no `package.json`; vendored ESM files only |
| No async DB driver, no threadpool around DB | MET | sync `dataset`; `asyncio.to_thread` only for PBKDF2, archive build, DNS |
| No bare HTTP client | MET | gate 3: client constructions only in `stealth.py` and `curl_transport.py` |
| No hardcoded `/static/` | MET | gate 5 on templates; `static_url()` in `molodetz/assets.py` (also used for the OG image); API test checks rendered HTML |
## Section 33.1 Structural gates
| # | Gate | Status | Where |
|---|---|---|---|
| 1 | Template env in one file | MET | `molodetz/gates.py`, `tests/unit/gates.py`, `make check-ui` |
| 2 | Breakpoints equal closed set | MET | same |
| 3 | HTTP client allowlist | MET | same (allowlist: `stealth.py`, `curl_transport.py`) |
| 4 | Client-render marker allowlist | MET | same (only `admin/post_form.html`) |
| 5 | No bare `/static/` | MET | same |
| 6 | No manual `is-loading` outside `LoadingButton.js` | MET | same |
| 7 | No em-dash characters or entities | MET | `gates.gate_em_dashes` + DPP `gate_emdash` |
| 9 | Reportable/unreportable partition | MET | `tests/unit/constants.py` (reportable set is empty because section 18 is not selected) |
| 10 | Documented auth equals real auth for anon/member/admin | MET | `tests/api/docs/api.py` over every endpoint in `molodetz/docs_api`; plus `tests/unit/docs_api/registry.py` (every product route is documented) |
| 11 | Index plans use the index, no temp b-tree | MET | `tests/unit/database/init_db.py` (EXPLAIN QUERY PLAN for the main read paths) |
| 12 | Full suite green, three tiers, one pass | MET | `out/pytest-full.log` |
## Section 33.2 Architectural gates
| # | Gate | Status | Note |
|---|---|---|---|
| 1 | Four faces | PARTIAL | HTML, JSON (`respond(..., model=)`), docs registry: yes. Assistant face skipped because section 16 is not selected |
| 2 | Soft-delete reads/inserts | MET | `deleted_at IS NULL` filters; inserts write `deleted_at`/`deleted_by` |
| 3 | Cache versions | MET | `cache_state` version sync for auth/settings; display caches have TTL env pins |
| 4 | Role writes invalidate admin cache | MET | `database/users.set_role` |
| 5 | File mutations guard writability | MET | blob writer and backup paths under `DATA_PATHS` |
| 6 | Outbound through the guarded factory | MET | `stealth.guarded_async_client` |
| 7 | Side effects through funnels | MET | `utils/audit.py`, `utils/notifications.py`, `services/queue.py` |
| 9 | Personal data disclosed | MET | `templates/privacy.html` covers join requests, sessions, visit counts |
| 10 | Events catalogued | MET | `events.md` |
| 11 | Section 31 features | N/A | no value, state-machine or multi-path features (31 not selected) |
| 12 | Production data untouched | PARTIAL | the app only writes its own `data/`; `hooks/prod_guard.py` exists but cannot be wired into this agent runtime |
| 13 | Tree matches `SELECTION.md` | MET | DPP `gate_selection_vs_tree` PASS |
## Other conventions checked
- Layout (section 2): routers and `tests/api`, `tests/e2e` mirror URL paths; `tests/unit` mirrors module paths; `cli/main.py`; `routers/__init__.py` does not aggregate; the admin package aggregates on its base router.
- Config (section 3): `DATA_PATHS` registry, `ensure_data_dirs()`, `APP_VERSION` via `tomllib`, `BOOT_ID`/`STATIC_VERSION`, `make prod` exports `MOLODETZ_STATIC_VERSION`. The pre-commit hook bumps the patch version (seen working: 1.0.0 to 1.0.4).
- Secrets: the admin password comes only from `.env` (mode 600, generated randomly, not in git). `.env.example` has no secrets.
- Browser tests use `wait_until="domcontentloaded"` on every `goto` and `wait_for_url`, and check for horizontal overflow on mobile.
1. Docker, nginx sidecar, `instance`, `docker-*` and `deploy` make targets: not shipped. Section 28.2 is not selected (bare metal only), and deploy would push to a remote, which was not requested.
2. Assistant face (8.2 item 3) and action catalogue: skipped (section 16 not selected).
3. Presence is the CORE subset and works by polling `/presence/roster` (20 s) plus server-side touch throttling. There is no WebSocket relay; the rest of section 17 is deferred in `SELECTION.md`.
4. There is no public signup. The only account is the env-bootstrapped admin (`retoor`), so the birth-date/terms signup flow and its shared e2e helper do not exist. Terms acceptance exists (`/terms/accept`, `TermsGate.js`).
5. Votes, reports, comments, maturity partials and the report dialog are absent (sections 18 and 20.2 not selected). The JobPoller, OptimisticAction, StickyScrollPane and emoji picker front-end modules are not built, because no selected feature needs them.
6. Mutation routes parse bodies with a shared `json_or_form(request, Model)` helper (form or JSON into one Pydantic model) instead of `Annotated[Model, Form()]`. Validation is equivalent; the signature style differs.
7. The repo-link check on join requests runs inline in the admin request, not as a durable job (section 14.4 not selected).
8. The notification type registry has 6 types (join, backup, system and similar), not the about 20 the template lists for bigger products.
9.`.coveragerc` uses `parallel = True`. The template text says "no parallelism", but subprocess coverage of the server (also required there) only works with per-process data files that are then combined. With `parallel = False` the server data was overwritten (47% instead of 76%).
10.`hooks/prod_guard.py` (28.3) is present but not wired into any agent hook runner on this box.
11. Web lock (`web.lock`) path is defined but unused: there is a single web role and services run under `service.lock`. A suspension check exists in auth, but there is no UI to suspend accounts.
12.`fuser` is not installed on the box, so `locust-run` port cleanup is skipped there (the `-` prefix ignores it).