phase: 98_sync_summary_visibility
Build and Push Containers / build-and-push-app (push) Successful in 1m51s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All verification complete. Final report:

**Phase 98 — Sync summary visibility: final verification pass** (all 5 tasks already complete; implementation verified against the design, no defects found, no code changes needed)

- **Implementation checked:** `SyncStatus` phase machine (4 new keys, terminal-keep counts), `on_progress` hook in `generate_folder_summaries`, `summary_pending` on `KbTreeSource`/`KbTreeFolder` + D3 rule in `build_kb_tree`, phase-aware sync labels + pending UI in `sources.js`, `.kb-summary-pending` CSS — all match decisions D1–D5.
- **Unit + integration:** `uv run pytest` → 2184 tests, 0 failed/errors (exit 0)
- **Coverage:** `uv run pytest --cov=app --cov-report=term-missing` → **99%** on `app/` (criterion >90% ✓; `app/api/sync.py` and `app/rag/folder_summaries.py` at 100%)
- **Lint/types:** `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings
- **Phase E2E (isolation):** `uv run pytest tests/e2e/test_sync_summary_visibility.py -v --no-cov` → **3 passed** (phase machine, live label, pending markers + gap-fill self-heal)
- **Regression suites (each isolated, `--no-cov`):** test_kb_tree ✓, test_ls_tree_drilldown 3 ✓, test_sync_button 3 ✓, test_sync_upload_progress 4 ✓, test_oneshot_llm_retry 2 ✓, test_local_directory_sources 3 ✓
- **Completion criteria:** all 7 verified green — status phase fields + terminal semantics; `Writing KB overview…`/`Summarizing folders… (n/m)` labels (title + aria-live); pending set == `missing_folder_summaries` (integration cross-check pinned at `test_docs_api.py:428`); CLI/`ls` byte-identity (no changes to those paths, pins green); suite/coverage/lint gates; dedicated + regression E2E. Commit left to the harness per protocol (no `git add`/`commit` run).
- **Decisions/deviations:** none — no fixes were required this pass.
- **Next pending phase:** `99_kb_tree_table_and_back_nav`.
This commit is contained in:
2026-09-13 00:23:05 -04:00
parent 909c96c7bc
commit f665a83b1a
39 changed files with 3265 additions and 112 deletions
@@ -729,6 +729,13 @@ def test_api_changed_sync_generates_folder_rows(
assert body["detail"]["added"] == 2
assert body["detail"]["overview"] is True
assert body["detail"]["sources_version"] == 1
# Phase 98 (task 01): the progress hook fired on the CHANGED-KB
# regeneration branch — the terminal clears phase + current_summary
# and keeps the hook's final counts (2 candidates: root + a/ — the
# only writer of those counters is the hook itself).
assert body["phase"] is None
assert body["current_summary"] is None
assert body["summaries_done"] == 2 and body["summaries_total"] == 2
# One LLM client for probe + import + overview + folder summaries;
# exactly two FOLDER_SUMMARY_MODE calls (root + the 2-doc a/ folder).
assert len(clients) == 1
@@ -770,6 +777,15 @@ def test_api_unchanged_resync_burns_zero_folder_calls(
body = _poll(sync_client, "success")
assert body["detail"]["overview"] is False
assert body["detail"]["sources_version"] == 1 # unchanged → no bump
# Phase 98 (task 01): the no-gap skip branch never CALLS the
# generator — the hook never fires, so the terminal's summary
# counters stay 0/0 (the run stayed in the import phase through to
# the terminal, which then cleared it). The counter keep at 0/0 is
# the deterministic pin of "never fired": a fired hook's counts
# would survive to the terminal (the keep-final-counts rule).
assert body["phase"] is None
assert body["current_summary"] is None
assert body["summaries_done"] == 0 and body["summaries_total"] == 0
assert len(clients) == 2
# Zero GENERATION calls — no folder summary, no KB overview. The
# only chat the re-sync's client makes is the phase-41 probe's ping.
@@ -849,6 +865,12 @@ def test_api_unchanged_resync_with_gap_fills_only_the_missing_row(
"chunks", "summaries", "summary_errors", "overview",
"sources_version",
}
# Phase 98 (task 01): the progress hook fired on the UNCHANGED-KB
# GAP-FILL branch — total is the missing count (the one deleted
# row), kept at the terminal next to the cleared phase keys.
assert body["phase"] is None
assert body["current_summary"] is None
assert body["summaries_done"] == 1 and body["summaries_total"] == 1
assert len(clients) == 2
# Targeted fill — exactly ONE folder call, the missing folder only
# (plus the phase-41 probe's ping on the same client).
@@ -910,6 +932,11 @@ def test_api_folder_lite_failure_keeps_rows_stays_green_and_bumps(
assert body["error"] is None
assert body["detail"]["updated"] == 1
assert body["detail"]["sources_version"] == 2 # the bump was not blocked
# Phase 98 (task 01): the failed (fail-soft) folder still advanced
# the hook's counter — the hook fires BEFORE the attempt, so the
# terminal keeps 2/2, not 1/2.
assert body["phase"] is None and body["current_summary"] is None
assert body["summaries_done"] == 2 and body["summaries_total"] == 2
assert current_sources_version(db) == 2
rows_after = _rows(db)
assert rows_after["LocalDocs", "a"] == rows_before["LocalDocs", "a"]
@@ -951,6 +978,11 @@ def test_api_empty_table_first_sync_regenerates(
assert body["detail"]["overview"] is False # unchanged → no overview
assert body["detail"]["sources_version"] == 1 # unchanged → no bump
# Phase 98 (task 01): the gap-fill branch over the emptied table —
# every candidate missing (2), the hook's final counts kept at the
# terminal next to the cleared phase keys.
assert body["phase"] is None and body["current_summary"] is None
assert body["summaries_done"] == 2 and body["summaries_total"] == 2
assert len(clients) == 2
assert len(_folder_calls(clients[1])) == 2 # the folders regenerated
assert set(_rows(db)) == {("LocalDocs", ""), ("LocalDocs", "a")}