# Compliance: Molodetz against `/workspace/dpp/dptemplate.md` Status date: 2026-10-05. Template read in full and never edited. Selection: `SELECTION.md`. Proof runs: `out/pytest-full.log` (full suite), `out/check-ui.log` (six gates), `out/dpp-gates.txt` (DPP tooling `dpp.gates.run_gates`, all PASS), `out/locust-smoke.log` (51 requests, 0 failures), `make coverage` (76% including the server subprocess). Legend: MET, PARTIAL (works, but narrower than the template text), NOT MET (missing, with the reason). ## Section 1.1 Backend choices | Choice | Status | Where / how | |---|---|---| | Python >= 3.12 | MET | `pyproject.toml` `requires-python = ">=3.12"`; venv runs 3.13 | | FastAPI + Jinja2, server-rendered first | MET | `molodetz/main.py`; one `Jinja2Templates` in `molodetz/templating.py` (gate 1) | | uvicorn (standard extras), reload in dev, workers in prod | MET | `uvicorn[standard]`; `make dev` (`--reload --reload-dir molodetz --backlog 4096`), `make prod` (`--workers`, `--backlog 8192`, proxy headers, pinned static version) | | 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 | | beautifulsoup4 + lxml | MET | `rendering.py` (media pass, plain text), `docs_search.py`, `link_check.py` | | uuid_utils (uuid7) | MET | `database/core.py` `generate_uid()`; blob sharding on the uuid7 tail | | brotli, zstandard | MET | `services/backup.py` archive codecs (`zstd` default, `brotli` option) | | aiofiles | MET | `routers/admin/backups.py` streams the backup download | | python-dotenv, one `load_dotenv()` | MET | only in `molodetz/config.py` | | locust | MET | `locustfile.py`, `make locust` / `make locust-headless` | | 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 | | Hand-written token-driven CSS | MET | `static/css/variables.css` tokens + per-area sheets; breakpoints exactly 360/480/768/1024 (gate 2) | | 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` | | 8 | `json_error` status-first (AST) | MET | `gates.gate_json_error_order` + DPP gate | | 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` | | 8 | Destructive assistant tools gated | N/A | no assistant | | 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. - Makefile (27.1): venv stamp, `PYTHONDONTWRITEBYTECODE=1`, dev/prod/test matrix, `check-ui`, `preflight`, `coverage*`, `locust*`, `prune`/`prune-dry-run`, `tree`/`tree-loc`/`zip`/`delete-pyc`/`clean`. ## Not met or narrowed (honest list) 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 (`/voorwaarden/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).