Skip to content

feat(BA-5504): Update file operation services to set vfolder updated_at on mutations#10821

Open
seedspirit wants to merge 2 commits intomainfrom
BA-5504
Open

feat(BA-5504): Update file operation services to set vfolder updated_at on mutations#10821
seedspirit wants to merge 2 commits intomainfrom
BA-5504

Conversation

@seedspirit
Copy link
Copy Markdown
Contributor

Summary

  • Add updated_at nullable timestamp column to VFolderRow and a repository method update_vfolder_updated_at() that sets it to now()
  • Call update_vfolder_updated_at from all VFolderFileService mutation methods: upload, rename, move, delete (sync/async), and v2 variants
  • Add tests verifying updated_at is set after each mutation and remains NULL on read-only operations

Test plan

  • Unit tests verify update_vfolder_updated_at is called for upload, rename, move, delete (sync), delete (async), move v2, delete v2
  • Unit tests verify read-only operations (list, download, mkdir) do NOT call update_vfolder_updated_at
  • pants test --changed-since=HEAD~3 passes
  • CI passes

Resolves BA-5504

Copilot AI review requested due to automatic review settings April 6, 2026 09:12
seedspirit added a commit that referenced this pull request Apr 6, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added size:L 100~500 LoC comp:manager Related to Manager component labels Apr 6, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR introduces a new updated_at timestamp on vfolders and updates the vfolder file-operation service to bump that timestamp whenever file mutations occur.

Changes:

  • Added nullable updated_at to VFolderRow/VFolderData and a repository helper to set it to now().
  • Updated VFolderFileService mutation methods (upload/rename/move/delete + v2 variants) to call update_vfolder_updated_at.
  • Expanded unit tests to assert updated_at is bumped for mutations and not bumped for certain non-mutation operations.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/manager/services/vfolder/test_vfolder_file_service.py Adds assertions and new cases to verify update_vfolder_updated_at is (or isn’t) called.
src/ai/backend/manager/services/vfolder/services/file.py Calls the repository method to bump updated_at after mutations (including v2).
src/ai/backend/manager/repositories/vfolder/repository.py Adds update_vfolder_updated_at() and maps updated_at into VFolderData.
src/ai/backend/manager/models/vfolder/row.py Adds the updated_at ORM column and includes it in to_data().
src/ai/backend/manager/data/vfolder/types.py Adds updated_at to VFolderData.
changes/10821.feature.md Adds a changelog entry for the behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ai/backend/manager/models/vfolder/row.py
.where(VFolderRow.id == vfolder_id)
.values(updated_at=sa.func.now())
)
await session.execute(query)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

update_vfolder_updated_at() ignores the case where vfolder_id doesn’t exist (the UPDATE affects 0 rows and silently succeeds). This can hide data integrity issues and differs from other repository methods that raise VFolderNotFound. Consider checking the execute result’s rowcount and raising VFolderNotFound() when it is 0.

Suggested change
await session.execute(query)
result = await session.execute(query)
if result.rowcount == 0:
raise VFolderNotFound()

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A vfolder_id that does not exist in the call path cannot be provided. The track_content_update method is called only after the file operation service has verified the existence of the vfolder

Comment thread src/ai/backend/manager/services/vfolder/services/file.py Outdated
Comment thread tests/unit/manager/services/vfolder/test_vfolder_file_service.py Outdated
@seedspirit seedspirit changed the title feat(BA-5504): update file operation services to set vfolder updated_at on mutations feat(BA-5504): (WIP) update file operation services to set vfolder updated_at on mutations Apr 6, 2026
@github-actions github-actions bot added the require:db-migration Automatically set when alembic migrations are added or updated label Apr 7, 2026
@seedspirit seedspirit changed the title feat(BA-5504): (WIP) update file operation services to set vfolder updated_at on mutations feat(BA-5504): Update file operation services to set vfolder updated_at on mutations Apr 7, 2026
@seedspirit seedspirit requested a review from a team April 7, 2026 04:43
Comment on lines +636 to +697
async def test_mkdir_tracks(
self,
file_service: VFolderFileService,
mock_vfolder_repository: MagicMock,
mock_vfolder_data: MagicMock,
user_uuid: uuid.UUID,
vfolder_uuid: uuid.UUID,
) -> None:
mock_vfolder_repository.get_by_id_validated = AsyncMock(return_value=mock_vfolder_data)

action = MkdirAction(
user_id=user_uuid,
vfolder_uuid=vfolder_uuid,
path="newdir",
parents=False,
exist_ok=False,
)
await file_service.mkdir(action)

mock_vfolder_repository.track_content_update.assert_called_once_with(vfolder_uuid)

async def test_list_files_does_not_track(
self,
file_service: VFolderFileService,
mock_vfolder_repository: MagicMock,
mock_vfolder_data: MagicMock,
user_uuid: uuid.UUID,
vfolder_uuid: uuid.UUID,
) -> None:
mock_vfolder_repository.get_by_id_validated = AsyncMock(return_value=mock_vfolder_data)

action = ListFilesAction(
user_uuid=user_uuid,
vfolder_uuid=vfolder_uuid,
path=".",
)
await file_service.list_files(action)

mock_vfolder_repository.track_content_update.assert_not_called()

async def test_download_does_not_track(
self,
file_service: VFolderFileService,
mock_vfolder_repository: MagicMock,
mock_vfolder_data: MagicMock,
user_uuid: uuid.UUID,
vfolder_uuid: uuid.UUID,
keypair_resource_policy: dict[str, str],
) -> None:
mock_vfolder_repository.get_by_id_validated = AsyncMock(return_value=mock_vfolder_data)
mock_vfolder_repository.ensure_host_permission_allowed = AsyncMock()

action = CreateDownloadSessionAction(
keypair_resource_policy=keypair_resource_policy,
user_uuid=user_uuid,
vfolder_uuid=vfolder_uuid,
path="data/file.bin",
archive=False,
)
await file_service.download_file(action)

mock_vfolder_repository.track_content_update.assert_not_called()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These tests look like they could be consolidated into a single parametrized test, for example by extracting the actions into fixtures.

Comment on lines +20 to +23
op.add_column(
"vfolders",
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it okay not to set last_used or a default value for updated_at?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:manager Related to Manager component require:db-migration Automatically set when alembic migrations are added or updated size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants