Skip to content

refactor: split chunk storage from chunk index#56

Merged
woutervanranst merged 45 commits into
masterfrom
refactor-chunkservice
Apr 11, 2026
Merged

refactor: split chunk storage from chunk index#56
woutervanranst merged 45 commits into
masterfrom
refactor-chunkservice

Conversation

@woutervanranst
Copy link
Copy Markdown
Owner

@woutervanranst woutervanranst commented Apr 9, 2026

Summary

  • extract repository-local path helpers, move chunk hydration status alongside chunk storage, and introduce IChunkStorageService/ChunkStorageService as the chunk-blob boundary
  • move chunk upload, download, hydration, rehydration, and cleanup mechanics behind ChunkStorageService, and simplify feature handlers to rely on higher-level shared services instead of raw blob plumbing
  • follow up with cleanup and hardening: remove stale restore blob dependencies, bound rehydrated cleanup concurrency, tighten docs/agent guidance around storage layers and domain language, and stabilize crash-recovery fault injection around completed uploads

Testing

  • dotnet test --project src/Arius.Core.Tests/Arius.Core.Tests.csproj
  • dotnet test --project src/Arius.Architecture.Tests/Arius.Architecture.Tests.csproj
  • dotnet test --project src/Arius.Integration.Tests/Arius.Integration.Tests.csproj --treenode-filter "/*/*/CrashRecoveryTests/*"

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR extracts chunk blob protocol logic into a new shared IChunkStorageService/ChunkStorageService, moves repository-local path helpers into RepositoryPaths, centralizes ChunkHydrationStatus in shared scope, and refactors archive/restore/hydration-query handlers and tests to use the new service and path helpers instead of touching blob-level abstractions or deriving repo paths themselves.

Changes

