Commit Graph
68 Commits
Author SHA1 Message Date
tao.chenandClaude Fable 5 4acfbb162f fix: P0-4 schedule node janitor + runtime reaper/lock invariants
S2: schedule worker add janitor task that force-terminals node_runs
whose deadline (timeout_seconds + retry_count*retry_interval + 120s
slack from started_at) has passed. Closes the gap where outbox retry
exhaustion (5 tries, capped 30s backoff) marked the *event* failed
but left the *node_run* stuck in queued/running forever. Re-reads the
row under FOR UPDATE before writing so a worker that races us to a
real terminal state is not overwritten; idempotency key uses
:timed_out variant so the :finished path cannot collide.

R1: extract _reap_once() from _reap_loop for testability; in the
dead-process branch, re-verify (process.pid, started_at) against the
live JUPYTER_PROCESSES entry before del. A start_workspace that
replaced the dead record mid-cycle used to have its new entry
silently erased by the reaper's stale snapshot — leaked the port.

R2: delete _drop_workspace_lock and its two call sites
(stop_workspace tail, get_workspace 404 path). Popping the lock
object after release breaks mutual exclusion for any coroutine still
holding the old reference while a fresh caller gets a new lock
object — same ws_id can race two starts. The dict is bounded by the
number of workspaces so the leak is negligible; invariant lives on
WORKSPACE_LOCKS in a comment.

Tests:
- schedule/tests/test_janitor.py — 8 tests covering normal kill /
  healthy-skip / worker-race / never-started / multi-row batch /
  cancellation propagation / per-iteration self-heal
- runtime/tests/test_process.py — 7 tests covering reaper identity
  match / replacement-skip / alive-preserved + lock
  same-object / concurrent-serialize / survives-stop /
  helper-removed guard

