13 KiB
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.mdfirst. 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:
langflowimage pinned via${LANGFLOW_VERSION:-1.12.0}(was:latestwith nightly Watchtower drift).Dockerfile.lfxpinslfx~=<same>(ARG LFX_VERSION=1.12.0). Backend test dep pinnedlfx~=1.12.0. Reason: official LFX contract —major.minorof Langflow and LFX must match, patches are independent.langflowmountsbackend/components(LANGFLOW_COMPONENTS_PATH) andbackend/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), botha+rX. Removedkirche-venv+kirche-node-modulesnamed volumes (they shadowed image content with empty volumes on first start = split-brain).backend/entrypoint.shuses/opt/venv/bin/*, ensuresdata/exists and isvscode-owned.frontend/entrypoint.shrunsnpm ciwhennode_modules/.bin/viteis missing (covers fresh prod checkouts). devcontainer.json: opencode session persistence via named volumeskirche-opencode-share → ~/.local/share/opencode(sessions DB) andkirche-opencode-config → ~/.config/opencode; interpreter path is now/opt/venv/bin/python.setup.shchowns first, then runsuv syncasvscode(was: rootuv syncinto/root-backed venv →Permission deniedfor vscode;backend/dataroot-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: nothing from the original list — Phases 1–4 are implemented
(see §0.2). Remaining eyeball checks: kirche category in the Langflow
palette, RJSF form UX in a browser, magic-link mail with real SMTP.
0.1 Follow-up fixes (commits after Phase 0)
fix: devcontainer build and setup on fresh clones + feat: magic-link auth + the LFX/stack repair commit:
docker-outside-of-dockerneeds"moby": false(python:3.12-slimis Debian trixie, no moby-cli in its repos). Removed the manual/var/run/docker.sockbind (breaks Windows, feature handles it).setup.shusedsu vscode -c(drops env incl.UV_PROJECT_ENVIRONMENT→ straybackend/.venv, blind sync). Nowsudo -u vscode -Ehelper. Missing.envis provisioned from.env.example, not fatal.- Compose
env_fileisrequired: falseeverywhere (fresh clones, CI). Images installcurl git ca-certificates;.dockerignorefiles keep hostnode_modules/.venv/dataout of build contexts. settings.database_urldefault is now a realsqlite:///URL (bare path crashedcreate_engine/alembic; api could never boot uncconfigured).Dockerfile.lfxCMD dropped the positional/flows:lfx>=1.12.1exits on an empty flow dir (restart-loop → whole stackunhealthy).lfxservice blanksLANGFLOW_API_KEY(server prefers it overLFX_API_KEY, so backend deploys 401'd and/flowsstayed empty).- Both entrypoints run via explicit
sh(git tracks them 100644; the bind-mount shadows image perms, breaking Windows checkouts). - Verified live: full
compose upgreen (api/frontend/langflow/lfx), startup sync deploysbackend/flowsto LFX,uv run pytest60 green. Before Reopen in Container:docker compose downany manual stack (same project name → port clash).
0.2 Phases 1–4 (done, with deviations)
- Phase 1 auth as planned (
app/auth/*, migrationa3f1c9e2b4d7). Additionally fixed:settings.database_urldefault is a realsqlite:///URL (bare path crashedcreate_engine/alembic). - Phase 2:
CustomInputFormhas the 10 planned inputs plus one hiddenform_values: MessageTextInput (show=False)plumbing input. Without it, submitted web-form values never reached the run (LFX routes run input to entry nodes; scalar fields are not connectable). Fixture flowintake_form.json:ChatInput → form.form_values,form.form_data → ChatOutput;collect()merges the envelope ({"payload": {...}}unwrapped like the other components) over field defaults. Verified live: run with values returns them in the output. - Phase 2 generator caveat: lfx 1.12.x
to_frontend_node()cannot re-instantiateMultiselectInput(emitslist, constructor forbids extras — their own builtinSearXNGToolComponentfails identically).kirche_node()therefore replicates the pre-validation dict build and appends thecodefield by hand. If a future lfx fixes this, simplify back to plainto_frontend_node(). - Phase 3 as planned (
input_schema.py,FlowVersion.input_schema, migrationb7e2d4a1c9f3,GET input-schema). Deviations: schema is cached instore.create_version(single choke point: sync, import, API all covered); parser skipsshow: falsefields; enum-mismatched defaults omitted; endpoint mapsInvalidError→ 404 too (get_latest_deployed_versionraises it, notNotFoundError). - Phase 4 as planned (
react-router-dom@7,@rjsf/core+@rjsf/validator-ajv8(v6), English routes, German labels,credentials: include, old tab shell dropped).FlowsTab.tsxdeleted;ApprovalsTab/AdminTabreused inside Tasks/Admin pages. Verified:npm run buildgreen, routes +/apiproxy return 200, served schema validates under JSON-Schema Draft 7.
0.3 Frontend design pass (Mantine)
Plain-HTML UI replaced with a coherent Mantine v9 design system:
@mantine/core + @mantine/hooks + @rjsf/mantine (v6, matches core)
@tabler/icons-react.src/theme.ts(kirchenblau primary, system fonts only, serif display headings),AppShellheader/nav, per-pageCard/Table/Alert/Badgevia sharedStatusBadge, dark mode via localStorage manager + toggle + inlineColorSchemeScriptequivalent. RJSF swap was import-only; SSR smoke test proved all 10 intake labels + textarea/password/checkbox/slider widgets render.npm run buildgreen.
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.
FileInputis 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+Componentsubclasses usinglfx.ioinputs only (stdlib +lfx, so the LFX runtime can execute them). - Watchtower:
langflowlabel removed (pinned images must not auto-update). Version bumps = deliberateLANGFLOW_VERSION+LFX_VERSIONchange, thenlfx upgrade --upgrade-flow=safecheck overbackend/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/requestspublic, rate-limited:{name,email,reason}→ rowpending+ notifyCONTACT_TO.POST /api/access/requests/{id}/approve|rejectadmin: approve → upsertUser+ 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 inADMIN_EMAILS.- Tenant from session (
user.tenant_id); keep oldtenant_idbody param as deprecated fallback.require_admindependency; keep API handlers thin (DB only viaapp/flows/store.py+ newapp/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 inputsStrInput,MultilineInput,SecretStrInput,IntInput,FloatInput,BoolInput,DropdownInput,MultiselectInput,SliderInput,FileInput(stub) +Output. Category dirkirche/= appears in Langflow GUI.- Regenerate/add fixture flow using it via
backend/scripts/make_flow_fixtures.py; keepsimple_rule.jsonas 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 withtype/display_namestartingCustomInput; iteratetemplateminuscode; 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 nullableinput_schemaJSON column via Alembic) + newGET /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 <Form> → 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 viadocker 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 foruv python install). In rebuilt containers this is unnecessary (/opt/venv). .env(gitignored) still lacksFRONTEND_URL/ADMIN_EMAILSfrom the.env.examplediff — 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=safeon the file, review diff, commit. - Definition of done per
AGENTS.md: new behavior has tests,uv run pytestgreen,npm run buildgreen when frontend changed, schema changes as new Alembic revisions.