feat: add workspace fs-at seam and retained root lease

This commit is contained in:
2026-08-11 13:20:32 +02:00
parent 11cc8628cf
commit c5a01e5e53
8 changed files with 1326 additions and 3 deletions
+29
View File
@@ -0,0 +1,29 @@
import { createRequire } from "node:module";
import type { WorkspaceFsAtBindingV1, NativeWorkspaceFsAtHandleV1, NativeWorkspaceFsAtStatV1 } from "../native/workspace-fs-at-binding.js";
const require = createRequire(import.meta.url);
const binding = require("../../native/workspace-fs-at/build/Release/workspace_fs_at.node") as WorkspaceFsAtBindingV1;
export interface WorkspaceFsAtStatV1 extends NativeWorkspaceFsAtStatV1 {}
export type LockFileName = "writer.lock" | "session-readers.lock";
export type WorkspaceFlockKindV1 = "shared" | "exclusive";
export type WorkspaceFlockWaitV1 = "blocking" | "nonblocking";
function component(value:string): string { if (typeof value!=="string" || value.length===0 || value.length>255 || value!==value.trim() || value==='.' || value==='..' || value.includes('/') || value.includes('\0')) throw new Error("invalid path component"); return value; }
function normalizeError(error: unknown): Error { if (error instanceof Error) return error; return new Error(String(error)); }
class Owned {
protected live=true;
constructor(protected readonly raw: NativeWorkspaceFsAtHandleV1, readonly opened: WorkspaceFsAtStatV1) {}
stat(): WorkspaceFsAtStatV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.opened; }
close(): void { if(!this.live)return; this.live=false; try { binding.close(this.raw); } catch(e){ throw normalizeError(e); } }
_raw(): NativeWorkspaceFsAtHandleV1 { if(!this.live) throw new Error("workspace descriptor is closed"); return this.raw; }
}
export class OwnedWorkspaceFsAtDirectory extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtDirectory(raw,stat);} }
export class OwnedWorkspaceFsAtRegularFile extends Owned { private constructor(raw:NativeWorkspaceFsAtHandleV1, stat:WorkspaceFsAtStatV1){super(raw,stat);} static from(raw:NativeWorkspaceFsAtHandleV1,stat:WorkspaceFsAtStatV1){return new OwnedWorkspaceFsAtRegularFile(raw,stat);} }
function rawDirectory(value:OwnedWorkspaceFsAtDirectory){ return value._raw(); }
export class WorkspaceFsAtV1 {
openRoot(): OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:null,name:"/",kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
openDirectoryAt(parent:OwnedWorkspaceFsAtDirectory, name:string):OwnedWorkspaceFsAtDirectory { const r=binding.openat({parent:rawDirectory(parent),name:component(name) as never,kind:"directory",createMode:0}); return OwnedWorkspaceFsAtDirectory.from(r.handle,r.openedStat); }
openOrCreateLockAt(parent:OwnedWorkspaceFsAtDirectory,name:LockFileName,mode:0o600):OwnedWorkspaceFsAtRegularFile { if(name!=="writer.lock"&&name!=="session-readers.lock"||mode!==0o600)throw new Error("invalid lock"); const r=binding.openat({parent:rawDirectory(parent),name:name as never,kind:"regular_lock",createMode:0o600}); return OwnedWorkspaceFsAtRegularFile.from(r.handle,r.openedStat); }
mkdirAt(parent:OwnedWorkspaceFsAtDirectory,name:string,mode:0o700):void { binding.mkdirat(rawDirectory(parent),component(name) as never,mode); }
statAtNoFollow(parent:OwnedWorkspaceFsAtDirectory,name:string):WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent),component(name) as never); }
fsyncDirectory(directory:OwnedWorkspaceFsAtDirectory):void { binding.fsyncDirectory(rawDirectory(directory)); }
flockOwnedLock(owned:OwnedWorkspaceFsAtRegularFile,kind:WorkspaceFlockKindV1,wait:WorkspaceFlockWaitV1):void { const fd=binding.fdNumberForSynchronousBorrow(owned._raw()); const fsExt=require("fs-ext") as {flockSync(fd:number,operation:string):void}; const operation=kind==="shared"?(wait==="blocking"?"sh":"shnb"):(wait==="blocking"?"ex":"exnb"); fsExt.flockSync(fd,operation); }
}
@@ -0,0 +1,32 @@
import { WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, type OwnedWorkspaceFsAtRegularFile, type WorkspaceFsAtStatV1 } from "./workspace-fs-at.js";
export type CanonicalWorkspaceId = string & { readonly __workspaceId: unique symbol };
export interface WorkspaceLockRootIdentityV1 { readonly schemaVersion:1; readonly workspaceId:CanonicalWorkspaceId; readonly device:bigint; readonly inode:bigint; }
export class CanonicalWorkspaceLockRootInput { private constructor(readonly workspaceId:CanonicalWorkspaceId, readonly owner:symbol){} static make(id:CanonicalWorkspaceId,owner:symbol){return new CanonicalWorkspaceLockRootInput(id,owner);} }
export class BorrowedVerifiedWorkspaceLockRootLease { private constructor(readonly identity:WorkspaceLockRootIdentityV1, private readonly check:()=>void){} static make(i:WorkspaceLockRootIdentityV1,c:()=>void){return new BorrowedVerifiedWorkspaceLockRootLease(i,c);} assertLive(){this.check();} }
function conflict(message="preprocessing conflict"):Error { const e=new Error(message); e.name="PreprocessingConflictError"; return e; }
function exactStat(stat:WorkspaceFsAtStatV1,uid:number):boolean { return (stat.mode&0o170000)===0o040000 && (stat.mode&0o777)===0o700 && stat.uid===uid && stat.nlink===1n; }
export class WorkspaceSessionReadersLockLease {
private live=true; private constructor(private readonly lock:OwnedWorkspaceFsAtRegularFile, private readonly root:OwnedWorkspaceFsAtDirectory, readonly rootIdentity:WorkspaceLockRootIdentityV1){}
static make(lock:OwnedWorkspaceFsAtRegularFile,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new WorkspaceSessionReadersLockLease(lock,root,id);}
transfer():WorkspaceSessionReadersLockLease { if(!this.live)throw conflict(); this.live=false; return WorkspaceSessionReadersLockLease.make(this.lock,this.root,this.rootIdentity); }
async close():Promise<void>{if(!this.live)return;this.live=false;let failure:unknown;try{this.lock.close();}catch(e){failure=e;}try{this.root.close();}catch(e){failure??=e;}if(failure)throw conflict();}
}
export class VerifiedWorkspaceLockRootLease {
private live=true; private borrowed=0; private constructor(private readonly owner:symbol,private readonly fs:WorkspaceFsAtV1,private readonly root:OwnedWorkspaceFsAtDirectory,readonly identity:WorkspaceLockRootIdentityV1){}
static make(owner:symbol,fs:WorkspaceFsAtV1,root:OwnedWorkspaceFsAtDirectory,id:WorkspaceLockRootIdentityV1){return new VerifiedWorkspaceLockRootLease(owner,fs,root,id);}
private check(){if(!this.live)throw conflict(); const s=this.root.stat();if(s.device!==this.identity.device||s.inode!==this.identity.inode)throw conflict("workspace root identity changed");}
async borrow<T>(action:(b:BorrowedVerifiedWorkspaceLockRootLease)=>Promise<T>):Promise<T>{this.check();this.borrowed++;try{return await action(BorrowedVerifiedWorkspaceLockRootLease.make(this.identity,()=>this.check()));}finally{this.borrowed--;}}
transfer():VerifiedWorkspaceLockRootLease{this.check();if(this.borrowed)throw conflict("workspace root is borrowed");this.live=false;return VerifiedWorkspaceLockRootLease.make(this.owner,this.fs,this.root,this.identity);}
async acquireSessionReadersShared():Promise<WorkspaceSessionReadersLockLease>{this.check();let lock:OwnedWorkspaceFsAtRegularFile|undefined;try{lock=this.fs.openOrCreateLockAt(this.root,"session-readers.lock",0o600);this.fs.flockOwnedLock(lock,"shared","nonblocking");this.check();this.live=false;return WorkspaceSessionReadersLockLease.make(lock,this.root,this.identity);}catch(e){try{lock?.close();}catch{this.live=false;try{this.root.close();}catch{}}throw conflict();}}
async close():Promise<void>{if(!this.live)return;if(this.borrowed)await new Promise<void>(resolve=>{const tick=()=>this.borrowed?setTimeout(tick,1):resolve();tick();});this.live=false;try{this.root.close();}catch{throw conflict();}}
// package-private operation used by the writer capability; never returns the underlying handle.
async _withRoot<T>(action:(root:OwnedWorkspaceFsAtDirectory,fs:WorkspaceFsAtV1)=>Promise<T>):Promise<T>{this.check();return action(this.root,this.fs);}
}
export class VerifiedWorkspaceLockRootLeaseFactory {
private readonly owner=Symbol(); private readonly parent:OwnedWorkspaceFsAtDirectory;
constructor(private readonly input:{readonly workspaceFsAt:WorkspaceFsAtV1;readonly installationId:string;readonly sessionsRootFromValidatedInstallationConfig:string;readonly serviceUid:number;readonly provisionedWorkspaceMode:0o700}) { if(!Number.isInteger(input.serviceUid)||input.serviceUid<0)throw new Error("invalid service uid");if(input.provisionedWorkspaceMode!==0o700)throw new Error("invalid workspace mode");if(!input.sessionsRootFromValidatedInstallationConfig.startsWith("/"))throw new Error("sessions root must be absolute");let d=input.workspaceFsAt.openRoot();try{for(const c of input.sessionsRootFromValidatedInstallationConfig.split("/").filter(Boolean)) {const n=input.workspaceFsAt.openDirectoryAt(d,c);d.close();d=n;}this.parent=d;}catch(e){try{d.close();}catch{}throw e;}}
canonicalInput(workspaceId:string):CanonicalWorkspaceLockRootInput{if(!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId))throw conflict();return CanonicalWorkspaceLockRootInput.make(workspaceId as CanonicalWorkspaceId,this.owner);}
private async open(input:CanonicalWorkspaceLockRootInput):Promise<VerifiedWorkspaceLockRootLease>{if(input.owner!==this.owner)throw conflict();const root=this.input.workspaceFsAt.openDirectoryAt(this.parent,input.workspaceId);if(!exactStat(root.stat(),this.input.serviceUid)){root.close();throw conflict();}return VerifiedWorkspaceLockRootLease.make(this.owner,this.input.workspaceFsAt,root,{schemaVersion:1,workspaceId:input.workspaceId,device:root.stat().device,inode:root.stat().inode});}
acquire(input:CanonicalWorkspaceLockRootInput){return this.open(input);}
async acquireOrProvision(input:CanonicalWorkspaceLockRootInput){if(input.owner!==this.owner)throw conflict();try{return await this.open(input);}catch{try{this.input.workspaceFsAt.mkdirAt(this.parent,input.workspaceId,0o700);}catch(e){/* EEXIST is the concurrent winner; all other errors are rechecked below. */}this.input.workspaceFsAt.fsyncDirectory(this.parent);return this.open(input);}}
}