phase: 191_source_name_labels_tags
All gates re-verified against a stable tree. Report: **Verified (all 6 tasks in `complete/`; this was the final pass)** - `SourceLabel`+alembic `0030` (dev DB `alembic current` = `0030 (head)`, table present, 0 rows); admin-only `GET/PUT/DELETE /api/source-labels` (`require_admin` router-wide, `resolve_source_name`, partial-update caps/404s); Sources-page "Names, labels and tags" card (read-only derived-name `<code>`, per-row `saveInFlight`, 404 note that survives the settle, chips reuse `.git-source-hidden-count`); `question_name_hits` D2 rule; ONE `all_labels` read per turn in `chat.py` feeding both the `source_name_hit` flag and the prompt surfaces; `source_name_hit_bonus=0.005` in `_selection_order`; ` name=`/` label=` in `build_high_prompt` + `build_overview_prompt(names=…)`. - **Completion criteria:** E2E `tests/e2e/test_source_name_labels.py` ✅ 7 passed (arms a–e: editor all kinds, control/named question, rename-never-breaks-identity, 404 note, reset) · full suite ✅ · coverage ✅ · README/AGENTS ✅ · phase dir in `complete/` ✅ · commit left to the harness. - **Gates:** `uv run pytest --cov=app --cov-report=term-missing` → **exit 0, TOTAL 7160 stmts / 16 missed = 99%** (>90%) · `uv run pytest tests/e2e/test_source_name_labels.py -v --no-cov` → **7 passed, exit 0** · regression E2E isolated, exit 0: `test_document_viewer`, `test_admin_auth`, `test_api_tokens`, `test_git_sources_admin`, `test_kb_tree`, `test_local_directory_sources` · `uv run ruff check .` → All checks passed · `uv run pyright` → 0 errors. - **Battery (task 04 criterion):** `uv run python -m scripts.eval_retrieval --from-file tests/fixtures/retrieval_battery.txt` at default vs `BOR_SOURCE_NAME_HIT_BONUS=0` → **`diff` empty (line-for-line identical)**, matching the recorded arms (dev KB is empty, so every question deflects; the observable boost is unit/E2E-pinned). - **Documentation:** README — fixed the `/git-sources.html` rows (nav label is **Sources**) and cross-linked the phase-191 card from that section; full audit otherwise clean: all 86 `BOR_*` settings documented with defaults matching `app/config.py`, 0 broken file/dir references and 0 broken anchors in README+AGENTS, AGENTS rule 17 re-checked line-by-line against the code. - **Notable / deviations:** (1) a **concurrent pipeline process** (`phased-execution/progress.mjs` for `191_…_00_phase`) was mutating this same working tree mid-pass — it rewrote `tests/e2e/test_source_name_labels.py` (11→7 tests), `frontend/assets/git-sources.js`, `tests/unit/test_source_labels_editor_ui.py` and `AGENTS.md` at 05:27; every gate above was (re-)run after those edits with the tree no source file newer than the green full run. (2) The task-06 report/phase text claims regression suites `test_chat_token_gate.py`, `test_git_sources_page.py`, `test_rag_browser.py` are green — **those files have never existed in git history** (those runs exit 4); I ran the real equivalents listed above instead (phase files are not mine to edit). (3) **No live changes**: the VM DB already has `source_labels` (0 rows) but the deployed app predates this uncommitted code, so deploying `/api/source-labels` to the live service is the owner's post-commit step, not part of this phase's criteria. - **Next pending phase:** none — `191_source_name_labels_tags` is the only entry in `.agents/phases/todo/`.
This commit is contained in:
@@ -77,6 +77,14 @@ Data model — see ``.agents/PLAN.md`` §Data Model:
|
||||
name, the value in its env-native string form;
|
||||
the row WINS over the environment (resolved
|
||||
inside ``app.config.get_settings``).
|
||||
* ``source_labels`` — one row per source the owner has named (phase
|
||||
191, TODO.md L4): the display name ("the git
|
||||
server"), the short label/category, and the tag
|
||||
list used to focus the model when prompting.
|
||||
``source`` mirrors ``documents.source`` (the
|
||||
DERIVED name) and is an ALIAS key only — a label
|
||||
row never renames a document, a locator, or a
|
||||
citation (phase 191 D1).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -717,3 +725,57 @@ class AppSetting(Base):
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), nullable=False
|
||||
)
|
||||
|
||||
|
||||
class SourceLabel(Base):
|
||||
"""One owner-given name / label / tag set per source (phase 191, task 01).
|
||||
|
||||
``TODO.md`` L4: "Sources need a name, a label, tag(s) so you can
|
||||
focus the model when prompting if you have a project name or a
|
||||
standard name you reference things by." One row per SOURCE (not per
|
||||
document), keyed by the source name exactly as ``Document.source``
|
||||
carries it (the derived basename — the clone dir / local dir /
|
||||
upload file stem), so the row joins the documents and the
|
||||
``git_sources`` row without a second identity.
|
||||
|
||||
**The display name is an ALIAS ONLY (D1, owner-confirmed):**
|
||||
``Document.source``, the ``source/path`` document identity, the
|
||||
read/grep/``show_text`` locators, the citations and the KB tree are
|
||||
ALL untouched by a label row — renaming a source never invalidates
|
||||
a saved chat, a shared page or a verbatim locator. The derived name
|
||||
stays the wire identity; the display name rides beside it and is
|
||||
consumed by the retrieval boost (task 04) and the prompt surfacing
|
||||
(task 05) only.
|
||||
|
||||
No row = no name/label/tags = the pre-phase behavior exactly (the
|
||||
migration creates an EMPTY table — no backfill, A13 reversible).
|
||||
"""
|
||||
|
||||
__tablename__ = "source_labels"
|
||||
|
||||
#: Mirrors ``documents.source`` (String(120)) — the DERIVED source
|
||||
#: name, the join key and the sole PK (one label row per source;
|
||||
#: the ``folder_summaries`` PK-part precedent).
|
||||
source: Mapped[str] = mapped_column(String(120), primary_key=True)
|
||||
#: The owner's name for the source ("the git server"). Plain TEXT —
|
||||
#: the ≤ 120-char trimmed limit is enforced at the API boundary
|
||||
#: (the phase-121 ``git_sources.branch`` pattern: plain column,
|
||||
#: boundary validation). NULL = no display name (pre-phase).
|
||||
display_name: Mapped[str | None] = mapped_column(Text, default=None)
|
||||
#: The short one-line description / category ("self-hosted forge +
|
||||
#: Gitea"). Plain TEXT, ≤ 200 trimmed enforced at the API. NULL =
|
||||
#: no label.
|
||||
label: Mapped[str | None] = mapped_column(Text, default=None)
|
||||
#: The focus tokens (the owner's "tag(s)"): ≤ 10 entries, each ≤ 32
|
||||
#: chars, non-empty, de-duplicated case-insensitively — normalized
|
||||
#: and guarded at the API boundary (``app.rag.source_labels.normalize_tags``).
|
||||
#: JSONB NOT NULL with server default ``'[]'`` (the phase-89
|
||||
#: ``ignore_paths`` shape): a source with no tags reads ``[]``.
|
||||
tags: Mapped[list] = mapped_column(
|
||||
JSONB, default=list, server_default=text("'[]'"), nullable=False
|
||||
)
|
||||
#: Fresh stamp on every write (the ``sources_meta.updated_at`` house
|
||||
#: style: server-side now(), onupdate now()).
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user