{
  "product": "Verdikt",
  "generated_at": "2026-09-17T04:00:00Z",
  "commit": "a574942",
  "summary": {
    "verdict": "APPROVED",
    "tests_total": 658,
    "tests_passing": 658,
    "lint_clean": true,
    "boundary_tests_passing": 4,
    "audits_passing": 4,
    "open_review_notes": 8
  },
  "gates": [
    {
      "name": "Ruff lint",
      "status": "passing",
      "scope": "src tests scripts",
      "evidence": ".github/workflows/ci.yml (Lint step)",
      "notes": "Ratcheted after adding pre-commit; 24 pre-existing violations fixed 2026-09-03."
    },
    {
      "name": "Pytest suite",
      "status": "passing",
      "scope": "tests/ (658 tests · 10 suites incl. dedicated E2E card)",
      "evidence": "reports/results.xml (658/658 green)",
      "notes": "pytest --forked isolates provider mocks per-test. 3m01s at HEAD. New end-to-end suite covers the 3-level model chain reaching the wire; new admin_ui suite covers inherit-toggle write-paths."
    },
    {
      "name": "License scan (\u00a712)",
      "status": "passing",
      "scope": "All PyPI deps",
      "evidence": "scripts/license_check.py",
      "notes": "pip-licenses + allowlist + explicit LGPL/dual-license exceptions. pypdfium2 exception documents the atypical compound license string."
    },
    {
      "name": "Tenant-scope audit",
      "status": "passing",
      "scope": "src/docgen/**/*.py",
      "evidence": "scripts/tenant_scope_audit.py",
      "notes": "Direct tenant-scoped queries without a tenant_id filter within 40 lines are blocked. Suppression via inline # tenant-audit: allow."
    },
    {
      "name": "Alembic upgrade head (fresh DB)",
      "status": "passing",
      "scope": "alembic/versions/0001 \u2192 0031",
      "evidence": "CI Apply DB migrations step",
      "notes": "Regression guarded by 0025 seed-filter (only kinds allowed by the migration's own CHECK constraint)."
    },
    {
      "name": "Boundary tests",
      "status": "passing",
      "scope": "tests/test_boundaries.py",
      "evidence": "4 boundary invariants",
      "notes": "See boundary_tests section below."
    },
    {
      "name": "Pre-commit hooks",
      "status": "passing",
      "scope": "Local dev",
      "evidence": ".pre-commit-config.yaml",
      "notes": "Ruff + hygiene + tenant-scope audit run on every commit. License scan CI-only (pip-licenses false-fails in shared venvs)."
    }
  ],
  "boundary_tests": [
    {
      "name": "docgen never imports pap_pipeline",
      "purpose": "Proves the substrate has no back-reference to the submission-window pipeline. Reviewers can strip pap_pipeline without breaking Quire.",
      "test": "tests/test_boundaries.py::test_docgen_never_imports_pap_pipeline"
    },
    {
      "name": "core never imports AI providers",
      "purpose": "Enforces 'no model at render time'. If the deterministic render path ever imports docgen.providers, the build fails.",
      "test": "tests/test_boundaries.py::test_core_never_imports_ai_layer"
    },
    {
      "name": "No LLM in reply path",
      "purpose": "Reply-letter template must be merge-time fill only. No stage between decision and outbound bytes may call a model.",
      "test": "tests/test_pap_inbox_hook.py (merge-only invariant)"
    },
    {
      "name": "Nebius default targets NVIDIA Nemotron",
      "purpose": "Hackathon requirement: the default model name must start with nvidia/. Regression guard on the compliance claim.",
      "test": "tests/test_provider_nebius.py::test_nebius_defaults_target_nvidia_nemotron"
    }
  ],
  "audits": [
    {
      "name": "Triage + 3-level config chain review",
      "shipped": "2026-09-17",
      "outcome": "4 blockers found, all fixed at HEAD. Route allowlist added to both routing_json POST handlers; dead routing-clobber code removed from program_models_save; inbound email subject now threaded to Triage classifier; bare assert replaced with explicit guard in _llm_classify (safe under python -O).",
      "reference": "src/pap_pipeline/stages/triage.py + src/pap_pipeline/inbox_hook.py + src/docgen/ui/routes.py"
    },
    {
      "name": "Multi-tenancy scope",
      "shipped": "2026-09-03",
      "outcome": "Passing on all in-scope models. 4 file-level suppressions justified (admin portal + 3 worker actors).",
      "script": "scripts/tenant_scope_audit.py"
    },
    {
      "name": "Caller-profile mapping engine threat model",
      "shipped": "2026-09-03",
      "outcome": "No eval / injection path. Closed grammar via strict regex; explicit-dispatch transforms; no dynamic attribute access.",
      "reference": "roadmap Security"
    },
    {
      "name": "N+1 audit (dashboard + templates)",
      "shipped": "2026-09-03",
      "outcome": "No true N+1 found. Fixed one wasted tenant re-query in _base_ctx.",
      "reference": "roadmap Performance"
    }
  ],
  "review_notes": [
    {
      "area": "Templates",
      "note": "PDF placement editor is the placement editor's hot path. Page rasterisation is cached to blob storage keyed by version hash.",
      "action": "Monitor cache hit rate once telemetry ships."
    },
    {
      "area": "Pipeline",
      "note": "Reason stage runs Nemotron in non-reasoning mode deliberately; reasoning variant burns output tokens on trace and returns empty content.",
      "action": "Documented in HACKATHON.md (regression-safe)."
    },
    {
      "area": "MDM",
      "note": "`_forbid_raw_history_events(extras)` runs on reason-stage entry. Rules pack, not the LLM, decides how history matters.",
      "action": "Keep boundary. CI test in ADR-0004 PR6 will enforce."
    },
    {
      "area": "Migrations",
      "note": "0025 seeds only kinds allowed by its own CHECK constraint; later kinds (history) seeded by their own migration after the constraint widens.",
      "action": "Repeat pattern for any new kind. Never widen 0025's seed."
    },
    {
      "area": "CI",
      "note": "MinIO pulls from quay.io, not docker.io. Shared runner IPs hit Docker Hub anonymous rate limits.",
      "action": "Keep quay mirror for the MinIO + mc images."
    },
    {
      "area": "Docs",
      "note": "Public framing scrub: README/demo/video may not name specific competitors or libraries the submission is compared against.",
      "action": "Memory rule enforced during any README edit."
    },
    {
      "area": "Pipeline \u00b7 inbox_hook",
      "note": "Duplicate helpers `_lookup_program_row` and `_load_program_row` (byte-identical) in src/pap_pipeline/inbox_hook.py:628,716. Both are called per case, doubling the DB round-trip in the happy path.",
      "action": "Consolidate to a single function and thread the resolved Program row through `_draft_reply_artifact` instead of re-fetching."
    },
    {
      "area": "UI \u00b7 programs create",
      "note": "src/docgen/ui/routes.py:5857-5860 hardcodes `extract_model=\"openbmb/MiniCPM-V-4_5\"` and `reason_model=\"nvidia/nemotron-3-super-120b-a12b\"` on program creation, so env-var-driven default rotation never propagates to newly created programs.",
      "action": "Seed with `None` (fall back to settings) or read from `get_settings()` at create time."
    },
    {
      "area": "Security \u00b7 CSP",
      "note": "Landing CSP was missing `media-src 'self'` after the demo card switched from YouTube iframe to a local <video>. Strict browsers would refuse to play. Also carried dead `frame-src youtube-nocookie` allowances.",
      "action": "FIXED 2026-09-16: landing_csp now emits `media-src 'self'` and drops the YouTube frame-src entries. Verified via `curl -I /`."
    },
    {
      "area": "Pipeline \u00b7 orchestrator",
      "note": "orchestrator.run_with_intake stacks extract_findings before verifier_findings intentionally (extraction disagreements read first). Undocumented in the code.",
      "action": "Add a one-line comment noting the ordering is intentional so a future reviewer doesn't reorder."
    },
    {
      "area": "UI \u00b7 program_models_save",
      "note": "src/docgen/ui/routes.py previously wrote prog.triage_routing on every Models-tab save with a dead form-field lookup (`route_{case_type}`) that never matched anything, silently clearing operator-set routing overrides.",
      "action": "FIXED 2026-09-17: removed the routing block from program_models_save; the dedicated Triage tab now owns routing writes."
    },
    {
      "area": "Security \u00b7 Triage routing writes",
      "note": "Both admin_save_tenant_triage_routing and program_triage_routing_save accepted arbitrary `route` strings from routing_json and persisted them to JSONB. A hand-crafted POST could inject values outside the UI's <select> vocabulary.",
      "action": "FIXED 2026-09-17: introduced _TRIAGE_ROUTES tuple as the single source of truth; both POST handlers now allowlist-check `route` before insert."
    },
    {
      "area": "Pipeline \u00b7 inbox_hook",
      "note": "_handler resolved the inbound email `subject` on the InboxEvent but never forwarded it to _run_pipeline. The Triage LLM classifier treats subject as its highest-precedence signal, so every production case was classifying against extras+intake only.",
      "action": "FIXED 2026-09-17: _run_pipeline gained a `subject` kwarg and _handler now threads msg.subject through to run_with_intake."
    },
    {
      "area": "Pipeline \u00b7 triage",
      "note": "_llm_classify used `assert config.base_url and config.api_key and config.model` as a runtime precondition. `python -O` compiles asserts out, breaking the documented fallback contract (return None on missing creds).",
      "action": "FIXED 2026-09-17: replaced with an explicit `if not (\u2026): return None`."
    },
    {
      "area": "Pipeline \u00b7 inbox_hook (pre-existing, unchanged)",
      "note": "Duplicate helpers _lookup_program_row and _load_program_row (byte-identical) still cause two DB round-trips per case in the happy path. Consolidation deferred so this review round stays focused on the Triage / config-chain surface.",
      "action": "Queued: consolidate to a single function and thread the resolved Program row through _draft_reply_artifact instead of re-fetching."
    }
  ],
  "conventions": [
    {
      "rule": "One-way import direction: pap_pipeline \u2192 docgen. Never reverse.",
      "enforced_by": "test_docgen_never_imports_pap_pipeline"
    },
    {
      "rule": "No model at render time.",
      "enforced_by": "test_core_never_imports_ai_layer"
    },
    {
      "rule": "Tenant-scoped queries carry a tenant_id filter within 40 lines.",
      "enforced_by": "tenant_scope_audit.py"
    },
    {
      "rule": "LGPL/dual-license deps enter EXPLICIT_EXCEPTIONS with justification, never the general allowlist.",
      "enforced_by": "scripts/license_check.py"
    },
    {
      "rule": "Migrations seed only kinds allowed by their own CHECK constraint; kinds added later are seeded by their own migration.",
      "enforced_by": "0025 comment + 0031 seed"
    },
    {
      "rule": "Public-facing framing (README, demo, video) scrubs specific competitors and prior-employer libraries.",
      "enforced_by": "Manual review pre-submission"
    }
  ],
  "recent_commits": [
    {
      "sha": "a574942",
      "subject": "Dashboard: E2E as its own suite + per-run history expansion"
    },
    {
      "sha": "9fc8941",
      "subject": "Tests: 17 new (E2E + admin UI) + dashboard refresh"
    },
    {
      "sha": "61135c3",
      "subject": "Demo assets + landing video swap + engineering dashboards refresh"
    },
    {
      "sha": "3b45c07",
      "subject": "Triage stage + 3-level model config + inherit toggles"
    },
    {
      "sha": "d0dbb79",
      "subject": "Per-tenant model selection + retrieve-stage truth-alignment"
    },
    {
      "sha": "34d7bd4",
      "subject": "PDF labeler: vision mode with chunked + cropped VLM calls"
    },
    {
      "sha": "c0c89ae",
      "subject": "README: compliance table + pipeline diagram"
    },
    {
      "sha": "0b3819c",
      "subject": "MCP: list_pending_reviews + signed-URL case reply downloads"
    }
  ]
}
