phase: 173_docs_push_creds_split
All gates green. Final report: **Phase 173 — final verification pass (all tasks already complete in `complete/`)** - Verified the full implementation is in the working tree: `docs_user`/`docs_token` settings + catalogue rows (task 01), `docs_repo` PUT 422 guard + `docs_token` write-only pin (task 02), `run_git(env)` + `push_document(user, token)` credential env (task 03), `settings_migrate.py` wired into `create_app()` (task 04), E2E suite (task 05). - **Completion criteria:** 1. GET `/api/admin/settings` lists `docs_user` (plain) + `docs_token` (secret, `value=null`), token never in any body — ✅ `test_docs_token_is_write_only_masked_end_to_end` + E2E sentinel-absent-from-DOM 2. PUT `docs_repo` with `://user:pass@` → 422 naming the fields; clean URL passes — ✅ integration + E2E UI-side 3. Token set → `GIT_CURL_USERNAME`/`GIT_CURL_PASSWORD`/`GIT_TERMINAL_PROMPT=0` on every git step; empty → `env=None` — ✅ mocked-git unit tests 4. Legacy env split once at startup, idempotent, one INFO line, `.env` untouched — ✅ `test_settings_migrate.py` (4 shapes) + `test_docs_push_migrate.py` 5. Full suite green, coverage >90%, lint clean — ✅ 6. E2E isolated: `test_docs_push_creds.py` 4/4; regression set (panel + layout + response_to_docs) 21/21 — ✅ 7. Commit + phase move — harness's job (per instructions, not mine). 8. README/AGENTS consistent — ✅ (below) - Tests/lint: `uv run pytest` exit 0 (4112 passed) · `uv run pytest --cov=app --cov-report=term-missing` TOTAL 99% · `uv run ruff check .` clean · `uv run pyright` 0 errors - Docs: README.md (incl. Docs-push config section, guard/migration paragraph, `.env.example` block) and AGENTS.md verified consistent in full; fixed two stale docstrings in `app/config.py` + `app/core/settings_catalog.py` (`is_set` → the API's actual `has_value` field). - No live-infrastructure changes (no live step in this phase). No next pending phase — `todo/` held only 173.
This commit is contained in:
+13
-2
@@ -317,7 +317,11 @@ def file_commit_dates(dest: Path | str) -> dict[str, datetime]:
|
||||
return {}
|
||||
|
||||
|
||||
def run_git(argv: list[str], cwd: Path) -> str:
|
||||
def run_git(
|
||||
argv: list[str],
|
||||
cwd: Path,
|
||||
env: dict[str, str] | None = None,
|
||||
) -> str:
|
||||
"""Run one git command, capturing output; raise GitSyncError on failure.
|
||||
|
||||
The single ``git`` invocation point for the whole app (A11). Every
|
||||
@@ -326,9 +330,16 @@ def run_git(argv: list[str], cwd: Path) -> str:
|
||||
handling stays uniform: captured stdout on success, and
|
||||
:class:`GitSyncError` carrying git's stderr on a non-zero exit (or
|
||||
when the git binary is missing from PATH).
|
||||
|
||||
``env`` is passed through verbatim to :func:`subprocess.run`:
|
||||
``None`` (the default — the sync path's only form) inherits the
|
||||
parent environment. **The subprocess env-replacement trap:** a dict
|
||||
REPLACES the parent environment, so a caller that needs add-on
|
||||
variables (the docs-push credential env, C2) must build a complete
|
||||
environment (a merge over ``os.environ``) itself.
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.run(argv, cwd=cwd, capture_output=True, text=True)
|
||||
proc = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, env=env)
|
||||
except FileNotFoundError:
|
||||
raise GitSyncError("git was not found on PATH — install git and retry") from None
|
||||
if proc.returncode != 0:
|
||||
|
||||
Reference in New Issue
Block a user