Cross-module callers get an opaque ref plus rotating lease tokens so stale writers fail closed, and file I/O is refused inside a unit of work. Co-authored-by: Cursor <cursoragent@cursor.com>
36 lines
912 B
Go
36 lines
912 B
Go
package workspace
|
|
|
|
import "sync"
|
|
|
|
// LeaseTable stores the current fencing token per workspace ID.
|
|
// Cross-replica correctness is the token itself, not this process mutex.
|
|
type LeaseTable struct {
|
|
mu sync.Mutex
|
|
tokens map[string]string
|
|
owner map[string]int64
|
|
}
|
|
|
|
func NewLeaseTable() *LeaseTable {
|
|
return &LeaseTable{tokens: map[string]string{}, owner: map[string]int64{}}
|
|
}
|
|
|
|
func (t *LeaseTable) Issue(ref WorkspaceRef) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
t.tokens[ref.ID] = ref.FencingToken
|
|
t.owner[ref.ID] = ref.ProjectScope
|
|
}
|
|
|
|
func (t *LeaseTable) Assert(ref WorkspaceRef) error {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
cur, ok := t.tokens[ref.ID]
|
|
if !ok || cur == "" || ref.FencingToken == "" || cur != ref.FencingToken {
|
|
return ErrStaleFencing
|
|
}
|
|
if scope, ok := t.owner[ref.ID]; ok && ref.ProjectScope != 0 && scope != 0 && scope != ref.ProjectScope {
|
|
return ErrCrossProject
|
|
}
|
|
return nil
|
|
}
|