Files
brain-of-reese/.agent/phases/complete/35_git_sources_admin/00_phase.md
T
ducoterra 02c76ad328 chore(agent): phase roadmap from TODO.md — 8 phases (40–47), 24 tasks
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/).
2026-08-27 18:25:53 -04:00

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, the BOR_GIT_SOURCES settings + git_source_list, the import resolution order (--source wins).
  • 32_admin_sync_button (complete) — the POST /api/sync / GET /api/sync/status pipeline 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 from app/api/sync.py), the soft-gate page pattern (sources.html), the fetchIsAdmin() 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

  1. 01_model_and_migration.md — GitSource model + migration 0006_git_sources.py (reversible).
  2. 02_git_sources_api.md — admin-only GET/POST /api/git-sources + DELETE /api/git-sources/{id} with validation, the env-fallback listing, and the integration suite.
  3. 03_sync_and_importer_use_db.md — effective_git_sources() shared by app/api/sync.py and scripts/import_docs.py (DB wins, env fallback, fail-loud unchanged) + test updates.
  4. 04_admin_page.md — /git-sources.html + git-sources.js (soft-gated, list / add / remove, env note, sync hint) + styles.
  5. 05_nav_link.md — the admin-only "Git sources" nav link on all five pages + the header.js reveal.
  6. 06_e2e_and_docs.md — the story E2E suite test_git_sources_admin.py, test_nav_consistency.py nav-inventory update, README/.env.example notes, regressions, commit.

Testing & Quality

  • Unit/integration: tests/unit/ for effective_git_sources (DB-wins / env-fallback / both-empty); tests/integration/test_git_sources_api.py for the CRUD contract (403/201/409/422/404, env fallback); the migration up/down test following the 0004/0005 pattern; the existing test_sync_api.py + test_import_docs_git.py suites 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_sources table exists with url unique.
  • GET /api/git-sources (admin) lists DB rows; while the table is empty it returns the env list with from_env: true; anonymous gets 403 on all three routes.
  • POST creates (201, trimmed, shape-validated, 409 duplicate without echoing the URL); DELETE removes (204/404).
  • POST /api/sync and import_docs resolve the list via effective_git_sources (origin logged db|env); both-empty still fails loudly; --source override 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 pytest green; uv run pytest --cov=app --cov-report=term-missing >90%; uv run pytest tests/e2e/test_git_sources_admin.py -v --no-cov green in isolation; regressions (task 06 list) green.
  • uv run ruff check . && uv run pyright clean.
  • UI Structure Check (AGENTS.md rule 5) + no CDN (rule 6).
  • One --no-gpg-sign commit; 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_SOURCES keeps 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-sign commit.