Catalog of what LQ.AI ships today, what is deferred, and how to verify each. Maintained per release. Current as of the fiduciary-grade agentic legal work milestone close (ADRs 0018–0021); migration head
0064. (Prior baselines: the legal-research + connectors (MCP) milestone close, #158–#193, head0055; before that, the M4 close plus the post-v0.4.0 "Donna" run, #115–#139, head0047.)
This document catalogs what LQ.AI ships today, what is deferred, and how an operator can verify each. We publish it in source because the verification path for an open-source project terminates in code, not in a vendor's marketing claims. If you find a discrepancy between this doc and the codebase, the codebase is canonical; please open an issue.
Each table has three columns:
- Capability — what the operator gets.
- Status —
M1/M2/M3/M4(shipped today, naming the milestone it landed in; a sub-tag likeM4orM3may name a specific phase for context),partial(shipped with explicit caveats, named inline),scaffold(plumbing only, feature work deferred), ordeferred-Mx/deferred (community-friendly)for roadmap or PRD §9 items not yet wired in source. - Verification — the file path, test command, or doc the operator can read to confirm the claim.
Status markers reference the roadmap milestones (M1 → M4) documented in README.md and PRD §8.
M1 — Foundation (shipped). Self-hostable conversational legal AI on the starter skills, with the engineering surfaces (audit, tier enforcement, projects/matters, knowledge bases + ingestion, saved prompts, receipts, the skill-creator pipeline) that later milestones build on. Provider adapters: Anthropic, OpenAI, Ollama (local Tier 1).
M2 — Citation Engine + Anonymization Layer (shipped). The four-stage citation verification cascade (exact → tolerant → paraphrase judge → ensemble) and the gateway anonymization middleware (Presidio + custom legal recognizers, streaming-aware rehydration, privileged + retrieval skips) both ship operational. The Azure OpenAI provider adapter rounds out the provider set.
M3 — Playbooks · Tabular Review · Word add-in · Slack/Teams intake bridge (shipped, with two honest caveats).
- Playbooks and Tabular / multi-document review ship operational end-to-end (real execution against documents, with cost tracking and export).
- The Word add-in ships as a scaffold — installable, authenticatable, version-safe — but its substantive in-Word feature surfaces (chat, skills with tracked changes, playbook execution) are deferred (DE-287); today its tabs deep-link to the web app.
- The Slack/Teams light intake bridge ships partial — the bridge services, OAuth install flows, encrypted persistence, and admin management are wired and unit-tested, but the flows have not been exercised end-to-end against live Slack/Microsoft endpoints (DE-312), and the
/lqslash-command surface is inert (DE-288).
M4 — Autonomous Layer (shipped). The background autonomous executor runs real in-loop work: a five-phase LangGraph state machine (intake → analysis → drafting → ethics_review → delivery) where every external action routes through a single guarded_tool_call chokepoint enforcing three brakes — R4 (per-session/per-trigger cost cap), R5 (external halt + idle watchdog), R6 (phase-gated tool grants). It ships the four primitives (watches, schedules, per-user memory, precedent board), honest per-session receipts, per-user opt-in, and a full web dashboard. The Contract Repository auto-relationship graph (a separate M4-roadmap capability) is not built.
Legal research + connectors (MCP) — gateway-brokered (shipped after M4; #158–#193). The assistant can now reach external tools — case-law lookup (CourtListener) and operator-approved MCP connectors — but only through the Inference Gateway, which is extended from sole inference egress to sole tool egress (ADR 0014/0015). A governed chat tool-loop routes every tool call through a single governed_tool_invocation chokepoint; destructive/connector tools pause for an in-chat persist-and-resume confirmation gate; case-law results carry external-source retrieval provenance ("Sources consulted", kept architecturally distinct from the Citation Engine's character-verified quotes); per-user connector tokens are Fernet-encrypted at rest and never logged. Detailed in §5.5.
Fiduciary-grade agentic legal work — gateway-brokered (shipped after the legal-research + MCP milestone; ADRs 0018–0021). Building on the governed tool-loop above, the assistant now keeps a per-turn Citation Ledger of every source and passage it actually read, computes a pass/fail fiduciary gate over every tool-retrieved citation, can run governed plain-language matter sessions through the Autonomous Layer's plan → act → observe → replan loop (the analysis phase's implementation, replacing the single scripted call M4 shipped with), reaches three more free authority sources (GovInfo, SEC EDGAR, EUR-Lex) via a content-source registry, and derives a "derived, not editorial" case-law treatment signal. Migration head 0064. Detailed in §5.6.
The honest reading: an operator can deploy LQ.AI today for everyday in-house work on the starter skills, with character-verified citation grounding, operator-configurable pseudonymization, codified-position playbooks, multi-document tabular review, an opt-in autonomous background layer with hard economic/temporal/contextual brakes, gateway-brokered legal-research + connector (MCP) access under a governed, human-gated tool-loop, and a fiduciary-grade layer (citation ledger, pass/fail gate, governed matter sessions, free authority sources, treatment signal) on top of it. The deferred edges are: in-Word feature surfaces (scaffold today), live-verified chat-platform intake (plumbing today), the contract relationship graph, a dedicated matter-intake UI (the governed-matter-session backend is shipped; it reuses the autonomous session UI today), chat/autonomous fiduciary-gate verdict-tier parity (DE-370, DE-371), and EUR-Lex full-text search / treaty coverage (DE-374, DE-375).
The surface in-house counsel touches every day. Every row is wired end-to-end in M1.
| Capability | Status | Verification |
|---|---|---|
| Multi-turn chat with persistent history | M1 | api/app/api/chats.py; web/cypress/e2e/chat.cy.ts |
| Matter (project) workspace with attached files / skills / KBs | M1 | api/app/api/projects.py; web/src/routes/lq-ai/matters/[id]/+page.svelte |
| Slash-invoked skills with provenance pill | M1 | web/cypress/e2e/wave-d2-skill-creator.cy.ts Test 4 |
| Built-in starter skills | M1 | skills/*/SKILL.md (read every prompt — no hidden instructions) |
Community skill catalog via LegalQuants/lq-skills submodule |
M1 (opt-in) | skills/community/ is a git submodule, empty until initialized — run git submodule update --init --remote skills/community to populate it. The loader walks built-in + community paths with built-in winning on slug collision (api/app/skills/loader.py); on a fresh clone with no submodule checkout there are no community skills. |
| Skill capture / wizard authoring / fork / versions tab | M1 | web/cypress/e2e/wave-d2-skill-creator.cy.ts Tests 1–6 |
| Saved Prompts library with one-click "Use in chat" | M1 | api/app/api/saved_prompts.py; web/cypress/e2e/wave-m1-final-surfaces.cy.ts Test 1 |
Knowledge bases — create, attach documents, ingest to ready (hybrid BM25 + vector retrieval) |
M1 | api/app/api/knowledge_bases.py; api/app/workers/document_pipeline.py |
Ingest formats — PDF (PyMuPDF/Docling) and plain text / Markdown (parse_text, verbatim canonical text → exact-match citations; non-UTF-8 → decode_error). DOCX is roadmap. |
M1 (PDF); post-v0.5.0 (text/md, DE-332) | api/app/pipeline/parsers.py; api/app/pipeline/ingest.py |
| Receipts drawer with per-event provenance | M1 | api/app/api/chat_receipts.py; web/cypress/e2e/wave-m1-final-surfaces.cy.ts Test 3 |
| Enhance Prompt (⌘E) | M1 | api/app/api/enhance_prompt.py |
| Audit log of all sensitive actions | M1 | api/app/audit.py; admin reads at /lq-ai/admin/audit-log |
| FTS over chat history | M1 | api/app/api/chats.py (search route); migration 0016_chat_messages_fts.py |
| GDPR-aligned export and account deletion | M1 | api/app/workers/user_export.py; api/app/workers/user_deletion.py; api/app/api/users.py |
Per-message ephemeral file attach (MessageCreate.file_ids → applied_file_ids echo; separate channel from skill_inputs, injected as document-context per Decision M2-1) |
post-v0.4.0 (#116/#117) | api/app/schemas/chats.py (file_ids, applied_file_ids); api/app/schemas/gateway.py (lq_ai_file_ids) |
Self-service profile edit (PATCH /api/v1/users/me, display_name only; email is DE-329) |
post-v0.4.0 (#118) | api/app/api/users.py |
Pending-deletion visibility (deletion_scheduled_at on GET /users/me; /users/me/delete/cancel clears it) |
post-v0.4.0 (#120) | api/app/api/users.py |
The Inference Gateway is the security boundary — the only component holding privileged provider API keys and the only component making outbound calls. As of the legal-research + MCP milestone this extends from inference egress to tool egress: case-law (CourtListener) and MCP-connector calls also leave only through the gateway, SSRF-guarded and audited (see §5.5). The backend (api/app) holds exactly one outbound HTTP client, pointed at the gateway. Four provider adapters ship: Anthropic, OpenAI, Azure OpenAI, Ollama (local Tier 1). Google Vertex AI and AWS Bedrock are spec'd in PRD §9 as contributor-friendly work and remain deferred.
| Capability | Status | Verification |
|---|---|---|
| Inference gateway with provider routing | M1 | gateway/app/router.py |
| Anthropic / OpenAI / Ollama provider adapters | M1 | gateway/app/providers/{anthropic,openai,ollama}.py; Ollama via docker compose --profile local |
| Azure OpenAI provider adapter | M2 | gateway/app/providers/azure_openai.py (DE-267, closed in M2) |
| Google Vertex AI / AWS Bedrock provider adapters | deferred (community-friendly) | Wire-format specs in PRD §9 (DE-034 / DE-035) |
| Tier enforcement (Tiers 1–5) + privileged-matter tier floor | M1 | gateway/app/tier_floor.py |
| Anonymization pre/post middleware | M2 | gateway/app/anonymization/middleware.py (wired on the request path at gateway/app/api/inference.py); see §3.2. Recognizer accuracy on a legal corpus is empirically unmeasured — DE-282. |
| Routing log (per-inference) | M1 | gateway/app/routing_log.py |
| Provider-key encryption at rest (Fernet master-key) | M1 | gateway/app/secrets.py; docs/security/encrypted-keys.md |
| Hot-reload of gateway config via SIGHUP | M1 | gateway/app/config_holder.py; docs/adr/0010-gateway-config-hot-reload.md |
Runtime provider keys / BYOK (gateway /admin/v1/provider-keys + backend is_admin proxy /api/v1/admin/provider-keys; GET masked last4/configured/source env|runtime; POST Fernet-encrypts into gateway.yaml and hot-applies the adapter in-place with no restart; PATCH rotates, DELETE revokes; requires LQ_AI_GATEWAY_MASTER_KEY else 400 failed_precondition; env keys still work and show source env) |
post-v0.4.0 (#128) | gateway/app/provider_keys.py, gateway/app/api/admin.py; api/app/api/admin.py (proxy); api/app/clients/gateway.py |
Character-level verification of every model-emitted citation against source documents; failed citations surface as "unverified" rather than confident wrong text.
- Cascade (
api/app/citation/verification.py): Stage 1verify_exact_match→ Stage 2verify_tolerant_match(rapidfuzz ≥95 + normalization) → Stage 3verify_paraphrase(LLM judge via gateway) → Stage 4verify_ensemble(N-model parallel, strict/majority, cost-budget fallback to Stage 3). - Endpoint:
GET /api/v1/chats/{chat_id}/messages/{message_id}/citations; rows persist inmessage_citations(migrations0025–0027). Candidates that miss every stage are not persisted — the UI reads the absence as "unverified" (red). - Verify:
cd api && pytest tests/citation/ tests/test_chat_citations.py; full reference indocs/citation-engine.md. - Known limitation: a quote spanning two retrieved chunks silently drops at extraction (DE-277; pinned by
api/tests/citation/test_edge_cases.py).
Pseudonymizes named entities before requests leave for the provider; rehydrates originals on the response path (streaming-aware). The middleware is wired and running — pre_anonymize_request is called on the request path in gateway/app/api/inference.py after tier derivation, before provider dispatch.
- Recognizers (
gateway/app/anonymization/engine.py): Presidio defaults (PERSON,ORGANIZATION,EMAIL_ADDRESS,PHONE_NUMBER,LOCATIONvia spaCyen_core_web_lg) + customCaseNumberRecognizer+MatterNumberRecognizer. - Skip conditions (any one short-circuits): master switch off; Tier 1 (local); privileged chat; per-request opt-out (citation judge calls use this); per-message retrieval-context skip (so the model sees intact source quotes for citation grounding).
- Mapper is per-request, in-memory only — never persisted, never logged.
- Verify:
cd gateway && pytest tests/anonymization/ tests/test_inference_anonymization.py; full reference indocs/security/anonymization.md. Open surfaces: pseudonymize-source-docs (DE-269), per-request salt (DE-274).
Codified legal positions with detection + redline strategy, plus an "easy" auto-generation pipeline from a document corpus.
| Capability | Status | Verification |
|---|---|---|
| Playbook CRUD + execute against a document | M3 | api/app/api/playbooks.py (GET/POST/PATCH/DELETE /api/v1/playbooks, POST /playbooks/{id}/execute, GET /playbook-executions/{id}); executor api/app/playbooks/ |
| Built-in seeded playbooks (NDA mutual/unilateral, MSA, DPA) | M3 | migrations 0032/0033; cd api && pytest tests/test_builtin_nda_playbooks.py |
| Easy playbook auto-generation (3-stage) | M3 | POST /api/v1/playbooks/easy + GET /playbooks/easy/{id}; migration 0035 |
Tables: playbooks, playbook_positions, playbook_executions, easy_playbook_generations |
M3 | migrations 0031, 0035 |
| Learn viz | M3 | web/static/learn/playgrounds/playbook-cascade.html |
Caveats (honest): execution runs in-process via FastAPI BackgroundTasks (not arq) per the M3 architecture decision; soft-delete only; built-ins are immutable (admins fork to edit); tracked-changes rendering into Word is deferred (DE-287).
Run a skill (or ad-hoc column spec) across a document corpus into a document × column grid with per-cell confidence + citations; export XLSX/CSV.
| Capability | Status | Verification |
|---|---|---|
| Cost preview · execute · list · get · cancel · soft-delete | M3 | api/app/api/tabular.py; arq job api/app/workers/tabular_worker.py; table tabular_executions (migration 0036) |
| Export to XLSX (cell comments) / CSV (citation links) | M3 | GET /api/v1/tabular/executions/{id}/export |
| Column-spec snapshot at execution start (auditable invariant) | M3 | api/app/models/tabular.py (columns JSONB snapshot) |
Navigable per-cell citations (read-side source_file_id/source_page/source_text enrichment on GET /tabular/executions/{id}, two batched IN-queries; existing executions included; NOT Citation-Engine-minted — DE-309; untyped in gen:api — DE-330) |
post-v0.4.0 (#125) | api/app/api/tabular.py (enrichment); api/app/schemas/tabular.py (source_file_id/source_page/source_text) |
Per-column ensemble verification honored at execution (ensemble cells run one Stage-4 ensemble pass over cited chunks; verification_method ensemble_strict/ensemble_majority/None persisted on cells + mirrored onto citations; preview adds ensemble_cells_count + ensemble_premium_usd, included in estimated_cost_usd; precedence column > skill snapshot > deployment default; no mid-run ceiling — DE-331) |
post-v0.4.0 (#127) | api/app/tabular/nodes.py (_verify_cell_ensemble), api/app/tabular/cost.py; api/app/schemas/tabular.py |
| Learn viz | M3 | web/static/learn/playgrounds/tabular-review.html |
Caveat (honest): tabular has unit/component backend coverage (api/tests/tabular/ — nodes, cost, export, schemas, worker, executor-spans), but no per-endpoint integration test driving the handlers end-to-end against a live DB yet — a known gap. Bulk-op sibling infrastructure (parent_execution_id) is present but not yet exercised.
| Capability | Status | Verification |
|---|---|---|
| Installable Office.js add-in (manifest, task pane, React shell) | scaffold (M3) | word-addin/manifest.xml, word-addin/src/taskpane/ |
| Admin manifest download + version handshake | M3 | api/app/api/word_addin.py (GET /api/v1/admin/word-addin/manifest, GET /api/v1/word-addin/version) |
OAuth sign-in (reuses /auth/login + refresh) |
M3 | word-addin/src/taskpane/auth.ts; web/src/routes/lq-ai/word-addin/oauth-start/ |
| Learn viz | M3 | web/static/learn/playgrounds/word-addin-flow.html |
| In-Word chat / skills (tracked changes + comments) / playbook execution | deferred | The three tabs render deep-link cards to the web app, not in-Word feature surfaces — DE-287 |
Honest assessment: the add-in is installable, authenticatable, and version-safe, but every substantive feature surface is a placeholder pointing to the web app. Do not market it as feature-shipped.
| Capability | Status | Verification |
|---|---|---|
| Slack bridge service + OAuth install + encrypted persistence | partial (M3) | slack-bridge/ (compose --profile slack, port 8002); table slack_workspaces (migration 0037, Fernet-encrypted bot token under a distinct master key); api/app/api/integrations_slack.py |
| Teams bridge service + multi-tenant admin-consent OAuth | partial (M3) | teams-bridge/ (compose --profile teams, port 8003); table teams_tenants (migration 0038); api/app/api/integrations_teams.py |
| Admin intake-bridges management (list + soft-delete) | M3 | api/app/api/admin_intake_bridges.py; web/src/routes/lq-ai/admin/intake-bridges/+page.svelte |
/lq slash-command intake surface |
deferred | Webhook handler is signature-verified but inert — DE-288 |
| End-to-end OAuth against live Slack/Microsoft | unverified | Never exercised against a live tunnel — DE-312; see docs/intake-bridges.md "Honest state up front" |
An opt-in background executor that does real in-loop agentic work under hard brakes. Not a skeleton — each phase calls real tools through the chokepoint. Full reference: docs/autonomous-layer.md. The analysis phase's implementation described below (a single scripted call) was later replaced by a governed plan → act → observe → replan loop under the same brakes — see §5.6.
| Capability | Status | Verification |
|---|---|---|
| Five-phase executor (intake → analysis → drafting → ethics_review → delivery) | M4 | api/app/autonomous/executor.py, nodes.py; arq job autonomous_session_job |
Real in-loop work: run_skill/run_playbook inference, emit_finding, propose_memory, propose_precedent, notify |
M4 | api/app/autonomous/guard.py (_dispatch); cd api && pytest tests/autonomous/test_executor_real_work.py |
Single chokepoint guarded_tool_call enforcing R5 → R6 → R4 |
M4 | api/app/autonomous/guard.py; tests/autonomous/test_executor_skeleton.py::test_no_tool_call_bypasses_chokepoint |
R4 per-session and per-trigger cost cap (max_cost_usd) |
M4 | api/app/autonomous/cost.py; migration 0045; tests/autonomous/test_r4_per_trigger_cap.py |
R5 external halt (POST /autonomous/sessions/{id}/halt) + idle watchdog |
M4 | api/app/workers/autonomous_worker.py (idle cron); tests/autonomous/test_idle_watchdog.py |
R6 phase-gated tool grants (PHASE_GRANTS) |
M4 | api/app/autonomous/enums.py; tests/autonomous/test_brakes.py |
| Watches (KB-attach-triggered sessions) | M4 | api/app/autonomous/watch_trigger.py; GET/POST/PATCH/DELETE /autonomous/watches; table autonomous_watches (migration 0039, max_cost_usd in 0045) |
| Schedules (in-repo cron dispatcher) | M4 | api/app/autonomous/cron.py; /autonomous/schedules; table autonomous_schedules |
| Per-user memory (proposed → kept/dismissed) | M4 | /autonomous/memory/*; table autonomous_memory |
| Precedent board (race-safe upsert, observed_count) + promote-to-Project proposals | M4 | /autonomous/precedents/*, /autonomous/project-context-proposals/*; tables precedent_entries (migration 0039), project_context_proposals (migration 0041) |
Honest per-session receipt (terminal_reason: completed / cost_cap_reached / external_halt / idle_timeout) |
M4 | api/app/autonomous/receipt.py (build_receipt / build_receipt_safe); stored in autonomous_sessions.result |
| In-app notifications (durable; best-effort email transport) | M4 | /autonomous/notifications/*; table autonomous_notifications (migration 0040) |
| Per-user opt-in (off by default) | M4 | User.autonomous_enabled (migration 0044); spawn paths + mutate endpoints gated |
Findings persistence (autonomous_findings written at the emit_finding chokepoint; GET /autonomous/sessions/{id}/findings; ?source_session_id= filter on GET /autonomous/memory, precedents excluded — recurrence-aggregated) |
post-v0.4.0 (#135) | api/app/api/autonomous.py; table autonomous_findings (migration 0046) |
Document-grade artifacts (opt-in emit_artifacts, default OFF; drafting emit_artifact chokepoint direct-writes a real KB document — MinIO upload-first, File ready + Document + chunks, direct KB attach bypassing watch-fire so no loop; markdown/plain only; GET /autonomous/sessions/{id}/artifacts, owner-gated; notification payload artifact_count) |
post-v0.4.0 (#138) | api/app/autonomous/guard.py (_handle_emit_artifact), api/app/api/autonomous.py; table autonomous_artifacts (migration 0047, session CASCADE / file SET NULL — the document outlives the session) |
Matter binding on schedules/watches (project_id accepted on create + PATCH incl. clear-to-null; ownership validated at all five assignment sites — create_schedule/create_watch/run-now/two PATCHes — closing a pre-existing IDOR) |
post-v0.4.0 (#133) | api/app/api/autonomous.py |
Retrieval ownership scoping (retrieve_chunks verifies the session owner owns the model-supplied kb_id/file_id before retrieval; mirrors the HTTP visibility predicates (owner scope + archived_at/deleted_at IS NULL); foreign or unknown ids fail closed with a 404-shaped error, closing a cross-user read reachable via prompt-injected planner args) |
post-v0.4.0 (#288 AG-01) | api/app/autonomous/guard.py |
Worker-side skill registry (shared app/skills/bootstrap.py::install_skill_registry from both the api lifespan and the arq-worker on_startup; uniform fail-fast on a missing/unreadable skills dir; arq-worker mounts ./skills:/skills:ro + LQ_AI_SKILLS_DIR; SIGHUP reload stays api-only) |
post-v0.4.0 (#139) | api/app/skills/bootstrap.py; api/app/workers/arq_setup.py; docker-compose.yml (arq-worker volume) |
| Web dashboard (sessions/receipt/halt, memory, precedents, watches, schedules, notifications, proposals) | M4 | web/src/routes/lq-ai/autonomous/; opt-in toggle at settings/autonomous/ |
| Learn viz | M4 | web/static/learn/playgrounds/autonomous-flow.html (phase walk + the four brake scenarios; the four primitives are not yet visualized — see §11) |
Honesty notes: the ethics-review phase is a light v1 (emits a privilege/scope-concerns finding from the structured output; a dedicated ethics LLM gate is a future enhancement). A gateway error mid-analysis produces an honest "analysis failed at the gateway" finding and a completed (not fabricated) receipt. Audit rows carry counts/types/IDs/enums only — never raw entity values or document text.
After M4, LQ.AI gained the ability to reach external tools — case-law lookup and operator-approved MCP connectors — without weakening the gateway boundary. The gateway is extended from sole inference egress to sole tool egress; a governed chat tool-loop routes every call through one chokepoint; destructive/connector calls pause for explicit human approval; and case-law results carry retrieval provenance kept architecturally distinct from character-verified citations. Shipped across #158–#193 (migrations 0048–0055); design in ADR 0014 / ADR 0015 and docs/proposals/legal-research-and-mcp.md (delivered).
| Capability | Status | Verification |
|---|---|---|
Gateway tool-provider egress boundary — sole tool egress, SSRF-guarded, audited (tool_egress_log, counts/types only) |
shipped | gateway/app/providers/tool/ (validate_egress_target); migration 0048_tool_egress_log.py; ADR 0014; api/app holds exactly one outbound client → the gateway (api/app/clients/gateway.py) |
| CourtListener case-law provider (search / clusters / citation-lookup against REST v4) | shipped | gateway/app/providers/tool/courtlistener.py; cd gateway && pytest tests/test_courtlistener_adapter.py |
Research API subsystem (/api/v1/research/*: capabilities, search + cursor pagination, verify-citations, clusters/opinions/find-in-case) |
shipped | api/app/api/research.py, api/app/research/service.py; migration 0049_research_metadata.py; cd api && pytest tests/test_research_endpoints.py tests/test_research_service.py |
| MCP client subsystem — gateway adapter (streamable_http, per-call OAuth token injection, tool discovery) | shipped | gateway/app/providers/tool/mcp.py; cd gateway && pytest tests/test_mcp_adapter.py |
MCP registry + discovery-cache + /api/v1/admin/mcp surface |
shipped | api/app/api/admin_mcp.py, api/app/mcp/service.py; migration 0050_mcp_tools.py; cd api && pytest tests/test_admin_mcp.py tests/test_mcp_service.py |
Per-user MCP OAuth (api flow, tokens Fernet-encrypted at rest, gateway passthrough, return_url + connections list) |
shipped | api/app/api/mcp_oauth.py, api/app/models/mcp_oauth.py, api/app/security/encryption.py; migrations 0051/0052; cd api && pytest tests/test_mcp_oauth_service.py tests/test_mcp_encryption.py |
Tool-governance substrate — single governed_tool_invocation chokepoint (shared with the autonomous layer) + tool_call_log |
shipped | api/app/tools/governance.py; migration 0053_tool_call_log.py; ADR 0015; cd api && pytest tests/test_tool_governance.py |
| Governed chat tool-loop + persist-and-resume confirmation gate (in-chat approve/deny resumes the turn; connect-on-demand) | shipped | api/app/chat/tool_loop.py, api/app/api/chats.py (resume route); migration 0054_chat_pending_tool_call.py; UI web/src/lib/lq-ai/components/ToolGatePrompt.svelte; cd api && pytest tests/integration/test_chat_tool_call_resume.py |
| External-source citations — case-law retrieval provenance ("Sources consulted"), distinct from Citation-Engine verified quotes | shipped | api/app/models/message_tool_source.py, api/app/chat/tool_loop.py (extract_tool_sources); migration 0055_message_tool_sources.py; UI web/src/lib/lq-ai/components/ToolSourcesPanel.svelte + ProvenancePill.svelte; cd api && pytest tests/test_message_tool_sources.py |
case-law-research skill + C5 tool-usage (declared via lq_ai.tool_usage, surfaced as "Uses: …" in the skill UI; surface-not-enforce) |
shipped | skills/case-law-research/SKILL.md; api/app/skills/schema.py; web/src/lib/lq-ai/skills/toolUsageNote.ts; cd api && pytest tests/test_skills_tool_usage.py |
| OpenWebUI MCP stub retired (DE-341) | shipped | web/backend/open_webui/utils/mcp/ removed; grep -r "utils.mcp.client" web/backend is empty; PRD §DE-341 (Resolved in PR6e) |
| Learn viz — governed-tool-boundary explorer | shipped | web/static/learn/playgrounds/governed-tool-flow.html (Learn → How it works) |
Caveats (honest):
- Tool connectors are operator-opt-in, off by default. The gateway makes no outbound tool calls until an operator enables a
tool_providersentry (external egress is operator-controlled — ADR 0014). Case-law requires settingCOURTLISTENER_API_TOKENin.env(forwarded into the gateway container bydocker-compose.yml) and uncommenting thecourtlistener-prodblock ingateway.yaml(template ingateway.yaml.example), then restarting the gateway;/api/v1/research/capabilitiesreportsenabled: trueonce on. MCP connectors register via/api/v1/admin/mcp+ per-user OAuth. See the README "Enabling legal-research connectors" section. Verified live on a fresh clone (build → migrate0055→ enable → real CourtListener retrieval →message_tool_sources→tool_egress_log). - The legal-research surface is intentionally scoped: case-law retrieval and external-source provenance ship; result-content accuracy judging (DE-280) remains deferred. PRD §3.6 marks the research capability PARTIAL accordingly.
- "Sources consulted" is retrieval provenance, not character-verified grounding — it records which external sources a tool call pulled in, and lives in a separate table (
message_tool_sources) from the Citation Engine's verified quotes (message_citations). The two are deliberately never conflated. - The Anthropic tool-loop has a known gap when a single turn fans out to multiple read-only tools (consecutive same-role messages the provider rejects) — deferred; single-tool and resume paths are unaffected.
- Chat-side governed tool calls are audit-logged (
tool_call_log— counts/types/args_digestonly, never raw arguments) but do not yet emit a dedicated OTelchat.tool_callspan (deferred). - Runtime BYOK admin provider-keys require
LQ_AI_GATEWAY_MASTER_KEY; env-configured keys work without it.
Building on the governed tool-loop in §5.5, a fiduciary-grade milestone shipped next: a per-turn ledger of every source the assistant actually read, a pass/fail gate over every tool-retrieved citation, governed plain-language matter sessions on the Autonomous Layer, three more free authority sources, and a derived case-law treatment signal. Migration head 0064 (ledger/gate 0058/0059; treatment 0061/0062; authority citations + text cache 0064). Design in ADR 0018 (citation ledger + fiduciary gate), ADR 0019 (treatment layer), ADR 0020 (matter sessions), and ADR 0021 (content-source registry) — all Status: Accepted, all merged.
| Capability | Status | Verification |
|---|---|---|
| Citation Ledger — per-turn record of every source/passage actually read (chat + autonomous sessions), one-click trace from a claim to its ledger entry | shipped | api/app/citation/ledger.py (assemble_ledger_entries/resolve_ledger_entries); GET /api/v1/chats/{chat_id}/ledger (api/app/api/chats.py), GET /api/v1/autonomous/sessions/{session_id}/ledger (api/app/api/autonomous.py); migration 0058_citation_ledger_entry.py |
| Fiduciary-grade gate — derive-don't-assert PASS/FAIL verdict per assistant message, computed from the ledger's own verification statuses | shipped | api/app/citation/gate.py (compute_and_record_gate); migration 0059_work_product_fiduciary_gate.py; ADR 0018 D3 |
| Governed agentic matter sessions — plain-language "describe your matter" query drives a governed plan → act → observe → replan loop confined to the analysis phase, under the same R4/R5/R6 brakes | shipped | api/app/autonomous/planner.py; PRs #239/#240; ADR 0020 |
| Content-source registry + free authority sources — GovInfo (US Code/CFR statutes and regulations), SEC EDGAR (public company filings), EUR-Lex (EU legislation + CJEU case law by CELEX), alongside CourtListener | shipped | api/app/research/registry.py (SOURCE_REGISTRY); ADR 0021 |
| Validity / treatment layer — judge-backed, derived case-law treatment signal (e.g. followed, distinguished, criticized) from citing opinions, with one-click trace back to each citing case read | shipped | api/app/citation/treatment.py (derive_treatment_for_message); migrations 0061_citation_treatment.py/0062_citation_treatment_signal.py; ADR 0019 |
Caveats (honest):
- No dedicated matter-intake UI yet. The governed matter-session backend (PRs #239/#240) is shipped, but the plain-language "describe your matter" entry point is a backend seam on session state — it reuses the existing autonomous session UI (
web/src/routes/lq-ai/autonomous/) rather than a purpose-built intake flow. - Chat/autonomous fiduciary-gate verdict-tier parity is incomplete. An attributed-authority FAIL tier for chat (DE-370) and an autonomous-path authority SUPPORTED tier (DE-371) are both still open.
- EUR-Lex is get-by-CELEX only.
SOURCE_REGISTRY["eurlex"].ops == ("get_authority",)— nosearch_authority. Full-text search via Cellar SPARQL (DE-374) and treaty/corrigendum CELEX support (DE-375) are both open. - The Citation Ledger stores no raw payloads. Per the ADR 0016 P3 no-raw-payload guarantee, ledger entries reference content by id and character offset only — never raw passages or tool payloads — in the audit layer.
- The treatment layer is derived, not editorial. It never emits a definitive "good law / bad law" verdict; every signal is labeled "derived, not editorial," and the per-case judge pass runs under a bounded cost budget (ADR 0019 D4) rather than reading every citing opinion exhaustively.
- Free authority sources are operator-opt-in, off by default. Each requires a configured
tool_providersentry ingateway.yaml(ADR 0021 D1/D5); an unconfigured registry entry is reported unavailable, never fabricated.
Honest milestone deferrals — the subsystem does not yet exist (or only as plumbing). Verifiable by absence.
| Capability | Status | Verification |
|---|---|---|
| In-Word feature surfaces (chat/skills/playbooks in the add-in) | deferred (M4 / community) | word-addin/ tabs are deep-link cards; DE-287 |
/lq Slack/Teams slash-command intake |
deferred | Bridge webhook handlers inert; DE-288 |
| Contract Repository auto-relationship detection (PRD §3.16) | deferred-M4+ | No contract_relationships table in api/alembic/versions/ |
The Compliance Alignment Pack at docs/compliance/ is a documented commitment; the per-framework alignment docs land incrementally. The pack is the project's contribution to the operator's certification work (pre-mapped control responses citing source), not a certification of the project itself.
| Document | Status | Verification |
|---|---|---|
| Threat model (STRIDE) / Architecture / Cryptography / Audit-logging / Encrypted-keys / Dependencies | M1 | docs/security/*.md, docs/architecture.md |
| Security policy + coordinated disclosure | M1 | SECURITY.md |
| SOC2 / ISO 27001 / ISO 42001 / GDPR / HIPAA / FedRAMP alignment | stub | docs/compliance/README.md describes the format; per-framework docs land incrementally |
| OWASP LLM Top 10 / NIST AI RMF profiles | not yet (community-friendly) | mini-PRDs at docs/contribute/mini-prds/ |
| Procurement Pack (SIG Lite + CAIQ) | starter | docs/procurement/sig-lite.md; full pack DE-086 |
Engineering rigor is measurable, not asserted. Test file counts below are verifiable without standing up the stack (find … | wc -l); pass counts run in CI (.github/workflows/ci.yml).
| Practice | Status | Verification |
|---|---|---|
| Backend tests (pytest, live Postgres) | M1–milestone | 233 test_*.py files in api/tests/ (incl. tests/autonomous/, tests/citation/, tests/tabular/; pass count refreshed in CI per the .github/workflows/ci.yml API gate); cd api && DATABASE_URL=… pytest |
| Gateway tests (pytest) | M1–milestone | 67 test_*.py files in gateway/tests/; cd gateway && pytest |
| Frontend unit tests (Vitest) | M1–milestone | 80 *.test.ts files in web/src/; cd web && npx vitest run |
| Cypress E2E (LQ.AI shell) | M1–milestone | 17 specs in web/cypress/e2e/ |
| Ruff lint + format (Python) | M1–M4 | .github/workflows/ci.yml: ruff check api scripts + ruff format --check |
| mypy (api standard, gateway strict) | M1–M4 | CI mypy app per subsystem |
| svelte-check (LQ.AI-owned code) | M1–M4 | cd web && npm run check:lq-ai (0 errors on src/{lib,routes}/lq-ai/**); inherited OpenWebUI debt tracked as DE-262 (§8.1) |
| Coverage gate (target 80% api / 90% gateway) | not enforced | CI runs pytest but does not fail below threshold |
| Mutation / property-based testing, eval harness, Cypress-in-CI | not yet | On the engineering-discipline roadmap |
| OpenSSF Scorecard / Best Practices Badge | not yet (community-friendly) | mini-PRDs at docs/contribute/mini-prds/ |
| SLSA-3 provenance / Sigstore-signed images / SBOM per release | committed | docs/security/releases/README.md |
| Annual third-party pen test + adversarial red-team | committed; not scheduled | First engagements targeted within 90 days of M1 release |
The web frontend is a fork of OpenWebUI (ADR 0001). npm run check (full scope) surfaces ~9,359 TypeScript strict-mode signals, all in upstream files inherited at fork time; none in LQ.AI-owned code, none in the (separate Python) gateway. CI scopes the check to LQ.AI code (npm run check:lq-ai). Migration tracked as DE-262.
| Surface | Status | Verification |
|---|---|---|
| Docker Compose reference deployment | M1–M4 | docker-compose.yml — always-on: postgres, redis, minio, gateway, api, ingest-worker, arq-worker, web |
| Local-only profile (Ollama) | M1 | docker compose --profile local up — adds the Ollama sidecar. Scanned-PDF OCR / PaddleOCR is not implemented (DE-320); the prior placeholder sidecar was removed. |
| Slack / Teams bridge profiles | M3 | docker compose --profile slack up / --profile teams up |
| Worker skill-registry bootstrap (operator-visible change, #139) | post-v0.4.0 | The api now fails fast on a missing/unreadable skills dir at startup (it previously logged a warning and booted with an empty registry); the arq-worker mounts ./skills:/skills:ro and installs the same registry. api/app/skills/bootstrap.py; docker-compose.yml |
| Helm chart for Kubernetes | drafted | deploy/helm/lq-ai/ (worker-migration parity with the compose single-migrator fix is a community item — DE-327) |
| OpenTelemetry instrumentation (traces + metrics + domain spans) | M1 baseline + M3 domain spans + M4 autonomous spans | docs/observability.md |
| Reverse-proxy/TLS recipes · backup tooling · runbooks · SLOs · status page · postmortem · DR cadence | not yet | mini-PRDs / deferred |
- Clone the repo and follow the Quickstart to stand the stack up (
docker compose up -d --build— the api runs migrations 0001→0064 on boot). - Browse the file path or run the test command in the Verification column.
- To read source without running the stack, the cited paths are all in the repository.
If a claim does not check out, the codebase is canonical — please open an issue. The point of publishing this in source is that verification runs through readable code, not a vendor's representation of it.
- Learn visualizations to add (shipped capabilities not yet visualized): intake-bridges (Slack/Teams OAuth + workspace lifecycle); the autonomous four primitives (watches/schedules/memory/precedent lifecycle —
autonomous-flow.htmlcovers phases + brakes only); projects/matters + org-profile + privilege tiers; KB hybrid retrieval (BM25 + vector); the §5.6 fiduciary-grade capabilities (Citation Ledger trace, fiduciary gate, governed matter sessions, content-source registry, treatment layer — none has a Learn playground yet). Tracked in the M4-D2 doc/Learn alignment plan. - Resolved this session (DEs): DE-325 (
build_receipt_safehardening), DE-326 (fresh-install worker alembic-migration race). DE-327 (Helm worker-migration parity) is open as a community-suitable item.
Maintained per release. Last rewritten at the M4 close (Autonomous Layer shipped end-to-end; fresh-install acceptance passed), reconciled against the post-v0.4.0 "Donna" run (#115–#139; head 0047), then reconciled against the legal-research + connectors (MCP) milestone (#158–#193; migration head 0055; the release-readiness verification pass for v0.5.0), then reconciled a third time against the fiduciary-grade agentic legal work milestone (ADRs 0018–0021; migration head 0064) as part of the DE-365 docs-honesty audit (docs/audits/2026-07-01-claims-vs-reality.md), which added §5.6 and refreshed the header, the M4 cross-reference, and the §8 test-file counts. Substantive content drivers: PRD §3 (capabilities), PRD §8 (roadmap), PRD §9 (deferrals), and the per-feature docs (docs/citation-engine.md, docs/playbooks.md, docs/tabular-review.md, docs/word-addin.md, docs/intake-bridges.md, docs/autonomous-layer.md, the ADRs docs/adr/0014/0015 + docs/proposals/legal-research-and-mcp.md for the tool-egress/MCP milestone, and ADRs docs/adr/0018–0021 for the fiduciary-grade milestone).