Skip to content

Repository repair (40 issues) + portfolio showcase#1

Merged
kanwa2006 merged 47 commits into
mainfrom
repair/debug-master-plan
Jul 21, 2026
Merged

Repository repair (40 issues) + portfolio showcase#1
kanwa2006 merged 47 commits into
mainfrom
repair/debug-master-plan

Conversation

@kanwa2006

Copy link
Copy Markdown
Owner

Full repair of the DocuMindAI backlog plus a portfolio-quality showcase, in 44 commits.

Repair (DEBUG_MASTER_PLAN)

All audited issues resolved at root cause, one per commit, each with a regression test that fails before and passes after:

  • Critical: get_embedding/generate added to LLMService (16 broken call sites); 4 workspace task modules registered on the worker; OCR orchestrator wired into ingestion (PaddleOCR 3.x); Veritas trust_report on /query/stream; deep-research retrieval rewritten. Discovered + fixed: missing generate, 1536→1024 embedding-column mismatch.
  • High: pgvector default + HNSW index migration; Celery Beat service; queues consumed / phantom routes removed; real research synthesis (extract-then-compute); S3 region fix; production billing gate; lazy LLM provider; alembic SSL fix (was silently breaking CI migrations).
  • Medium/Low: loud AI degradation (no silent zero-vectors/fake scores), HS256-only middleware, prompt-injection guard, lock-free key-rotator cooldown, tenant-scoped cache purge, real SSE progress, workspace-aware insights, Node 20 CI, pagination, LLM timeouts, scrubbed error strings, and the L-1..L-13 polish set.

Tests: 2 → 82, all passing. Migrations verified up/down on a clean pgvector container. Frontend builds on Node 20.

Showcase

  • README rewritten as a complete technical presentation (20 sections, 9 Mermaid diagrams).
  • 21 screenshots captured from the running, seeded app (no mockups).
  • docs/demo-documents/: 13 synthetic sample PDFs used to seed the demo.
  • docs/demo/documind-demo.mp4 (81.5s) + .gif (6.0MB): subtitled Playwright-driven tour of the real app.

Docs updated in lockstep (issue statuses, dependency graph, FINAL_AUDIT annotations).

🤖 Generated with Claude Code

Kanwa Munipalle and others added 30 commits July 18, 2026 16:35
The four workspace */search endpoints and four worker task modules called
llm_service.get_embedding(), which never existed (AttributeError -> 500).
Add a single async adapter on LLMService that delegates to
embedding_service.generate_embeddings (1024-dim, single source of truth)
via run_in_executor, with a lazy import so llm_service import stays free
of the embedding model load. Raises on empty provider result.

Also checks in the audit/knowledge-base document set referenced by
CLAUDE.md, with C-1 marked RESOLVED.

Regression tests: backend/tests/test_get_embedding.py (5 passed total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…orker

The four workspace /process endpoints dispatch process_*_batch tasks that
were routed in task_routes but absent from the worker include list, so the
worker never registered them and async processing for those workspaces
never ran. Adds the four modules to include (all verified to import
cleanly). email_tasks deliberately left unregistered: email is sent
synchronously via email_service; the module is dead code pending its own
issue.

Regression test: backend/tests/test_worker_registration.py.
Depends on C-1 (tasks call llm_service.get_embedding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…u_queue

task_routes routed embedding_tasks/retrieval_tasks to queues although those
modules never existed (dead config), and export_queue/ocr_gpu_queue had no
consumer, so async exports and OCR-queue tasks enqueued forever. Removes
the phantom routes and expands the single deployed worker's -Q to
main-queue,celery,export_queue,ocr_gpu_queue (compose, linux runner,
CONTRIBUTING, installation docs). Routes/queue names preserved so a
dedicated GPU worker can take over ocr_gpu_queue later.

Regression tests: routes must reference importable modules; every routed
queue must appear in the compose worker's -Q list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Newly discovered during C-5 verification: legal risk-report/compare,
finance ratios/compare, research citations/gaps, report naming,
report_tasks, and deep-research steps 2/4 all call
llm_service.generate(...), but only the provider defined generate — every
call raised AttributeError (HTTP 500). The original audit conflated the
service with its provider and wrongly graded these endpoints as working.

Adds a one-method service-level delegation; the provider keeps ownership
of key rotation, model fallback, and safe text extraction.

Regression test: backend/tests/test_llm_service_generate.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…unks

Step 1 imported a nonexistent retrieval_service singleton and called a
nonexistent .query() method, so document RAG always raised and was
silently swallowed (agent ran web-only over empty evidence). Rewritten to
call RetrievalService.retrieve_chunks with the caller's db session and
UUID document ids, render grounding-format <evidence> blocks, and
synthesize the doc answer via llm_service.generate_answer. The failure
path now logs at ERROR with traceback instead of a quiet warning.

Discovered and recorded: C-6 (fixed separately) and N-1
(deep_research_agent has no caller anywhere — feature never exposed).

Regression tests: backend/tests/test_deep_research_agent.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The frontend already parsed a trust_report SSE event that the main query
path never emitted (Veritas was only reachable from the orphaned
deep-research agent). The stream now accumulates the answer on both the
retrieval and map-reduce summary paths and emits a trust_report frame
after tokens/disclaimer, before done. The payload is adapted at the
caller to the frontend TrustReport interface (level HIGH|MEDIUM|LOW,
evidence_items, factors[{name,weight,score}]); veritas_engine.py is
untouched per REPAIR_RULEBOOK 8a. Grounded answers only: scoring
general-knowledge answers with a document-grounding heuristic would
fabricate meaning (UI shows the Ungrounded badge there). Veritas failures
log at ERROR and skip the event; token delivery is never blocked.

Regression tests: backend/tests/test_trust_report_event.py.
Full suite: 17 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scanned/image pages previously fell back to raw page.get_text() (a stub
yielding ~nothing) while the marketed PaddleOCR/Docling orchestrator sat
unreachable behind an unconsumed queue. extract_document_stream now
renders non-native pages to a 2x PNG and routes them through the
orchestrator (Paddle primary via hint, Docling fallback) using a fresh
event loop in the sync worker. Native pages and PPTX are unchanged. OCR
failure degrades loudly (ERROR log + ocr_failed metadata), and
OCR_SCANNED_ENABLED (Settings + .env.example, default true) provides a
config-only rollback for the heavy engines.

Prerequisite fix: ocr_orchestrator targeted the PaddleOCR 2.x API while
requirements install 3.5.0 - init kwargs (use_gpu/use_angle_cls) no
longer exist and the result format changed. Init now uses the 3.x API
(device follows the paddlepaddle build, resolving the CPU-host concern),
engine init failures log at ERROR, and the engine parses both 3.x and
legacy result formats.

Regression tests: backend/tests/test_ocr_ingestion.py (5).
Full suite: 22 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selecting STORAGE_PROVIDER=s3 crashed at provider init because storage.py
read settings.AWS_REGION, a key that never existed in Settings (the real
key is S3_REGION, which documents.py already used). Guard test pins that
the phantom attribute stays gone.

Regression tests: backend/tests/test_storage_s3_init.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beat_schedule defined eight scheduled jobs (health check, key rotation,
daily digest, db cleanup, subscription/GST/model checks, stale-review
flagging) but the compose stack had no scheduler process, so none ever
fired. Adds a single beat service mirroring the worker's env (Gemini keys
included - the health check probes Gemini), with the schedule file in
/tmp to keep the bind mount clean. Dedicated service, not worker -B, so
scaling workers never duplicates schedules.

Guard test: exactly one Beat command in the compose stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…import

The module-level llm_service singleton constructed GeminiLLMProvider at
import and raised without keys (ENVIRONMENT != test), making the whole
query stack unimportable keyless (CI/test friction, brittle boot). The
provider now builds on first .provider access; injected providers are
honored unchanged and the no-silent-mock policy is preserved at first
use. Also fixes a latent bug: keyless GeminiLLMProvider raises
ValueError, which the old 'except RuntimeError' never caught, so the
intended friendly error message never fired.

Regression tests: backend/tests/test_llm_service_lazy_init.py.
Full suite: 29 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the default RAZORPAY_ENABLED=false, POST /billing/upgrade activated
any tier (including enterprise) for free - revenue bypass when deployed
with defaults. The sandbox path now returns 403 payments_disabled (with a
WARNING log) when ENVIRONMENT=production and payments are off. Dev/test
sandbox upgrades and the Razorpay-enabled 402 flow are unchanged.

Regression tests: backend/tests/test_billing_upgrade_gate.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… in prod

Zero-vector embeddings and fabricated 0.85/0.99 rerank scores let a
'grounded' answer look fine while being meaningless, with no signal that
anything was wrong. Now:
- Gemini embed failure raises (ERROR log) instead of appending a zero
  vector; ingestion dead-letters to FAILED, queries surface an SSE error.
- Primary bge-m3 load failure logs at ERROR naming the dimension-mixing
  hazard of the padded fallback.
- DummyLocalReranker and DummyEmbeddingProvider refuse outright in
  production (fail loud at use, not import) and log at ERROR elsewhere.
- _get_default_reranker logs why it fell back instead of a bare pass.

Regression tests: backend/tests/test_silent_degradation.py (5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discovered during H-1 verification: connect_args ssl=require was applied
to every Postgres URL, so alembic upgrade head failed with 'rejected SSL
upgrade' against any non-SSL server - including the CI pgvector service
container and local scratch databases. The hardcode served the Supabase
DATABASE_URL (no sslmode param). Now: an explicit ssl/sslmode URL param
wins (stripped from the URL, since the asyncpg dialect rejects it as a
kwarg); otherwise SSL is required only for non-local hosts, preserving
the Supabase path.

Verified: full 56-migration chain green on scratch ankane/pgvector:v0.5.1
(failed at connect before this fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'faiss' default was an in-memory NumPy cosine scan (FAISS imported
but never used, not even a dependency) - O(N) per query, no index, and
contradicting the pgvector marketing. Changes:
- VECTOR_BACKEND defaults to pgvector (config.py, .env examples).
- New migration d0aab53082d2 adds HNSW index on
  document_chunks.embedding with vector_cosine_ops (matches the
  cosine_distance operator used by retrieval), built CONCURRENTLY.
- retrieval_service: dead faiss import removed; the NumPy branch remains
  as an explicit dev-only fallback with a once-per-process WARNING.

Verified on a scratch ankane/pgvector:v0.5.1 container: full chain
upgrades green, index present (access method hnsw), downgrade drops it,
re-upgrade recreates it. Tests: backend/tests/test_vector_backend_default.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Newly discovered while implementing H-4: all seven workspace embedding
columns (hr_candidates, legal_clauses, finance_transactions, study_notes,
study_flashcards, research_papers, research_findings) were created as
vector(1536) - an OpenAI-ada-era scaffold - while the pipeline produces
1024-dim bge-m3 vectors. Inserts and l2_distance comparisons fail on
dimension mismatch, nullifying C-1/C-2 at the DB layer. document_chunks
hit the identical bug earlier and was resized (a1b2c3d4e5f7); the domain
tables were missed.

Models now declare Vector(1024); migration 2a2aee1828d4 resizes with
USING NULL (safe: both writer paths were broken since inception, so the
columns cannot hold real data). Verified upgrade/downgrade on a scratch
ankane/pgvector:v0.5.1 container - all columns report vector(1024).

Guard test: backend/tests/test_embedding_dimensions.py (8 models pinned
to EMBEDDING_DIM).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
synthesize_project returned literal placeholder clusters and a fabricated
'Paper A vs Paper B' contradiction regardless of data. Now, following the
repo's extract-then-compute pattern: findings are scoped to the project
(join via research_papers, not the whole workspace), clustered by greedy
cosine grouping over stored embeddings with Python-computed consensus
scores; top cross-paper candidate pairs are classified by the LLM
(ContradictionVerdictSchema - relationship + description only), Python
assigns severity, and contradict verdicts persist ContradictionReport
rows (table used for the first time). LLM failure skips the pair with an
ERROR log - never fabricates. Response keys unchanged.

Regression tests: backend/tests/test_research_synthesis.py (3).
Full suite: 51 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The middleware accepted ["HS256", "RS256"] against the symmetric
AUTH_SECRET_KEY - the exact algorithm-confusion pattern BUG-013 removed
from auth.py. Decode now pins settings.JWT_ALGORITHM, consistent with the
hardened verifier.

Regression tests: backend/tests/test_tenant_middleware_jwt.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uploaded document text was injected verbatim into system prompts with no
instruction isolation beyond <evidence> tags, so a crafted document could
attempt 'ignore previous instructions' overrides. Adds
EVIDENCE_INJECTION_GUARD + idempotent _harden_system_prompt at the LLM
service boundary (_build_system_prompt and LLMService.generate, covering
all grounded QA, generate_json, and custom domain prompts) and at the two
direct provider.generate_stream call sites embedding domain content
(study tutor, research copilot).

Regression tests: backend/tests/test_prompt_injection_guard.py (3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When every key was cooling, get_key() slept inside self._lock, blocking
all other callers (get_key, report_rate_limit, report_invalid_key) for
the full cooldown - and blocking the event loop when reached from async
paths. Restructured into a re-check loop: the wait is computed under the
lock, slept outside it, and state re-evaluated afterwards. Also excludes
permanently-bad keys' stale cooldown entries from the wait computation.
All-invalid behavior and rotation order unchanged.

Regression tests: backend/tests/test_key_rotator_lock.py (4, including a
threaded proof that report_rate_limit completes during another thread's
cooldown wait).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t-scoped)

The cache was written as retrieval:{workspace}:{hash} while
delete_document purged retrieval:uid_{uid}:* - the patterns never
matched, so deleted-document content could be served from cache for up
to the 300s TTL, and the key was not tenant-scoped. A single helper now
builds retrieval:uid_{user}:{workspace}:{hash}; the purge side is
unchanged and old-format entries simply expire.

Regression tests: backend/tests/test_retrieval_cache_key.py (3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The five workspace progress endpoints emitted fabricated progress:i*10
frames on a timer, unrelated to any task. They now stream real persisted
state via services/processing_events.py: Document.status transitions for
legal/finance/study/research (fresh short-lived session per 2s poll,
5-minute cap, not_found/failed/timeout frames) and the real JobMatch
count for HR (complete when non-zero and stable). Response shape kept
with additive stage/candidates_processed fields.

Discovery recorded: no frontend consumer exists for these endpoints (the
UI polls document status) - DB-state polling was chosen over new Redis
pub/sub plumbing accordingly.

Regression tests: backend/tests/test_processing_events.py (4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
process_document dispatched insights with getattr(doc, 'workspace_type')
- an attribute Document never had - so every document produced 'general'
insights and the domain prompts (legal risk, finance anomalies, HR
standouts) never ran. The workspace slug is stored on the upload's
ChatSession at creation, so the dispatch now reads it from there (uuid5
workspace ids cannot be reversed; no schema change). Sessionless docs
still default to general.

Regression tests: backend/tests/test_insights_workspace_resolution.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dockerfile.frontend was already node:20-alpine; the CI workflow was the
only stale Node 18 pin, so CI could warn/fail or diverge from real
builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…limit

config.py defaulted OTEL/Prometheus ON while .env.example said off (and
a collector-less stack spams span-export errors) - defaults are now OFF
everywhere, opt-in for production. Trial nudge emails were hardcoded at
uses 3/4, a relic of a 5-query limit (the limit is 10): thresholds now
derive from TRIAL_QUERY_LIMIT (nudge at LIMIT-2, reminder at LIMIT-1).
Stale '5th query' comment in lib/api.ts corrected; its logic already
keyed on queriesRemaining === 0 and is unchanged.

Regression tests: backend/tests/test_config_defaults.py (3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The suite has grown 2 -> 78 tests across the repair phase (a regression
test shipped with every fix, including the worker-registration and
get_embedding tests the rulebook called out). This change adds the
missing auth coverage (bcrypt roundtrip, HS256 pinning, refresh-token
typing and longer expiry, expired-token rejection) and flips pip-audit
from '|| echo' to blocking - triaged first: the audit is clean today, and
future findings get explicit --ignore-vuln triage instead of a swallowed
failure. Also updates the C-6 delegation test for the M-8 guard
composition.

Full suite: 78 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
celerybeat-schedule.*, frontend/build.log, and frontend/tmp/next-build
were committed before their .gitignore entries existed, so git kept
tracking them. Untracked from the index only - local files are preserved
- and the existing ignore rules now take effect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
extract-tables read doc.storage_path from local disk, which 404s on S3
deployments where storage_path is an object key. Now downloads to a temp
file via storage_service (same pattern as document_tasks) and cleans up,
working for both local and S3. Depends on H-5 (S3 provider init).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six handlers derived workspace UUIDs with an inline uuid5 that did not
lowercase the slug, diverging from resolve_workspace_id's canonical
derivation for any mixed-case slug. All derivations now go through the
single resolver (same namespace, so existing rows remain reachable).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Health endpoints echoed raw exception strings (which can leak DSNs and
hostnames) and the /query/stream SSE error event forwarded str(e) to the
browser. Clients now get generic messages; the full exceptions are logged
server-side (with traceback for the stream path). Correlation IDs remain
available for support via middleware.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The product streams over SSE; repo-wide search found no frontend
WebSocket usage (no EventSource either targets it). The unauthenticated-
surface risk outweighed a dead feature: the router mount and
endpoints/ws.py are removed. Git history preserves the module if a
realtime channel is ever productized.

Verified: api_router builds with 161 routes; contract tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kanwa Munipalle and others added 17 commits July 20, 2026 02:44
Long Gemini calls had no server-side cap; a slow upstream pinned a worker
thread until the browser's AbortSignal fired (or forever for non-browser
callers). All LLMService generation paths (generate, generate_answer,
generate_json) now run under asyncio.wait_for with LLM_TIMEOUT_SECONDS
(default 120, Settings + .env.example). Timeouts propagate loudly.
Streaming keeps client-side cancellation semantics.

Regression tests: backend/tests/test_llm_timeout.py (3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The candidates list 'search' param was ILIKE keyword matching with the
pgvector version commented out (blocked historically by the missing
get_embedding, C-1) - and nothing ever populated
CandidateProfile.embedding. hr_tasks now embeds new profiles (name +
skills + resume head; loud non-fatal on failure), and the search branch
ranks by L2 distance with embedding-less legacy rows sorted last. If the
query embedding fails, the endpoint falls back loudly to ILIKE.

Depends on C-1 (get_embedding) and C-7 (1024-dim columns).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
L-4: /exams/generate/diagram returned one hardcoded Mermaid template for
every topic. It now asks the LLM for topic-specific Mermaid, validates
the output shape, and returns 502 on failure instead of a fake diagram.
Response contract unchanged ({type, content}; the frontend consumer in
lib/api.ts is unaffected).

L-10: /exams/process/voice claimed 'Voice processing queued via Celery'
while no voice-to-exam pipeline exists. It now returns 501
not_implemented honestly (no frontend consumer exists).

Both stubs in one file; verified module imports clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
legal /rules and /contracts, finance /documents and /findings, study
/decks, and research /projects returned unbounded scalars().all().
Each now takes limit (default 100, max 500) and offset query params -
additive, so existing callers keep working with bounded defaults.
(Chats/messages already paginated; per-parent sublists are naturally
bounded by their parent.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four parallel answer paths risked behavioral drift. /query/search and
/query/debug had no frontend consumer (API_AUDIT 2.4) and are removed;
the two remaining paths have documented, distinct roles: /query/stream
(interactive SSE: trial, cache, Veritas) and /query/ask (synchronous,
used by the /chats persistence flow). Both already ground through the
same GroundingService + llm_service pipeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MiniLM (384-dim) is kept for JD-resume similarity: its vectors are used
only as a scalar blended into fit_score, never stored in pgvector
columns or mixed with the bge-m3 retrieval space, and it is ~10x cheaper
to load. The docstring now records this decision and forbids persisting
MiniLM vectors into embedding columns (bge-m3 space, C-7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…earch

The 4-step deep-research pipeline (RAG -> gap analysis -> Tavily web
search -> synthesis, with Veritas trust score) existed but had no caller
anywhere - discovered during C-5. New SSE endpoint streams one JSON frame
per ResearchEvent (terminated by [DONE]) and validates document ownership
before the agent runs. Frontend gains a runDeepResearch wrapper in
lib/api.ts following the existing SSE-fetch pattern; panel-level UI
adoption is left to product.

Regression test: backend/tests/test_deep_research_endpoint.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…claim

docs/architecture/project-map.md described the doubled-/api/v1 prefix
bug and the workspace-UUID crash as open (both fixed long ago) and
carried the 'never modify' file freeze superseded by REPAIR_RULEBOOK 8a.
A banner now marks those sections historical and points to the current
architecture docs.

README: after this repair phase the headline claims (pgvector default,
multi-engine OCR on ingestion, Veritas trust score) are actually
delivered; the one imprecision left - 'trust score on every answer' - is
corrected to 'every grounded answer' (ungrounded replies show the
Ungrounded badge by design, C-4).

docs/marketing/ remains untracked (out of repo scope).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion

Full restructure into a 20-section landing document: executive summary,
problem statement, honest tool-by-tool comparison, the five design rules
(evidence-only generation, extract-then-compute, measured trust, loud
failure, first-class scans), a per-workspace guide for all seven domains,
twelve engineering-highlight deep dives (key rotation internals, HNSW +
dimension pinning, the worker three-way rule, injection defense, tenant
isolation layers), nine Mermaid diagrams, tech-stack tradeoffs, security/
performance sections, deployment guide, verified-only roadmap, and a
technical FAQ.

Every claim is checked against the implemented code: heuristic Veritas
factors, Gemini-only providers, pending screenshots, 501 stubs, and all
unbuilt work are labeled as such or listed under Roadmap. Adds a 'For
Reviewers: Where to Look First' section mapping engineering concerns to
specific files. Mermaid labels lint-checked; all TOC anchors verified
against headings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discovered standing up the live demo stack: sync_database_url appended
sslmode=require to every DSN, db/session.py set asyncpg ssl=require
unconditionally, and the health ping hardcoded sslmode=require - so the
app could not connect to ANY non-SSL Postgres, including its own
docker-compose db/pgbouncer services. It only ever worked against
SSL-terminating managed hosts. Same family as H-8 (which fixed the
identical hardcode in alembic/env.py only).

Shared LOCAL_DB_HOSTS policy in core/config.py now governs all three
sites; explicit sslmode URL params are always honored; the health ping
uses prefer for local hosts.

Regression tests: backend/tests/test_db_ssl_policy.py (5).
Live verification: scratch non-SSL pgvector container now reports
api/db/redis all ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /hr/jobs/{id}/candidates returned raw ORM objects with no response
model and 500'd with PydanticSerializationError the first time real
JobMatch rows existed - unreachable before because candidate processing
was broken since inception (C-1/C-2/C-7), so no data ever hit the
serializer. Discovered live during the showcase run. The payload is now
explicitly shaped to what CandidateRankingsPanel consumes.

Also declares paddlepaddle in requirements: paddleocr 3.x does not pull
the paddle runtime, so fresh installs silently lost the primary OCR
engine (the confidence-gated Docling fallback carried the pipeline -
observed live, working as designed - but Paddle should be primary).

Regression tests: backend/tests/test_hr_candidates_serialization.py (2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… video

Captured from the running, seeded application (no mockups):
- 21 workspace screenshots wired into README section 16 (general grounded
  answer with citations + trust, scanned-doc OCR proof, HR candidate
  ranking, research contradiction synthesis, proactive computed ratios,
  finance ratio engine, exam builder with mark validation, student
  flashcards, etc.)
- docs/demo-documents/: 13 synthetic sample PDFs used to seed the demo
- docs/demo/documind-demo.mp4 (81.5s, 2.1MB) + .gif (6.0MB): a subtitled
  Playwright-driven tour of the real app; captions burned in via libass,
  timed to each workspace.

README section 16 replaces the placeholder gallery with the real assets and
embeds the demo GIF/MP4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Alembic-migrations step failed with 'ValidationError: 10 validation
errors for Settings' — core/config.Settings has ten required fields with
no defaults (AUTH_SECRET_KEY, CSRF_SECRET_KEY, FRONTEND_URL, POSTGRES_*,
REDIS_URL, CELERY_*). Locally these come from backend/.env; CI has none,
so importing settings (which alembic/env.py and every app module do)
raised before a single migration ran. The test step would have hit the
same wall next.

Adds a job-level env block with test-only values so migrations and the
82+ pytest suite both run. Reproduced the failure locally (import with
only DATABASE_URL) and verified the fix (import succeeds; full suite green
with no .env present).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI failed test collection with ModuleNotFoundError: No module named 'app'.
CI runs 'pytest tests/' (console script), which does not put the CWD on
sys.path — unlike 'python -m pytest', which is what worked locally. With
no pytest config or conftest present, none of the 24 test modules could
import the app package.

Adds backend/pytest.ini with 'pythonpath = .' (pytest >= 7 native), so
'import app...' resolves under any invocation. Reproduced the collection
error with the bare console script and verified all 89 tests pass with it
(and with no .env present, matching CI).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The auth test failed in CI with 'ValueError: password cannot be longer
than 72 bytes' for a 9-byte password. requirements.txt pinned
passlib[bcrypt]==1.7.4 but left bcrypt unpinned, so a fresh CI install
pulled bcrypt 5.x. passlib 1.7.4's bcrypt handler reads the removed
bcrypt.__about__ and then mis-applies the 72-byte check, breaking
hash_password() even for short inputs. Local passed only because the venv
had bcrypt 4.0.1 cached.

Pins bcrypt==4.0.1 (last 4.0.x, passlib-compatible). Verified advisory-
clean via pip-audit and the 5 auth tests pass. CI showed exactly this one
failure (1 failed, 88 passed), so this completes the backend job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kanwa2006
kanwa2006 merged commit e3ecae0 into main Jul 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant