phase: 184_sync_bounded_session
Build and Push Containers / build-and-push-app (push) Successful in 1m53s
Build and Push Containers / build-and-push-db (push) Successful in 13s

All gates green; no code changes were needed — the prior failure was a missing final report, not a test failure.

**Verified / implemented (phase 184 final pass)**
- All 4 tasks present in `.agents/phases/complete/184_sync_bounded_session/`; confirmed their output in the worktree: `sync_session_reset_files=200` / `sync_prune_batch=500` on `Settings` (+ non-negative validators), both `SettingMeta` rows under `"Import & sources"`, `.env.example` + `README.md` entries.
- `import_sources` expunges at **exactly two** sites (per-file loop top L972, source boundary L1012) — grep proves no third `expunge`/`expire_all` anywhere in `app/`; `app/db.py:30` untouched.
- `_prune` uses `load_only(id, source, path, is_image, image_path, page_count).order_by(Document.id)` + per-batch commits, with the phase-122/126 guards and `changed_paths` intact.

**Tests / lint / coverage (all 100% green)**
- `uv run pytest --cov=app --cov-report=term-missing` → **4506 passed**, `app/` TOTAL **99%** (>90%).
- `uv run pytest tests/integration/test_import_session_retention.py -v --no-cov` → **12 passed**.
- `uv run pytest tests/e2e/test_sync_session_reset.py -v --no-cov` → **4 passed in isolation**.
- Regression sweep of neighbouring sync suites (each isolated): `test_sync_changed_only` 3, `test_sync_summary_visibility` 3, `test_prune_cascade_cleanup` 3, `test_sync_git_freeze` 2 — all passed.
- `uv run ruff check .` → All checks passed. `uv run pyright` → 0 errors, 0 warnings.

**Completion criteria**
1. Settings + CATALOG + `.env.example` + README; `test_settings_catalog.py` → ✅ green
2. Retention test green with the post-fix bound and both measured numbers in the docstring (pre-fix 12-row monotone staircase / ≈265 MB extrapolated; post-fix ends at 0, add-path peak 14 vs 21→49) → ✅
3. Expunge only at the two documented-safe points; light-row batched prune with guards intact → ✅
4. Every existing importer/sync/removal test green with unchanged expectations → ✅
5. Full suite green, coverage >90%, ruff + pyright clean → ✅
6. Phase E2E green in isolation → ✅
7. README + AGENTS accurate/consistent → ✅
8. Atomic commit — left to the harness (I ran no git write commands).

**Documentation** — Full README (1886 lines) + AGENTS.md (189 lines) review: **verified consistent — no changes needed.** Programmatic cross-checks: every `Settings` field appears in `.env.example` and in the README config reference; every `.env.example` var is documented in that reference; every `tests/…` and `app/…` path cited in README/AGENTS exists; all 10 README page URLs are registered in `app/main.py`; documented E2E port vars (`E2E_APP_PORT_SYNCRESET[_ZERO]`) exist in the test. The only reference-var without a `Settings` field is `BOR_THEME`, which the README explicitly documents as retired (correct as written).

**Notable decisions/deviations** — None. No code, test, or doc edits were made this pass; changes remain entirely in the working tree for the harness to stage. `00_phase.md` still sits in `todo/` while tasks 01–04 are in `complete/` — harness-owned, so I left it untouched.

**Next pending phase:** `185_sync_defer_content_fast_path`.
This commit is contained in:
2026-10-15 23:19:27 -04:00
parent 7b5ea7c2ee
commit 065304b52f
40 changed files with 3512 additions and 13 deletions
+51
View File
@@ -446,6 +446,29 @@ class Settings(BaseSettings):
#: the validator fails loudly at startup (the ``agent_max_rounds``
#: pattern).
upload_max_mb: int = 512
#: Files between identity-map releases inside
#: ``app.rag.importer.import_sources`` (phase 184,
#: ``BOR_SYNC_SESSION_RESET_FILES``): every Nth file of the walk — at
#: the one point where no object from a finished file is reachable —
#: the run's session calls ``expunge_all()``, so whatever the walk
#: still holds goes before the next file is read. ``0`` = NEVER reset
#: — the exact pre-phase-184 behaviour (the operator's no-redeploy
#: rollback, the ``BOR_LOG_LARGE_RESPONSE_BYTES`` precedent). A
#: negative value is a typo — the validator fails startup loudly (the
#: ``agent_max_rounds`` pattern). Read per run (the importer reads
#: ``llm.settings``, so a saved override applies to the NEXT sync).
sync_session_reset_files: int = 200
#: Pruned documents per commit inside ``app.rag.importer._prune``
#: (phase 184, ``BOR_SYNC_PRUNE_BATCH``): the delete loop commits
#: every Nth document instead of once at the end of the whole import,
#: so a mass prune releases its rows as it goes. ``0`` = ONE commit at
#: the end — the exact pre-phase-184 behaviour (the same rollback
#: knob). A negative value is a typo — the validator fails startup
#: loudly (the ``agent_max_rounds`` pattern). Note the deliberate
#: atomicity change of a non-zero batch (phase 184 A2): a crash
#: 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
# --- Image documents (phase 122: standalone images as documents) ---
#: Master switch for image-document indexing (phase 122,
@@ -809,6 +832,34 @@ class Settings(BaseSettings):
raise ValueError("upload_max_mb must be > 0 (MiB)")
return v
@field_validator("sync_session_reset_files")
@classmethod
def _sync_session_reset_files_non_negative(cls, v: int) -> int:
"""``0`` is the pre-phase-184 rollback (never reset) — a negative
value is a typo that would make the cadence comparison constant,
so it fails startup naming the field (the
``agent_max_rounds`` pattern, phase 184)."""
if v < 0:
raise ValueError(
"sync_session_reset_files must be >= 0 (0 = never reset the"
" import session)"
)
return v
@field_validator("sync_prune_batch")
@classmethod
def _sync_prune_batch_non_negative(cls, v: int) -> int:
"""``0`` is the pre-phase-184 rollback (one commit at the end of
the prune) — a negative value is a typo that would make the batch
comparison constant, so it fails startup naming the field (the
``agent_max_rounds`` pattern, phase 184)."""
if v < 0:
raise ValueError(
"sync_prune_batch must be >= 0 (0 = one commit at the end of"
" the prune)"
)
return v
@field_validator("chat_image_max_mb")
@classmethod
def _chat_image_max_mb_positive(cls, v: int) -> int: