Skip to content

feat: unify session resources and files - #190

Merged
arthur-zhang merged 35 commits into
mainfrom
codex/issue-184-session-resource-file-unification
Aug 5, 2026
Merged

feat: unify session resources and files#190
arthur-zhang merged 35 commits into
mainfrom
codex/issue-184-session-resource-file-unification

Conversation

@pxguan

@pxguan pxguan commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

概要

  • 删除 filestore_entriesfse_ identity,以 session_resources 作为 Session namespace 唯一事实源
  • 将真实文件 identity、元数据和对象事实统一保存到 files
  • Input attach 只创建 Resource 并引用 Source File,请求与响应复用 Source file_id
  • 同一 Source 多次 attach 由不同 sesrsc_ 区分,Session Catalog 按真实 File 去重
  • Output/普通 Filestore 文件通过 Owned File + Resource 原子维护配额和清理生命周期
  • Skill Archive 改为 Resource 引用 Skill Version,ZIP 动态成员不持久化

背景

PR #182 引入 Session File projection 后,Resource、Filestore Entry 和 File projection 形成多重事实源,导致 create、overwrite、move、delete、TTL 与 cleanup 都需要同步维护。

本 PR 按 #184 的最终设计进行一次性切换,不引入双写、双读、兼容 view、trigger 或 feature flag。Input 不再生成 Session File Alias;一次 Attach 的 identity 由 sesrsc_ 表达,files.delete(file_...) 始终面向真实 File,活动 Resource 引用存在时返回冲突。

这与 Anthropic 当前 attach 返回新 session-scoped file_id 的行为存在已确认的兼容性偏差,设计取舍和迁移行为已记录在文档及 #184 评论中。

Main 合并

已合入 origin/main 的最新代码,并保留双方功能:

  • Environment package provisioning
  • managed-agent-sandbox 默认模板与 migration 00035
  • epochless OTLP lifecycle telemetry
  • Session Resource/File 统一模型

统一迁移因此顺延为 00036_unify_session_resources_and_files.sql

验证

  • 全新 PostgreSQL 按 00035 → 00036 顺序迁移
  • go test ./... -count=1:所有 package 通过;tests package 中 TestEnvironmentRunnerLaunchesManagedAgentCloudSession 在全套并行负载下出现队列时序超时
  • 上述 Runner 测试单独连续运行 10 次通过
  • Session/File、Output Catalog、Environment package provisioning 与 OTLP 聚焦测试通过
  • staged pre-commit hooks 通过
  • 改动包 golangci-lint、死代码、重复代码、Go/TypeScript 复杂度与格式检查通过

仓库级 golangci-lint ./... 仍会扫描 web/node_modules/flatted 并命中两条既有 govet 告警;本次改动包 lint 与提交 hook 均通过。

Closes #184

Summary by CodeRabbit

  • New Features
    • Unified session resources and files for consistent directory listings, identity, pagination, and lifecycle handling.
    • Session file mounts/attachments now follow the updated session-scoped resource model.
    • Skill archives now use session-scoped resource + ZIP snapshot behavior with atomic replacement and dynamically composed /skills contents.
    • Added richer file metadata support (detected MIME type, tags, hashes, and more S3 object details).
  • Bug Fixes
    • Improved cleanup/TTL expiration, quota accounting, and clearer cleanup anomaly reporting.
    • Strengthened validation for mounts, missing references, and unsafe copy/overwrite cases.
  • Documentation
    • Updated backend design/migration guides and refined API/wire contract guidance and checklists.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces filestore_entries with unified session_resources and files, adds migration 00037 for skill ZIP snapshots, updates Filestore and Files adapters, changes cleanup and quota accounting, and expands migration, catalog, skill, lifecycle, and storage tests.

Changes

Session resource and persistence model

Layer / File(s) Summary
Unified schema and migration
internal/db/migrations/*, internal/db/files*, internal/db/sessions*, docs/design/be/*
The unified Resource/File model adds metadata, paths, UUID links, visibility predicates, constraints, indexes, migration guards, and skill snapshot backfill.
Session resource namespace operations
internal/db/session_resource_*.go, internal/db/filestore_*.go, internal/filestore/service.go, internal/filestore/persistent_backend.go
Reads, pagination, mounts, writes, copies, moves, removals, and payload encoding now use SessionResourceFile records.
Files catalog and skill archives
internal/db/files_sqlx.go, internal/db/session_skill_archive_resources.go, internal/filestore/skill_archives.go, internal/environments/runner.go
Session-scoped Files listing reads Resource/File relationships, while skill archives use immutable ZIP File snapshots linked through file_uuid.
Cleanup, accounting, and validation
internal/db/filestore_cleanup.go, internal/db/workspace_storage.go, internal/filestore/cleanup.go, tests/*
Cleanup reports anomalies, storage usage derives from Resource/File ownership, runtime wiring uses the new APIs, and integration tests cover migration, catalog, lifecycle, TTL, and cleanup behavior.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Possibly related PRs

Suggested reviewers: arthur-zhang

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: unifying session resources and files.
Linked Issues check ✅ Passed The changes align with the stated goals: session_resources/files are unified, filestore_entries is removed, and adapters, tests, docs, and migrations are updated.
Out of Scope Changes check ✅ Passed The changes stay focused on the session resource/file unification effort, with docs, migrations, DB adapters, and tests all supporting the linked objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-184-session-resource-file-unification

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pxguan
pxguan marked this pull request as ready for review July 29, 2026 12:35
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/db/session_namespace_mutations.go (1)

430-445: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Owned files row is left active when a single file is removed.

RemoveFilestoreDirectory soft-deletes the backing files rows for owned file resources in the subtree (Lines 519-537), but RemoveFilestoreFile only retires the resource and enqueues object cleanup. The files row stays deleted_at is null while its object is deleted by the cleanup worker. Consequences:

  • a dangling files row pointing at a deleted object;
  • activeFileReferenceQuery no longer blocks it, so a later files.delete succeeds and subtracts files_bytes even though filestore_bytes was already released here — the ledger double-releases.

The overwrite branch of MoveFilestoreFile (Lines 212-224) has the same gap. Both should soft-delete the owned files row in the same transaction, using the directory path as the reference implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/session_namespace_mutations.go` around lines 430 - 445, Update
RemoveFilestoreFile and the overwrite branch of MoveFilestoreFile to soft-delete
the owned files row within the same transaction that retires the session
namespace node, matching the existing RemoveFilestoreDirectory implementation.
Ensure the files row is retired before cleanup can delete its object, while
preserving the existing storage-delta and cleanup-job behavior.
🧹 Nitpick comments (2)
tests/sessions_file_resources_api_test.go (1)

798-813: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Global DDL on the shared files table can leak into other tests.

alter table files add constraint ... not valid takes an ACCESS EXCLUSIVE lock and applies database-wide for the duration, so any concurrently running test that inserts a scoped files row fails spuriously; a failure in the deferred t.Fatalf also leaves the constraint installed for the rest of the run. If this package can share a database with other test packages, consider forcing the write failure through a session-local mechanism instead (e.g. a before insert trigger on a savepoint, or asserting atomicity via a deliberately invalid blob) or at minimum guarantee the drop always executes without t.Fatalf.

(The ast-grep SQL-injection hint on line 808 is a false positive: constraint is a local const and DDL identifiers cannot be parameterized.)

♻️ Make cleanup non-fatal so the constraint never leaks
 	defer func() {
 		if _, err := app.db.Pool.Exec(
 			context.Background(),
 			"alter table files drop constraint if exists "+constraint,
 		); err != nil {
-			t.Fatalf("drop catalog failure constraint: %v", err)
+			t.Errorf("drop catalog failure constraint: %v", err)
 		}
 	}()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sessions_file_resources_api_test.go` around lines 798 - 813, Replace
the global files-table constraint setup with a session-local failure mechanism,
avoiding database-wide DDL and interference with concurrent tests. Update the
test around the current constraint setup and cleanup so the intended insert
failure and atomicity assertion remain covered; if DDL must remain, make
deferred cleanup non-fatal and ensure it always executes without t.Fatalf.

Source: Linters/SAST tools

internal/db/session_file_mounts_sqlx.go (1)

258-269: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

unbindSessionFileResourceTx is now only a lock acquisition — make that explicit.

The body no longer unbinds anything (the mount lives on the session_resources row that the caller soft-deletes), so the name now misdescribes the behavior and future readers may assume path/file cleanup still happens here. Consider renaming to something like lockSessionFileResourceUnbindTx and adding a one-line comment stating that removing the resource row removes the /uploads node, and that no quota/object cleanup is required because Input Resources own no bytes.

♻️ Suggested clarification
-func unbindSessionFileResourceTx(
+// lockSessionFileResourceUnbindTx 只负责获取文件系统变更锁:Input Resource 的
+// 命名空间路径与 Resource 同行,调用方软删除该行即解除挂载;引用型资源不拥有
+// 字节,无需配额调整或对象清理。
+func lockSessionFileResourceUnbindTx(
 	ctx context.Context,
 	tx *sqlx.Tx,
 	session Session,
 	resource SessionResource,
 ) error {
 	if resource.ResourceType != SessionResourceTypeFile {
 		return nil
 	}
 	_, err := lockSessionFilestoreMutationTx(ctx, tx, session)
 	return err
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/session_file_mounts_sqlx.go` around lines 258 - 269, Rename
unbindSessionFileResourceTx to reflect that it only acquires the mutation lock,
such as lockSessionFileResourceUnbindTx, and update all call sites. Add a
concise comment explaining that removing the session_resources row removes the
/uploads node and no quota or object cleanup is needed because Input Resources
own no bytes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/research/pr-182-session-file-catalog-summary.md`:
- Around line 1-13: Fix Markdown lint issues in the PR `#182` summary: keep the
introductory blockquote continuous by removing the blank line between its quoted
lines, and adjust the standalone lines beginning with “#182” (including the one
near the summary and the later occurrence) so they no longer parse as malformed
ATX headings while preserving their intended text.

In `@internal/db/files_sqlx.go`:
- Around line 46-63: The not-exists subquery in visibleFileSQLXPredicate cannot
use the existing partial file_uuid index because its predicates do not match the
index condition. Add a matching partial index on session_resources covering
workspace_id and file_uuid with file_uuid is not null and payload is null, or
restructure both subqueries to share an index-eligible predicate while
preserving the current visibility behavior.

In `@internal/db/filestore_cleanup.go`:
- Around line 199-215: Update both cleanup loops around
enqueueSessionNamespaceNodeCleanupJobTx to detect malformed file namespace nodes
with nil S3Bucket or S3Key before enqueueing. Skip those entries and log the
condition, mirroring the existing ReferencesSourceFile guard, so
ErrPreconditionFailed from malformed rows does not abort the transaction while
valid cleanup jobs continue.

In `@internal/db/filestore_scan.go`:
- Around line 92-112: Update the filestore_filesystems join in the query to
require filesystem.deleted_at is null alongside the existing session_uuid and
workspace_uuid predicates. Keep the remaining joins and filters unchanged so
retired filesystem rows cannot duplicate or restore namespace nodes.
- Around line 13-15: Update the filesystem row query returned by the visible
scan-query function so the correlated session lookup cannot produce a NULL for
the non-pointer filestoreFilesystemRow.SessionID field. Preserve the
missing-session case as an explicit domain-level ErrNotFound (using the existing
filesystem read path and error symbols), rather than allowing SQL scanning to
fail implicitly; ensure valid session rows continue returning their session ID.

In `@internal/db/migrations/00036_unify_session_resources_and_files.sql`:
- Around line 146-176: Update the migration’s file materialization flow around
the INSERT into files to reconcile workspace_storage_usage for every affected
workspace after the new rows are created. Ensure the reconciliation recalculates
files_bytes plus filestore_bytes, either inline per workspace or through the
established ReconcileWorkspaceStorageUsage step, so usage remains consistent.

In `@internal/db/session_namespace_helpers.go`:
- Around line 250-271: Map sql.ErrNoRows to an appropriate domain error, such as
ErrNotFound or ErrInvalidState, immediately after the directory insert’s
namedGetContext call and before its unique-violation handling. Apply the same
mapping to the inserted_file CTE insert at
internal/db/session_namespace_helpers.go lines 394-432; both sites require this
change so missing or archived sessions never expose the raw sql.ErrNoRows
sentinel.

In `@internal/db/session_namespace_mutations.go`:
- Around line 243-245: Update the moved-node re-fetch in the session namespace
mutation to include the same workspace_id and session_id tenant scope used by
the preceding update, in addition to id and deleted_at filtering. Adjust the
bound arguments passed to getSessionNamespaceNodeSQLX so the select cannot
return a node outside the current tenant.

In `@internal/db/workspace_storage.go`:
- Around line 61-77: The files_bytes query currently multiplies a file’s size by
each matching session_resources attachment. Update the aggregation to sum each
file once, using EXISTS/NOT EXISTS checks for active resource_type = 'file'
resources instead of joining session_resources, while preserving the workspace,
file deletion, payload, and resource activity filters.

In `@tests/sessions_api_test.go`:
- Around line 3611-3613: Update the ErrNotFound retry branch in the session API
test to wait or back off before continuing while the deadline remains active.
Preserve the existing deadline check and retry behavior, but ensure repeated
stale-work lookups cannot hot-spin.

---

Outside diff comments:
In `@internal/db/session_namespace_mutations.go`:
- Around line 430-445: Update RemoveFilestoreFile and the overwrite branch of
MoveFilestoreFile to soft-delete the owned files row within the same transaction
that retires the session namespace node, matching the existing
RemoveFilestoreDirectory implementation. Ensure the files row is retired before
cleanup can delete its object, while preserving the existing storage-delta and
cleanup-job behavior.

---

Nitpick comments:
In `@internal/db/session_file_mounts_sqlx.go`:
- Around line 258-269: Rename unbindSessionFileResourceTx to reflect that it
only acquires the mutation lock, such as lockSessionFileResourceUnbindTx, and
update all call sites. Add a concise comment explaining that removing the
session_resources row removes the /uploads node and no quota or object cleanup
is needed because Input Resources own no bytes.

In `@tests/sessions_file_resources_api_test.go`:
- Around line 798-813: Replace the global files-table constraint setup with a
session-local failure mechanism, avoiding database-wide DDL and interference
with concurrent tests. Update the test around the current constraint setup and
cleanup so the intended insert failure and atomicity assertion remain covered;
if DDL must remain, make deferred cleanup non-fatal and ensure it always
executes without t.Fatalf.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a3e41ae9-1f96-42ea-b4f8-525da356cdc2

📥 Commits

Reviewing files that changed from the base of the PR and between ca5882d and 64337a0.

📒 Files selected for processing (49)
  • docs/design/be/filestore.md
  • docs/design/be/managed-agent-skills-runtime.md
  • docs/research/anthropic-session-file-resource-contract.md
  • docs/research/pr-182-session-file-catalog-summary.md
  • internal/db/AGENTS.md
  • internal/db/files.go
  • internal/db/files_sqlx.go
  • internal/db/files_sqlx_test.go
  • internal/db/filestore.go
  • internal/db/filestore_archive_entries.go
  • internal/db/filestore_cleanup.go
  • internal/db/filestore_cleanup_sqlx_test.go
  • internal/db/filestore_entry_methods.go
  • internal/db/filestore_scan.go
  • internal/db/filestore_sqlx.go
  • internal/db/filestore_test.go
  • internal/db/migrations/00036_unify_session_resources_and_files.sql
  • internal/db/migrations_postgres_test.go
  • internal/db/session_file_mounts_sqlx.go
  • internal/db/session_file_mounts_sqlx_test.go
  • internal/db/session_namespace_helpers.go
  • internal/db/session_namespace_mutations.go
  • internal/db/session_namespace_node_methods.go
  • internal/db/session_namespace_nodes.go
  • internal/db/session_skill_archive_resources.go
  • internal/db/session_skill_archive_resources_test.go
  • internal/db/sessions.go
  • internal/db/sessions_sqlx.go
  • internal/db/workspace_storage.go
  • internal/environments/runner.go
  • internal/filestore/cleanup.go
  • internal/filestore/cleanup_test.go
  • internal/filestore/persistent_backend.go
  • internal/filestore/protocol_test.go
  • internal/filestore/service.go
  • internal/filestore/service_test.go
  • internal/filestore/service_test_support_test.go
  • internal/filestore/skill_archives.go
  • internal/filestore/skill_archives_test.go
  • internal/skills/handler.go
  • tests/deployments_api_test.go
  • tests/environments_runner_cloud_test.go
  • tests/files_api_test.go
  • tests/filestore_db_test.go
  • tests/filestore_fixed_roots_test.go
  • tests/filestore_provision_roots_test.go
  • tests/sessions_api_test.go
  • tests/sessions_file_resources_api_test.go
  • tests/workspace_storage_usage_test.go
💤 Files with no reviewable changes (2)
  • internal/db/filestore_entry_methods.go
  • internal/db/filestore_archive_entries.go

Comment thread docs/research/pr-182-session-file-catalog-summary.md Outdated
Comment thread internal/db/files_sqlx.go Outdated
Comment thread internal/db/filestore_cleanup.go Outdated
Comment thread internal/db/filestore_scan.go Outdated
Comment thread internal/db/filestore_scan.go Outdated
Comment thread internal/db/migrations/00047_unify_session_resources_and_files.sql
Comment thread internal/db/session_resource_file_helpers.go Outdated
Comment thread internal/db/session_namespace_mutations.go Outdated
Comment thread internal/db/workspace_storage.go Outdated
Comment thread tests/sessions_api_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/filestore/cleanup_test.go (1)

255-282: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Place this success case after the failure case.

TestCleanupWorkerRunTTLSweepOnceLogsMetadataAnomalies succeeds, but it precedes TestCleanupWorkerRunTTLSweepOnceReturnsDatabaseError. Move it below the failure test. As per coding guidelines, “**/*_test.go: 测试组织顺序先写失败场景,再写成功场景”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/filestore/cleanup_test.go` around lines 255 - 282, Reorder the tests
so TestCleanupWorkerRunTTLSweepOnceReturnsDatabaseError appears before the
successful TestCleanupWorkerRunTTLSweepOnceLogsMetadataAnomalies case. Do not
change either test’s implementation or behavior.

Source: Coding guidelines

internal/db/filestore.go (1)

273-280: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use unified Resource terminology in the cleanup contract.

This PR removes filestore_entries, but the new exported field still uses EntryExternalID and produces an entry_external_id operational key. Rename it to ResourceExternalID or SessionResourceExternalID and update the worker/tests/logging together.

Proposed rename
-	EntryExternalID string
+	ResourceExternalID string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/filestore.go` around lines 273 - 280, Rename the exported
FilestoreCleanupAnomaly field EntryExternalID to ResourceExternalID (or
SessionResourceExternalID), then update every worker, test, and logging
reference to use the corresponding unified resource operational key instead of
entry_external_id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/db/migrations/00036_unify_session_resources_and_files.sql`:
- Around line 277-279: Update the session_resources_owned_file_uuid_v1_idx
migration to build the index with CREATE INDEX CONCURRENTLY and mark the Goose
migration as NO TRANSACTION, preserving the existing columns and partial-index
predicate.

---

Nitpick comments:
In `@internal/db/filestore.go`:
- Around line 273-280: Rename the exported FilestoreCleanupAnomaly field
EntryExternalID to ResourceExternalID (or SessionResourceExternalID), then
update every worker, test, and logging reference to use the corresponding
unified resource operational key instead of entry_external_id.

In `@internal/filestore/cleanup_test.go`:
- Around line 255-282: Reorder the tests so
TestCleanupWorkerRunTTLSweepOnceReturnsDatabaseError appears before the
successful TestCleanupWorkerRunTTLSweepOnceLogsMetadataAnomalies case. Do not
change either test’s implementation or behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2aaa21b6-a307-4169-b821-ab509891e1ed

📥 Commits

Reviewing files that changed from the base of the PR and between 64337a0 and 0d2587d.

📒 Files selected for processing (17)
  • docs/design/be/filestore.md
  • internal/db/files_sqlx_test.go
  • internal/db/filestore.go
  • internal/db/filestore_cleanup.go
  • internal/db/filestore_scan.go
  • internal/db/filestore_sqlx.go
  • internal/db/migrations/00036_unify_session_resources_and_files.sql
  • internal/db/session_namespace_helpers.go
  • internal/db/session_namespace_mutations.go
  • internal/db/session_namespace_review_test.go
  • internal/db/workspace_storage.go
  • internal/filestore/cleanup.go
  • internal/filestore/cleanup_test.go
  • tests/filestore_db_test.go
  • tests/sessions_api_test.go
  • tests/sessions_file_resources_api_test.go
  • tests/workspace_storage_usage_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/sessions_api_test.go
  • internal/db/workspace_storage.go
  • internal/db/filestore_sqlx.go
  • internal/db/session_namespace_mutations.go
  • internal/db/session_namespace_helpers.go
  • internal/db/filestore_cleanup.go
  • tests/filestore_db_test.go
  • tests/sessions_file_resources_api_test.go

Comment thread internal/db/migrations/00047_unify_session_resources_and_files.sql

pxguan commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Session Resource 与 File 统一持久化

Issue #184 ·
最终决策 ·
PR #190 ·
Filestore 总体设计

建议评审顺序

顺序 代码 只回答一个问题
1 migration 00036 历史数据能否无歧义切换且不丢 identity?
2 filestore_scan.go Resource + File 是否是唯一 namespace 读模型?
3 files_sqlx.go Input/Output Catalog identity 与可见性是否正确?
4 session_file_mounts_sqlx.go Input 是否只借用 Source File?
5 session_namespace_helpers.go / mutations.go Resource、Owned File、账本与 job 是否原子?
6 filestore_cleanup.go / workspace_storage.go 对象和容量是否只释放一次?
7 测试证据 关键失败场景是否被覆盖?

变化概览

flowchart LR
    subgraph Before["统一前:三份事实"]
        A["session_resources\nResource 声明"]
        B["filestore_entries\npath + object"]
        C["files\n真实 File + projection"]
        A <--> B
        B <--> C
    end

    subgraph After["统一后:两份事实"]
        D["session_resources\nSession + path + reference"]
        E["files\nidentity + metadata + object"]
        F["filestore_filesystems\nfilesystem -> Session"]
        F --> D
        D --> E
    end

    Before -->|"00036 一次切换"| After
Loading
事实 唯一所有者
Session 归属、Attach identity、namespace path、资源引用 session_resources
File identity、metadata、S3 定位、计费、对象清理责任 files
Filestore identity 与 public Session 的绑定 filestore_filesystems
用量 workspace_storage_usage 事务账本;不是文件事实源

最终裁决

议题 最终选择 有意放弃
Input file_id 请求/响应复用 Source file_ 新 session-scoped File/Alias
Attach identity 每次 Attach 独立 sesrsc_ file_ 区分 Attach
Namespace membership path is not null attachedfilesystem_uuid
公开 Resource payload is not null 单独 public/role 列
Catalog role 活动 Resource 位于 /uploads/outputs catalogednamespace_role
Source 删除 有活动 Resource 引用时拒绝 Source identity 删除后由 Attach 保活
FUSE readiness 保留现有 filesystem/mount 合同 逐文件 copy/preparation 状态机
发布 migration 一次切换 双写、双读、view、trigger、feature flag
旧 identity 保留 Source file_、Input sesrsc_、Output file_ fse_ 与 Input projection alias

兼容性取舍:Input attach 不返回 Anthropic 当前行为中的新 session-scoped file_id
本项目用 sesrsc_ 表达 Attach identity。

数据模型

下图是应用维护的逻辑引用;数据库不创建 FK。

flowchart LR
    WS["Workspace"] --> SES["Session"]
    SES --> FS["Filestore filesystem"]
    SES --> RES["Session Resource"]
    WS --> FILE["File"]
    RES -. "file_uuid" .-> FILE
    RES -. "skill_version_uuid" .-> SKILL["Skill Version"]
Loading
Resource payload path 引用 identity/metadata 所有者 容量/cleanup
Attached Input 非 NULL /uploads/... Source file_uuid Source File 不重复计费、不清理 Source
Owned Output NULL /outputs/... Owned file_uuid Owned File filestore_bytes + object cleanup
其他 Owned File NULL 其他 namespace Owned file_uuid Owned File filestore_bytes + object cleanup
Directory NULL 非根 path Resource
Skill Archive NULL /skills/... skill_version_uuid Skill Version 不计费、不清理 catalog object
/ 虚拟 filesystem filesystem 无持久化节点

必须保持的不变量

# 不变量 保护方式
1 活动 (workspace_id, session_id, path) 唯一 部分唯一索引
2 path / parent_path 同空同有 CHECK
3 File、Directory、Skill Archive 引用形状互斥 CHECK
4 内部 Resource(payload=NULL)不能有 secret_payload CHECK
5 Input 不拥有对象;Owned File 才返回 OwnedBytes() DTO 映射 + mutation guard
6 Source 有活动引用时不能删除 workspace lock + reference query
7 所有跨表查询显式携带 tenant scope 命名 SQL + 集成测试
8 过期立即不可见,容量在 sweep 提交后释放 查询 predicate + TTL transaction

三个 adapter,共用一份事实

flowchart TB
    FACT["Resource + File"]
    FACT --> S["Sessions adapter\npayload 非空"]
    FACT --> F["Files adapter\n/uploads + /outputs"]
    FACT --> FS["Filestore adapter\n完整 namespace"]
Loading
Adapter 可见集合 返回 identity 关键过滤
Sessions Resource 公开 Resource sesrsc_;Input payload 保留 Source file_ payload is not null
scoped Files /uploads Input Catalog Source file_ 公开、活动、未过期、按 file_uuid 去重
scoped Files /outputs Output Catalog Owned file_ 内部、活动、未过期、路径位于 /outputs
Filestore 根下完整 namespace node sesrsc_ filesystem → Session → path
Catalog 行为 结果
同一 Source 多次 Attach Resource API 返回多个 sesrsc_;Files Catalog 返回一个 Source File
删除最新 Attach 仍有 Attach 时继续可见,以剩余最新 Resource 排序
Output move 出 /outputs 立即退出 Catalog,File ID 不变
Output move 回 /outputs 恢复同一个 file_
/transcripts/tool_results/skills 不进入 Files Catalog

写入与生命周期

flowchart TD
    I["Attach Input"] --> IR["只写 Resource\n引用 Source File"]
    IR --> IN["不创建 File\n不计费\n不清理对象"]

    O["Put / Copy Output"] --> OBJ["唯一 S3 key + orphan guard"]
    OBJ --> TX["同一事务:Resource + Owned File\nquota delta + cleanup job"]

    M["Move"] --> MP["只改 Resource path\nFile ID / object key 不变"]
    D["Remove / TTL"] --> RT["同一事务退休 Resource + File\n释放容量 + cleanup job"]
Loading
操作 事务内必须一起发生 失败保护
Input attach Session 容量、目录、Resource path、Source 引用 Session/workspace/filesystem/File locks
Input delete 精确退休一个 Resource 不改 Source File/账本/object job
Output put/copy Resource、File、quota、取消 orphan guard commit 未知时保留 guard
Overwrite 新 File 事实、旧对象 job、容量差值 全部回滚或全部提交
Move path/parent path File identity 与对象不动
Remove/TTL Resource/File 软删、容量释放、对象 job retireSessionNamespaceNodeTx 集中处理
Session delete 退休公开 Resource/filesystem、创建父 cleanup job worker 每批最多 100 个 Owned File
Skill replacement 全量替换 skill_archive Resource 不复制 catalog 对象 metadata

锁序

操作 顺序
Input attach Session → workspace → filesystem → Source File FOR SHARE
Input Resource delete Session → workspace → filesystem
Files delete workspace → File FOR UPDATE → active reference check
Namespace mutation workspace → filesystem
TTL batch workspace ID 升序 → filesystem ID 升序
Session cleanup workspace → filesystem

配额与对象所有权

对象 files_bytes filestore_bytes 谁删除对象
普通/Source File 计一次 0 Files cleanup
任意数量 Input Attach 0 0
Owned File 0 计一次 Filestore cleanup
Skill Archive Resource 0 0 当前保留 catalog object
特殊情况 策略
正常 mutation 只更新一行事务账本,不实时聚合
Migration 00036 不改账本:物理对象和 ownership 未变化
cleanup 缺失 bucket/key 退休逻辑节点、继续健康行、重算 workspace 账本、worker 告警
commit 结果未知 不立即删对象,由延迟 orphan guard 裁决

Migration 00036

flowchart LR
    A["扩展列/约束"] --> B{"Preflight"}
    B -->|"失败"| R["整体回滚\n保留旧表"]
    B -->|"通过"| C["合并 Input"]
    C --> D["Materialize 非 Input File"]
    D --> E["创建内部 Resource"]
    E --> F["删除 Input projection"]
    F --> G["建索引/验证 CHECK"]
    G --> H["删除 filestore_entries"]
Loading

Preflight

检查 失败结果
活动 Entry 无法解析同租户 filesystem/Session 显式异常,整次 migration 回滚
Input 找不到原 Resource 或 Source File 显式异常,整次 migration 回滚
Input 带 TTL 显式异常,整次 migration 回滚
重复活动 path 唯一索引创建失败并回滚
Resource shape 非法 CHECK validation 失败并回滚

Identity 矩阵

旧数据 统一后 保留/失效
Source File 原 File UUID、file_ 保留
Input Resource 原 Resource 接管 path/Source UUID sesrsc_ 保留
Input projection File 删除 旧 projection file_ 失效
Output Entry + projection File 补全原 File;新建内部 Resource Output UUID、file_ 保留;fse_ 失效
普通 File Entry Entry UUID 成为 File UUID;新建 Resource file_sesrsc_fse_ 失效
Directory/固定根 Directory Resource sesrsc_fse_ 失效
Skill Archive 引用具体 Skill Version 的 Resource sesrsc_;不创建 File
软删除 Entry 历史 不迁移 历史失效
/ filesystem 虚拟节点 无持久化 identity

Down 不可无损实现,仅 select 1;部署回退依赖数据库备份,不支持新旧二进制混跑。

Reviewer checklist 与证据

检查 主要证据
Migration 分叉会回滚,identity 符合矩阵 migrations_postgres_test.go
运行时不再依赖 filestore_entries TestSessionNamespaceUsesResourcesAndFiles
Input 复用 Source file_id,多 Attach 按真实 File 去重 TestSessionFileResourceContract
Source metadata/download policy 不被复制或改写 TestSessionInputResourcePreservesSourcePolicy
Source delete 与 Attach 并发不会留下悬空引用 TestSessionFileResourceProtectsSourceFileTestSessionFileResourceBindSerializesWithSourceDelete
workspace 间不泄漏 Catalog TestSessionFileCatalogWorkspaceIsolation
Resource/File/quota/job 原子提交 TestSessionOutputCatalogWriteIsAtomic
Input 不能被通用 Filestore mutation 修改 TestSessionInputResourceRejectsGenericFilestoreMutations
Output ID、move/remove/TTL 生命周期稳定 TestSessionOutputFileLifecycle
Session cleanup 有界,毒数据不阻塞健康行 TestDeleteSessionQueuesBoundedFilesystemCleanup
对象容量不重复计费或释放 TestWorkspaceStorageUsageLedger
Skill Resource 不误清理 catalog 对象 TestFilestoreSkillArchivesUseResources
历史 ownership 查询可使用匹配索引 TestVisibleFileHistoryPredicateHasMatchingIndex
tenant scope 与 domain error 显式保持 session_namespace_review_test.go

非目标与已知代价

项目 状态
rclone/FUSE、Token、五个 mount 重设计 非目标
逐文件 Sandbox copy preparation 非目标
Output revision/history 非目标
Skill catalog reference-aware GC 非目标
fse_ / Input projection alias 明确不保留
软删除 Entry 历史 明确不迁移
Catalog 跨页数据库快照 不保证;当前是实时键集分页
缺失对象位置后的物理删除 无法保证;退休逻辑状态并告警

Rename the SessionNamespaceNode type and all related identifiers across
the codebase to SessionResourceFile, aligning the naming with the unified
session resource and file model introduced in this PR.

- Rename type, methods, constants and helper functions in internal/db
- Rename five session_namespace*.go source files to session_resource*
- Update SQL derived-table alias from namespace_nodes to session_resource_files
- Update error messages and comments to drop the obsolete node wording
- Sync design docs (filestore.md, session-resource-file-unification.md)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/db/filestore_cleanup.go (1)

204-224: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Retire expired source references rather than skipping them.

continue leaves an invalid historical source reference active. Because the query is bounded and ordered, enough such rows can be selected on every sweep and permanently block later owned files from expiring. Resolve its scope and soft-delete only the Resource; do not enqueue object cleanup or decrement quota.

Proposed fix
 for _, entry := range entries {
+    scope, found := cleanupScopeByFilesystemUUID[entry.FilesystemUUID]
+    if !found {
+        return nil, nil, ErrNotFound
+    }
     if entry.ReferencesSourceFile() {
+        if err := retireSessionResourceFileTx(ctx, tx, scope.WorkspaceID, entry.ID, now); err != nil {
+            return nil, nil, err
+        }
         continue
     }
-    scope, found := cleanupScopeByFilesystemUUID[entry.FilesystemUUID]
-    if !found {
-        return nil, nil, ErrNotFound
-    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/filestore_cleanup.go` around lines 204 - 224, Update the entry
handling in the cleanup loop around ReferencesSourceFile so source-file
references still resolve their cleanup scope and are passed through retirement
via retireSessionResourceFileTx. For these entries, skip
sessionResourceFileCleanupAnomaly and enqueueSessionResourceFileCleanupJobTx,
ensuring only the Resource is soft-deleted without object cleanup or quota
decrement; continue normal cleanup processing for owned entries.
🧹 Nitpick comments (3)
tests/sessions_file_resources_api_test.go (3)