uv run --package schedule pytest schedule/tests → 14 passed
uv run --package runtime pytest runtime/tests  →  7 passed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 10:49:47 +08:00
tao.chenandClaude Fable 5 dfe3f0b118 fix(security): P0-1 — port exposure + service-token auth on /internal/* + jupyter RPC
The fix lands in three concentric layers, all backed by a single
INTERNAL_SERVICE_TOKEN shared secret so we have one mechanism
instead of three:

1. docker-compose: drop the backend.ports: 8891:8000 and
   runtime.ports: 8892:8000 mappings. Nginx is the only host
   ingress again (architecture §2.2).
2. /internal/v1/*: the storage control plane had six endpoints, five
   of which were dead code (frontend already migrated to
   /api/v1/data-resources/* with JWT; schedule only ever called
   POST /internal/v1/objects). Delete the dead routes, mount the
   one survivor with Depends(require_internal_service) that
   compares the X-Internal-Service-Token header against
   settings.internal_service_token with secrets.compare_digest.
3. POST /api/v1/jupyter on the runtime container: previously open
   inside the Docker network. Same token mechanism — backend's
   runtime_http_client now carries the header, runtime's
   handle_jupyter_action requires the same header. /api/v1/health
   stays open for the Nginx and compose healthchecks.

The schedule worker was already configured to call
POST /internal/v1/objects; build_storage_http_client now
sets the token header so its existing call site keeps working
without changes.

Files touched:
  backend/src/backend/storage_api.py   # 5 dead routes deleted + token guard
  backend/src/backend/main.py          # runtime_http_client header
  runtime/src/runtime/main.py          # require_internal_service Depends
  common/src/common/config.py          # internal_service_token setting
  schedule/src/schedule/service.py     # httpx client header
  docker-compose.yml                   # ports dropped, INTERNAL_SERVICE_TOKEN env
  .env.example                         # INTERNAL_SERVICE_TOKEN placeholder
  API.md / README.md / DEVELOP.md      # §9 trimmed to 1 endpoint

Verified:
  compileall -> 0 errors
  pytest backend/tests -> 37 passed
  in-process ASGI smoke:
    POST /internal/v1/objects no/wrong/correct token -> 401/401/200
    POST /api/v1/jupyter   no/wrong/correct token -> 401/401/200
    5 deleted internal routes -> 404
  docker compose config (with env) -> OK

P0-1 still has one open sub-item (rclone RC --rc-no-auth) that
the user has explicitly deferred; not touched here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:48:46 +08:00
tao.chen 6258cf5d12 fix: offline deploy 2026-08-17 15:59:19 +08:00
tao.chen 0974881fbb fix: offline deploy 2026-08-17 15:30:21 +08:00
tao.chen 45cb2f8409 fix: build error 2026-08-14 17:16:53 +08:00
tao.chen de1682c00d update: Dockerfile 2026-08-14 17:03:27 +08:00
tao.chen 341cc77a79 update: Dockerfile 2026-08-14 16:02:19 +08:00
tao.chen 2014d59681 update: Dockerfile 2026-08-14 15:55:00 +08:00
tao.chen af9466c4e6 update: UV_HTTP_TIMEOUT 2026-08-14 15:27:16 +08:00
Winnie be475dd0d9 fix: support local storage runtime and schedule logs 2026-08-14 15:06:36 +08:00
tao.chen a378ea5352 feat: add pytest in dev 2026-08-12 12:21:48 +08:00
tao.chen 38cd06ee7a update: clean dockerfile 2026-08-11 18:57:34 +08:00
tao.chen c63f745374 fix: error 2026-08-11 18:42:15 +08:00
tao.chen ca12646387 update: env error 2026-08-11 18:26:37 +08:00
tao.chen 1b0083be47 refactor: kernel spec 2026-08-11 17:47:39 +08:00
tao.chen 5969f2f749 reformatter 2026-08-11 11:13:13 +08:00
tao.chen 10347efa73 update: exclude hidden dir 2026-08-07 15:08:36 +08:00
tao.chen 245b522d36 feat: add seed to venv 2026-08-06 12:30:33 +08:00
tao.chen 6e6f880eb0 rollback 2026-08-06 11:00:41 +08:00
tao.chen a7963406b3 fix: default kernel 2026-08-06 10:55:12 +08:00
tao.chen e2329ea429 fix: default kernel 2026-08-06 10:46:21 +08:00
tao.chen bf2cd9bd69 update: rename JUPYTER_VENV 2026-08-06 10:40:03 +08:00
tao.chen b9a028a1db update: multi python version 2026-08-06 10:03:58 +08:00
tao.chen f63ddc59b5 update: remove jupyter env install kernel /opt/venv/python3.12 2026-08-05 19:16:09 +08:00
tao.chen c4529782b5 test: Isolation jupyter env 2026-08-05 18:59:33 +08:00
Winnie 78c3f1e954 Merge remote-tracking branch 'origin/develop' into develop 2026-08-05 17:53:49 +08:00
tao.chen 264faecb0d fix: python3.10 build error 2026-08-05 17:49:53 +08:00
Winnie cec6a4468e Merge remote-tracking branch 'origin/develop' into develop 2026-08-05 17:49:29 +08:00
tao.chen 450609d868 feat: support python3.10 2026-08-05 17:46:13 +08:00
Winnie 2bfe43a63f Merge branch 'develop' of http://8.153.151.51:8888/team_group/model-develop into develop 2026-08-05 17:46:05 +08:00
tao.chen c2617da61c feat: support python3.8 2026-08-05 17:10:53 +08:00
tao.chen d851f98581 fix: runtime error 2026-08-05 15:04:07 +08:00
tao.chen 07d2423c13 update: remove workspace operation table and refactor 2026-08-05 14:43:48 +08:00
tao.chen d2bb450d30 storage: add local filesystem backend option (STORAGE_BACKEND toggle)
The factory now picks between two backends based on
settings.storage_backend ("s3" default, "local" for dev / single-node /
air-gapped deployments). The new factory helper build_storage_config()
takes one of the 4 PURPOSE_BUCKETS ("workspace" | "version" |
"run_log" | "trash") and returns the kwargs for create_storage(...).

  s3   mode: AsyncStorageBackend over an S3-compatible service
           (S3_WORKSPACE_BUCKET etc. as separate buckets).
  local mode: AsyncStorageBackend over on-disk files; the 4 buckets
           become subdirectories of LOCAL_STORAGE_BASE_DIR (default
           "/data"), so the same 4-bucket layout works in both modes.

Concretely:
  - common/config.py: add storage_backend (default "s3") +
    local_storage_base_dir (default "/data").
  - common/storage/factory.py: add PURPOSE_BUCKETS constant +
    build_storage_config(bucket_name) helper.
  - backend/main.py + backend/storage_api.py: lifespan collapses the
    4-instance construction into one dict comprehension:
      app.state.object_stores = {
        name: create_storage(build_storage_config(name))
        for name in PURPOSE_BUCKETS
      }
    (was 4x ~10-line dicts, one per bucket).
  - runtime/mount.py: when STORAGE_BACKEND=local, skip the rclone mount
    entirely (the shared docker volume at LOCAL_STORAGE_BASE_DIR is the
    store; runtime reads directly).
  - docker-compose.yml: mount the shared local-storage volume at /data
    in both backend and runtime containers.
  - .env.example: document STORAGE_BACKEND + LOCAL_STORAGE_BASE_DIR.

Dependencies added to support both backends:
  - aiofiles>=25.1.0 (local async I/O) to backend + common + runtime.
  - aioboto3>=15.5.0 (async S3) to common.
  - uv.lock regenerated.

After this commit, both modes deploy end-to-end. The s3 mode is the
production default; local mode is opt-in via STORAGE_BACKEND=local.
2026-08-05 13:10:05 +08:00
tao.chen 7c456a04ce update: update custom css 2026-08-05 11:07:54 +08:00
tao.chen 87382d7bda refactor: update Dockerfile 2026-08-04 18:39:30 +08:00
tao.chen 7cf3a9d02a fix: runtime build failed 2026-08-04 18:00:06 +08:00
tao.chen a6692338cc fix: runtime build failed 2026-08-04 17:58:10 +08:00
tao.chen 004d7289ca update: update jupyter css 2026-08-04 17:45:23 +08:00
tao.chen 4919fe0909 update: create file 2026-08-04 12:40:38 +08:00
tao.chen a27cda5a0c fix: jupyter start params 2026-08-04 12:03:43 +08:00
tao.chen 60b17f5bef update: jupyter start dir 2026-08-04 11:59:04 +08:00
tao.chen ddf112034d update: jupyter api payload, wire os.environ 2026-08-04 11:29:33 +08:00
tao.chen e1172a0091 update: rclone add remote control 2026-08-03 20:02:42 +08:00
tao.chen 85791ab1ea update: rclone add remote control 2026-08-03 19:57:29 +08:00
tao.chen e19340332a update: hide top-panel 2026-08-03 19:51:24 +08:00
tao.chen 6c4bf47487 update: hide top-panel 2026-08-03 19:43:51 +08:00
tao.chen 2c54f88181 fix: notebook-dir 2026-08-03 19:12:40 +08:00
Winnie d7bd88335c merge: integrate feat/auth into develop 2026-08-03 17:44:00 +08:00
tao.chen a44b984203 chore: rollback mount path 2026-07-31 19:49:02 +08:00