10 KiB
10 KiB
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/e2emirror URL paths;tests/unitmirrors module paths;cli/main.py;routers/__init__.pydoes not aggregate; the admin package aggregates on its base router. - Config (section 3):
DATA_PATHSregistry,ensure_data_dirs(),APP_VERSIONviatomllib,BOOT_ID/STATIC_VERSION,make prodexportsMOLODETZ_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.examplehas no secrets. - Browser tests use
wait_until="domcontentloaded"on everygotoandwait_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)
- Docker, nginx sidecar,
instance,docker-*anddeploymake targets: not shipped. Section 28.2 is not selected (bare metal only), and deploy would push to a remote, which was not requested. - Assistant face (8.2 item 3) and action catalogue: skipped (section 16 not selected).
- 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 inSELECTION.md. - 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). - 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.
- Mutation routes parse bodies with a shared
json_or_form(request, Model)helper (form or JSON into one Pydantic model) instead ofAnnotated[Model, Form()]. Validation is equivalent; the signature style differs. - The repo-link check on join requests runs inline in the admin request, not as a durable job (section 14.4 not selected).
- The notification type registry has 6 types (join, backup, system and similar), not the about 20 the template lists for bigger products.
.coveragercusesparallel = 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. Withparallel = Falsethe server data was overwritten (47% instead of 76%).hooks/prod_guard.py(28.3) is present but not wired into any agent hook runner on this box.- Web lock (
web.lock) path is defined but unused: there is a single web role and services run underservice.lock. A suspension check exists in auth, but there is no UI to suspend accounts. fuseris not installed on the box, solocust-runport cleanup is skipped there (the-prefix ignores it).