774-842: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Check for orphan files rows after a failed catalog write.

The current assertions prove that no active session resource or scoped catalog entry remains, but they do not detect an unscoped files row left behind. Because the injected constraint allows scope_id IS NULL, a partial write could insert an orphan File and still pass these checks. Assert that the failed output’s File/S3 identity is absent from both scoped and unscoped catalog views.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sessions_file_resources_api_test.go` around lines 774 - 842, Extend
TestSessionOutputCatalogWriteIsAtomic to capture the failed output’s File/S3
identity and assert it is absent from both scoped and unscoped catalog views
after PutFilestoreFile fails. Reuse the existing file-listing or catalog lookup
helpers, ensuring the assertions detect orphan files with scope_id IS NULL as
well as scoped entries, while preserving the current resource and storage-byte
checks.

1157-1194: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Verify expiry quota and cleanup side effects.

ExpireSessionResourceFiles returns cleanup jobs and releases workspace bytes, but this test discards those results and only checks catalog visibility. A regression that hides expired rows without releasing quota or enqueueing cleanup would pass. Assert the expected ledger reduction for both expired owned files and validate the returned cleanup work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sessions_file_resources_api_test.go` around lines 1157 - 1194, Extend
the expiry scenario around ExpireSessionResourceFiles to retain its returned
cleanup jobs and released-byte result instead of discarding them. Assert that
the workspace byte ledger decreases by the expired owned file’s size, and verify
the returned cleanup work contains the expected expired resource. Keep the
existing catalog-visibility assertions.

