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:
@@ -312,6 +312,99 @@
|
||||
quoted cosine/lexical margins, so editing a note there moves the numbers in
|
||||
the module docstring and the pins together.
|
||||
|
||||
17. **A source label is an alias, never an identity (phase 191):** the
|
||||
`source_labels` row (alembic `0030`, PK = the DERIVED source name
|
||||
exactly as `Document.source` carries it) holds the owner's
|
||||
`display_name` / `label` / `tags`, and only
|
||||
`app/api/source_labels.py` writes it — `GET /api/source-labels` (the
|
||||
catalogue: the `effective_sources` rows of BOTH kinds — or the
|
||||
`BOR_GIT_SOURCES` names while the table is empty — plus every indexed
|
||||
document source no row derives, de-duplicated and sorted, ONE
|
||||
`all_labels` read for the whole list), `PUT` (partial update: omitted
|
||||
keeps, explicit `null` clears, `tags: []` clears) and `DELETE` (the
|
||||
reset); `require_admin` router-wide, and every name resolution goes
|
||||
through `app.rag.source_removal.resolve_source_name` — never
|
||||
re-derive the basename here or the row keys nothing. A `PUT` for a
|
||||
name that is neither a registry row's derived name nor an indexed
|
||||
document source is 404 (no orphan label rows) and a validation
|
||||
failure writes NOTHING. NEVER rename, resolve or join on the display
|
||||
name: `Document.source`, the `source/path` read/grep/`show_text`
|
||||
locators, the citations, the KB tree and the verbatim caption stay on
|
||||
the derived name, so a rename cannot invalidate a saved chat, a
|
||||
shared page or a locator — and the name reaches the model only
|
||||
through the prompt/retrieval layers (tasks 04/05), never by replacing
|
||||
the identity in a chip, a table or a caption. Pinned by
|
||||
`tests/integration/test_source_labels_api.py` (the 403 gate for
|
||||
anonymous AND token users, the git+local+upload union sorted and
|
||||
de-duplicated at three SELECTs with no `documents.content` read, the
|
||||
partial-update shape, every cap as the framework's 422 with the row
|
||||
byte-identical, both 404 arms, the `ValueError` → 422 funnel, and the
|
||||
"a rename writes nothing but the label row" arm).
|
||||
The Sources-page editor (task 03) is the owner-visible half of the SAME
|
||||
contract and must stay that way: its row list comes from
|
||||
`GET /api/source-labels` (the catalogue union — never a re-derived
|
||||
basename, never a join on the display name), the row's identity cell is a
|
||||
read-only `<code>` of the DERIVED name (an input there WOULD be the
|
||||
alias-contract bug), Save/Reset `PUT`/`DELETE` the per-source path and
|
||||
re-read the ROW from that response (no second GET), the save state is
|
||||
scoped PER ROW (`saveInFlight` — one request per row, the `finally` always
|
||||
settles it, the 404 note names the arm, a collapse discards the draft) —
|
||||
and a FAILED save's note SURVIVES that settle: `updateLabelRowUI` returns
|
||||
before the dirty hint whenever the line carries `.is-error`, so
|
||||
"That source no longer exists — reload the page." cannot be painted over
|
||||
by "unsaved changes" in the tick that wrote it (the defect the task-06
|
||||
story suite walked into); only the next edit (`clearLabelError`) or a
|
||||
collapse clears a note, never the settle path),
|
||||
and the Tags token input's chips reuse the phase-89 tag rule
|
||||
(`.source-label-tag` sits in the `.git-source-hidden-count` rule group — no
|
||||
bespoke tag-input CSS). Pinned by
|
||||
`tests/unit/test_source_labels_editor_ui.py` and, through the real UI over
|
||||
one git + one local + one uploaded source, by
|
||||
`tests/e2e/test_source_name_labels.py` (task 06: the editor saves and
|
||||
re-populates all three kinds, a question that USES the name moves that
|
||||
source's document into the seeded `<documents>` tier with
|
||||
`name`/`label` on the wire while the same row with a name-free question
|
||||
moves nothing, the vanished-source 404 note is visible, Reset reverts the
|
||||
tier, and the alias never appears in a locator, a nearby-doc link, the KB
|
||||
tree or the viewer); the chat source chips, the RAG
|
||||
source tree and the verbatim caption keep the derived name untouched.
|
||||
The selection boost (task 04) is the retrieval half of the SAME
|
||||
contract: ONE `all_labels` read per turn at the
|
||||
retrieval boundary (`app/api/chat.py`, after `retrieve`, before the
|
||||
first selection call) flags `RetrievedChunk.source_name_hit` on the
|
||||
chunks whose `document.source` is a D2 hit (the pure
|
||||
`question_name_hits` rule applied per view — a loop over the few
|
||||
dozen chunks, never a per-chunk query; that ONE result is also what
|
||||
task 05's prompt surfaces consume, so a turn issues one label query
|
||||
and the standalone `question_name_hit_sources` wrapper never runs in
|
||||
one), and `_selection_order` adds
|
||||
`source_name_hit_bonus` (default 0.005; `0` = the byte-identical
|
||||
kill switch; a negative value refuses startup) as a SECOND,
|
||||
independent bonus — once per DOCUMENT, at most two bonuses stacked
|
||||
with the phase-119 `name_hit_bonus` (bounded 0.010 on the fused
|
||||
scale), SELECTION layer only (`score`/`cosine`/`fts_hit`/
|
||||
`query_log.top_score` and the per-turn log line untouched). Pinned
|
||||
by `tests/unit/test_retriever.py` (the kill-switch byte-identity,
|
||||
the bounded two-bonus application, the `fuse` OR-merge, the three
|
||||
selection helpers pulling the knob from settings).
|
||||
The prompt surfacing (task 05) is the model-facing half of the SAME
|
||||
read: `build_high_prompt(names=…)` gives the suggested `<document>`
|
||||
block the ADDITIVE ` name="…"` (immediately after `source="…"`) and
|
||||
` label="…"` attributes — the order is locked
|
||||
(`source` → `name?` → `label?` → `path` → `title` → `date`), the two
|
||||
values escape through the ONE `_attr` helper, and the identity
|
||||
attributes keep their pre-phase unescaped bytes; `regenerate_overview`
|
||||
derives the KB-overview INPUT lines' name map from its own ONE
|
||||
`all_labels` read (`build_overview_prompt(names=…)` swaps the raw
|
||||
source name for the display name in the lite model's input ONLY — the
|
||||
stored outline and its deterministic `Sources: …` tail stay derived
|
||||
name). An unset field adds NOTHING (never `name=""`), tags never
|
||||
render, the LOW prompt never carries a name, and no map / no entry is
|
||||
byte-identical to pre-phase on BOTH builders (the kill pins in
|
||||
`tests/unit/test_prompts.py` and `tests/unit/test_overview.py`, which
|
||||
also marker-pin the one-read-per-surface contract, as
|
||||
`tests/unit/test_chat_gate.py` does on the chat call site).
|
||||
|
||||
## Quick reference
|
||||
```bash
|
||||
podman compose up -d db # start Postgres 17 + pgvector
|
||||
|
||||
Reference in New Issue
Block a user