Commit Graph
100 Commits
Author SHA1 Message Date
tao.chen 6a8bdf6b16 fix: monaco-editor offline 2026-08-20 20:11:06 +08:00
tao.chen d3259ba452 fix: monaco-editor offline 2026-08-20 19:30:23 +08:00
tao.chen 5c15e68a40 Merge branch 'feat/schedule-cleanup-handover' into develop 2026-08-20 19:27:32 +08:00
tao.chen e4d010ec76 fix: monaco-editor offline 2026-08-20 19:23:42 +08:00
tao.chen ae3b6d63e2 Merge remote-tracking branch 'aliyun/develop' into develop 2026-08-20 12:08:10 +08:00
tao.chen b600c6810b fix: P0-5 — upload-status rollback, streaming copy (LOCAL only), user re-verify, honest lock
B1: `_mark_upload_failed_and_raise` now commits on a separate session
  - Helper takes `request + upload_id`, opens a fresh session from
    `request.app.state.session_factory` and commits there before raising.
  - Closes the named-lock connection-pool leak Codex flagged: the old
    "commit-on-the-same-session" implementation could return the
    GET_LOCK connection to the pool before the enclosing
    `finally: release_named_lock` ran, leaking `mp:<hash>` for up to
    `pool_recycle` and re-opening the same-key upload race.
  - Same helper now used by `create_server_object_payload`'s put-failure
    branch — two failure paths have identical semantics.

B2: streaming copy for soft-delete + restore (`get_stream() + put()`)
  - LOCAL backend: zero-copy (aiofiles stream write). OOM fixed.
  - S3 backend: still OOMs on multi-GB objects — `put()` materializes
    the async iter via `b"".join(chunks)`. Multipart `put` is a
    follow-up; do NOT claim "OOM fixed on production" since production
    defaults to S3.

C1: worker re-verifies `Users.status='active' AND is_deleted=0`
  - `_assert_user_active` called from `_execution_context` after
    resolving `triggered_by`; skips `SYSTEM_CRON_USER_ID`.
  - `USER_DISABLED` error_code goes into the `NODE_FINISHED_EVENT`
    outbox payload — `schedule_node_runs` has no `error_code` column,
    the row only carries the `message` text. Docstrings corrected to
    say so explicitly (previous docstring falsely promised row-level
    observability).

F1: honest browser-local file lock
  - `api.ts` `acquireFileLock/heartbeatFileLock/releaseFileLock/
    releaseFileLockOnUnload` are now no-ops with comments stating they
    never call the network.
  - `scriptWorkspaceStore` dropped `tickHeartbeats`; `tickCleanup`
    simplified to just clear cache.
  - `useEditSessionLifecycle` dropped its 15s heartbeat `setInterval`.
  - `ScriptWorkspace.tsx` renders `.local-lock-banner` info bar when
    `isEditing`. Two tabs may still silently last-write — banner is the
    only guard (acceptable disclosure-only tradeoff).

Dead code: deleted the duplicate `upload_bytes_to_session` in
`backend/src/backend/storage_api.py`. The `services.storage` import
is now the only source of the function; `create_upload_record`'s
docstring updated to point at `backend.resources`.
2026-08-20 12:07:52 +08:00
tao.chen cc8ac220a5 perf(schedule): 减少重渲染与冗余动画
- useCanvasNodeDrag.moveDrag 改 ref 直改 DOM,松手才写 store,避免拖拽期间反复重渲染 SchedulePage
- runs 轮询改 setTimeout 链式 + 移除 runs 依赖,避免每 1.5s 重建 interval
- SchedulePage 拆 PositionDraftBadge / RunNowButton,删除冗余的 runs/runsLoading/positionDraftCount 订阅
- 拆 ScheduleEdge 组件 + React.memo + useMemo,切换 edge 选中时不再重算所有 edgePath
- 删除 schedule.css 中引用未定义 keyframes 的 modal-in/spin 死动画
- 删除 .schedule-edge-line transition、.schedule-run-result-backdrop backdrop-filter;补 .artifact-card transition

Refs: kaneo #19-#24
2026-08-20 11:27:45 +08:00
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.chen e5633cc95a fix: artifact_bucket 2026-08-20 10:26:44 +08:00
tao.chen d8a31bde95 fix: update_script rewrite metadata 2026-08-20 09:57:46 +08:00
tao.chen 3cbf04bcba merge develop-2 → develop: P0-1 security fix
Brings the P0-1 service-token auth, /internal/* 端点收紧到 1, and
docker-compose 端口映射删除到 develop. rclone RC 子项按用户决定
明确忽略。详见 develop-2 commit dfe3f0b.
2026-08-17 16:49:53 +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 cbfbee7fe0 chore: add CODE_REVIEW.md 2026-08-14 20:55:59 +08:00
tao.chen b6eb069849 fix(backend): enhance storage/resource concurrency, idempotency, and deletion safety
Summary of changes:

- Resources Bind Idempotency (High 1+2):
  - Check existing active storage object bindings before duplicate check.
  - Return existing binding (`reused: true`) on retry with same upload_id.
  - Filter by `status == "active"` to bypass dead/deleted rows during reuse check.

- Storage Concurrent Overwrite (High 3):
  - Add `acquire_named_lock` and `release_named_lock` helpers using MySQL `GET_LOCK`/`RELEASE_LOCK` hashed to <= 64 chars.
  - Wrap `upload_bytes_to_session` PUT+INSERT critical section with named lock on `object_key`.
  - Re-check key collision inside lock; append ULID suffix on collision.

- Shared Reference Deletion Protection (Medium 4):
  - Check active references before deleting storage objects in `delete_resource`.
  - Delete only `DataResources` record if storage object is still referenced elsewhere.

- Robust Usage Type Fallback (Medium 5):
  - Replace direct dict lookup for `USAGE_TYPE_TO_PURPOSE[item.usage_type]` with `.get(..., "workspace")` default.

- Idempotency Key Path Matching (Medium 6):
  - Move `file_name`/`target_path` validation forward and include path dimension in comparison.
  - Strip uniqueness suffix via `_strip_uniqueness_suffix` before key comparison to avoid false 409s on valid retries.

- Usage Type & Bind Concurrency Control (Low 7 & 8):
  - Reject bind requests with 409 if upload session purpose is not `data_resource`.
  - Wrap resource duplicate check and creation in named lock using `(owner, directory, name)`.

- Trash Key Uniqueness & Restore Compatibility (Low 9):
  - Update `trash_key` format to `{purpose}/{object_key}-{storage_object_id}` to prevent collisions.
  - Update `object_key_hash` on trash move.
  - Update restore logic in `storage_api.py` to strip suffix while maintaining backward compatibility with legacy keys.

- Dead Code Removal (Low 10):
  - Remove unreachable `upload_status = "failed"` and redundant `session.rollback()` in `IntegrityError` block.

- Tests & Mocks:
  - Add/update 5 test cases covering non-data_resource bind rejection, suffix stripping, and path recovery.
  - Add named lock statement mocks for DB testing.
2026-08-14 20:53:07 +08:00
tao.chen f72dfd10e8 fix: delete bug 2026-08-14 20:37:25 +08:00
tao.chen 1079b62f0a update 2026-08-14 20:08:51 +08:00
tao.chen 85b2916850 fix: local_storage_base_dir 2026-08-14 20:03:40 +08:00
tao.chen 25e563dcaa update: ruff check --fix 2026-08-14 19:51:58 +08:00
tao.chen d855912791 fix: soft delete helper 2026-08-14 19:39:09 +08:00
tao.chen 76af0f763c update: README.md 2026-08-14 19:30:25 +08:00
tao.chen 139f2c02c1 fix: delete error 2026-08-14 19:19:41 +08:00
tao.chen 2e070fae78 fix: pre check 2026-08-14 19:13:17 +08:00
tao.chen 5c5dec9a4a fix: allows_same_name_different_parent 2026-08-14 18:49:27 +08:00
tao.chen 5d49ff5e34 fix: file upload error 2026-08-14 18:22:46 +08:00
tao.chen c65d6dc684 fix: build error 2026-08-14 17:19:07 +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 7347b409e5 update: port 2026-08-14 15:32:34 +08:00
tao.chen af9466c4e6 update: UV_HTTP_TIMEOUT 2026-08-14 15:27:16 +08:00
tao.chen e9a7e1a24e update: docker-compose.yml 2026-08-14 15:19:29 +08:00
tao.chen 65d14b8610 update: dir tree 2026-08-14 15:02:55 +08:00
tao.chen 4530811441 update: spec 2026-08-14 14:40:34 +08:00
tao.chen d415154f41 update: migrate 2026-08-14 14:32:44 +08:00
tao.chen c45687ef18 fix: update unique key 2026-08-14 13:44:57 +08:00
tao.chen 7330f313ff update: add custom dep 2026-08-14 13:15:18 +08:00
tao.chen 518853cbb0 fix 2026-08-14 12:40:36 +08:00
tao.chen 604e66ffe0 fix: DataResourceUploadModal.tsx 2026-08-14 12:28:22 +08:00
tao.chen 480e1cc638 update: data source 2026-08-14 11:59:06 +08:00
tao.chen 225a585499 update: validate_target_path 2026-08-14 11:32:15 +08:00
tao.chen b9becffab2 update: 数据资源文件上传 2026-08-14 11:31:48 +08:00
tao.chen 462617b66d feat: datasource 2026-08-13 20:13:37 +08:00
tao.chen 0b5b50edb9 update: rel path 2026-08-13 19:51:20 +08:00
tao.chen 63a14b1884 fix: params error
NameError: name 'session' is not defined
2026-08-13 19:20:36 +08:00
tao.chen f5cb22e8cd feat: upload resource 2026-08-13 19:09:13 +08:00
tao.chen 912c0ed22d Merge branch 'refactor/storage' into develop 2026-08-13 18:11:50 +08:00
tao.chen 19157ad20b test 2026-08-13 18:08:12 +08:00
tao.chen 532f0d6c91 fix: dir tree 2026-08-13 12:12:58 +08:00
tao.chen 3094a47298 fix: path error 2026-08-13 11:48:24 +08:00
tao.chen 83d0e0adca fix: bug
1. Cannot find name 'PythonEditorBuffer'
2. Cannot find name 'PythonEditor'
2026-08-13 11:26:31 +08:00
tao.chen 1ad0ec9dbf update: docker-compose.yml 2026-08-13 11:03:08 +08:00
tao.chen dc68739216 Merge remote-tracking branch 'aliyun/develop' into develop 2026-08-13 11:01:43 +08:00
tao.chen 49c8a27841 update: update storage struct 2026-08-13 11:01:37 +08:00
tao.chen da9a1ec472 update: storage path 2026-08-13 09:35:27 +08:00
tao.chen 302bda2ee9 fix: directory tree 2026-08-12 20:45:29 +08:00
tao.chen d600d5dbb1 fix: directory cached 2026-08-12 20:41:44 +08:00
tao.chen b1ceaeb173 fix: storage and directories bug 2026-08-12 20:18:29 +08:00
tao.chen 0bcdbb1068 fix: add logging limit 2026-08-12 19:52:14 +08:00
tao.chen 9d0ca1e8b6 fix: READINESS_TARGETS 2026-08-12 19:49:05 +08:00
tao.chen 8abe505d17 fix: multi owner 2026-08-12 18:58:18 +08:00
tao.chen 495018de34 update: workspace lazy load 2026-08-12 18:36:17 +08:00
tao.chen c6c2481b96 feat: workspace 目录树服务端懒加载
- api.ts: listWorkspaceDirectories 支持 parent_path 查询; WorkspaceDirectory 增加 has_children; 同步 WorkspaceBoundApi 签名
- AuthContext: 绑定透传 parentPath
- scriptWorkspaceStore: 新增 expandedPaths/loadingChildrenPaths/loadedChildPaths, loadChildren/toggleExpanded, 局部刷新 createFolder/deleteDirectory
- WorkspaceTree: 移除 useState, 改为受控展开/加载状态
- ScriptExplorer: 从 store 读取并透传展开状态与 toggle
2026-08-12 18:06:35 +08:00
tao.chen a09551bd3c update: workspace members 403 2026-08-12 17:14:47 +08:00
tao.chen 6b9f851810 update: scripts.py api 2026-08-12 17:14:18 +08:00
tao.chen a86432b102 update: PythonEditor.tsx 2026-08-12 16:58:00 +08:00
tao.chen 2fcdf51cfc update: PythonEditor.tsx 2026-08-12 16:49:21 +08:00
tao.chen dc4d190935 fix: python script 404 2026-08-12 13:45:15 +08:00
tao.chen 91339aa993 update: docker-compose.yml 2026-08-12 13:33:29 +08:00
tao.chen 416ff4d06a feat: add logger 2026-08-12 12:43:31 +08:00
tao.chen 3981c32781 refactor: directory 2026-08-12 12:23:19 +08:00
tao.chen a378ea5352 feat: add pytest in dev 2026-08-12 12:21:48 +08:00
tao.chen e23df83e0d fix: bugs 2026-08-11 22:03:01 +08:00
tao.chen bff7b7f9a6 fix: bugs 2026-08-11 21:53:52 +08:00
tao.chen dd6f176ca8 fix: file lock 2026-08-11 21:26:55 +08:00
tao.chen d4013a07a9 rollback 2026-08-11 20:41:15 +08:00
tao.chen e1360c5c38 test: backend 2026-08-11 20:09:44 +08:00
tao.chen e80cbbd81c feat: execute node python version 2026-08-11 19:00:55 +08:00
tao.chen 38cd06ee7a update: clean dockerfile 2026-08-11 18:57:34 +08:00
tao.chen 05091c425a update: filter by owner / visibility 2026-08-11 18:56:47 +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 78e83e1765 fix: migrate bug 2026-08-11 18:13:15 +08:00
tao.chen ed7a6cd858 fix: migrate bug 2026-08-11 18:05:34 +08:00
tao.chen 1b0083be47 refactor: kernel spec 2026-08-11 17:47:39 +08:00
tao.chen 6c6e7e6500 update: python script run in python intercept 2026-08-11 16:20:40 +08:00
tao.chen ec9effc254 update: npm registry 2026-08-11 16:19:34 +08:00
tao.chen b49d804718 update: upgrade base image 2026-08-11 16:12:24 +08:00
tao.chen fe7f1a744e feat: schedule node add python version 2026-08-11 15:56:05 +08:00
tao.chen e28bc39328 fix: default user 2026-08-11 15:13:26 +08:00
tao.chen 4269eed018 fix: 422 error 2026-08-11 15:13:13 +08:00
tao.chen b44d057241 feat: preview components 2026-08-11 15:02:16 +08:00
tao.chen 5969f2f749 reformatter 2026-08-11 11:13:13 +08:00
tao.chen b00a0e9ae1 fix: hook 2026-08-07 20:42:36 +08:00
tao.chen 567786215e refactor: SchedulePage.tsx 2026-08-07 20:40:41 +08:00
tao.chen 084de5b2d6 fix: storage.py 2026-08-07 20:14:29 +08:00
tao.chen a97aad3179 fix: schedule error 2026-08-07 19:59:04 +08:00
tao.chen e7bce03794 fix: storage_api.py 2026-08-07 19:58:42 +08:00