Cohort / File(s) Summary
ChunkStorage service & types
src/Arius.Core.Shared.ChunkStorage/ChunkStorageService.cs, IChunkStorageService.cs, ChunkUploadResult.cs, IRehydratedChunkCleanupPlan.cs, RehydratedChunkCleanupResult.cs, ChunkHydrationStatus.cs
Add new IChunkStorageService and ChunkStorageService implementation (upload large/tar/thin, DownloadAsync, GetHydrationStatusAsync, StartRehydrationAsync, PlanRehydratedCleanupAsync) and supporting DTOs/interfaces/enums.
Repository path helpers
src/Arius.Core.Shared/RepositoryPaths.cs, src/Arius.Core.Shared.ChunkIndex/ChunkIndexService.cs
Add RepositoryPaths static helper; remove repo-path helpers from ChunkIndexService and switch chunk-index L2 dir init to use RepositoryPaths; add single-hash LookupAsync overload.
Feature handlers
src/Arius.Core/Features/ArchiveCommand/ArchiveCommandHandler.cs, src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs, src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs
Refactor handlers to depend on IChunkStorageService instead of using blob services directly; delegate uploads, downloads, hydration/status, rehydration start, and cleanup planning to ChunkStorageService; switch index usage to ChunkIndexService.LookupAsync(string).
DI wiring & shared services
src/Arius.Core/ServiceCollectionExtensions.cs, src/Arius.Core.Shared/FileTree/FileTreeService.cs, src/Arius.Core.Shared/Snapshot/SnapshotService.cs
Register ChunkStorageService/IChunkStorageService in DI; update shared services to use RepositoryPaths for cache/log directories.
Unit tests updated / added
src/Arius.Core.Tests/... (ChunkStorageRead/Upload tests, RepositoryPathsTests, renamed hydration tests, updated handler tests, many existing tests adjusted)
Add comprehensive ChunkStorageService unit tests (read/upload/rehydration/cleanup), add RepositoryPaths tests, update numerous unit tests to construct handlers with ChunkStorageService and to use RepositoryPaths/LookupAsync overloads; rename tests to reflect API shifts.
Integration / E2E tests & fixtures
src/Arius.Integration.Tests/..., src/Arius.E2E.Tests/E2ETests.cs, src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
Update fixtures to construct and inject ChunkStorageService; adjust cleanup to use RepositoryPaths.GetRepositoryDirectory; update integration assertions and faulting semantics to align with new upload lifecycle.
CLI & tooling
src/Arius.Cli/CliBuilder.cs, src/Arius.Cli.Tests/*, AGENTS.md, README.md
CliBuilder now uses RepositoryPaths.GetLogsDirectory for audit logging; tests/imports updated for shared namespace changes; docs updated to include ChunkStorageService and new service boundaries.
Explorer UI imports
src/Arius.Explorer/...
Update using directives to reference shared ChunkHydrationStatus type from ChunkStorage namespace.
Design/spec docs
openspec/..., openspec/specs/chunk-storage-service/spec.md
Add design/proposal/spec/task documents describing the split and service contract.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant ArchiveHandler
participant ChunkIndexService
participant ChunkStorageService
participant BlobContainer
ArchiveHandler->>ChunkIndexService: LookupAsync(contentHash)
alt not-found
ArchiveHandler->>ChunkIndexService: Record pending ShardEntry
end
ArchiveHandler->>ChunkStorageService: UploadLarge/Tar/ThinAsync(chunkHash, stream, ...)
ChunkStorageService->>BlobContainer: OpenWrite/Upload/SetMetadata (gzip+encrypt stream)
BlobContainer-->>ChunkStorageService: Blob stored / metadata
ChunkStorageService-->>ArchiveHandler: ChunkUploadResult (StoredSize, AlreadyExisted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main refactoring objective: extracting chunk storage responsibilities from the chunk index service into a separate shared service.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-chunkservice

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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 90.70632% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.76%. Comparing base (e620745) to head (ba0a58c).
⚠️ Report is 46 commits behind head on master.

Files with missing lines Patch % Lines
...us.Core/Shared/ChunkStorage/ChunkStorageService.cs 89.26% 9 Missing and 10 partials ⚠️
...e/Features/RestoreCommand/RestoreCommandHandler.cs 85.71% 2 Missing and 2 partials ⚠️
...e/Features/ArchiveCommand/ArchiveCommandHandler.cs 94.11% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #56      +/-   ##
==========================================
+ Coverage   72.38%   72.76%   +0.37%     
==========================================
  Files          63       66       +3     
  Lines        4817     4807      -10     
  Branches      640      651      +11     
==========================================
+ Hits         3487     3498      +11     
+ Misses       1194     1167      -27     
- Partials      136      142       +6     
Flag Coverage Δ
linux 81.96% <90.70%> (+0.46%) ⬆️
windows 69.02% <87.73%> (+0.51%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (2)
src/Arius.E2E.Tests/E2ETests.cs (1)

274-280: ⚠️ Potential issue | 🔴 Critical

RestoreCommandHandler construction is missing the new chunk-storage dependency.

This fixture still instantiates RestoreCommandHandler with the old argument list. With the refactored constructor, this is a build-break for the E2E test project.

🐛 Minimal fix
+using Arius.Core.Shared.ChunkStorage;
@@
     private RestoreCommandHandler CreateRestoreHandler() =>
         new(BlobContainer, Encryption, Index,
+            new ChunkStorageService(BlobContainer, Encryption),
             FileTreeService,
             Snapshot,
             _mediator,
             NullLogger<RestoreCommandHandler>.Instance,
             _account, _container);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.E2E.Tests/E2ETests.cs` around lines 274 - 280, The test helper
CreateRestoreHandler instantiates RestoreCommandHandler with the old parameter
list; update the constructor call in CreateRestoreHandler to pass the new
chunk-storage dependency required by RestoreCommandHandler (add the test
fixture's chunk storage instance—e.g., ChunkStorage, IChunkStorage, or the
fixture field like _chunkStorage—immediately after Index or in the correct
position per the refactored constructor) while keeping the existing parameters
(BlobContainer, Encryption, Index, FileTreeService, Snapshot, _mediator,
NullLogger<RestoreCommandHandler>.Instance, _account, _container).
src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs (1)

405-423: ⚠️ Potential issue | 🟠 Major

RehydrationStartedEvent still undercounts tar bundle bytes.

totalRehydrateBytes is updated from the first file entry's proportional CompressedSize. For tar-backed chunks that only captures one file's share, not the full tar blob, so the emitted rehydration size can be far too small. Reuse SumCompressedBytes(chunkHash) here instead.

💡 Suggested fix
-                        if (indexEntries.TryGetValue(filesByChunkHash[chunkHash][0].ContentHash, out var ie))
-                            totalRehydrateBytes += ie.CompressedSize;
+                        totalRehydrateBytes += SumCompressedBytes(chunkHash);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs` around lines
405 - 423, The RehydrationStartedEvent size metric is undercounting because
totalRehydrateBytes is using the first file's proportional CompressedSize;
replace that logic by calling SumCompressedBytes(chunkHash) for each chunk to
accumulate the full tar blob size; inside the loop where
_chunkStorage.StartRehydrationAsync(chunkHash, ...) is called, on success add
totalRehydrateBytes += SumCompressedBytes(chunkHash) (using the existing
SumCompressedBytes method) instead of reading indexEntries/filesByChunkHash,
then publish the RehydrationStartedEvent as before.
🧹 Nitpick comments (4)
src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs (1)

49-53: Prefer mocking IChunkStorageService in handler-level tests.

These tests now exercise the real ChunkStorageService path, which can blur whether failures come from handler orchestration or storage internals. Consider substituting IChunkStorageService here and keeping concrete storage behavior coverage in ChunkStorageService tests.

As per coding guidelines **/*.Tests/**: “Test projects. Focus on test coverage gaps and assertion quality rather than style.”

Also applies to: 94-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs` around
lines 49 - 53, The handler-level test instantiates a real ChunkStorageService
which leaks storage behavior into orchestration tests; replace the concrete new
ChunkStorageService(...) with a mocked IChunkStorageService (e.g., a test double
for IChunkStorageService) when creating the ChunkHydrationStatusQueryHandler so
the test only verifies handler logic; update both instantiations that create
ChunkHydrationStatusQueryHandler (the one assigning handler at line ~49 and the
one at ~94) to inject the mocked IChunkStorageService, configure the mock’s
methods to return the expected results for the test cases, and keep concrete
ChunkStorageService coverage in its own ChunkStorageService tests.
openspec/changes/split-chunk-storage-from-index/proposal.md (1)

18-19: Remove or populate the empty “Modified Capabilities” section.

Leaving an empty heading makes the spec delta harder to scan.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/changes/split-chunk-storage-from-index/proposal.md` around lines 18
- 19, The "Modified Capabilities" heading in
openspec/changes/split-chunk-storage-from-index/proposal.md is empty; either
remove this heading or add a brief bullet list describing which capabilities
changed (or confirm "none") so the spec delta is self-contained—edit the
"Modified Capabilities" section header to be removed if not needed or populate
it with concise entries referencing the specific capability names affected by
the split-chunk-storage-from-index proposal.
src/Arius.Cli.Tests/DependencyInjectionTests.cs (1)

40-41: Strengthen DI assertion to guard against duplicate ChunkStorageService instances.

These checks confirm resolvability, but they don’t verify that interface + concrete resolve to the same object. That invariant helps prevent split service graphs.

♻️ Suggested assertion tightening
-        serviceProvider.GetRequiredService<IChunkStorageService>().ShouldBeOfType<ChunkStorageService>();
-        serviceProvider.GetRequiredService<ChunkStorageService>().ShouldBeOfType<ChunkStorageService>();
+        var chunkStorageViaInterface = serviceProvider.GetRequiredService<IChunkStorageService>();
+        var chunkStorageViaConcrete  = serviceProvider.GetRequiredService<ChunkStorageService>();
+        chunkStorageViaInterface.ShouldBeOfType<ChunkStorageService>();
+        ReferenceEquals(chunkStorageViaInterface, chunkStorageViaConcrete).ShouldBeTrue();

As per coding guidelines "Register shared services once per repository/session in DI and feature handlers should consume those shared instances through constructor injection" and "Avoid duplicate service graphs for the same repository because that can split cache state and validation state".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Cli.Tests/DependencyInjectionTests.cs` around lines 40 - 41, The
test currently only verifies that IChunkStorageService and ChunkStorageService
are resolvable but not that they refer to the same instance; update the
assertion in DependencyInjectionTests to resolve both using
serviceProvider.GetRequiredService<IChunkStorageService>() and
serviceProvider.GetRequiredService<ChunkStorageService>() and assert they are
the same object (e.g., use ReferenceEquals or your test framework's
ShouldBeSameAs/BeSameAs) to guarantee the DI container returns a single shared
ChunkStorageService instance for both the interface and concrete type.
src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceSurfaceTests.cs (1)

14-36: Assert the public contract on IChunkStorageService, not just the concrete class.

Most of this test reflects over ChunkStorageService, but the feature-facing boundary introduced by this PR is IChunkStorageService. An explicit-interface implementation or an extra public helper would make this suite fail or pass for the wrong reason. I'd keep the IsAssignableFrom check, but move the signature assertions to typeof(IChunkStorageService).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceSurfaceTests.cs` around
lines 14 - 36, Keep the IsAssignableFrom assertion for
typeof(IChunkStorageService) vs typeof(ChunkStorageService), but change the
reflection-based signature checks to inspect the interface type: replace
typeof(ChunkStorageService).GetMethods(...) with
typeof(IChunkStorageService).GetMethods(...) (or simply
typeof(IChunkStorageService).GetMethods()) and then use the same
BindingFlags/ToDictionary and the existing method-name checks (UploadLargeAsync,
UploadTarAsync, UploadThinAsync, DownloadAsync, GetHydrationStatusAsync,
StartRehydrationAsync, PlanRehydratedCleanupAsync) so the test asserts the
public contract defined by IChunkStorageService rather than the concrete class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@openspec/changes/split-chunk-storage-from-index/specs/chunk-storage-service/spec.md`:
- Around line 55-56: The implementation of GetHydrationStatusAsync in
ChunkStorageService returns ChunkHydrationStatus.Unknown when the primary chunk
blob is absent, but the spec and Shared/ChunkIndex/ChunkHydrationStatus.cs
define this state as Missing; update GetHydrationStatusAsync to return
ChunkHydrationStatus.Missing when the primary blob does not exist and update any
local checks in ChunkStorageService (and related handlers/tests that rely on
Unknown) to use Missing so the service and spec are consistent about the
missing-state name.

In `@src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs`:
- Around line 13-35: Add a test that seeds both the primary archived blob and
the rehydrated copy and asserts GetHydrationStatusAsync returns Available:
create a new test method (e.g.,
GetHydrationStatusAsync_ReturnsAvailable_WhenArchiveChunkRehydratedCopyExists)
that uses FakeMetadataOnlyBlobContainerService, set
blobs.Metadata[BlobPaths.Chunk("abc")] = new BlobMetadata { Exists = true, Tier
= BlobTier.Archive } and blobs.Metadata[BlobPaths.ChunkRehydrated("abc")] = new
BlobMetadata { Exists = true }, instantiate ChunkStorageService and call
GetHydrationStatusAsync("abc", ...), then assert the result is
ChunkHydrationStatus.Available to cover the case where the rehydrated copy
exists.

In
`@src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs`:
- Around line 18-33: The handler currently injects and uses
ChunkHydrationStatusQueryHandler._chunkStorage.GetHydrationStatusAsync to
resolve hydration, but per architecture it should drop the IChunkStorageService
dependency and use IBlobContainerService (_blobs) directly; modify the
constructor to remove the IChunkStorageService parameter and field, remove all
calls to _chunkStorage.GetHydrationStatusAsync in
ChunkHydrationStatusQueryHandler, instead use ChunkIndexService to map file
content hashes to chunk hashes (reuse _index) and call the raw blob metadata
methods on _blobs to determine hydration state; update any method signatures and
tests that relied on _chunkStorage accordingly and ensure _blobs is actually
referenced where hydration state is computed.

In `@src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs`:
- Around line 262-272: The loop in ExecuteAsync increments deleted and adds
meta.ContentLength unconditionally even when the blob no longer exists; modify
ExecuteAsync to only count removals and add freed bytes when the blob actually
existed and was deleted: call _blobs.GetMetadataAsync(blobName) and if the
returned meta is null (or indicates non-existence) skip counting; otherwise call
_blobs.DeleteAsync(blobName) and only increment deleted and add
meta.ContentLength to freed if the delete call indicates success (or if
DeleteAsync has no result, rely on a non-null meta to signal that an actual
deletion occurred). Ensure the returned RehydratedChunkCleanupResult uses these
corrected deleted and freed totals.
- Around line 130-139: The BlobAlreadyExistsException handler in
ChunkStorageService (the catch block using blobName, existing,
BlobMetadataKeys.AriusType, ChunkUploadResult,
_blobs.GetMetadataAsync/_blobs.DeleteAsync and the retry/goto logic) must only
treat an existing blob as a successful recoverable upload when the blob is fully
committed and matches the expected state: verify the AriusType value equals the
expected type for this upload AND validate other required indicators of a
completed upload (e.g., existing.ContentLength == expected length and any other
required metadata keys/values that indicate finalization); if any check fails,
remove or repair the blob (call _blobs.DeleteAsync or perform metadata fixup)
reset content.Position when content.CanSeek and retry (preserve the existing
goto retry logic), and only return a ChunkUploadResult with AlreadyExisted: true
when all checks pass.
- Around line 136-139: The retry-after-delete path in ChunkStorageService.cs
uses goto retry after calling _blobs.DeleteAsync but does not handle
non-seekable source streams, so a non-seekable content can be reused
already-consumed and cause truncated uploads; fix by ensuring the stream is
rewindable before retrying: either (a) if content.CanSeek is false, create a
buffered rewindable copy (e.g., copy into a MemoryStream or temp file) and use
that rewindable stream for the upload attempts, or (b) if buffering is not
acceptable, avoid deleting/ retrying and instead surface an error — implement
this logic around the retry label and replace the current content.Position reset
with code that swaps in the bufferedStream when needed, referencing the content
variable, content.CanSeek, content.Position, the retry label and the call to
_blobs.DeleteAsync.
- Around line 281-314: ChunkDownloadStream only implements DisposeAsync, so
synchronous disposal leaks resources; add a protected override void Dispose(bool
disposing) in ChunkDownloadStream that checks disposing, disposes _inner,
_decryptStream, _progressStream, and _downloadStream (safely handling nulls and
avoiding double-dispose), and then calls base.Dispose(disposing); keep
DisposeCoreAsync as-is (or have it call the same disposal logic) so both async
and sync paths use the same cleanup code and optionally use a private disposed
flag to prevent double disposal.

In `@src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs`:
- Around line 94-95: The tests stopped injecting faults into the chunk-upload
path because ChunkStorageService writes via OpenWriteAsync while
FaultingBlobService only throws from UploadAsync; update the fault injection so
OpenWriteAsync (and the returned stream's Write/Flush/Dispose as appropriate)
can fault as well: modify or extend FaultingBlobService (or wrap blobService
passed into ChunkStorageService in CrashRecoveryTests) to throw during
OpenWriteAsync and/or when the returned stream is written to, ensuring the
crash-recovery tests still exercise “crash during chunk upload” paths used by
ChunkStorageService.OpenWriteAsync.

---

Outside diff comments:
In `@src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs`:
- Around line 405-423: The RehydrationStartedEvent size metric is undercounting
because totalRehydrateBytes is using the first file's proportional
CompressedSize; replace that logic by calling SumCompressedBytes(chunkHash) for
each chunk to accumulate the full tar blob size; inside the loop where
_chunkStorage.StartRehydrationAsync(chunkHash, ...) is called, on success add
totalRehydrateBytes += SumCompressedBytes(chunkHash) (using the existing
SumCompressedBytes method) instead of reading indexEntries/filesByChunkHash,
then publish the RehydrationStartedEvent as before.

In `@src/Arius.E2E.Tests/E2ETests.cs`:
- Around line 274-280: The test helper CreateRestoreHandler instantiates
RestoreCommandHandler with the old parameter list; update the constructor call
in CreateRestoreHandler to pass the new chunk-storage dependency required by
RestoreCommandHandler (add the test fixture's chunk storage instance—e.g.,
ChunkStorage, IChunkStorage, or the fixture field like _chunkStorage—immediately
after Index or in the correct position per the refactored constructor) while
keeping the existing parameters (BlobContainer, Encryption, Index,
FileTreeService, Snapshot, _mediator,
NullLogger<RestoreCommandHandler>.Instance, _account, _container).

---

Nitpick comments:
In `@openspec/changes/split-chunk-storage-from-index/proposal.md`:
- Around line 18-19: The "Modified Capabilities" heading in
openspec/changes/split-chunk-storage-from-index/proposal.md is empty; either
remove this heading or add a brief bullet list describing which capabilities
changed (or confirm "none") so the spec delta is self-contained—edit the
"Modified Capabilities" section header to be removed if not needed or populate
it with concise entries referencing the specific capability names affected by
the split-chunk-storage-from-index proposal.

In `@src/Arius.Cli.Tests/DependencyInjectionTests.cs`:
- Around line 40-41: The test currently only verifies that IChunkStorageService
and ChunkStorageService are resolvable but not that they refer to the same
instance; update the assertion in DependencyInjectionTests to resolve both using
serviceProvider.GetRequiredService<IChunkStorageService>() and
serviceProvider.GetRequiredService<ChunkStorageService>() and assert they are
the same object (e.g., use ReferenceEquals or your test framework's
ShouldBeSameAs/BeSameAs) to guarantee the DI container returns a single shared
ChunkStorageService instance for both the interface and concrete type.

In `@src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceSurfaceTests.cs`:
- Around line 14-36: Keep the IsAssignableFrom assertion for
typeof(IChunkStorageService) vs typeof(ChunkStorageService), but change the
reflection-based signature checks to inspect the interface type: replace
typeof(ChunkStorageService).GetMethods(...) with
typeof(IChunkStorageService).GetMethods(...) (or simply
typeof(IChunkStorageService).GetMethods()) and then use the same
BindingFlags/ToDictionary and the existing method-name checks (UploadLargeAsync,
UploadTarAsync, UploadThinAsync, DownloadAsync, GetHydrationStatusAsync,
StartRehydrationAsync, PlanRehydratedCleanupAsync) so the test asserts the
public contract defined by IChunkStorageService rather than the concrete class.

In `@src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs`:
- Around line 49-53: The handler-level test instantiates a real
ChunkStorageService which leaks storage behavior into orchestration tests;
replace the concrete new ChunkStorageService(...) with a mocked
IChunkStorageService (e.g., a test double for IChunkStorageService) when
creating the ChunkHydrationStatusQueryHandler so the test only verifies handler
logic; update both instantiations that create ChunkHydrationStatusQueryHandler
(the one assigning handler at line ~49 and the one at ~94) to inject the mocked
IChunkStorageService, configure the mock’s methods to return the expected
results for the test cases, and keep concrete ChunkStorageService coverage in
its own ChunkStorageService tests.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2fc79423-b59f-4e43-9870-969325f94df5

📥 Commits

Reviewing files that changed from the base of the PR and between e620745 and 74e5909.

📒 Files selected for processing (43)
  • README.md
  • openspec/changes/split-chunk-storage-from-index/.openspec.yaml
  • openspec/changes/split-chunk-storage-from-index/design.md
  • openspec/changes/split-chunk-storage-from-index/proposal.md
  • openspec/changes/split-chunk-storage-from-index/specs/chunk-storage-service/spec.md
  • openspec/changes/split-chunk-storage-from-index/tasks.md
  • src/Arius.Cli.Tests/CliTests.cs
  • src/Arius.Cli.Tests/DependencyInjectionTests.cs
  • src/Arius.Cli/CliBuilder.cs
  • src/Arius.Core.Tests/Archive/ArchiveRecoveryTests.cs
  • src/Arius.Core.Tests/ChunkIndex/ShardTests.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceSurfaceTests.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceUploadTests.cs
  • src/Arius.Core.Tests/FileTree/FileTreeServiceTests.cs
  • src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs
  • src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs
  • src/Arius.Core.Tests/Repository/RepositoryPathsTests.cs
  • src/Arius.Core.Tests/Restore/RestoreCommandHandlerTests.cs
  • src/Arius.Core/Features/ArchiveCommand/ArchiveCommandHandler.cs
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusResolver.cs
  • src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs
  • src/Arius.Core/ServiceCollectionExtensions.cs
  • src/Arius.Core/Shared/ChunkIndex/ChunkHydrationStatus.cs
  • src/Arius.Core/Shared/ChunkIndex/ChunkIndexService.cs
  • src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
  • src/Arius.Core/Shared/ChunkStorage/ChunkUploadResult.cs
  • src/Arius.Core/Shared/ChunkStorage/IChunkStorageService.cs
  • src/Arius.Core/Shared/ChunkStorage/IRehydratedChunkCleanupPlan.cs
  • src/Arius.Core/Shared/ChunkStorage/RehydratedChunkCleanupResult.cs
  • src/Arius.Core/Shared/FileTree/FileTreeService.cs
  • src/Arius.Core/Shared/RepositoryPaths.cs
  • src/Arius.Core/Shared/Snapshot/SnapshotService.cs
  • src/Arius.E2E.Tests/E2ETests.cs
  • src/Arius.E2E.Tests/RehydrationE2ETests.cs
  • src/Arius.Explorer/RepositoryExplorer/FileItemViewModel.cs
  • src/Arius.Explorer/RepositoryExplorer/RepositoryExplorerViewModel.cs
  • src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cs
  • src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
  • src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
  • src/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cs

Comment thread src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs
Comment thread src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs Outdated
Comment thread src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
Comment thread src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
Comment thread src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs Outdated
Comment thread src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
Comment thread src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs`:
- Around line 39-75: Add a unit test to verify that GetHydrationStatusAsync
prefers a ready rehydrated copy over the primary's IsRehydrating flag: create a
FakeMetadataOnlyBlobContainerService where "chunks/abc" has Exists = true, Tier
= BlobTier.Archive, IsRehydrating = true and "chunks-rehydrated/abc" has Exists
= true and Tier = BlobTier.Hot (or any non-Archive), instantiate
ChunkStorageService and assert that GetHydrationStatusAsync("abc", ...) returns
ChunkHydrationStatus.Available; name the test clearly (e.g.,
GetHydrationStatusAsync_RehydratedCopyReady_TakesPrecedence).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9f30f6b4-61b5-4beb-9a77-423261e77a48

📥 Commits

Reviewing files that changed from the base of the PR and between 74e5909 and a4c9bc0.

📒 Files selected for processing (3)
  • src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusResolver.cs
  • src/Arius.E2E.Tests/E2ETests.cs
💤 Files with no reviewable changes (1)
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusResolver.cs

Comment thread src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs (1)

37-60: 🛠️ Refactor suggestion | 🟠 Major

Restore now hides repository blob operations behind the shared chunk service.

This handler now routes hydration status, download, rehydration copy, and cleanup through _chunkStorage, but the current restore guideline keeps those raw blob operations in RestoreCommandHandler so the feature remains the read-only repository orchestrator. Please either keep these calls on _blobs here or update the architectural guidance alongside this refactor. As per coding guidelines, src/Arius.Core/Features/**/*RestoreCommand*.cs: RestoreCommandHandler should use SnapshotService to resolve the target snapshot, FileTreeService to traverse filetrees, ChunkIndexService to resolve content hashes to chunk metadata, and raw IBlobContainerService for chunk download, rehydration status, copy, and cleanup.

