# Continuation plan — kirche-einfach next steps (written after Phase 0) > For a future agent restoring this session (e.g. after a container rebuild). > Read this file + `AGENTS.md` + `README.md` first. All code/comments in > English; only user-visible website strings are German. ## 0. Where we are **Phase 0 (this commit) is done** — Docker/devcontainer reproducibility: * `langflow` image pinned via `${LANGFLOW_VERSION:-1.12.0}` (was `:latest` with nightly Watchtower drift). `Dockerfile.lfx` pins `lfx~=` (`ARG LFX_VERSION=1.12.0`). Backend test dep pinned `lfx~=1.12.0`. Reason: official LFX contract — `major.minor` of Langflow and LFX must match, patches are independent. * `langflow` mounts `backend/components` (`LANGFLOW_COMPONENTS_PATH`) and `backend/flows` (`LANGFLOW_LOAD_FLOWS_PATH`), both `:ro`. Files stay source of truth; the API watcher (`app/flows/sync.py`) remains the ONLY sync path — never add a second watcher. * Backend venv moved to `/opt/venv` (image-local, `UV_PROJECT_ENVIRONMENT`), managed Python to `/opt/python` (`UV_PYTHON_INSTALL_DIR`), both `a+rX`. Removed `kirche-venv` + `kirche-node-modules` named volumes (they shadowed image content with empty volumes on first start = split-brain). `backend/entrypoint.sh` uses `/opt/venv/bin/*`, ensures `data/` exists and is `vscode`-owned. `frontend/entrypoint.sh` runs `npm ci` when `node_modules/.bin/vite` is missing (covers fresh prod checkouts). * `devcontainer.json`: opencode session persistence via named volumes `kirche-opencode-share → ~/.local/share/opencode` (sessions DB) and `kirche-opencode-config → ~/.config/opencode`; interpreter path is now `/opt/venv/bin/python`. `setup.sh` chowns first, then runs `uv sync` as `vscode` (was: root `uv sync` into `/root`-backed venv → `Permission denied` for vscode; `backend/data` root-owned → DB unwritable). * Stale local `backend/.venv/` (bind-mount leftover) can be deleted manually after rebuild: `rm -rf backend/.venv`. It is gitignored; nothing references it anymore. **Verified in this session:** `uv run pytest` green (after local venv repair, see §5). NOT verifiable here (no node/docker in this sandbox): `npm run build`, `docker compose config`, container rebuild. CI covers all three (`.forgejo/workflows/`). After rebuilding the devcontainer, re-run: `uv run pytest` (attached api, from `backend/`), `docker compose exec frontend npm run build`, `docker compose config --quiet`. **Still open (docs-only):** `README.md` + `.env.example` uncommitted diff describes the target vision (access mgmt, dynamic forms, new Web UI) that is NOT yet implemented: `app/auth/` is empty, `backend/components/kirche/` is empty, frontend is still the old 3-tab UI, no `input-schema` endpoint. ## 1. Locked decisions (do not re-ask) * Auth: SMTP magic-link, 15-min single-use tokens (`secrets.token_urlsafe`, store `sha256`, `used_at` set before session creation), session = opaque id in **DB table**, cookie `HttpOnly; Secure; SameSite=Lax; Path=/`. SMTP creds already in `.env` (`SMTP_HOST/PORT/USERNAME/PASSWORD`, `MAIL_FROM`, `CONTACT_TO`); dev fallback logs the link when SMTP unset. * `FileInput` is a **stub** for now (string reference only); real upload later. * Frontend: `react-router-dom` + RJSF (`@rjsf/core` + `@rjsf/validator-ajv8`). RJSF renders backend-supplied `{json_schema, ui_schema}`; server re-validates (client validation is UX only). * **Routes are pure English** (user correction — no German slugs anywhere, including URLs): `/`, `/access` (was once sketched as `/zugang` — do NOT use), `/login?token=`, `/app/workflows`, `/app/tasks`, `/app/admin`. Visible labels/nav are German (`Start`, `Zugang`, `Workflows`, `Aufgaben`, `Verwaltung`); code, props, comments, routes English. * Langflow preload + mount (done in Phase 0). Custom-component lib in `backend/components/kirche/` **must stay GUI-editable**: category folder + `__init__.py` + `Component` subclasses using `lfx.io` inputs only (stdlib + `lfx`, so the LFX runtime can execute them). * Watchtower: `langflow` label removed (pinned images must not auto-update). Version bumps = deliberate `LANGFLOW_VERSION` + `LFX_VERSION` change, then `lfx upgrade --upgrade-flow=safe` check over `backend/flows/*.json`. ## 2. Phase 1 — Backend auth (magic-link + RBAC) Files: `backend/app/models/user.py` (`User`, `AccessRequest`, `MagicLinkToken`, `Session`), `backend/app/auth/{schemas,service,mail,api, deps}.py`, wire router in `app/main.py`, settings in `app/settings.py` (`frontend_url, admin_emails, contact_to, mail_from, smtp_*`), ONE new Alembic revision (never edit applied migrations). Endpoints: * `POST /api/access/requests` public, rate-limited: `{name,email,reason}` → row `pending` + notify `CONTACT_TO`. * `POST /api/access/requests/{id}/approve|reject` admin: approve → upsert `User` + emit magic-link + SMTP send. * `POST /api/auth/magic/consume {token}` → session cookie; `GET /api/auth/me`; `POST /api/auth/logout` (delete session row). * `POST /api/auth/admin-bootstrap`: only when user table empty AND email in `ADMIN_EMAILS`. * Tenant from session (`user.tenant_id`); keep old `tenant_id` body param as deprecated fallback. `require_admin` dependency; keep API handlers thin (DB only via `app/flows/store.py` + new `app/auth/*` service funcs). Tests (all in `backend/tests/`): request→approve→consume→me→logout happy path; token expiry boundary (15 min); token reuse → 400; admin-only → 403; bootstrap-once; cookie flags (`HttpOnly`, `SameSite`, `Path`); assert no token/secret in logs. ## 3. Phase 2 — `CustomInput*` library (GUI-editable) * `backend/components/kirche/__init__.py` + `backend/components/kirche/form_inputs.py`: `class CustomInputForm(Component)` with inputs `StrInput`, `MultilineInput`, `SecretStrInput`, `IntInput`, `FloatInput`, `BoolInput`, `DropdownInput`, `MultiselectInput`, `SliderInput`, `FileInput` (stub) + `Output`. Category dir `kirche/` = appears in Langflow GUI. * Regenerate/add fixture flow using it via `backend/scripts/make_flow_fixtures.py`; keep `simple_rule.json` as the textarea-fallback case. Commit flow JSON (source of truth). ## 4. Phase 3 — Dynamic-form parsing (no second watcher) * New `backend/app/flows/input_schema.py`: `extract_form_schema(langflow_json) -> {json_schema, ui_schema}`. Find nodes with `type`/`display_name` starting `CustomInput`; iterate `template` minus `code`; map `_input_type` → JSON Schema type/widget (`StrInput`/`MessageTextInput`→string, `MultilineInput`→string+textarea, `SecretStrInput`→string+password, `IntInput`→integer, `FloatInput`→number, `BoolInput`→boolean, `DropdownInput`→string+enum, `MultiselectInput`→array+enum, `SliderInput`→number+range, `FileInput`→string stub); `display_name/info/value/options/required/ advanced` → `title/description/default/enum/required/uiSchema`. No such node → `{fallback: "textarea"}`. * Called from existing `sync_from_directory` (cache per version in new nullable `input_schema` JSON column via Alembic) + new `GET /api/flows/{id}/input-schema → {flow_id, version_no, json_schema, ui_schema}` (latest **deployed** version; 404 when none). * Tests: per-type mapping table, fallback, sync persistence, endpoint 404. ## 5. Phase 4 — Frontend (router + RJSF, German UI, English routes) Deps: `react-router-dom`, `@rjsf/core`, `@rjsf/validator-ajv8`. Routes (English, see §1): `/`, `/access`, `/login`, `/app/workflows` (flow select → `GET input-schema` → RJSF `
` → `POST run`), `/app/tasks` (own runs + approval states), `/app/admin` (admin-only: access-request queue, ALL tasks incl. open human-intervention with approve/reject + note, import/sync/deploy). `fetch` with `credentials: "include"`. Refactor `FlowsTab/ApprovalsTab/AdminTab` into route components; drop old tab shell. Verify with `npm run build`. ## 6. Operational notes for the next agent * Commands: backend from `backend/`: `uv run pytest`, `uv run alembic upgrade head`, `uv run python scripts/sync_flows.py --sync`; frontend via `docker compose exec frontend npm run build`. * Local venv repair (this sandbox had a root-backed broken `.venv`): `rm -rf backend/.venv && uv sync` (network needed for `uv python install`). In rebuilt containers this is unnecessary (`/opt/venv`). * `.env` (gitignored) still lacks `FRONTEND_URL`/`ADMIN_EMAILS` from the `.env.example` diff — copy them over locally when starting Phase 1. Never commit `.env`. * Flow JSON that fails LFX after a version bump: run `lfx upgrade --upgrade-flow=safe` on the file, review diff, commit. * Definition of done per `AGENTS.md`: new behavior has tests, `uv run pytest` green, `npm run build` green when frontend changed, schema changes as new Alembic revisions.