1205-1267: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Verify namespace and object state after every rejected mutation.

The matrix checks only ErrPreconditionFailed, storage bytes, and source File metadata. A partial move, remove, or overwrite could still alter the session resource path or backing object while leaving those checks unchanged. After each rejection, assert that the original resource still references the same source and that destination/parent paths and object contents remain unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sessions_file_resources_api_test.go` around lines 1205 - 1267, Extend
the per-mutation assertions in the test loop around mutate to verify namespace
and object state after every ErrPreconditionFailed result: confirm the session
resource still references the original file and /locked/input.txt path,
destination and parent paths remain unchanged, and the backing file
object/content is unchanged. Keep the existing storage-byte and source-metadata
checks, and use the database/resource helpers already used by this test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design/be/session-resource-file-unification.md`:
- Around line 18-20: Update the review-map references in the design document:
replace the removed session_namespace_* file links with their current
session-resource equivalents, change the line 20 test-evidence link to the
existing heading anchor, and rename the test file reference at the later
review-test section from session_namespace_review_test.go to
session_resource_file_review_test.go. Preserve the surrounding review-map
structure and wording.

---

Outside diff comments:
In `@internal/db/filestore_cleanup.go`:
- Around line 204-224: Update the entry handling in the cleanup loop around
ReferencesSourceFile so source-file references still resolve their cleanup scope
and are passed through retirement via retireSessionResourceFileTx. For these
entries, skip sessionResourceFileCleanupAnomaly and
enqueueSessionResourceFileCleanupJobTx, ensuring only the Resource is
soft-deleted without object cleanup or quota decrement; continue normal cleanup
processing for owned entries.

---

Nitpick comments:
In `@tests/sessions_file_resources_api_test.go`:
- Around line 774-842: Extend TestSessionOutputCatalogWriteIsAtomic to capture
the failed output’s File/S3 identity and assert it is absent from both scoped
and unscoped catalog views after PutFilestoreFile fails. Reuse the existing
file-listing or catalog lookup helpers, ensuring the assertions detect orphan
files with scope_id IS NULL as well as scoped entries, while preserving the
current resource and storage-byte checks.
- Around line 1157-1194: Extend the expiry scenario around
ExpireSessionResourceFiles to retain its returned cleanup jobs and released-byte
result instead of discarding them. Assert that the workspace byte ledger
decreases by the expired owned file’s size, and verify the returned cleanup work
contains the expected expired resource. Keep the existing catalog-visibility
assertions.
- Around line 1205-1267: Extend the per-mutation assertions in the test loop
around mutate to verify namespace and object state after every
ErrPreconditionFailed result: confirm the session resource still references the
original file and /locked/input.txt path, destination and parent paths remain
unchanged, and the backing file object/content is unchanged. Keep the existing
storage-byte and source-metadata checks, and use the database/resource helpers
already used by this test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3df1db5f-e789-4085-b1d8-f80fbcf9f219

📥 Commits