Also applies to: 253-275, 412-440, 585-635

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs` around lines
37 - 60, The refactor moved blob operations (hydration status, download,
rehydration copy, cleanup) to _chunkStorage but the restore handler must remain
the repository read-only orchestrator and use IBlobContainerService (_blobs) per
guidelines; update RestoreCommandHandler to call _blobs for those raw blob
operations (hydration status, Download, StartRehydrate/Copy, Delete) while
keeping SnapshotService, FileTreeService and ChunkIndexService usage for
snapshot resolution, tree traversal and chunk metadata resolution;
alternatively, if you intend to centralize these ops in _chunkStorage, update
the architecture/guideline comments and adjust all referenced sections (around
lines noted) to reflect the new responsibility so code and docs align.
♻️ Duplicate comments (4)
src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs (1)

17-27: 🛠️ Refactor suggestion | 🟠 Major

This still crosses the current feature/shared boundary.

ChunkHydrationStatusQueryHandler now delegates hydration lookup to IChunkStorageService, but the repo rule for this handler keeps raw blob metadata calls in the feature after ChunkIndexService maps content hashes to chunk hashes. If ChunkStorageService is the new intended boundary, that architectural rule should be updated in the same PR; otherwise hydration-state resolution should stay on _blobs here. As per coding guidelines, src/Arius.Core/Features/**/*ChunkHydrationStatusQuery*.cs: ChunkHydrationStatusQueryHandler should use ChunkIndexService to map file content hashes to chunk hashes and raw IBlobContainerService metadata calls for hydration state.

Also applies to: 61-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs`
around lines 17 - 27, ChunkHydrationStatusQueryHandler currently delegates
hydration lookup to IChunkStorageService (_chunkStorage) which violates the
feature/shared boundary rule; either update the architectural boundary in the PR
or revert this handler to resolve hydration state locally: use ChunkIndexService
(_index) to map content hashes to chunk hashes and then call the raw blob
metadata methods on IBlobContainerService (_blobs) to determine hydration state
(instead of using IChunkStorageService), ensuring
ChunkHydrationStatusQueryHandler follows the
src/Arius.Core/Features/**/*ChunkHydrationStatusQuery*.cs guideline.
src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs (1)

24-49: ⚠️ Potential issue | 🟡 Minor

Still missing the rehydrated-copy-precedence case.

The suite does not cover the combination where the primary chunk is Archive with IsRehydrating = true, but chunks-rehydrated/<hash> is already in a readable tier. That is the branch-order case most likely to regress if GetHydrationStatusAsync() gets reordered later. As per coding guidelines, **/*.Tests/**: Test projects. Focus on test coverage gaps and assertion quality rather than style.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs` around lines
24 - 49, Add a test covering the precedence case where the primary blob is
Archive with IsRehydrating=true but the rehydrated copy already exists in a
readable tier; specifically, in List/FileHydrationStatusResolverTests create a
scenario using FakeMetadataOnlyBlobContainerService where Metadata["chunks/abc"]
= { Exists=true, Tier=BlobTier.Archive, IsRehydrating=true } and
Metadata["chunks-rehydrated/abc"] = { Exists=true, Tier=BlobTier.Cool } then
call ChunkStorageService.GetHydrationStatusAsync("abc", CancellationToken.None)
and assert the result is ChunkHydrationStatus.Available (and optionally assert
the requested blob checks include both "chunks/abc" and "chunks-rehydrated/abc")
so the rehydrated-copy-precedence branch is exercised.
src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs (2)

284-289: ⚠️ Potential issue | 🟡 Minor

Cleanup totals still overcount after concurrent deletion.

deleted++ and freed += ... run even when the rehydrated blob is already gone by the time ExecuteAsync() reaches it. A concurrent cleanup, or a second ExecuteAsync() call, will therefore return inflated totals. Skip counting when meta.Exists is false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs` around lines 284 -
289, The loop in ExecuteAsync currently increments deleted and adds to freed
regardless of whether the blob still exists; after calling
_blobs.GetMetadataAsync(blobName, cancellationToken) check meta.Exists and only
call _blobs.DeleteAsync(blobName, cancellationToken) and increment deleted and
freed (using meta.ContentLength) when meta.Exists is true; leave the rest
unchanged so concurrent deletions don't inflate the totals (refer to _blobNames,
_blobs.GetMetadataAsync, _blobs.DeleteAsync, meta.Exists, deleted, and freed).

131-136: ⚠️ Potential issue | 🟠 Major

Tighten the “already exists” recovery checks in both upload paths.

Both catch blocks still treat “metadata contains AriusType” as sufficient proof that the existing blob is safe to reuse. That can falsely accept the wrong chunk kind or a half-finalized blob and skip the repair/delete path. Please require the expected AriusType value and the rest of the completion metadata before returning success here.

Also applies to: 198-202

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs`:
- Line 27: The test helper ShouldFaultUpload currently uses
Interlocked.Increment(ref _uploadCount) > throwAfterN + 1 which shifts the fault
boundary by one; update the failure logic so throwAfterN accurately represents
the number of faultable uploads — for example change ShouldFaultUpload to use
Interlocked.Increment(ref _uploadCount) > throwAfterN (remove the + 1), or
refactor to count only the specific operation types the tests target (e.g.,
separate counters or a predicate for uploads vs tar operations) so tests like
Archive_CrashAfterFirstUpload_Rerun_ProducesCorrectSnapshot and
Archive_CrashAfterTarBeforeThinChunks_Rerun_CorrectSnapshot fault exactly at the
intended operation.

---

Outside diff comments:
In `@src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs`:
- Around line 37-60: The refactor moved blob operations (hydration status,
download, rehydration copy, cleanup) to _chunkStorage but the restore handler
must remain the repository read-only orchestrator and use IBlobContainerService
(_blobs) per guidelines; update RestoreCommandHandler to call _blobs for those
raw blob operations (hydration status, Download, StartRehydrate/Copy, Delete)
while keeping SnapshotService, FileTreeService and ChunkIndexService usage for
snapshot resolution, tree traversal and chunk metadata resolution;
alternatively, if you intend to centralize these ops in _chunkStorage, update
the architecture/guideline comments and adjust all referenced sections (around
lines noted) to reflect the new responsibility so code and docs align.

---

Duplicate comments:
In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs`:
- Around line 24-49: Add a test covering the precedence case where the primary
blob is Archive with IsRehydrating=true but the rehydrated copy already exists
in a readable tier; specifically, in List/FileHydrationStatusResolverTests
create a scenario using FakeMetadataOnlyBlobContainerService where
Metadata["chunks/abc"] = { Exists=true, Tier=BlobTier.Archive,
IsRehydrating=true } and Metadata["chunks-rehydrated/abc"] = { Exists=true,
Tier=BlobTier.Cool } then call
ChunkStorageService.GetHydrationStatusAsync("abc", CancellationToken.None) and
assert the result is ChunkHydrationStatus.Available (and optionally assert the
requested blob checks include both "chunks/abc" and "chunks-rehydrated/abc") so
the rehydrated-copy-precedence branch is exercised.

In
`@src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs`:
- Around line 17-27: ChunkHydrationStatusQueryHandler currently delegates
hydration lookup to IChunkStorageService (_chunkStorage) which violates the
feature/shared boundary rule; either update the architectural boundary in the PR
or revert this handler to resolve hydration state locally: use ChunkIndexService
(_index) to map content hashes to chunk hashes and then call the raw blob
metadata methods on IBlobContainerService (_blobs) to determine hydration state
(instead of using IChunkStorageService), ensuring
ChunkHydrationStatusQueryHandler follows the
src/Arius.Core/Features/**/*ChunkHydrationStatusQuery*.cs guideline.

In `@src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs`:
- Around line 284-289: The loop in ExecuteAsync currently increments deleted and
adds to freed regardless of whether the blob still exists; after calling
_blobs.GetMetadataAsync(blobName, cancellationToken) check meta.Exists and only
call _blobs.DeleteAsync(blobName, cancellationToken) and increment deleted and
freed (using meta.ContentLength) when meta.Exists is true; leave the rest
unchanged so concurrent deletions don't inflate the totals (refer to _blobNames,
_blobs.GetMetadataAsync, _blobs.DeleteAsync, meta.Exists, deleted, and freed).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 878b3a98-4b3f-4e1a-b57a-208cb286e338

📥 Commits

Reviewing files that changed from the base of the PR and between a4c9bc0 and 09c876c.

📒 Files selected for processing (12)
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceUploadTests.cs
  • src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs
  • src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs
  • src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs
  • src/Arius.Core/ServiceCollectionExtensions.cs
  • src/Arius.Core/Shared/ChunkIndex/ChunkHydrationStatus.cs
  • src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
  • src/Arius.Explorer/RepositoryExplorer/FileItemViewModel.cs
  • src/Arius.Explorer/RepositoryExplorer/RepositoryExplorerViewModel.cs
  • src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/Arius.Core/Shared/ChunkIndex/ChunkHydrationStatus.cs
  • src/Arius.Explorer/RepositoryExplorer/RepositoryExplorerViewModel.cs
  • src/Arius.Core.Tests/List/ResolveFileHydrationStatusesHandlerTests.cs
  • src/Arius.Explorer/RepositoryExplorer/FileItemViewModel.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceUploadTests.cs

