phase: 191_source_name_labels_tags
Build and Push Containers / build-and-push-app (push) Successful in 7m24s
Build and Push Containers / build-and-push-db (push) Successful in 36s

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:
2026-10-19 06:13:32 -04:00
parent c6ffa050a6
commit 4f20c256d7
70 changed files with 7378 additions and 50 deletions
+85
View File
@@ -0,0 +1,85 @@
"""source_labels: per-source name, label and tags (phase 191, task 01)
Revision ID: 0030
Revises: 0029
Create Date: 2026-10-18
``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." This revision carries the storage half
(phase 191 D1) — the matching rule (D2), the boost (D3), the API (D5)
and the UI (D6) are the later tasks.
* ``source_labels`` — one row per SOURCE the owner has named:
* ``source`` — VARCHAR(120) NOT NULL, PRIMARY KEY. The source name
EXACTLY as ``documents.source`` carries it (the derived basename —
the clone dir / local dir / upload file stem): the join key to the
documents and to the ``git_sources`` row. The PK is the uniqueness
guard (one label row per source — the ``app_settings`` 0028
reasoning). **An ALIAS key only (D1):** a label row never renames a
document, a ``source/path`` locator, a citation or the KB tree.
* ``display_name`` — TEXT nullable (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 name.
* ``label`` — TEXT nullable (the short one-line description /
category, ≤ 200 trimmed, same boundary rule). NULL = no label.
* ``tags`` — JSONB NOT NULL with server default ``'[]'`` (the
phase-89 ``git_sources.ignore_paths`` shape): the focus tokens,
each ≤ 32 chars, ≤ 10 entries, guarded at the API boundary
(``app.rag.source_labels.normalize_tags``).
* ``updated_at`` — TIMESTAMPTZ NOT NULL, ``now()`` server default
(the ``app_settings`` / ``folder_summaries`` house style).
NO backfill (D1): the table is created EMPTY — a source without a row
has no name/label/tags and behaves exactly as pre-phase, so "no label
row → every surface byte-identical" holds without a data step. No index
beyond the PK: every read is a PK lookup or a whole-table walk of a
handful of rows (the ``app_settings`` "never a query surface" note).
One additive, fully reversible migration (A13).
"""
from __future__ import annotations
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
revision = "0030"
down_revision = "0029"
branch_labels = None
depends_on = None
def upgrade() -> None:
# No index beyond the PK (every read is a PK lookup or the whole
# handful-row table), no backfill (an empty table = every source
# keeps its pre-phase "no row" behavior).
op.create_table(
"source_labels",
sa.Column("source", sa.String(length=120), primary_key=True),
sa.Column("display_name", sa.Text(), nullable=True),
sa.Column("label", sa.Text(), nullable=True),
sa.Column(
"tags",
postgresql.JSONB(astext_type=sa.Text()),
server_default=sa.text("'[]'"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
)
def downgrade() -> None:
# The table is the only 0030 artefact — dropping it leaves 0029's
# schema byte-identical (A13, fully reversible). No owner data is
# trapped: the names/labels/tags are re-enterable from the Sources
# page (task 03), and nothing else was mutated by 0030.
op.drop_table("source_labels")