Reviewing files that changed from the base of the PR and between 0af03a5 and 68e4e73.

📒 Files selected for processing (29)
  • docs/design/be/filestore.md
  • docs/design/be/session-resource-file-unification.md
  • internal/db/filestore.go
  • internal/db/filestore_cleanup.go
  • internal/db/filestore_cleanup_sqlx_test.go
  • internal/db/filestore_scan.go
  • internal/db/filestore_sqlx.go
  • internal/db/filestore_test.go
  • internal/db/session_resource_file_helpers.go
  • internal/db/session_resource_file_methods.go
  • internal/db/session_resource_file_mutations.go
  • internal/db/session_resource_file_review_test.go
  • internal/db/session_resource_files.go
  • internal/db/session_skill_archive_resources.go
  • internal/db/session_skill_archive_resources_test.go
  • internal/db/sessions.go
  • internal/filestore/cleanup.go
  • internal/filestore/cleanup_test.go
  • internal/filestore/persistent_backend.go
  • internal/filestore/service.go
  • internal/filestore/service_test.go
  • internal/filestore/service_test_support_test.go
  • internal/filestore/skill_archives.go
  • internal/filestore/skill_archives_test.go
  • tests/environments_runner_cloud_test.go
  • tests/filestore_db_test.go
  • tests/filestore_provision_roots_test.go
  • tests/sessions_file_resources_api_test.go
  • tests/workspace_storage_usage_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/db/sessions.go
  • docs/design/be/filestore.md

Comment thread docs/design/be/session-resource-file-unification.md Outdated
arthur-zhang and others added 5 commits July 30, 2026 16:05
…n layer

将运行时代码中 4 处在 SQL 层用 gen_random_uuid()+concat 生成 uuid 与
external_id 的逻辑,改为在 Go 代码层用 ids.New()+uuid.NewString() 统一生成,
使 external_id 采用与项目其他位置一致的 24 位 Base62 格式,而非去横线 UUID。

涉及 directory、owned file(File + Resource 两层)与 skill archive 创建路径。
migration 00036 属一次性数据迁移,保留 SQL 层生成。
…-file-unification' into codex/issue-184-session-resource-file-unification

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/db/session_resource_file_helpers.go (1)

405-422: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse the two identity helpers into one prefixed generator.

Both functions are structurally identical; a single newIdentity(prefix string) (string, string, error) keeps the duplicate-code budget headroom and the call sites just as readable.

♻️ Suggested consolidation
-func newSessionResourceIdentity() (resourceUUID, resourceExternalID string, err error) {
-	resourceExternalID, err = ids.New("sesrsc_")
-	if err != nil {
-		return "", "", err
-	}
-	return uuid.NewString(), resourceExternalID, nil
-}
-
-// newFileIdentity 在应用层生成真实 File 的 uuid 与 file_ external ID。
-func newFileIdentity() (fileUUID, fileExternalID string, err error) {
-	fileExternalID, err = ids.New("file_")
-	if err != nil {
-		return "", "", err
-	}
-	return uuid.NewString(), fileExternalID, nil
-}
+// newIdentity 在应用层生成 uuid 与带前缀的 external ID。
+func newIdentity(prefix string) (identityUUID, externalID string, err error) {
+	externalID, err = ids.New(prefix)
+	if err != nil {
+		return "", "", err
+	}
+	return uuid.NewString(), externalID, nil
+}