Comment thread src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs (2)

14-15: Consider using BlobPaths helper for consistency.

The tests use hardcoded path strings like "chunks/abc" while ChunkStorageServiceReadTests.cs uses BlobPaths.Chunk("abc"). Using the helper would improve consistency across test files and make tests more resilient to path format changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs` around lines
14 - 15, Replace the hardcoded blob key "chunks/abc" with the BlobPaths helper
to keep path formatting consistent; locate where
FakeMetadataOnlyBlobContainerService is instantiated and its Metadata dictionary
is populated (blobs.Metadata) and change the key to BlobPaths.Chunk("abc") so
the test uses the same path helper as ChunkStorageServiceReadTests.

9-89: Missing precedence test for rehydrated copy ready over primary IsRehydrating flag.

The test suite covers the main hydration status scenarios well. However, a test is still needed where the primary chunk is Archive with IsRehydrating = true, but chunks-rehydrated/<hash> exists in a non-Archive tier. The expected status should be Available to confirm the implementation's branch order is correct.

✅ Suggested test addition
+    [Test]
+    public async Task GetHydrationStatusAsync_ReturnsAvailable_WhenPrimaryIsRehydratingButRehydratedCopyIsReady()
+    {
+        var blobs = new FakeMetadataOnlyBlobContainerService();
+        blobs.Metadata["chunks/abc"] = new BlobMetadata { Exists = true, Tier = BlobTier.Archive, IsRehydrating = true };
+        blobs.Metadata["chunks-rehydrated/abc"] = new BlobMetadata { Exists = true, Tier = BlobTier.Cool };
+        var service = new ChunkStorageService(blobs, new Arius.Core.Shared.Encryption.PlaintextPassthroughService());
+
+        var status = await service.GetHydrationStatusAsync("abc", CancellationToken.None);
+
+        status.ShouldBe(ChunkHydrationStatus.Available);
+        blobs.RequestedBlobNames.ShouldBe(["chunks/abc", "chunks-rehydrated/abc"]);
+    }

As per coding guidelines, "**/*.Tests/**: Test projects. Focus on test coverage gaps and assertion quality rather than style."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs` around lines 9
- 89, Add a test in ChunkStorageHydrationStatusTests that verifies a rehydrated
copy takes precedence over the primary IsRehydrating flag: create
blobs.Metadata["chunks/abc"] = new BlobMetadata { Exists = true, Tier =
BlobTier.Archive, IsRehydrating = true } and
blobs.Metadata["chunks-rehydrated/abc"] = new BlobMetadata { Exists = true, Tier
= BlobTier.Cool } (or Hot), call
ChunkStorageService.GetHydrationStatusAsync("abc", ...), and assert the result
is ChunkHydrationStatus.Available and that blobs.RequestedBlobNames includes
"chunks/abc" and "chunks-rehydrated/abc" to confirm the branch ordering.
src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs (1)

