Files
ducoterra 0f77e9a876
Build and Push Containers / build-and-push-app (push) Successful in 2m3s
Build and Push Containers / build-and-push-db (push) Failing after 14s
phase: 121_git_source_tokens
**Phase 121 final verification pass — all green** (all 4 tasks already in `complete/`; verified, no defects found, no changes needed)

- Verified implementation vs phase design: migration `0021` (reversible, round-tripped via `alembic downgrade base` + `upgrade head` → head `0021`), `GitSource.token` column, `normalize_credential`/`clone_url_for`/`sanitize_url`, clone callers switched (`sync.py`, `import_docs.py`), masked token fields in add form + editor, `extra="forbid"` output shapes
- Tests: `uv run pytest` → 2662 passed, 0 failed (exit 0); `uv run pytest --cov=app --cov-report=term-missing` → TOTAL **99%** (≥90% gate)
- Lint/types: `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings
- E2E in isolation: `uv run pytest tests/e2e/test_git_source_tokens.py -v --no-cov` → **4 passed**

Completion criteria:
1. Private repo (UI add or pasted embedded-token URL) clones with injected token; token absent from every API response, page text, title attr, and full HTML — **PASS** (integration raw-JSON assertions + E2E `_assert_token_nowhere`)
2. Legacy embedded-token rows still clone from stored URL; output sanitized — **PASS** (`test_sync_legacy_row_clones_with_original_stored_url`, `test_get_masks_legacy_embedded_token_row`, env-fallback masking)
3. Public/local sources byte-identical — **PASS** (verbatim-URL + no-userinfo-unchanged tests)
4. pytest / coverage / ruff / pyright — **PASS** (see above)
5. Commit + phase move — harness responsibility; task files already in `complete/`, changes left in working tree (no commit made, per protocol)

Notable: no deviations; DB left at head, functional. Next pending phase: **122_image_documents** (then 123_chat_image_questions).
2026-09-24 20:51:39 -04:00

48 lines
1.7 KiB
Python

"""git_sources.token: private-repo credential column (phase 121)
Revision ID: 0021
Revises: 0020
Create Date: 2026-09-24
Phase 121 (private git sources: a token that never reaches the UI or
the API — task 01, storage only):
* ``git_sources.token`` — TEXT NULLABLE, no server default: the private
repo credential (LOCKED A2) the owner types into the masked
Sources-page field. NULL = public repo (or a legacy row whose
credential is still embedded in ``url`` — those rows keep their
stored value, which is what authenticates the clone, and are
sanitized on OUTPUT only, task 02). Stored plaintext BY NECESSITY:
the repo must remain cloneable, so the raw credential must be
recoverable at sync time; the Postgres DB is the trusted store and is
never served to the UI. The column is injected into the clone URL
ONLY at clone time (task 02's ``clone_url_for``) and is NEVER
returned by any API shape (the output models gain no token field —
the omission is a documented contract).
One additive, fully reversible migration (A13); no other schema
change. Normalization of embedded-token URLs on write and output
sanitization are code (tasks 02/03) — this revision only carries the
column.
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "0021"
down_revision = "0020"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("git_sources", sa.Column("token", sa.Text(), nullable=True))
def downgrade() -> None:
# The token column is the only 0021 artefact — dropping it leaves
# 0020's schema byte-identical (A13, fully reversible).
op.drop_column("git_sources", "token")