Co-Study4Grid / docs /architecture /deployment-trust.md
github-actions[bot]
Deploy 7688ef1
13d4e44
|
Raw
History Blame Contribute Delete
4.68 kB

Deployment trust & reproducibility (D7, 2026-07)

Deep revision D7 from 2026-07-full-repo-review.md. Addresses finding #5 ("the trust model no longer matches deployment") and the deployment gaps in dimension 12: desktop-era filesystem RPCs shipping unauthenticated on the public Space, and a continuous, ungated, force-pushed deploy with no rollback primitive.

The security-critical half — the lockdown profile — shipped in full. The reproducibility tail (a pinned Python closure) is a documented follow-up because it must be resolved on the deployment's interpreter.

The lockdown profile

The filesystem RPCs assume the caller is the local operator:

Endpoint Desktop intent Risk on the public Space
POST /api/config-file-path point the app at a custom config JSON read any JSON on the container
POST /api/save-session save a study to a chosen folder write anywhere on the container
GET /api/list-sessions browse saved studies enumerate any directory
POST /api/load-session reload a saved study read any session.json
GET /api/pick-path native OS file/dir picker n/a headless (already a no-op)

On the Space (anonymous visitors, Game Mode) none of these are needed — studies come from the bundled presets. So when COSTUDY4GRID_LOCKDOWN is truthy (1/true/yes/on), each is disabled:

403  {"detail": "This operation is disabled on the hosted deployment.",
      "code": "LOCKED_DOWN"}
  • The flag is read once at import into main._LOCKDOWN; the guard _reject_when_locked_down() is the first statement of each locked handler. LOCKED_DOWN is a stable code in the D2 error envelope (services/api_errors.py, mapped from status 403) and the frontend ApiErrorCode union.
  • The read-only app config (GET /api/user-config, GET /api/config-file-path) stays available so the SPA still boots and reads its bundled config.json.
  • The Dockerfile sets COSTUDY4GRID_LOCKDOWN=1. Local dev and the test suite leave it unset, so behaviour is unchanged off the Space — TestLockdownProfile in test_api_endpoints.py exercises both states (403 + LOCKED_DOWN when on; normal 200 when off; read-only config reachable either way).

Disabling (rather than confining to a fixed directory) is the conservative choice for a single-player ephemeral container; confinement is a possible future refinement if the Space ever needs session persistence.

Test-gated deploy + rollback

.github/workflows/deploy-huggingface.yml previously fired on every push to main and force-pushed a history-free snapshot to the Space — no test gate, and (because the Space push is force-pushed + squashed) no rollback trail. D7 tightens both:

  • Test gate: the deploy now triggers on workflow_run of the Tests workflow completing successfully on main. A red build no longer ships. workflow_dispatch still runs unconditionally (the rollback path).
  • Rollback pointer: every successful deploy tags the exact commit it shipped on origin as space-deploy-<UTC-timestamp>-<shortsha>. Since the Space's own history is squashed away, this origin tag is the only durable record of what shipped. Roll back by re-dispatching the workflow from a prior space-deploy-* tag (workflow_dispatch is not gated). Full steps in deploy/huggingface/SETUP.md → "Rolling back a bad deploy".

Reproducible Python closure (tracked follow-up)

The image resolves the dependency tree at build time (version floors in pyproject.toml plus the expert_op4grid_recommender floor), so a zero-change rebuild can still pick up newer transitive releases — the Dockerfile's "mirrors CI" claim isn't literally true. The fix is a committed lockfile consumed by both the Dockerfile and the Tests workflow:

# on Python 3.10 (the image base — NOT a dev 3.11, or it pins wrong wheels):
pip install pip-tools
pip-compile --output-file requirements.lock pyproject.toml

This is left as a follow-up rather than generated in-repo precisely because it must be resolved on the deployment's 3.10 interpreter; a lockfile pinned on the wrong Python is worse than none. See SETUP.md → "Reproducible Python closure".

Remaining (tracked)

  • Generate + wire the requirements.lock (above).
  • Confinement (vs. outright disabling) of the session RPCs if the Space ever needs persistence.
  • HEALTHCHECK in the Dockerfile and dropping the image's dead weight (dimension-12 items, independent of the trust model).