As per coding guidelines: “修改 Go 或 TypeScript/TSX 生产代码后运行重复代码检查;遵守 Go 3.75% … 的生产代码预算”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/session_resource_file_helpers.go` around lines 405 - 422,
Optionally consolidate newSessionResourceIdentity and newFileIdentity into a
shared newIdentity(prefix string) helper that generates the prefixed external ID
and UUID, preserving the existing error propagation and return ordering. Update
both call sites to pass their respective prefixes while keeping their behavior
unchanged.

Source: Coding guidelines

internal/db/filestore_cleanup.go (1)

242-244: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Prefer entry.OwnedBytes() over filestoreInt64(entry.SizeBytes) for released-byte accounting.

OwnedBytes() already encodes the ownership rule (0 for non-file kinds and source-file references), which is what the mutation paths in internal/db/session_resource_file_mutations.go use. Using raw SizeBytes here releases bytes for any expired/cleaned resource kind that carries a size but is not charged (e.g. archive snapshots), which can drive usage negative until reconciliation.

♻️ Suggested change
 		releasedBytes, err := addWorkspaceStorageDelta(
-			releasedBytesByWorkspace[scope.WorkspaceID], filestoreInt64(entry.SizeBytes),
+			releasedBytesByWorkspace[scope.WorkspaceID], entry.OwnedBytes(),
 		)
 				releasedBytes, err = addWorkspaceStorageDelta(
 					releasedBytes,
-					filestoreInt64(entry.SizeBytes),
+					entry.OwnedBytes(),
 				)

Also applies to: 370-373

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/db/filestore_cleanup.go` around lines 242 - 244, The released-byte
accounting in the cleanup flow should use the ownership-aware value from
entry.OwnedBytes() instead of filestoreInt64(entry.SizeBytes). Update both
occurrences around addWorkspaceStorageDelta so non-file kinds and source-file
references contribute zero, matching the mutation paths and preventing uncharged
resources from reducing workspace usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/db/migrations/00037_snapshot_session_skills.sql`:
- Around line 57-62: Update the `insert into files` query in migration
`00037_snapshot_session_skills.sql` to select only `skill_archive` resources
whose `file_uuid` is non-null and have no corresponding row in `files`. Preserve
the existing filters and ensure reruns or pre-existing snapshot rows cannot
reinsert an already-existing file UUID.

In `@tests/workspace_storage_usage_test.go`:
- Line 279: Move the “failure expired file path remains reserved until ttl
cleanup” subtest before the “success files and filestore maintain one
transactional total”, “success overwrite move ...”, and “success expired bytes
are released by the ttl transaction” subtests, keeping the failure scenarios
grouped before success scenarios without changing test behavior.

---

Nitpick comments:
In `@internal/db/filestore_cleanup.go`:
- Around line 242-244: The released-byte accounting in the cleanup flow should
use the ownership-aware value from entry.OwnedBytes() instead of
filestoreInt64(entry.SizeBytes). Update both occurrences around
addWorkspaceStorageDelta so non-file kinds and source-file references contribute
zero, matching the mutation paths and preventing uncharged resources from
reducing workspace usage.

In `@internal/db/session_resource_file_helpers.go`:
- Around line 405-422: Optionally consolidate newSessionResourceIdentity and
newFileIdentity into a shared newIdentity(prefix string) helper that generates
the prefixed external ID and UUID, preserving the existing error propagation and
return ordering. Update both call sites to pass their respective prefixes while
keeping their behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a905a68-3501-4a94-a607-f8b5093d4020

📥 Commits

Reviewing files that changed from the base of the PR and between 68e4e73 and a6a361d.

📒 Files selected for processing (22)
  • docs/design/be/filestore.md
  • docs/design/be/managed-agent-skills-runtime.md
  • docs/design/be/session-resource-file-unification.md
  • internal/db/AGENTS.md
  • internal/db/filestore.go
  • internal/db/filestore_cleanup.go
  • internal/db/filestore_scan.go
  • internal/db/filestore_sqlx.go
  • internal/db/migrations/00037_snapshot_session_skills.sql
  • internal/db/migrations_postgres_test.go
  • internal/db/session_resource_file_helpers.go
  • internal/db/session_resource_file_mutations.go
  • internal/db/session_skill_archive_resources.go
  • internal/db/session_skill_archive_resources_test.go
  • internal/db/sessions.go
  • internal/db/sessions_sqlx.go
  • internal/environments/runner.go
  • internal/filestore/skill_archives.go
  • internal/filestore/skill_archives_test.go
  • tests/filestore_db_test.go
  • tests/sessions_file_resources_api_test.go
  • tests/workspace_storage_usage_test.go
💤 Files with no reviewable changes (4)
  • internal/filestore/skill_archives_test.go
  • internal/db/sessions.go
  • internal/filestore/skill_archives.go
  • tests/sessions_file_resources_api_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/design/be/managed-agent-skills-runtime.md
  • internal/environments/runner.go
  • docs/design/be/filestore.md

Comment thread internal/db/migrations/00048_snapshot_session_skills.sql
Comment thread tests/workspace_storage_usage_test.go Outdated
pxguan and others added 4 commits July 31, 2026 13:05
将 origin/main 合并到当前分支,冲突按语义处理:

核心矛盾:HEAD 的 session_resource 重构(filestore_entries → session_resources)
与 main 的 bigint→UUID 标识符迁移在同一代码区域正交碰撞。

合并策略:保留 HEAD 的 session_resource 业务模型,完整 UUID 化以适配 main 的 schema。

主要变更:
- Migration:HEAD 00036-00038 保留在前,main 00036-00046 顺延为 00039-00049
- SQL 语义修复:移除所有 select uuid from workspaces where id = :workspace_uuid
  旧模式,改为 CAST(:workspace_uuid AS uuid) 直接比较
- 方法签名 UUID 化:filestore cleanup job ID 从 int64 改为 string (UUID)
- filestore filesystem 分页游标从 (path, id) 改为 (path, uuid)
- 测试全面 UUID 化:所有 int64 ID 引用更新为 UUID 字符串
…on-resource-file-unification

# Conflicts:
#	internal/db/filestore_archive_entries.go
#	internal/db/filestore_cleanup.go
#	internal/db/filestore_entry_helpers.go
#	internal/db/filestore_scan.go
#	internal/db/filestore_sqlx.go
#	internal/db/filestore_test.go
#	internal/db/session_file_mounts_sqlx.go
#	internal/db/session_file_mounts_sqlx_test.go
#	internal/db/session_resource_file_mutations.go
#	internal/db/session_resource_files.go
#	internal/db/sessions.go
#	internal/db/workspace_storage.go
恢复 Session File Catalog 的可见性与删除边界。

将清理任务、Skill Archive 和测试夹具适配 UUID schema,避免继续引用已移除的 identity 列或 filestore_entries。

Refs: #184

Co-authored-by: Codex <noreply@openai.com>
@duckpr

duckpr Bot commented Jul 31, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using anthropic/glm-5.2𝕏

@duckpr duckpr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuckPR reviewer: opencode
Model: anthropic/glm-5.2

✅ 未发现新问题。

Reviewed changes — 本次复评审仅覆盖增量提交 f24fcb6("fix(db): 对齐 Session 与清理任务 UUID 边界"),该提交将清理任务和 Session 分页查询的 UUID 绑定对齐到原生 uuid 类型。

  • 对齐 Session 分页游标的 UUID 绑定ListSessionsPageListSessionThreadsPageListSessionEventsPageparams.Cursor.UUID 包入 dbUUID(),使游标参数以原生 uuid 类型绑定到 s.uuid / uuid 列,而非文本。
  • 清理任务查询改用原生 JSONB UUID 比较leasedFilestoreCleanupJobQueryleaseFilestoreCleanupJobs 等 join 条件从 cast(fs.uuid as text) = j.payload->>'filesystem_uuid' 切换为 j.payload->'filesystem_uuid' = to_jsonb(fs.uuid)enqueueFilestoreFilesystemCleanupJobQueryinsertFilestoreObjectCleanupJobSQLX 改用 fs.uuid = :filesystem_uuid bind 参数 join。
  • Payload 构造迁移到应用层enqueueFilestoreFilesystemCleanupJobTxinsertFilestoreObjectCleanupJobSQLX 将 payload 从 SQL jsonb_build_object 改为 Go json.Marshal + cast(:payload as jsonb) bind,列投影不再 cast(... as text)
  • 引入 typed cleanup job row structs — 新增 filestoreObjectCleanupJobRow / filestoreFilesystemCleanupJobRow 直接扫描 uuid.UUID 字段,Lease 入口从直接扫描 public job struct 改为先扫描 row 再 .job() 转换。

Pullfrog  | View workflow run | Using anthropic/glm-5.2𝕏

…-file-unification' into codex/issue-184-session-resource-file-unification

# Conflicts:
#	internal/db/filestore_cleanup.go
#	internal/db/filestore_sqlx.go

@duckpr duckpr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuckPR reviewer: opencode
Model: anthropic/glm-5.2

✅ 未发现新问题。

Reviewed changes — 本次复评审覆盖自上一次 Pullfrog 评审(f24fcb6)以来的增量提交,核心是 commit b5f0b91 将整个 DB 层从手写 sqlx 查询迁移到 Yourbatis mapper(XML SQL + sqlmapgen 生成代码),并合并了 origin/main 的 Yourbatis 基础设施与 admin API 拆分。

  • 将 DB 层迁移到 Yourbatis mappersession_resource_file_mutations.gosession_resource_file_helpers.gofilestore_cleanup.gofiles.goworkspace_storage.gofilestore_filesystems.go 等全部从 BeginTxx/namedExecContext 切换为 yourbatis.DB.Transaction + typed mapper interface(FileMapperSessionResourceMapperFilestoreCleanupMapper 等),SQL 移入对应 *_mapper.xml,事务边界和配额扣减逻辑保持不变。
  • 迁移文件重编号 — 因 origin/main 合入新迁移,统一迁移从 00036 顺延为 00047_unify_session_resources_and_files.sql,skill 快照迁移从 00037 顺延为 00048_snapshot_session_skills.sql;SQL 内容不变。
  • 停止跟踪生成代码*.gen.go 加入 .gitignore,新增 go-generate pre-commit hook,CI lint/complexity/dead-code 工作流在检查前增加 go generate ./... 步骤。
  • 合并 admin API 拆分 — 从 internal/db/admin.go(1162 行)拆分为 admin_api_keys.goadmin_users.goadmin_workspaces.go 等聚焦文件,引入对应的 Yourbatis mapper 与 PostgreSQL mapper 测试。

本次增量评审通过正确性与数据完整性两个 lens 并行检查,重点验证了 Yourbatis 迁移后的事务原子性、错误传播、sql.ErrNoRows 到领域错误的映射、多租户隔离以及存储账本一致性。所有业务逻辑与 f24fcb6 版本保持 1:1 对应,未发现迁移引入的新问题。先前 CodeRabbit 评审已标记的迁移账本对齐和清理字节核算问题仍为 open 状态,此处不再重复。

PullfrogView workflow run | Using anthropic/glm-5.2𝕏

Pullfrog  | View workflow run | Using anthropic/glm-5.2𝕏

Co-authored-by: Codex <noreply@openai.com>

@duckpr duckpr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuckPR reviewer: opencode
Model: anthropic/glm-5.2

✅ 未发现新问题。

Reviewed changes — 本次复评审仅覆盖增量提交 d895da6("feat(sessions): 支持相对挂载路径输入"),该提交将 Session 文件资源的挂载路径输入从绝对路径(/uploads/reports/input.csv)改为相对路径(reports/input.csv),提升输入体验。

  • 挂载路径校验改为相对路径语义isValidSessionFileMountPath 不再要求以 /uploads/ 开头,改为拒绝空串、前导 /、尾随 /// 以及 ./.. 路径段;sessionFileRuntimePathsessionFileAPIMountPath 的前缀拼接逻辑同步调整。
  • API 契约保持不变sessionFileAPIMountPath 仍为相对路径补 / 后发送给后端(/reports/input.csv),后端接收的 mount_path 格式未变。
  • 前端组件、i18n 与设计文档同步更新 — placeholder、帮助文案(mountHelp)、中英文 messages 与 session-file-resources-form.md 均改为相对路径表述;单元测试和 suite 测试用例已同步覆盖新的合法/非法输入。

改动范围小且自洽,校验逻辑、运行时路径预览与 API 边界三者一致。后端仍是路径规则、Workspace 隔离与冲突检测的权威来源,无安全性问题。

Pullfrog  | View workflow run | Using anthropic/glm-5.2𝕏

删除 ExpireSessionResourceFiles 数据层路径、filestore cleanup worker 的 TTL 扫描循环、Yourbatis mapper 中到期查询与相关测试,并同步更新 filestore 设计文档:expires_at 只影响 Resource/Catalog 查询可见性,不再触发自动软删除、配额释放或对象清理任务。

@duckpr duckpr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuckPR reviewer: opencode
Model: anthropic/glm-5.2

✅ 未发现新问题。

Reviewed changes — 本次复评审覆盖自上一次 Pullfrog 评审(b5f0b91)以来的两个增量提交:d895da6(前端相对挂载路径输入)和 54472b1(移除 session resource 文件的 TTL 到期清理逻辑)。

  • 移除 TTL 到期自动清理 — 删除了 ExpireSessionResourceFiles 数据层路径、cleanup worker 的 TTL sweep 循环与定时器、Yourbatis mapper 中的 ListExpiredScopes / ListExpiredFiles 查询,以及对应的 mapper 测试和 worker 测试。设计文档同步更新:expires_at 现在只影响 Resource/Catalog 查询的可见性,不再触发自动软删除、配额释放或对象清理。
  • 保留目录覆盖时的过期子树回收retireExpiredFilestoreSubtreeTx 及其 ListExpiredSubtreeFiles 查询被正确保留,确保目录 move/overwrite 时仍能回收已过期的目标文件。
  • 清理无残留引用filestoreTTLSweepBatchSizefilestoreTTLSweepIntervalrunTTLSweepAndLog 等 TTL 相关常量与方法已全部移除,filestoreCleanupDatabase interface 已同步删除 ExpireSessionResourceFiles 方法;全仓库无残留调用。FilestoreCleanupAnomalylogCleanupAnomalies 仍被 filesystem cleanup 路径使用,不属于死代码。
  • 前端改用相对挂载路径file-resource-path.ts 将校验逻辑从要求 /uploads/ 前缀改为要求非空相对路径(不以 / 开头或结尾),sessionFileAPIMountPath 在提交时补 / 前缀以保持 API 合同不变。i18n 文案与测试同步更新。

Pullfrog  | View workflow run | Using anthropic/glm-5.2𝕏

…on-resource-file-unification

# Conflicts:
#	internal/environments/runner.go
@arthur-zhang
arthur-zhang merged commit 3f853b0 into main Aug 5, 2026
9 checks passed
@arthur-zhang
arthur-zhang deleted the codex/issue-184-session-resource-file-unification branch August 5, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Files] 统一 Session Resource 与 File 数据模型并移除 filestore_entries

2 participants