fix: resolve fork() silent failure on cloud storage adapters

Fixed critical bug where fork() completed without errors but branches
were not persisted to storage, causing checkout() to fail with
"Branch does not exist" errors.

Root Cause:
- COW metadata paths (_cow/*) were being branch-scoped incorrectly
- resolveBranchPath() applied branch prefixes to COW paths
- Result: refs written to branches/main/_cow/... instead of _cow/...
- COW metadata (refs, commits, blobs) must be global, not per-branch

Changes:
1. baseStorage.ts (resolveBranchPath):
   - Bypass branch scoping for _cow/ paths
   - COW metadata now stored globally as designed
   - Fixes fork() persistence across all storage adapters

2. brainy.ts (fork):
   - Add branch creation verification after copyRef()
   - Throw descriptive error if branch wasn't created
   - Prevents silent failures in production

3. tests/integration/fork-persistence.test.ts:
   - Comprehensive integration tests for fork workflow
   - Tests: persist → listBranches → checkout
   - Covers Workshop snapshot use case
   - Verifies COW metadata is globally accessible

Impact:
- Affects: FileSystem, GCS, R2, S3, Azure storage adapters
- Workshop snapshot restoration now works
- Zero breaking changes, production-scale ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-11-05 09:04:38 -08:00
parent 99d732cfe4
commit 7977132e9f
3 changed files with 287 additions and 1 deletions

View file

@ -2178,7 +2178,18 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// Step 2: Copy storage ref (COW layer - instant!)
await refManager.copyRef(currentBranch, branchName)
// Step 2: Create new Brainy instance pointing to fork branch
// CRITICAL FIX (v5.3.6): Verify branch was actually created to prevent silent failures
// Without this check, fork() could complete successfully but branch wouldn't exist,
// causing subsequent checkout() calls to fail (see Workshop bug report).
const verifyBranch = await refManager.getRef(branchName)
if (!verifyBranch) {
throw new Error(
`Fork failed: Branch '${branchName}' was not created. ` +
`This indicates a storage adapter configuration issue. Please report this bug.`
)
}
// Step 3: Create new Brainy instance pointing to fork branch
const forkConfig = {
...this.config,
storage: {

View file

@ -362,6 +362,14 @@ export abstract class BaseStorage extends BaseStorageAdapter {
* @protected - Available to subclasses for COW implementation
*/
protected resolveBranchPath(basePath: string, branch?: string): string {
// CRITICAL FIX (v5.3.6): COW metadata (_cow/*) must NEVER be branch-scoped
// Refs, commits, and blobs are global metadata with their own internal branching.
// Branch-scoping COW paths causes fork() to write refs to wrong locations,
// leading to "Branch does not exist" errors on checkout (see Workshop bug report).
if (basePath.startsWith('_cow/')) {
return basePath // COW metadata is global across all branches
}
if (!this.cowEnabled) {
return basePath // COW disabled, use direct path
}