10-123: Good test coverage for the new ChunkStorageService read operations.

The test suite covers the key hydration status scenarios, download behavior, rehydration kickoff, and cleanup planning/execution. The test at lines 37-48 addresses the previously requested "rehydrated copy already exists" case.

However, a precedence test is still missing: when the primary chunk is Archive with IsRehydrating = true but the rehydrated copy already exists in a non-Archive tier, the expected result should be Available (rehydrated copy takes precedence). This protects the branch-order contract in GetHydrationStatusCoreAsync.

✅ Suggested test addition
+    [Test]
+    public async Task GetHydrationStatusAsync_ReturnsAvailable_WhenPrimaryIsRehydratingButRehydratedCopyIsReady()
+    {
+        var blobs = new FakeMetadataOnlyBlobContainerService();
+        blobs.Metadata[BlobPaths.Chunk("abc")] = new BlobMetadata { Exists = true, Tier = BlobTier.Archive, IsRehydrating = true };
+        blobs.Metadata[BlobPaths.ChunkRehydrated("abc")] = new BlobMetadata { Exists = true, Tier = BlobTier.Cool };
+        var service = new ChunkStorageService(blobs, new PlaintextPassthroughService());
+
+        var status = await service.GetHydrationStatusAsync("abc", CancellationToken.None);
+
+        status.ShouldBe(ChunkHydrationStatus.Available);
+    }

As per coding guidelines, "**/*.Tests/**: Test projects. Focus on test coverage gaps and assertion quality rather than style."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs` around
lines 10 - 123, Add a new unit test to assert that when the primary blob
(BlobPaths.Chunk("abc")) is Archive with IsRehydrating = true but a rehydrated
copy (BlobPaths.ChunkRehydrated("abc")) already exists in a non-Archive tier,
ChunkStorageService.GetHydrationStatusAsync returns
ChunkHydrationStatus.Available; use FakeMetadataOnlyBlobContainerService, set
blobs.Metadata for the primary to BlobMetadata { Exists = true, Tier =
BlobTier.Archive, IsRehydrating = true } and for the rehydrated path to
BlobMetadata { Exists = true, Tier = BlobTier.Cold }, call
service.GetHydrationStatusAsync("abc", ...), and assert the result equals
ChunkHydrationStatus.Available to protect the branch ordering in
GetHydrationStatusCoreAsync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs`:
- Around line 10-123: Add a new unit test to assert that when the primary blob
(BlobPaths.Chunk("abc")) is Archive with IsRehydrating = true but a rehydrated
copy (BlobPaths.ChunkRehydrated("abc")) already exists in a non-Archive tier,
ChunkStorageService.GetHydrationStatusAsync returns
ChunkHydrationStatus.Available; use FakeMetadataOnlyBlobContainerService, set
blobs.Metadata for the primary to BlobMetadata { Exists = true, Tier =
BlobTier.Archive, IsRehydrating = true } and for the rehydrated path to
BlobMetadata { Exists = true, Tier = BlobTier.Cold }, call
service.GetHydrationStatusAsync("abc", ...), and assert the result equals
ChunkHydrationStatus.Available to protect the branch ordering in
GetHydrationStatusCoreAsync.

In `@src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs`:
- Around line 14-15: Replace the hardcoded blob key "chunks/abc" with the
BlobPaths helper to keep path formatting consistent; locate where
FakeMetadataOnlyBlobContainerService is instantiated and its Metadata dictionary
is populated (blobs.Metadata) and change the key to BlobPaths.Chunk("abc") so
the test uses the same path helper as ChunkStorageServiceReadTests.
- Around line 9-89: Add a test in ChunkStorageHydrationStatusTests that verifies
a rehydrated copy takes precedence over the primary IsRehydrating flag: create
blobs.Metadata["chunks/abc"] = new BlobMetadata { Exists = true, Tier =
BlobTier.Archive, IsRehydrating = true } and
blobs.Metadata["chunks-rehydrated/abc"] = new BlobMetadata { Exists = true, Tier
= BlobTier.Cool } (or Hot), call
ChunkStorageService.GetHydrationStatusAsync("abc", ...), and assert the result
is ChunkHydrationStatus.Available and that blobs.RequestedBlobNames includes
"chunks/abc" and "chunks-rehydrated/abc" to confirm the branch ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46ceed47-3927-4aa4-8ccf-1312aa68c130

📥 Commits

Reviewing files that changed from the base of the PR and between 09c876c and ef2596c.

📒 Files selected for processing (9)
  • openspec/changes/split-chunk-storage-from-index/specs/chunk-storage-service/spec.md
  • src/Arius.Core.Tests/ChunkStorage/ChunkStorageServiceReadTests.cs
  • src/Arius.Core.Tests/List/FileHydrationStatusResolverTests.cs
  • src/Arius.Core/Features/ChunkHydrationStatusQuery/ChunkHydrationStatusQuery.cs
  • src/Arius.Core/Features/RestoreCommand/RestoreCommandHandler.cs
  • src/Arius.Core/Shared/ChunkIndex/ChunkHydrationStatus.cs
  • src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs
  • src/Arius.Explorer/RepositoryExplorer/FileItemViewModel.cs
  • src/Arius.Explorer/RepositoryExplorer/RepositoryExplorerViewModel.cs
✅ Files skipped from review due to trivial changes (2)
  • src/Arius.Explorer/RepositoryExplorer/FileItemViewModel.cs
  • openspec/changes/split-chunk-storage-from-index/specs/chunk-storage-service/spec.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Arius.Core/Shared/ChunkIndex/ChunkHydrationStatus.cs
  • src/Arius.Core/Shared/ChunkStorage/ChunkStorageService.cs

@woutervanranst
Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

✅ Actions performed

Reviews resumed.

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.

1 participant