## Outcome - Reviewed exact snapshot `0e2cd9f7291e941050a21e52b0c56d4f59ea6b7a`. - Branch `sprint20c/performance-activation-hardening-v1` is **clean and identical to `origin/main`** (`0 ahead / 0 behind`). - Therefore, **no Sprint 20C implementation exists yet at this snapshot**; this is the Sprint 20B merge base. - No files were modified. ## Exact release gates ### Required local gate sequence Install the same pinned toolchains as CI first: ```bash cd /home/agent/.hermes/worktrees/FinanceManager-sprint20c python -m pip install -e . -r requirements-ci.txt npm --prefix frontend ci ``` Run: ```bash python -m pytest tests -q npm --prefix frontend test npm --prefix frontend run typecheck npm --prefix frontend run build python -m ruff check src tests scripts python -m compileall -q src tests scripts/ci_portfolio_phase3_gate.py env \ PYTHONPATH=src \ JARVIS_FINANCE_ENV=test \ JARVIS_FINANCE_DB_PATH=/tmp/financemanager-sprint20c-controls/finance.sqlite3 \ JARVIS_FINANCE_RUNTIME_DIR=/tmp/financemanager-sprint20c-controls/runtime \ JARVIS_FINANCE_CI_ROOT=/tmp/financemanager-sprint20c-migration \ JARVIS_FINANCE_WRITE_MODE=disabled \ python scripts/ci_portfolio_phase3_gate.py python -m jarvis_finance.cli.main git-safety-scan . git diff --check origin/main...HEAD git ls-files --others --exclude-standard git status --short --untracked-files=all ``` Repository-mandated aggregate gate: ```bash make PYTHON=.venv/bin/python verify ``` Defined in `Makefile`; execution order is: 1. `compile` 2. backend tests 3. frontend tests 4. frontend typecheck 5. frontend build 6. git-safety scan Important: `Makefile:test` invokes bare `pytest`, so `PYTHON=.venv/bin/python` does **not** control the backend test interpreter unless the venv is activated or `PATH` is set correctly. ### Current test counts At this exact HEAD, the tracked Sprint 20B release evidence records: - Backend: **922 passed** - Frontend: **222 passed** Source: `docs/finance-manager-2.0/sprint20b-performance-activation-daily-valuations-v1.md:81-82`. These are recorded executed results, not fresh collection results from this audit. Fresh recollection was unavailable because the review worktree has neither a Python test venv nor frontend `node_modules`. ### CI jobs Workflow: `.github/workflows/portfolio-phase3-integration.yml` Triggers: - PRs targeting `main` - Manual `workflow_dispatch` - **No push/main post-merge trigger** Jobs: 1. **`backend-full`** - Ubuntu 24.04, Python 3.11, 30-minute timeout - `python -m pip install -e . -r requirements-ci.txt` - `python -m pytest tests -q` - Enforces parsed passed count `>= 667` 2. **`frontend`** - Node 22, 20-minute timeout - `npm ci` - `npm test` - `npm run typecheck` - `npm run build` - No numeric test-count floor 3. **`controls`** - Python 3.11, 25-minute timeout - `git diff --check "origin/${GITHUB_BASE_REF:-main}...HEAD"` - repository safety scan - partial-surface Ruff command - `python -m compileall -q src tests scripts/ci_portfolio_phase3_gate.py` - `python scripts/ci_portfolio_phase3_gate.py` - focused security/reconciliation/performance test group ### Migration/schema gate Files: - `scripts/ci_portfolio_phase3_gate.py` - `src/jarvis_finance/storage/migrations.py` - `docs/operations/finance-manager-deployment-runbook.md` Current schema contract: ```python MIGRATION_VERSION = 49 EXPECTED_SCHEMA = 49 ``` The CI migration gate proves: - empty DB migrates to schema 49; - `PRAGMA integrity_check == "ok"`; - Sprint 5 schema 40 migrates to 49; - synthetic business digest preservation; - expected scope normalization/audit record; - no unexpected ingestion/analysis rows; - second migration run is a no-op; - test settings reject productive runtime paths. Sprint 20C should additionally retain a focused regression proving that the enabled daily valuation CLI: - accepts an already-current schema 49 DB; - does not call `apply_migrations()` or execute DDL; - rejects any schema other than exactly 49 before provider calls/writes; - remains fail-closed when `JARVIS_FINANCE_DAILY_VALUATION_ENABLED != 1`. ## PR, CI and merge procedure No tracked publication script exists. Required GitHub flow: ```bash git status --short --untracked-files=all git diff --check origin/main...HEAD git push -u origin sprint20c/performance-activation-hardening-v1 gh pr create \ --base main \ --head sprint20c/performance-activation-hardening-v1 \ --title '' \ --body-file '' gh pr checks --watch gh pr view --json headRefOid,mergeStateStatus,statusCheckRollup gh pr merge --merge git fetch origin main DEPLOY_SHA="$(git rev-parse origin/main)" ``` Before merge, require all three CI jobs green and final P0/P1/P2 = `0/0/0`. Because CI only runs on PR/manual dispatch, preserve the exact successful PR head and resulting merge SHA explicitly. ## Exact-SHA production deployment Canonical tracked runbook: `docs/operations/finance-manager-deployment-runbook.md` Canonical runtime: - Worktree: `/home/agent/.hermes/worktrees/FinanceManager-main-deploy` - Backend unit: `finance-manager-backend.service` - Frontend unit: `finance-manager-frontend.service` - Backend health: `http://127.0.0.1:18061/api/health` - Frontend: `http://127.0.0.1:15177/` ### 1. Timer fail-closed preflight Before checkout/restart: ```bash systemctl --user is-active finance-manager-market-valuation.timer || true systemctl --user is-enabled finance-manager-market-valuation.timer || true systemctl --user show finance-manager-market-valuation.service \ -p FragmentPath -p WorkingDirectory -p ExecStart -p Environment ``` For the required disabled release state: ```bash systemctl --user disable --now finance-manager-market-valuation.timer systemctl --user daemon-reload test "$(systemctl --user is-active finance-manager-market-valuation.timer)" = inactive test "$(systemctl --user is-enabled finance-manager-market-valuation.timer)" = disabled systemctl --user show finance-manager-market-valuation.service -p Environment | grep -F 'JARVIS_FINANCE_DAILY_VALUATION_ENABLED=0' ``` Do **not** manually start `finance-manager-market-valuation.service` as a smoke test: with activation enabled it is a financial writer. Tracked unit contract: - `deploy/systemd/finance-manager-market-valuation.service` - `deploy/systemd/finance-manager-market-valuation.timer` - daily at `23:30 Europe/Zurich` - `Persistent=true` - service activation gate defaults to `JARVIS_FINANCE_DAILY_VALUATION_ENABLED=0` If installing/updating the reviewed units: ```bash install -m 0644 deploy/systemd/finance-manager-market-valuation.service \ "$HOME/.config/systemd/user/finance-manager-market-valuation.service" install -m 0644 deploy/systemd/finance-manager-market-valuation.timer \ "$HOME/.config/systemd/user/finance-manager-market-valuation.timer" systemctl --user daemon-reload systemctl --user disable --now finance-manager-market-valuation.timer ``` ### 2. Production no-write sentinel Immediately before deployment, capture an owner-only sentinel outside Git containing only: - schema version; - `PRAGMA integrity_check`; - `PRAGMA foreign_key_check`; - canonical hash of every user table’s ordered rows; - table count; - strict hashes/counts for audit, market-run, valuation, analysis, benchmark, coverage and ingestion state. Do not print raw financial rows. Repeat the identical sentinel after restarts and after browser UAT; all hashes/counts must match. ### 3. Exact checkout and build Sprint 20C is schema-preserving, so validate schema 49 read-only and **do not run `migrate` merely as a startup step**: ```bash DEPLOY_SHA='' WORKTREE='/home/agent/.hermes/worktrees/FinanceManager-main-deploy' SOURCE="$HOME/jarvis_runtime/finance-system/data/finance.sqlite3" git -C "$WORKTREE" fetch origin main test "$(git -C "$WORKTREE" rev-parse origin/main)" = "$DEPLOY_SHA" test -z "$(git -C "$WORKTREE" status --porcelain)" git -C "$WORKTREE" checkout --detach "$DEPLOY_SHA" test "$(git -C "$WORKTREE" rev-parse HEAD)" = "$DEPLOY_SHA" ``` Stop the live frontend before replacing/building `dist`, then build: ```bash systemctl --user stop finance-manager-frontend.service (cd "$WORKTREE/frontend" && npm ci && npm run build) ``` Validate production DB read-only: ```bash PYTHONPATH="$WORKTREE/src" \ "$HOME/jarvis_runtime/finance-system/venv/bin/python" - "$SOURCE" <<'PY' import sqlite3, sys conn = sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True) assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 49 assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok" assert list(conn.execute("PRAGMA foreign_key_check")) == [] conn.close() PY ``` ### 4. Restart and identity verification Restart each service as a separate confirmation-gated action: ```bash systemctl --user restart finance-manager-backend.service ``` Then verify: ```bash systemctl --user is-active finance-manager-backend.service systemctl --user show finance-manager-backend.service \ -p MainPID -p WorkingDirectory -p ExecStart curl --fail --silent --show-error http://127.0.0.1:18061/api/health ``` Frontend: ```bash systemctl --user restart finance-manager-frontend.service ``` Then: ```bash systemctl --user is-active finance-manager-frontend.service systemctl --user show finance-manager-frontend.service \ -p MainPID -p WorkingDirectory -p ExecStart curl --fail --silent --show-error http://127.0.0.1:15177/ >/dev/null test "$(git -C "$WORKTREE" rev-parse HEAD)" = "$DEPLOY_SHA" ``` Also verify the expected Sprint 20C OpenAPI/runtime route and read-only mode; HTTP 200 alone is not release identity proof. ## Browser UAT gate There is no tracked Playwright/browser harness. Production UAT is manual/external and must be sentinel-bracketed. Required exact widths: - `1440` - `820` - `390` At each width verify: - actual performance/setup deep section loaded; - one setup card with three tasks; - five KPI cards; - no page-level horizontal overflow; - readable values and labels; - navigation/touch controls reachable; - no technical reason-code leakage; - no external render requests; - zero `console.error`, `pageerror`, failed API requests; - no Confirm/import/backfill/provider-refresh action. After all three widths, rerun the production sentinel and confirm the timer remains inactive, disabled and activation-gated to `0`. ## Release risks 1. **No Sprint 20C candidate yet:** HEAD equals `origin/main`; only Sprint 20B code exists. 2. **Stale backend count protection:** CI advertises/enforces `667`, while HEAD’s release evidence records `922`. 3. **Frontend has no count floor:** deleted tests can pass if the remaining suite exits zero. 4. **Ruff is partial in CI:** it omits important Sprint 20B/20C surfaces such as `cli/main.py`, `daily_valuations.py`, `performance_activation.py`, and their new tests. 5. **`make verify` is incomplete:** omits Ruff, migration gate and `git diff --check`. 6. **Make interpreter mismatch:** `PYTHON=...` does not govern the bare `pytest` subprocess. 7. **No post-merge CI trigger.** 8. **No deployment automation:** exact SHA, unit installation, sentinel, timer state and UAT are manual. 9. **Runbook is migration-stale:** backup text is Sprint-17D/schema-48-specific and the generic deployment block always invokes `migrate`; Sprint 20C should perform a read-only exact-schema-49 check instead. 10. **Runbook builds before stopping frontend**, potentially replacing live assets in place. 11. **Runbook does not install/update the valuation systemd units.** 12. **Canonical backend/frontend units are not tracked**, so installed systemd definitions/environment remain the operational source of truth. 13. **No tracked production-sentinel command or browser automation exists.** 14. Legacy `scripts/restart_backend.sh`/`restart_frontend.sh` target the wrong checkout and ports `8000/5173`, kill listeners with `fuser`, and are explicitly not the production deployment path. ## Audit issues - Fresh suite collection/execution was not possible without altering the review environment: - no local Python venv with `pytest`; - no frontend `node_modules`; - inherited Vitest could not resolve local Vite/Vue packages. - Counts above are therefore accurately labeled as tracked HEAD release evidence, not newly executed results. - Files created or modified: **none**.