- Fix doc-service delete endpoint: admins could not delete non-owned,
non-shared documents — they hit 404 because the initial query filtered
by owner/watch/group even before the is_admin bypass was checked.
Admins now get an unconditional fetch, consistent with intent.
- Add 18 new checklist tests covering: group admin role (4.9–4.10),
delete permission variants (12.16b–12.16e), can_delete sharing
(13.11–13.14), category scopes / PascalCase naming (14.7–14.17),
and three-dots portal fix (19.11).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Keep HEAD's get_user_admin_groups dep and richer delete permission logic (can_delete via share OR group admin path)
- Use sa.text("false") for migration server_default (correct SQLAlchemy form)
- Preserve 0006/0007 migration entries in doc-service CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add can_delete column to document_shares (migration 0005)
- Inject x-user-is-admin header from backend proxy to doc-service
- Add get_user_is_admin() dep in doc-service
- Delete endpoint now allows: owner, admin, or group member with can_delete=true
- Watch documents (user_id='watch') deletable by admins only
- DocumentOut gains viewer_can_delete (computed per-request)
- Share UI: 'Allow group members to delete' checkbox + trash badge on shares
- RowActionsMenu dropdown portaled to document.body — fixes overflow-hidden clipping
- Delete mutation onError handler — no more silent failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-create {service-id}-admin groups at startup (group_bootstrap.py)
- get_service_admin() dep: grants access to superusers OR service group members
- /api/settings/ai and /api/settings/documents/limits now allow service admins
- AI service exposes /plugin/manifest (ai-service-admin access group)
- DocServiceSettingsPage: combined upload limits + watch directory on one page
- ServiceAdminRoute in frontend guards new /apps/documents/settings and /apps/ai/settings
- Single Settings button per app card (visible to admins and service group members)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a manifest contract so feature containers self-describe their
settings (JSON Schema + access rules). Backend and frontend gain generic
plugin proxy and dynamic Extensions UI with zero feature-specific code.
Doc-service is the first plugin consumer: exposes /plugin/manifest and
/plugin/settings, adds a watchdog-based file watcher that auto-ingests
PDFs from a mounted directory, maps subfolders to categories, supports
AI-suggested folder/filename (user-confirmed), and enforces a no-remove
policy. Access is gated by is_superuser or doc-service-admin group.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full codebase analysis embedded: file tree, all API endpoints, all DB
model columns+constraints, schema conventions, security standards (JWT,
bcrypt, sanitization, XSS/SQLi prevention, admin 404 pattern), frontend
patterns (Axios client, TanStack Query keys/mutations, route guards),
naming conventions, HTTP status codes, default limits, Docker infra,
and all workflow checklists in one place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Users can pin/unpin any available service on their home page via a
Customize mode; preferences persisted via PATCH /api/users/me/preferences
- Time-aware greeting renders the user's display name through React JSX
(HTML-escaped by design — no dangerouslySetInnerHTML used)
- Added dashboard_app_ids JSON column to users table (migration c7e8f9a0b1d2)
- /settings now routes to a placeholder page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New backend: Group + GroupMembership models, schemas, CRUD router at
/api/admin/groups (list, create, get detail, update, delete, add/remove members)
- New Alembic migration: groups and group_memberships tables
- Frontend: Admin sidebar item is now an expandable accordion with
Users and Groups sub-items; AdminPage redirects to /admin/users;
new AdminUsersPage and AdminGroupsPage with inline member management panel
- API client: 7 new group functions + TypeScript types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend polls each registered service's /health endpoint every 30 s via a
background asyncio task. GET /api/services exposes the live status snapshot.
The Apps page now renders from this endpoint — showing "Unavailable" (dimmed,
non-clickable) when a service is registered but its container is unreachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resets status to pending, clears error_message, and re-enqueues the
background AI extraction task. Button is disabled while the document
is already pending or processing; returns 409 in that case from the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar: Apps accordion expands to Documents, which expands to list all
user categories; clicking a category navigates to /apps/documents?category_id=<id>
- DocumentsPage: reads category_id from URL and applies filter; shows active
category chip in FilterBar with dismiss; removed TagEditor (deferred)
- doc-service GET /documents: new category_id query param filters via subquery
- doc-service POST /documents/categories: detects similar category names and
triggers background re-analysis of affected documents so the new category
surfaces as a pending AI suggestion on relevant docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each feature service owns its system prompt in its config JSON on the
shared volume. The AI Settings page now has General and System Prompts
tabs — admins can view and edit any service's prompts at runtime with
changes taking effect within 30 s (config cache TTL).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Introduce async priority queue service in ai-service; all /chat calls now route through it
- Refactor chat router to separate execute_chat (core logic) from the HTTP handler
- Add /queue endpoints (status, pause, resume, cancel) for queue management
- Update ai-service config to use Pydantic v2 model_config style
- Add STATUS.md files for backend, ai-service, doc-service, and frontend
- Document STATUS.md workflow in CLAUDE.md
- Update doc-service documents router and schemas; frontend DocumentsPage and API client
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All feature containers now POST messages to ai-service (port 8010) instead
of calling AI providers directly. ai-service routes to LM Studio, Ollama,
or Anthropic based on /config/ai_service_config.json. doc-service AI
providers removed; replaced by httpx ai_client.py. Backend settings
restructured to /api/settings/ai. Frontend gets dedicated AIAdminSettingsPage
and AI Service card in AppsPage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pytest suite for doc-service: 20+ tests covering category CRUD,
document upload/get/delete/patch, ownership isolation, category
assignment, AI processing (mock), and live PDF tests (auto-skipped
when tests/pdfs/ is empty)
- Minimal in-memory PDF builder in conftest so tests run without any
fixture files; real PDFs can be dropped into tests/pdfs/ to activate
live extraction tests
- AI prompt updated to return suggested_categories (2–5 short names)
- Frontend: SuggestionChip component in DocumentRow shows AI-suggested
categories after processing; "Assign" links to an existing category,
"Create & Assign" creates it first, ✕ dismisses locally
- Default AI provider changed to LM Studio at
http://host.docker.internal:1234/v1 (host.docker.internal resolves
to the macOS host from inside Docker Desktop)
- tests/pdfs/ directory tracked via .gitkeep; *.pdf excluded by .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New `features/doc-service` FastAPI microservice: PDF upload, async
text extraction (pdfplumber), AI classification via Anthropic/Ollama/
LM Studio, per-user categories, file download
- Alembic migration isolated with `alembic_version_doc_service` table
- Main backend: httpx proxy routers for /api/documents/* and
/api/documents/categories/*, admin settings API at /api/settings/*
- Runtime config in /config/doc_service_config.json (shared Docker
volume); api_key masking on reads; atomic write with os.replace()
- Frontend: DocumentsPage, DocumentAdminSettingsPage, updated AppsPage
launcher hub, simplified Nav (removed Settings link), new routes
- docker-compose: doc-service service, doc_data + app_config volumes,
removed internal:true from backend-net for outbound AI API calls
- Fix pre-commit hook: probe Docker socket path so git subprocess picks
up Docker Desktop on macOS
- Fix security_check.py: use sys.executable for bandit so venv python
is used instead of system python
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backend-net (internal: true): db ↔ backend ↔ frontend reverse proxy
- frontend-net: frontend only; single host port binding (80 prod / 5173 dev)
- Remove ports: from db (5432) and backend (8000) — unreachable from host
- Security auditor: hard rule to never add host ports to db or backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- frontend prod: USER root for adduser, then USER appuser (1001:1001); fixes
build failure caused by nginx-unprivileged already setting USER nginx
- docker-compose: frontend user updated to 1001:1001 (was 101:101)
- CLAUDE.md: add infrastructure change protocol (update README + test both
stacks after any Dockerfile/compose/nginx change); fix stale passlib ref
- README: container table shows nginx-unprivileged image, UID column, internal
port 8080 note; Current State notes all containers run as non-root
Both dev and prod stacks tested and verified (health, login, /users/me,
frontend serving, all containers confirmed non-root via docker inspect).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backend: appuser UID/GID 1001 via useradd, USER directive, --chown on COPY
- frontend builder: appuser UID/GID 1001 via adduser, USER directive
- frontend prod: switch to nginxinc/nginx-unprivileged:alpine (nginx UID 101), listen on 8080
- docker-compose: explicit user: for all services (70:70 db, 1001:1001 backend/frontend-dev, 101:101 frontend-prod)
- nginx.conf: listen 8080 to match unprivileged image
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- vite.config.ts: proxy target via VITE_API_TARGET env var (falls back to localhost)
- docker-compose.dev.yml: set VITE_API_TARGET=http://backend:8000
- Add /login-success and /register-success placeholder pages
- Show real API error messages in login/register forms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Generate frontend/package-lock.json (required by npm ci)
- Add network: host to BuildKit build stages to fix DNS in pip installs
- Switch pyproject.toml build backend to setuptools.build_meta (stable)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>