phase: 194_sync_pending_count
**Phase 194 — final verification pass: all green.**
**Verified (all 5 tasks already complete; nothing re-implemented needed)**
- `app/rag/importer.py`: `_prescan_pending` + `_pending_hash_stmt` (scalar `documents.file_hash`, own session, ONE `asyncio.to_thread` span, REPLACES the stats pre-walk), `on_scan_progress`, `progress` pair = pending position, `0` branch = byte-identical phase-193 walk.
- `app/config.py`/`settings_catalog.py`/`.env.example`: `sync_pending_count=1` + validator; `app/api/sync.py`: `"scan"` phase, `scan_done`/`scan_total` (additive after `files_total`, kept at every terminal), forced/throttled RSS split; `frontend/assets/sources.js`: `fmtSyncPhaseLabel` scan branch first-arm, `fmtSyncLabel` untouched.
**Tests / lint / coverage (exact commands)**
- `uv run pytest --cov=app --cov-report=term-missing` → **4835 passed**, `app/` 99% (7240 stmts / 16 miss), exit 0
- `uv run pytest tests/e2e/test_sync_pending_count.py -v --no-cov` → **8 passed in 150.81s** (phase suite, isolated)
- 14 sync-lineage E2E suites, one pytest process each (`test_sync_remaining_count`, `…_chunk_progress`, `…_progress_box`, `…_peak_memory_visible`, `…_label_mobile`, `…_upload_progress`, `test_archive_upload_sources`, `…_image_progress`, `…_summary_visibility`, `…_status_freshness`, `test_smoke`, `test_fast_path_deferred_content`, `test_sync_session_reset`, `test_sync_git_freeze`) → **all exit 0**
- `uv run ruff check .` → All checks passed; `uv run pyright` → **0 errors**
**Completion criteria** — all met: `N remaining` = predicted change set (E2E arms 1/2/5, first import counts the walk, no-change → no count part); `scan` phase visible + flips to `import`, scan pair reset/kept at terminals (`test_sync_scan_phase.py`); `BOR_SYNC_PENDING_COUNT=0` walk semantics ("7 remaining" arm) + **negative refuses startup naming the field (verified live by importing `app.main`)**; pre-scan off-loop + cancel lands `cancelled` (integration pins); CLI/upload byte-identical (unedited pins green); payload additive (`_PRE_187_STATUS_KEYS | _MEMORY_KEYS | _SCAN_KEYS`); suite green, coverage >90%, lint/types clean.
**Documentation** — 3 fixes: README sync "States" bullet had an **unclosed parenthesis** (pre-existing, 193/194) and the scan-label "total is 0" clause was ambiguous about a `=0` run (it still counts the walk once the import starts); **AGENTS.md rule 17 had a stray `)`** (pre-existing from phase 191). Also verified consistency both directions for all 87 settings ↔ README ↔ `.env.example` ↔ settings catalog, the pages table ↔ `app/main.py` routes, no stale `BOR_` names, and README/AGENTS claims through phase 194.
**Notes/deviations** — no live-infra changes (prod sync knob ships default-on, no deployment touched); no git index/history commands run (all changes left in the working tree for the harness); commit criterion left to the harness as instructed.
**Next pending phase:** none — `.agents/phases/todo/` holds only `194_sync_pending_count`, which is now ready to move to `complete/`.
This commit is contained in:
@@ -524,6 +524,22 @@ class Settings(BaseSettings):
|
||||
#: mid-prune leaves a PREFIX pruned, and the next sync converges
|
||||
#: because ``seen`` is recomputed from the filesystem every run.
|
||||
sync_prune_batch: int = 500
|
||||
#: Predict the run's change set BEFORE the walk, so the sync's running
|
||||
#: count is the count of the WORK and not of the walk (phase 194,
|
||||
#: ``BOR_SYNC_PENDING_COUNT``). ``1`` (default): ``import_sources``
|
||||
#: pre-scans the importable files (one streamed sha256 + one scalar
|
||||
#: ``documents.file_hash`` read each, off the event loop) and the
|
||||
#: per-file ``progress`` hook then reports ``done``/``total`` as the
|
||||
#: position in that PENDING set — the owner's "1216 remaining" fix.
|
||||
#: ``0`` = the exact phase-193 behaviour (the stats-only pre-walk and
|
||||
#: ``done = summary.files``, i.e. "every file left to WALK") — the
|
||||
#: operator's no-redeploy rollback if the extra read cost ever bites.
|
||||
#: A negative value is a typo — the validator fails startup loudly
|
||||
#: (the ``agent_max_rounds`` pattern). Read per run (the importer reads
|
||||
#: ``llm.settings`` once per sync, so a saved override applies to the
|
||||
#: NEXT sync) and only by a caller that passes ``progress`` — the CLI
|
||||
#: and the upload job pay nothing.
|
||||
sync_pending_count: int = 1
|
||||
|
||||
# --- Image documents (phase 122: standalone images as documents) ---
|
||||
#: Master switch for image-document indexing (phase 122,
|
||||
@@ -930,6 +946,20 @@ class Settings(BaseSettings):
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("sync_pending_count")
|
||||
@classmethod
|
||||
def _sync_pending_count_non_negative(cls, v: int) -> int:
|
||||
"""``0`` is the phase-193 rollback (count the walk, not the work) —
|
||||
a negative value is a typo that no branch of the importer can mean,
|
||||
so it fails startup naming the field (the ``agent_max_rounds``
|
||||
pattern, phase 194)."""
|
||||
if v < 0:
|
||||
raise ValueError(
|
||||
"sync_pending_count must be >= 0 (0 = count the whole walk, "
|
||||
"the pre-phase-194 behaviour)"
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("chat_image_max_mb")
|
||||
@classmethod
|
||||
def _chat_image_max_mb_positive(cls, v: int) -> int:
|
||||
|
||||
Reference in New Issue
Block a user