Converts the 9 TODO items into an executable phase roadmap (Protocol B, appended after phase 39): - 40 tuning toggle anonymous flash (TODO L3) - 41 sync fail-fast + modal when a model is down (TODO L4) - 42 no reply autoscroll (TODO L5) - 43 thinking scroll back — user scroll + gated autoscroll (TODO L7) - 44 markdown tables (TODO L6) - 45 agent unlimited tool calls behind BOR_AGENT_MAX_ROUNDS (TODO L8) - 46 mobile hamburger nav (TODO L9) - 47 quadlet + jinja import formats, A9 revision (TODO L10–L11) Each phase carries a user story, a dedicated Playwright E2E suite plan, and owner-locked decisions (R1 A9 format extension, R2 phase-37 budget revision, A1–A5 scope decisions) confirmed 2026-08-27. Also records the completed phases 30–39 todo/ -> complete/ moves that were pending in the working tree. TODO.md is cleared (items now live in .agent/phases/todo/).
5.4 KiB
Phase 35 — Admin Page to Add / Remove Git Sources
Source: TODO.md L4 — "I need a page only the admin can access where I can add and remove git sources for docs"
Story: .agent/user_stories/git-sources-admin.md
Context: Phase 28 introduced git-based sources (BOR_GIT_SOURCES env var + scripts/git_sync.clone_or_pull) and phase 32 the one-click Sync button (POST /api/sync) — but the list itself can only be changed by editing .env and restarting. This phase makes the list admin-managed: a Postgres-backed table, an admin-only CRUD API, and a dedicated admin page, with the sync pipeline and import_docs resolving the effective list from the DB (env var demoted to an empty-table fallback).
Objective
Deliver a page only the admin can access (/git-sources.html, soft-gated like Sources) to add and remove git sources, stored in a new git_sources table; the Sync button (phase 32) and import_docs (phase 28) use the stored list, BOR_GIT_SOURCES remains a fallback while the table is empty, and phase 32's fail-loud "no git sources configured" is preserved when both are empty.
Dependencies
28_git_based_sources(complete) —scripts/git_sync.clone_or_pull,repo_name, theBOR_GIT_SOURCESsettings +git_source_list, the import resolution order (--sourcewins).32_admin_sync_button(complete) — thePOST /api/sync/GET /api/sync/statuspipeline this phase re-points at the DB list; the Sync button the page's hint refers to.16_admin_auth(complete) —require_admin(the router-level pattern fromapp/api/sync.py), the soft-gate page pattern (sources.html), thefetchIsAdmin()frontend gate.34_consistent_navbar(todo) — the identical five-page header this phase's admin-only "Git sources" nav link plugs into (phase 29 pattern).29_tuning_nav_link(complete) — the admin-only ship-hidden nav-link pattern to copy.
Tasks
01_model_and_migration.md—GitSourcemodel + migration0006_git_sources.py(reversible).02_git_sources_api.md— admin-onlyGET/POST /api/git-sources+DELETE /api/git-sources/{id}with validation, the env-fallback listing, and the integration suite.03_sync_and_importer_use_db.md—effective_git_sources()shared byapp/api/sync.pyandscripts/import_docs.py(DB wins, env fallback, fail-loud unchanged) + test updates.04_admin_page.md—/git-sources.html+git-sources.js(soft-gated, list / add / remove, env note, sync hint) + styles.05_nav_link.md— the admin-only "Git sources" nav link on all five pages + theheader.jsreveal.06_e2e_and_docs.md— the story E2E suitetest_git_sources_admin.py,test_nav_consistency.pynav-inventory update, README/.env.examplenotes, regressions, commit.
Testing & Quality
- Unit/integration:
tests/unit/foreffective_git_sources(DB-wins / env-fallback / both-empty);tests/integration/test_git_sources_api.pyfor the CRUD contract (403/201/409/422/404, env fallback); the migration up/down test following the 0004/0005 pattern; the existingtest_sync_api.py+test_import_docs_git.pysuites stay green with the resolution indirection. - Coverage: >90% on
app/for the new module + API. - E2E (mandatory, A16):
tests/e2e/test_git_sources_admin.py— the story gate, run in isolation.
Completion Criteria
- Migration 0006 applied (
uv run alembic upgrade head);git_sourcestable exists withurlunique. GET /api/git-sources(admin) lists DB rows; while the table is empty it returns the env list withfrom_env: true; anonymous gets 403 on all three routes.POSTcreates (201, trimmed, shape-validated, 409 duplicate without echoing the URL);DELETEremoves (204/404).POST /api/syncandimport_docsresolve the list viaeffective_git_sources(origin loggeddb|env); both-empty still fails loudly;--sourceoverride unchanged./git-sources.html: anonymous sees the sign-in gate; the admin sees list + add + remove with a never-stale button and inline errors; the admin-only "Git sources" nav link is visible on all five pages for the admin and hidden for anonymous.uv run pytestgreen;uv run pytest --cov=app --cov-report=term-missing>90%;uv run pytest tests/e2e/test_git_sources_admin.py -v --no-covgreen in isolation; regressions (task 06 list) green.uv run ruff check . && uv run pyrightclean.- UI Structure Check (AGENTS.md rule 5) + no CDN (rule 6).
- One
--no-gpg-signcommit; phase directory moved to.agent/phases/complete/.
Locked decisions
- A3 / A13 honoured — the list lives in Postgres via Alembic (no JSON file, no new store).
- A10 extended per the phase-16 pattern — a new admin-only router behind
require_admin; the public API surface stays stateless; no new auth mechanism. - A11 untouched — vanilla HTML/CSS/JS, no CDN, no new packages.
- Env var demoted, not removed —
BOR_GIT_SOURCESkeeps working exactly as today while the table is empty (the fallback); once the table has rows it is ignored (the UI is the source of truth). Phase 32's fail-loud empty-config behavior is preserved. - Scope boundary — adding/removing a repo does NOT immediately clone, import, or prune: the existing Sync button performs that (removal prunes on the next sync,
prune=True). The page's hint says so. - A16 / A17 honoured — one new story E2E suite + one atomic
--no-gpg-signcommit.