diff --git a/periscope/src/backend/Dockerfile b/periscope/src/backend/Dockerfile index 7583ccf..b11dda7 100644 --- a/periscope/src/backend/Dockerfile +++ b/periscope/src/backend/Dockerfile @@ -3,24 +3,23 @@ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* WORKDIR /app -# Overlay: PinScope inherited tree, then native Periscope modules. COPY periscope/src/backend/requirements*.txt /app/backend/ RUN pip install --no-cache-dir -r /app/backend/requirements.txt \ && if [ -f /app/backend/requirements-gateway.txt ]; then \ pip install --no-cache-dir -r /app/backend/requirements-gateway.txt; \ fi -COPY periscope/dependency/backend/ /app/backend/ COPY periscope/src/backend/ /app/backend/ - -COPY periscope/dependency/taxonomy/ /app/taxonomy/ COPY periscope/src/taxonomy/ /app/taxonomy/ -COPY periscope/dependency/skills/ /app/skills/ COPY periscope/src/skills/ /app/skills/ -COPY periscope/dependency/frontend/content/changelog.md /app/changelog.md COPY periscope/src/frontend/content/changelog.md /app/changelog.md COPY vendor/ /app/vendor/ +# Leftover seams (not PinScope product): unused LLM providers + GCS storage. +COPY periscope/dependency/backend/services/llm/anthropic_provider.py /app/backend/services/llm/anthropic_provider.py +COPY periscope/dependency/backend/services/llm/gemini_provider.py /app/backend/services/llm/gemini_provider.py +COPY periscope/dependency/backend/services/storage_gcs.py /app/backend/services/storage_gcs.py + EXPOSE 8080 CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/periscope/src/backend/repo_paths.py b/periscope/src/backend/repo_paths.py index bbfa4d6..4926cb8 100644 --- a/periscope/src/backend/repo_paths.py +++ b/periscope/src/backend/repo_paths.py @@ -2,10 +2,9 @@ Physical split: periscope/src native Periscope - periscope/dependency inherited PinScope (in-tree dependency) + periscope/dependency leftover seams + fixtures (not copied wholesale into Docker) -Docker copies dependency then src for backend, taxonomy, skills, and -frontend content (changelog / legal / file-guide). +Docker copies native src, vendor/, and leftover seam files only. """ from __future__ import annotations diff --git a/periscope/src/docs/development/DEPENDENCY_AUDIT.md b/periscope/src/docs/development/DEPENDENCY_AUDIT.md new file mode 100644 index 0000000..b99549b --- /dev/null +++ b/periscope/src/docs/development/DEPENDENCY_AUDIT.md @@ -0,0 +1,44 @@ +# Dependency audit — PinScope product vs leftover seams (2026-09-20) + +Docker images no longer copy the unused PinScope product tree. `periscope/dependency/` remains **in git** for leftover seams, `simple_project/`, docs, and historical copies. Git-delete of the directory is **not** done: leftover shadcn/Clerk/billing/analytics, unused LLM providers, GCS storage, and landing png/gif are still required for overlay CI / optional seams. + +No product import from `dependency/` is required to boot FastAPI (`backend.main`) when `PYTHONPATH` is `periscope/src`. + +## Skip (not PinScope product) + +- `frontend/src/components/ui/*` (shadcn) +- Clerk: `use-optional-auth.ts`, `clerk-theme-provider.tsx`, `proxy.ts` +- billing: `components/billing/*`, `pricing-section.tsx` +- analytics/reddit pixels +- `vendor/impedancefinder/` (third-party solver) + +## Remaining `periscope/dependency` hits (live / Docker / imports) + +| Kind | Path | Why it remains | +| --- | --- | --- | +| Docker leftover | `backend/services/llm/anthropic_provider.py` | optional provider; factory defaults DeepSeek | +| Docker leftover | `backend/services/llm/gemini_provider.py` | optional provider | +| Docker leftover | `backend/services/storage_gcs.py` | optional GCS | +| Docker leftover | `frontend/src/components/ui/*` | shadcn | +| Docker leftover | `frontend/src/hooks/use-optional-auth.ts` | Clerk/open-core seam | +| Docker leftover | `frontend/src/proxy.ts` | Clerk seam | +| Docker leftover | `frontend/src/components/theme/clerk-theme-provider.tsx` | Clerk seam | +| Docker leftover | `frontend/src/components/billing/*` | billing seam | +| Docker leftover | `frontend/src/components/analytics/*` | reddit pixels | +| Docker leftover | `frontend/src/components/marketing/pricing-section.tsx` | billing marketing | +| Docker leftover | `frontend/public/report.png` `datasheet.gif` `derating.png` | landing captures still inherited shots | +| Git fixture | `simple_project/` | tests + README demo | +| Git docs | `docs/how-it-works.svg` | README image | +| Historical copies | `dependency/backend/**`, `dependency/frontend/**` product modules | unused by Docker; not git-deleted | + +## Faradworks / PinScope string hits (not skipped seams) + +Attribution / legal / changelog / README (AGPL lineage). Dual-read JWT issuer `pinscope-local` in `pinscope_compat`. Compose network aliases `pinscope-backend` / `pinscope-frontend` for Caddy. `LICENSE` AGPL unchanged. + +## Product modules no longer copied into the image + +Inherited graph/parsers/pipeline/Next shell/taxonomy/skills under `dependency/` are **not** `COPY`'d. Native equivalents live in `periscope/src`. + +## Boot proof + +`tests/test_periscope_boot_without_dependency_product.py` runs `backend.main` with `PYTHONPATH=periscope/src` only. diff --git a/periscope/src/frontend/content/changelog.md b/periscope/src/frontend/content/changelog.md index 78dcfd5..3c452b4 100644 --- a/periscope/src/frontend/content/changelog.md +++ b/periscope/src/frontend/content/changelog.md @@ -2,6 +2,13 @@ What's new in Periscope. +## 2.59.0 — 2026-09-20 — Docker without unused PinScope product tree + +Images copy `periscope/src` plus leftover seams (shadcn/Clerk/billing, unused LLM providers, GCS, landing png/gif) and `vendor/`. They do not copy the inherited PinScope product tree. `dependency/` stays in git. Audit: `periscope/src/docs/development/DEPENDENCY_AUDIT.md`. Pad≠Via unchanged. + +- [Changed] Backend/frontend Dockerfiles drop wholesale `COPY periscope/dependency/{backend,frontend}`. +- [New] Boot test runs FastAPI with `PYTHONPATH=periscope/src` only. + ## 2.58.0 — 2026-09-20 — Native JWT middleware and useAuthApi `backend/middleware/auth.py` and `frontend/src/hooks/use-auth-api.ts` are original Periscope code. Skip paths, Bearer/query token, Clerk RS256 leftover, and local HS256 (`local_jwt.decode_token`) stay the same HTTP contract. `routers/auth.py`, `data/auth/users`, and `AUTH_JWT_SECRET` are unchanged. Pad≠Via unchanged. diff --git a/periscope/src/frontend/dockerfile b/periscope/src/frontend/dockerfile index 5d3f44b..7e021cb 100644 --- a/periscope/src/frontend/dockerfile +++ b/periscope/src/frontend/dockerfile @@ -10,9 +10,22 @@ ENV NEXT_PUBLIC_AUTH_MODE=$NEXT_PUBLIC_AUTH_MODE COPY periscope/src/frontend/package.json periscope/src/frontend/package-lock.json ./ RUN npm ci -COPY periscope/dependency/frontend/ ./ COPY periscope/src/frontend/ ./ +# Leftover seams (shadcn/Clerk/billing/analytics) — not PinScope product. +COPY periscope/dependency/frontend/src/components/ui ./src/components/ui +COPY periscope/dependency/frontend/src/components/billing ./src/components/billing +COPY periscope/dependency/frontend/src/components/analytics ./src/components/analytics +COPY periscope/dependency/frontend/src/components/theme/clerk-theme-provider.tsx ./src/components/theme/clerk-theme-provider.tsx +COPY periscope/dependency/frontend/src/components/marketing/pricing-section.tsx ./src/components/marketing/pricing-section.tsx +COPY periscope/dependency/frontend/src/hooks/use-optional-auth.ts ./src/hooks/use-optional-auth.ts +COPY periscope/dependency/frontend/src/proxy.ts ./src/proxy.ts + +# Landing captures still PinScope shots until native screenshots exist. +COPY periscope/dependency/frontend/public/report.png ./public/report.png +COPY periscope/dependency/frontend/public/datasheet.gif ./public/datasheet.gif +COPY periscope/dependency/frontend/public/derating.png ./public/derating.png + ENV NEXT_TELEMETRY_DISABLED=1 RUN echo "BUILD API URL=$NEXT_PUBLIC_API_URL AUTH_MODE=$NEXT_PUBLIC_AUTH_MODE" diff --git a/periscope/src/frontend/package-lock.json b/periscope/src/frontend/package-lock.json index e17b2f4..20fcf5f 100644 --- a/periscope/src/frontend/package-lock.json +++ b/periscope/src/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "periscope-web", - "version": "2.58.0", + "version": "2.59.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "periscope-web", - "version": "2.58.0", + "version": "2.59.0", "dependencies": { "@base-ui/react": "^1.3.0", "@types/dagre": "^0.7.54", diff --git a/periscope/src/frontend/package.json b/periscope/src/frontend/package.json index 38a4417..7a73bae 100644 --- a/periscope/src/frontend/package.json +++ b/periscope/src/frontend/package.json @@ -1,6 +1,6 @@ { "name": "periscope-web", - "version": "2.58.0", + "version": "2.59.0", "private": true, "scripts": { "sync-version": "node scripts/sync-version.mjs", diff --git a/scripts/update-periscope.sh b/scripts/update-periscope.sh index ab5dcd9..8f72718 100755 --- a/scripts/update-periscope.sh +++ b/scripts/update-periscope.sh @@ -126,6 +126,9 @@ if [[ ! -f .env ]]; then if [[ -f backend/.env ]]; then log "No ./.env — copying backend/.env" cp backend/.env .env + elif [[ -f periscope/src/backend/.env.example ]]; then + log "No ./.env — copying periscope/src/backend/.env.example (you must set DEEPSEEK_API_KEY)" + cp periscope/src/backend/.env.example .env elif [[ -f periscope/dependency/backend/.env.example ]]; then log "No ./.env — copying periscope/dependency/backend/.env.example (you must set DEEPSEEK_API_KEY)" cp periscope/dependency/backend/.env.example .env diff --git a/tests/test_periscope_boot_without_dependency_product.py b/tests/test_periscope_boot_without_dependency_product.py new file mode 100644 index 0000000..a7bd328 --- /dev/null +++ b/tests/test_periscope_boot_without_dependency_product.py @@ -0,0 +1,55 @@ +"""Docker copies leftover seams only; FastAPI boots without dependency product.""" + +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +BE_DOCKER = (ROOT / "periscope" / "src" / "backend" / "Dockerfile").read_text(encoding="utf-8") +FE_DOCKER = (ROOT / "periscope" / "src" / "frontend" / "dockerfile").read_text(encoding="utf-8") +AUDIT = ROOT / "periscope" / "src" / "docs" / "development" / "DEPENDENCY_AUDIT.md" + + +def test_audit_is_written(): + text = AUDIT.read_text(encoding="utf-8") + assert "leftover seams" in text.lower() or "Leftover" in text + assert "simple_project" in text + assert "Git-delete of the directory is **not** done" in text + + +def test_dockerfiles_do_not_copy_dependency_product_trees(): + assert "COPY periscope/dependency/backend/ /app/backend/" not in BE_DOCKER + assert "COPY periscope/dependency/taxonomy/" not in BE_DOCKER + assert "COPY periscope/dependency/skills/" not in BE_DOCKER + assert "COPY periscope/dependency/frontend/ ./" not in FE_DOCKER + assert "COPY vendor/" in BE_DOCKER + assert "COPY periscope/src/backend/" in BE_DOCKER + assert "anthropic_provider.py" in BE_DOCKER + assert "use-optional-auth.ts" in FE_DOCKER + assert "report.png" in FE_DOCKER + + +def test_boot_without_dependency_on_pythonpath(): + src = ROOT / "periscope" / "src" + env = os.environ.copy() + env["PYTHONPATH"] = str(src) + env.pop("AUTH_JWT_SECRET", None) + proc = subprocess.run( + [ + sys.executable, + "-c", + "from backend.main import app; from backend.middleware.auth import verify_request_user; " + "print(app.title); print(verify_request_user.__name__)", + ], + cwd=ROOT, + env=env, + capture_output=True, + text=True, + check=False, + ) + assert proc.returncode == 0, proc.stderr + assert "PeriscopeX" in proc.stdout + assert "verify_request_user" in proc.stdout diff --git a/tests/test_periscope_frontend_content_overlay.py b/tests/test_periscope_frontend_content_overlay.py index ebea717..84dd5fb 100644 --- a/tests/test_periscope_frontend_content_overlay.py +++ b/tests/test_periscope_frontend_content_overlay.py @@ -28,9 +28,9 @@ def test_legal_pages_are_operator_not_faradworks_controller(): assert "This Service is **not** operated by Faradworks, Inc." in terms -def test_changelog_stamp_is_2_58_0_and_src_wins(): +def test_changelog_stamp_is_2_59_0_and_src_wins(): text = (SRC / "changelog.md").read_text(encoding="utf-8") - assert "## 2.58.0 — 2026-09-20" in text + assert "## 2.59.0 — 2026-09-20" in text first = changelog_paths()[0] assert first.parts[-3:] == ("src", "frontend", "content") or first.name == "changelog.md" assert first == SRC / "changelog.md" diff --git a/tests/test_periscope_leftover_package_requirements.py b/tests/test_periscope_leftover_package_requirements.py index 57e8b20..dc35c08 100644 --- a/tests/test_periscope_leftover_package_requirements.py +++ b/tests/test_periscope_leftover_package_requirements.py @@ -17,7 +17,7 @@ DOCKERIGNORE = ROOT / ".dockerignore" def test_package_json_is_periscope_web(): text = PKG.read_text(encoding="utf-8") assert '"name": "periscope-web"' in text - assert '"version": "2.58.0"' in text + assert '"version": "2.59.0"' in text assert "Native Periscope overlay" not in text[:400] assert LOCK.is_file() lock = LOCK.read_text(encoding="utf-8") @@ -45,6 +45,7 @@ def test_docker_installs_from_src(): assert "COPY periscope/src/frontend/package.json" in fe assert "periscope/src/frontend/package-lock.json" in fe assert "COPY periscope/src/backend/requirements*.txt" in be + assert "COPY periscope/dependency/backend/ /app/backend/" not in be ignore = DOCKERIGNORE.read_text(encoding="utf-8") assert "faradworks-logo-white.png" in ignore assert "power-tree.gif" in ignore