@effect-vfs/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +9 -0
  2. package/dist/Caller.d.ts +2 -0
  3. package/dist/Caller.d.ts.map +1 -1
  4. package/dist/Caller.js +3 -1
  5. package/dist/LiveVolume.d.ts +69 -1
  6. package/dist/LiveVolume.d.ts.map +1 -1
  7. package/dist/LiveVolume.js +80 -3
  8. package/dist/Search.d.ts.map +1 -1
  9. package/dist/Search.js +12 -4
  10. package/dist/VfsError.d.ts +3 -1
  11. package/dist/VfsError.d.ts.map +1 -1
  12. package/dist/VfsError.js +3 -1
  13. package/dist/VirtualFileSystem.d.ts +25 -1
  14. package/dist/VirtualFileSystem.d.ts.map +1 -1
  15. package/dist/VirtualFileSystem.js +10 -4
  16. package/dist/Watch.d.ts +18 -0
  17. package/dist/Watch.d.ts.map +1 -1
  18. package/dist/Watch.js +12 -0
  19. package/dist/internal/callerWatch.d.ts +32 -0
  20. package/dist/internal/callerWatch.d.ts.map +1 -0
  21. package/dist/internal/callerWatch.js +184 -0
  22. package/dist/internal/confinement.d.ts +16 -0
  23. package/dist/internal/confinement.d.ts.map +1 -0
  24. package/dist/internal/confinement.js +79 -0
  25. package/dist/internal/directoryEntries.d.ts +6 -0
  26. package/dist/internal/directoryEntries.d.ts.map +1 -0
  27. package/dist/internal/directoryEntries.js +79 -0
  28. package/dist/internal/fixture.js +1 -1
  29. package/dist/internal/path.js +2 -2
  30. package/dist/internal/resolution.d.ts +8 -4
  31. package/dist/internal/resolution.d.ts.map +1 -1
  32. package/dist/internal/resolution.js +68 -13
  33. package/dist/internal/tokenRegistry.d.ts +6 -0
  34. package/dist/internal/tokenRegistry.d.ts.map +1 -1
  35. package/dist/internal/tokenRegistry.js +21 -3
  36. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  37. package/dist/internal/virtualFileSystem.js +245 -80
  38. package/dist/internal/watchHub.d.ts +6 -2
  39. package/dist/internal/watchHub.d.ts.map +1 -1
  40. package/dist/internal/watchHub.js +31 -6
  41. package/package.json +5 -3
@@ -23,8 +23,11 @@ import { Mode, OwnerUpdate, Times } from "../Metadata.js";
23
23
  import { isEntry, Target } from "../Target.js";
24
24
  import { make as makeError } from "../VfsError.js";
25
25
  import { OverlayChange, OverlayChangesOptions, ReferenceKey, VolumeId, VolumeIdentity, VolumeIncarnation } from "../Volume.js";
26
- import { WatchOptions } from "../Watch.js";
26
+ import { CallerWatchOptions, WatchOptions } from "../Watch.js";
27
27
  import { sameBytes } from "./bytes.js";
28
+ import * as CallerWatch from "./callerWatch.js";
29
+ import * as Confinement from "./confinement.js";
30
+ import * as DirectoryEntries from "./directoryEntries.js";
28
31
  import { argumentFailure, decodeConfiguration, errorPath, fsFailure, imageFailure, OpContext, retargetFailure, VfsError } from "./errors.js";
29
32
  import { KeySecret, VolumeEpoch } from "./hex128.js";
30
33
  import { hmacSha256, sameTag } from "./hmac.js";
@@ -112,14 +115,16 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
112
115
  finished: undefined,
113
116
  entries: base.entries,
114
117
  usedBytes: base.usedBytes,
118
+ renames: [],
115
119
  events: [],
116
120
  after: [],
117
121
  removed: []
118
122
  });
119
- const get = (ino) => Effect.sync(() => {
123
+ const lookup = (ino) => {
120
124
  const draft = MutableRef.get(cell);
121
125
  return draft.pending.has(ino) ? draft.pending.get(ino) : InodeTable.get(base.inodes, ino);
122
- });
126
+ };
127
+ const get = (ino) => Effect.sync(() => lookup(ino));
123
128
  const openCount = (ino) => Effect.sync(() => MutableRef.get(cell).opens?.get(ino) ?? base.open.get(ino) ?? 0);
124
129
  const put = (node, changed) => Effect.sync(() => {
125
130
  MutableRef.update(cell, (draft) => {
@@ -130,6 +135,7 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
130
135
  });
131
136
  return {
132
137
  revision,
138
+ lookup,
133
139
  get,
134
140
  put: (node) => put(node, true),
135
141
  putQuiet: (node) => put(node, false),
@@ -167,6 +173,9 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
167
173
  addUsedBytes: (amount) => Effect.sync(() => {
168
174
  MutableRef.update(cell, (draft) => ({ ...draft, usedBytes: draft.usedBytes + amount }));
169
175
  }),
176
+ rename: (rename) => Effect.sync(() => {
177
+ MutableRef.get(cell).renames.push(rename);
178
+ }),
170
179
  publish: (event) => Effect.sync(() => {
171
180
  MutableRef.get(cell).events.push(event);
172
181
  }),
@@ -203,19 +212,15 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
203
212
  return finished;
204
213
  }),
205
214
  actions: Effect.sync(() => {
206
- const { events, after, removed } = MutableRef.get(cell);
207
- return { events, after, removed };
215
+ const { events, after, removed, renames } = MutableRef.get(cell);
216
+ return { events, after, removed, renames };
208
217
  })
209
218
  };
210
219
  })
211
220
  }) {
212
221
  }
213
222
  const withMetadata = (node) => ({ ...node.metadata, revision: node.revision });
214
- const withEntries = (directory, edit) => {
215
- const entries = new Map(directory.entries);
216
- edit(entries);
217
- return { ...directory, entries };
218
- };
223
+ const withEntries = (directory, entries) => ({ ...directory, entries });
219
224
  const withoutLink = (links, parent, name) => {
220
225
  const index = links.findIndex((link) => link.parent === parent && link.name === name);
221
226
  return index < 0 ? links : [...links.slice(0, index), ...links.slice(index + 1)];
@@ -320,6 +325,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
320
325
  if (live !== undefined)
321
326
  state = live.value;
322
327
  const view = (ino) => Effect.flatMap(Effect.serviceOption(Draft), (draft) => Option.isSome(draft) ? draft.value.get(ino) : Effect.sync(() => getNode(state, ino)));
328
+ // Select the operation's namespace at execution time; never retain it across operations.
329
+ const authorityView = Effect.map(Effect.serviceOption(Draft), (draft) => Option.isSome(draft) ? draft.value.lookup : (ino) => getNode(state, ino));
323
330
  const registry = TokenRegistry.make(volumeIdentity, view);
324
331
  const referenceFor = registry.referenceFor;
325
332
  const makeFileReference = registry.file;
@@ -337,7 +344,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
337
344
  for (const apply of actions.after)
338
345
  apply();
339
346
  const after = state;
340
- watchHub.publishUnsafe(() => actions.events.flatMap((events) => Array.from(events(after))), { before, after });
347
+ watchHub.publishUnsafe(() => actions.events.flatMap((events) => Array.from(events(after))), {
348
+ before,
349
+ after,
350
+ renames: actions.renames
351
+ });
341
352
  });
342
353
  const transition = Effect.fnUntraced(function* (change) {
343
354
  const current = yield* Draft.make(state);
@@ -420,10 +431,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
420
431
  return SLASH_HEX + names.reverse().join(SLASH_HEX);
421
432
  });
422
433
  const entryPath = (prefix, name) => ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + name));
423
- const publishEntry = (_tag, parent, name, ino) => Effect.flatMap(Draft, (draft) => draft.publish((installed) => {
424
- const prefix = pathOf((at) => getNode(installed, at), parent);
425
- return prefix === undefined ? [] : [{ change: { _tag, path: entryPath(prefix, name) }, parent, ino }];
426
- }));
434
+ const publishEntry = Effect.fnUntraced(function* (_tag, parent, name, ino) {
435
+ const before = state;
436
+ const draft = yield* Draft;
437
+ yield* draft.publish((installed) => {
438
+ const prefix = pathOf((at) => getNode(installed, at), parent) ?? pathOf((at) => getNode(before, at), parent);
439
+ return prefix === undefined ? [] : [{ change: { _tag, path: entryPath(prefix, name) }, parent, ino, name }];
440
+ });
441
+ });
427
442
  const publishNode = Effect.fnUntraced(function* (ino) {
428
443
  const draft = yield* Draft;
429
444
  const target = yield* draft.get(ino);
@@ -599,7 +614,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
599
614
  mtimeNs: now,
600
615
  ctimeNs: now
601
616
  }
602
- }, (entries) => entries.set(name, child.ino)));
617
+ }, DirectoryEntries.set(parent.entries, name, child.ino)));
603
618
  if (child.kind === "directory")
604
619
  yield* d.put({ ...child, parent: parent.ino, name });
605
620
  else {
@@ -667,12 +682,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
667
682
  yield* publishNode(file.ino);
668
683
  });
669
684
  const fileHandle = (ref) => {
670
- const get = Effect.fnUntraced(function* (op, access) {
685
+ const get = Effect.fnUntraced(function* (op, access, cleanup = false) {
671
686
  const ino = TokenRegistry.inode(ref);
672
687
  const node = ino === undefined ? undefined : yield* view(ino);
673
688
  if (node?.kind !== "file" || (access === "read" && ref.access === "write") ||
674
689
  (access === "write" && ref.access === "read"))
675
690
  return yield* op.fail("InvalidHandle");
691
+ if (!cleanup && ref.authority !== undefined)
692
+ yield* ref.authority(node, op);
676
693
  return node;
677
694
  });
678
695
  const read = (maximum, position) => {
@@ -754,19 +771,19 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
754
771
  const closeOp = OpContext.make("close");
755
772
  const handle = Object.freeze({
756
773
  [FileHandleId]: true,
757
- read: Effect.fn("FileHandle.read")(function* (maximum) {
774
+ read: Effect.fnUntraced(function* (maximum) {
758
775
  return (yield* read(maximum)).bytes;
759
776
  }),
760
- pread: Effect.fn("FileHandle.pread")(function* (maximum, offset) {
777
+ pread: Effect.fnUntraced(function* (maximum, offset) {
761
778
  return yield* read(maximum, offset);
762
779
  }),
763
- write: Effect.fn("FileHandle.write")(function* (bytes) {
780
+ write: Effect.fnUntraced(function* (bytes) {
764
781
  return yield* write(bytes);
765
782
  }),
766
- pwrite: Effect.fn("FileHandle.pwrite")(function* (bytes, offset) {
783
+ pwrite: Effect.fnUntraced(function* (bytes, offset) {
767
784
  return yield* write(bytes, offset);
768
785
  }),
769
- seek: Effect.fn("FileHandle.seek")(function* (offset, mode) {
786
+ seek: Effect.fnUntraced(function* (offset, mode) {
770
787
  const op = OpContext.make("seek");
771
788
  return yield* coordinatedRead(op, Effect.uninterruptible(Effect.gen(function* () {
772
789
  const file = yield* get(op);
@@ -791,23 +808,55 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
791
808
  return next;
792
809
  })));
793
810
  }),
794
- truncate: Effect.fn("FileHandle.truncate")(function* (length) {
811
+ truncate: Effect.fnUntraced(function* (length) {
795
812
  const op = OpContext.make("truncate");
796
813
  return yield* coordinated(op, Effect.gen(function* () {
797
814
  yield* resize(yield* get(op, "write"), length, op);
798
815
  }));
799
816
  }),
800
- stat: coordinatedRead(statOp, Effect.map(Effect.suspend(() => get(statOp)), withMetadata)).pipe(Effect.withSpan("FileHandle.stat")),
801
- sync: coordinatedRead(syncOp, Effect.suspend(() => Effect.asVoid(get(syncOp)))).pipe(Effect.withSpan("FileHandle.sync")),
802
- close: closeFile(ref, closeOp, Effect.suspend(() => get(closeOp))).pipe(Effect.ensuring(closeReleasedScope(ref)), Effect.withSpan("FileHandle.close"))
817
+ stat: coordinatedRead(statOp, Effect.map(Effect.suspend(() => get(statOp)), withMetadata)),
818
+ sync: coordinatedRead(syncOp, Effect.suspend(() => Effect.asVoid(get(syncOp)))),
819
+ close: closeFile(ref, closeOp, Effect.suspend(() => get(closeOp, undefined, true))).pipe(Effect.ensuring(closeReleasedScope(ref)))
803
820
  });
804
821
  registry.registerFile(handle, ref);
805
822
  return handle;
806
823
  };
807
- const createCaller = (reference, identity, umask) => {
824
+ const createCaller = (reference, identity, umask, roots = [], inherited = []) => {
825
+ const boundary = roots.length === 0 ? undefined : Confinement.make(authorityView, roots);
826
+ inherited = inherited.filter((chain, index) => !((chain.length <= roots.length && chain.every((ino, at) => roots[at] === ino)) ||
827
+ inherited.slice(0, index).some((other) => other.length === chain.length && chain.every((ino, at) => other[at] === ino))));
828
+ const additional = inherited.map((chain) => Confinement.make(authorityView, chain));
829
+ const checkAuthority = Effect.fnUntraced(function* (node, op) {
830
+ if (boundary !== undefined)
831
+ yield* boundary.check(node, op);
832
+ for (const authority of additional)
833
+ yield* authority.check(node, op);
834
+ });
835
+ const inheritedFor = (target) => Target.$is("Handle")(target)
836
+ ? registry.handleBoundaries(target.handle)
837
+ : Target.$is("Path")(target) && target.relativeTo !== undefined
838
+ ? registry.handleBoundaries(target.relativeTo)
839
+ : [];
840
+ const boundariesFor = (target) => [...(roots.length > 0 ? [roots] : []), ...inherited, ...(target === undefined ? [] : inheritedFor(target))];
841
+ const assignAuthority = (ref, target) => {
842
+ const chains = boundariesFor(target).filter((chain, index, all) => !all.some((other, at) => at !== index && other.length >= chain.length && chain.every((ino, offset) => other[offset] === ino) &&
843
+ (other.length > chain.length || at < index)));
844
+ if (chains.length === 0)
845
+ return;
846
+ ref.boundaries = chains;
847
+ const authorities = chains.map((chain) => Confinement.make(authorityView, chain));
848
+ ref.authority = Effect.fnUntraced(function* (node, op) {
849
+ for (const authority of authorities)
850
+ yield* authority.check(node, op, true);
851
+ });
852
+ };
853
+ const callerPath = boundary?.path ?? pathOfCurrent;
808
854
  const resolver = Resolution.make({
809
855
  caller: reference,
856
+ confinement: boundary,
857
+ additional: additional.length === 0 ? undefined : additional,
810
858
  get: view,
859
+ authorityView,
811
860
  identity,
812
861
  maxPathBytes: limits.maxPathBytes,
813
862
  registry
@@ -965,6 +1014,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
965
1014
  : Effect.void;
966
1015
  const claimName = Effect.fnUntraced(function* (entry, trailingSlash = "allowed") {
967
1016
  const parent = yield* directoryNow(entry.parent);
1017
+ if (boundary !== undefined && parent.ino === boundary.root &&
1018
+ (entry.name === undefined || isDotComponent(entry.name)))
1019
+ return yield* entry.op.fail("NotPermitted");
968
1020
  if (isDotComponent(entry.name)) {
969
1021
  return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : "AlreadyExists");
970
1022
  }
@@ -1071,7 +1123,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1071
1123
  mtimeNs: now,
1072
1124
  ctimeNs: now
1073
1125
  }
1074
- }, (entries) => entries.delete(name)));
1126
+ }, DirectoryEntries.remove(parent.entries, name)));
1075
1127
  yield* publishEntry("Remove", parent.ino, name, child.ino);
1076
1128
  yield* detach(child, parent.ino, name, now);
1077
1129
  yield* d.addEntries(-1);
@@ -1080,8 +1132,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1080
1132
  // Authorizes removing from the entry's directory and returns the named child. Only a path can name a dot
1081
1133
  // entry, and each verb reports it with its own code. Search permission comes before the lookup and write
1082
1134
  // permission after it, as on Linux; `beforeWrite` holds the verb's own checks that Linux makes in between.
1135
+ const protectsRoot = (parent, name) => boundary !== undefined &&
1136
+ ((parent.ino === boundary.root && (name === undefined || isDotComponent(name))) ||
1137
+ (name === "2e2e" && parent.parent === boundary.root));
1083
1138
  const removalTarget = Effect.fnUntraced(function* (entry, dotNameCode, beforeWrite) {
1084
1139
  const parent = yield* directoryNow(entry.parent);
1140
+ if (protectsRoot(parent, entry.name))
1141
+ return yield* entry.op.fail("NotPermitted");
1085
1142
  if (isDotComponent(entry.name)) {
1086
1143
  return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : dotNameCode);
1087
1144
  }
@@ -1090,6 +1147,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1090
1147
  const child = childIno === undefined ? undefined : (yield* view(childIno));
1091
1148
  if (child === undefined)
1092
1149
  return yield* entry.op.fail("NotFound");
1150
+ if (child.ino === boundary?.root)
1151
+ return yield* entry.op.fail("NotPermitted");
1093
1152
  const rejected = beforeWrite?.(child);
1094
1153
  if (rejected !== undefined)
1095
1154
  return yield* rejected;
@@ -1127,6 +1186,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1127
1186
  const sameDirectory = source.parent === destination.parent;
1128
1187
  const sourceName = source.name;
1129
1188
  const destinationName = destination.name;
1189
+ if (protectsRoot(sourceDirectory, sourceName) || protectsRoot(destinationDirectory, destinationName))
1190
+ return yield* source.op.fail("NotPermitted");
1130
1191
  if (isDotComponent(sourceName) || isDotComponent(destinationName)) {
1131
1192
  return yield* source.op.fail("InvalidArgument");
1132
1193
  }
@@ -1142,6 +1203,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1142
1203
  return yield* source.op.fail("NotFound");
1143
1204
  const replacedIno = destinationDirectory.entries.get(destinationName);
1144
1205
  const replaced = replacedIno === undefined ? undefined : (yield* view(replacedIno));
1206
+ if (boundary !== undefined && (child.ino === boundary.root || replaced?.ino === boundary.root)) {
1207
+ return yield* source.op.fail("NotPermitted");
1208
+ }
1145
1209
  // A trailing slash on either side asks for a directory; a missing slashed destination is fine when the
1146
1210
  // source is one, as Linux allows.
1147
1211
  if ((source.trailingSlash || destination.trailingSlash) && child.kind !== "directory") {
@@ -1190,6 +1254,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1190
1254
  }
1191
1255
  const now = yield* timestamp(op);
1192
1256
  const d = yield* Draft;
1257
+ yield* d.rename({
1258
+ ino: child.ino,
1259
+ from: { parent: source.parent, name: sourceName },
1260
+ to: { parent: destination.parent, name: destinationName }
1261
+ });
1193
1262
  yield* publishEntry("Remove", sourceDirectory.ino, sourceName, child.ino);
1194
1263
  yield* d.put(withEntries({
1195
1264
  ...sourceDirectory,
@@ -1199,7 +1268,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1199
1268
  mtimeNs: now,
1200
1269
  ctimeNs: now
1201
1270
  }
1202
- }, (entries) => entries.delete(sourceName)));
1271
+ }, DirectoryEntries.remove(sourceDirectory.entries, sourceName)));
1203
1272
  const destinationNow = yield* directoryNow(destination.parent);
1204
1273
  yield* d.put(withEntries({
1205
1274
  ...destinationNow,
@@ -1209,7 +1278,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1209
1278
  mtimeNs: now,
1210
1279
  ctimeNs: now
1211
1280
  }
1212
- }, (entries) => entries.set(destinationName, child.ino)));
1281
+ }, DirectoryEntries.set(destinationNow.entries, destinationName, child.ino)));
1213
1282
  const moved = yield* nodeNow(child.ino);
1214
1283
  if (moved.kind === "directory") {
1215
1284
  yield* d.put({
@@ -1309,6 +1378,21 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1309
1378
  return yield* acquireHandle(acquired, (acquire) => coordinatedRead(op, Effect.uninterruptible(acquire)), Effect.gen(function* () {
1310
1379
  const directory = yield* resolveDirectory(target, op);
1311
1380
  yield* authorize(directory.node, identity, EXECUTE, directory.op);
1381
+ assignAuthority(acquired, target);
1382
+ const candidates = acquired.boundaries ?? [];
1383
+ if (candidates.length === 1)
1384
+ acquired.namespaceRoots = candidates[0];
1385
+ let current = directory.node;
1386
+ while (candidates.length > 1) {
1387
+ const nearest = candidates.find((chain) => chain.at(-1) === current.ino);
1388
+ if (nearest !== undefined) {
1389
+ acquired.namespaceRoots = nearest;
1390
+ break;
1391
+ }
1392
+ if (current.ino === ROOT_INO)
1393
+ break;
1394
+ current = yield* directoryNow(current.parent);
1395
+ }
1312
1396
  holdDirectory(directory.node.ino);
1313
1397
  TokenRegistry.publish(acquired, directory.node.ino);
1314
1398
  return acquired;
@@ -1345,6 +1429,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1345
1429
  const file = resolved.kind === "existing" ? resolved.node : undefined;
1346
1430
  const result = yield* openFile({ parent: parent.ino, name, trailingSlash: path.trailingSlash, addressing: "path", op: pathOp }, file, chosen, acquired, op);
1347
1431
  opened(result.ino);
1432
+ assignAuthority(acquired, target);
1348
1433
  return fileHandle(acquired);
1349
1434
  }));
1350
1435
  });
@@ -1368,6 +1453,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1368
1453
  yield* draft.retain(node.ino);
1369
1454
  yield* bindFile(acquired, node.ino);
1370
1455
  opened(node.ino);
1456
+ assignAuthority(acquired, target);
1371
1457
  return fileHandle(acquired);
1372
1458
  }));
1373
1459
  });
@@ -1425,7 +1511,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1425
1511
  const result = yield* openFile({ parent: mutationParent, name: mutationName, trailingSlash: false, addressing: "entry", op }, file, { ...chosen, initialSize: chosen.initialSize ?? 0n }, acquired, op);
1426
1512
  opened(result.ino);
1427
1513
  return {
1428
- handle: fileHandle(acquired),
1514
+ handle: (() => {
1515
+ assignAuthority(acquired, entry.directory);
1516
+ return fileHandle(acquired);
1517
+ })(),
1429
1518
  reference: referenceFor(result.ino),
1430
1519
  created: result.created,
1431
1520
  directory: result.directory
@@ -1492,10 +1581,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1492
1581
  // through a reference or a handle is held by the object it resolved to, as a descriptor holds it.
1493
1582
  // It steps down the chain in a loop, so a tree of any depth reaches its frames without deepening the stack.
1494
1583
  const reachFrame = (frame, at) => Effect.gen(function* () {
1584
+ if (boundary !== undefined)
1585
+ yield* boundary.alive(at);
1495
1586
  const chain = [];
1496
1587
  for (let link = frame; link !== undefined; link = link.up)
1497
1588
  chain.push(link);
1498
1589
  let node = yield* view((chain.at(-1) ?? frame).parent);
1590
+ if (node !== undefined)
1591
+ yield* checkAuthority(node, at);
1499
1592
  for (let index = chain.length - 1; index >= 0; index--) {
1500
1593
  const link = chain[index];
1501
1594
  if (link === undefined || node?.kind !== "directory" || node.metadata.nlink === 0)
@@ -1582,13 +1675,15 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1582
1675
  // failure, which names the entry's path under the one the caller gave. Removing an empty directory reveals nothing, so only a directory with entries must be readable, and
1583
1676
  // no permission is changed to make one so. With `force`, the entry itself going missing leaves nothing to
1584
1677
  // empty.
1585
- const emptyDirectory = Effect.fnUntraced(function* (prepared, op, force) {
1678
+ const emptyDirectory = Effect.fnUntraced(function* (prepared, op, force, guard) {
1586
1679
  const prefix = prepared.kind === "path" ? prepared.path.bytes : nameBytes(prepared.name);
1587
1680
  const locate = (path) => ownedPath(joinPath(prefix, path));
1588
- const listable = (directory, at) => directory.entries.size === 0 ? Effect.void : authorize(directory, identity, READ, at);
1681
+ const targetGuard = guard.pipe(Effect.catchIf((error) => force && error.code === "NotFound", () => Effect.void));
1682
+ const listable = (directory, at) => Effect.andThen(targetGuard, directory.entries.size === 0 ? Effect.void : authorize(directory, identity, READ, at));
1589
1683
  let anchor;
1590
1684
  let targetAt = preparedOp(prepared, op);
1591
1685
  const first = coordinatedRead(op, Effect.gen(function* () {
1686
+ yield* guard;
1592
1687
  const entry = yield* resolveEntry(prepared, op);
1593
1688
  const parent = yield* directoryNow(entry.parent);
1594
1689
  yield* authorize(parent, identity, EXECUTE, entry.op);
@@ -1607,6 +1702,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1607
1702
  const removeFrame = (frame) => {
1608
1703
  const at = op.at(locate(frame.path));
1609
1704
  return Effect.andThen(seams.beforeTreeRemoval, coordinated(op, Effect.gen(function* () {
1705
+ yield* targetGuard;
1610
1706
  if (anchor !== undefined && !holds(yield* view(anchor.parent), anchor)) {
1611
1707
  return yield* (force ? Effect.void : Effect.fail(targetAt.fail("NotFound")));
1612
1708
  }
@@ -1630,7 +1726,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1630
1726
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
1631
1727
  return yield* coordinated(op, Effect.flatMap(resolveEntry(prepared, op), (entry) => body(entry, op)));
1632
1728
  });
1633
- const openDispatch = Effect.fn("Caller.open")(function* (input, options) {
1729
+ const openDispatch = Effect.fnUntraced(function* (input, options) {
1634
1730
  const op = OpContext.make("open");
1635
1731
  if (isEntry(input))
1636
1732
  return yield* openEntry(input, options, op);
@@ -1642,11 +1738,34 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1642
1738
  function open(input, options) {
1643
1739
  return openDispatch(input, options);
1644
1740
  }
1645
- const removeDispatch = Effect.fn("Caller.remove")(function* (input, options) {
1741
+ const removeDispatch = Effect.fnUntraced(function* (input, options) {
1646
1742
  const op = OpContext.make("remove");
1647
1743
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
1648
1744
  const chosen = yield* decodeRemoveOptions(options ?? {}).pipe(Effect.mapError((cause) => fail(preparedOp(prepared, op), cause)));
1649
- const once = coordinated(op, Effect.flatMap(resolveEntry(prepared, op), (entry) => removeEntry(entry, op)));
1745
+ const imported = prepared.kind === "entry"
1746
+ ? inheritedFor(prepared.directory)
1747
+ : prepared.base === undefined
1748
+ ? []
1749
+ : registry.handleBoundaries(prepared.base);
1750
+ const guard = chosen.expected === undefined && imported.length === 0 ? Effect.void : Effect.gen(function* () {
1751
+ const entry = yield* resolveEntry(prepared, op);
1752
+ const parent = yield* directoryNow(entry.parent);
1753
+ if (protectsRoot(parent, entry.name))
1754
+ return yield* entry.op.fail("NotPermitted");
1755
+ if (chosen.expected === undefined)
1756
+ return;
1757
+ const expected = yield* registry.referenceIdentity(chosen.expected, op);
1758
+ const expectedNode = yield* view(expected);
1759
+ if (expectedNode !== undefined && expectedNode.metadata.nlink > 0)
1760
+ yield* checkAuthority(expectedNode, op);
1761
+ yield* authorize(parent, identity, EXECUTE, entry.op);
1762
+ const found = entry.name === undefined ? parent.ino : parent.entries.get(entry.name);
1763
+ if (found === undefined)
1764
+ return yield* entry.op.fail("NotFound");
1765
+ if (found !== expected)
1766
+ return yield* entry.op.fail("VolumeBusy", { field: "expected" });
1767
+ });
1768
+ const once = coordinated(op, Effect.andThen(guard, Effect.flatMap(resolveEntry(prepared, op), (entry) => removeEntry(entry, op))));
1650
1769
  const target = (effect) => chosen.force === true
1651
1770
  ? Effect.catchIf(effect, (error) => error.code === "NotFound", () => Effect.undefined)
1652
1771
  : effect;
@@ -1655,7 +1774,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1655
1774
  return removed.success;
1656
1775
  if (chosen.recursive !== true || removed.failure.code !== "NotEmpty")
1657
1776
  return yield* removed.failure;
1658
- yield* emptyDirectory(prepared, op, chosen.force === true);
1777
+ yield* emptyDirectory(prepared, op, chosen.force === true, guard);
1659
1778
  return yield* target(once);
1660
1779
  });
1661
1780
  function remove(input, options) {
@@ -1663,10 +1782,43 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1663
1782
  }
1664
1783
  const caller = Object.freeze({
1665
1784
  [CallerId]: true,
1785
+ limits,
1786
+ watch: Effect.fnUntraced(function* (input, options) {
1787
+ const op = OpContext.make("watch");
1788
+ const decoded = yield* Effect.fromResult(decodeConfiguration(CallerWatchOptions, options ?? {}, "watch"));
1789
+ const target = asTarget(input);
1790
+ const seams = yield* VolumeTestSeams;
1791
+ const select = Effect.gen(function* () {
1792
+ yield* checkAvailable("watch");
1793
+ const resolved = yield* resolveTarget(target, op);
1794
+ yield* authorize(resolved.node, identity, READ, resolved.op);
1795
+ if (resolved.node.metadata.nlink === 0)
1796
+ return yield* resolved.op.fail("NotFound");
1797
+ if (decoded.alias === "resolved" && resolved.kind === "node" && resolved.node.kind !== "directory") {
1798
+ return yield* op.fail("InvalidArgument");
1799
+ }
1800
+ const selection = {
1801
+ roots,
1802
+ additionalRoots: [...inherited, ...inheritedFor(target)],
1803
+ target: resolved.node.ino,
1804
+ recursive: decoded.recursive ?? true
1805
+ };
1806
+ return CallerWatch.make(decoded.alias === "resolved" && resolved.kind === "entry"
1807
+ ? { ...selection, selected: { parent: resolved.parent.ino, name: resolved.name } }
1808
+ : selection);
1809
+ });
1810
+ return Stream.map(yield* admit(op, watchHub.subscribe(select, seams.afterSubscribe)), (event) => event.change);
1811
+ }),
1666
1812
  root: coordinatedRead(rootOp, Effect.suspend(() => TokenRegistry.inode(reference) === undefined
1667
1813
  ? Effect.fail(rootOp.fail("ClosedCaller"))
1668
- : Effect.succeed(referenceFor(ROOT_INO)))).pipe(Effect.withSpan("Caller.root")),
1669
- lookup: Effect.fn("Caller.lookup")(function* (input) {
1814
+ : Effect.gen(function* () {
1815
+ const node = yield* view(boundary?.root ?? ROOT_INO);
1816
+ if (node === undefined)
1817
+ return yield* rootOp.fail("ClosedCaller");
1818
+ yield* checkAuthority(node, rootOp);
1819
+ return referenceFor(node.ino);
1820
+ }))),
1821
+ lookup: Effect.fnUntraced(function* (input) {
1670
1822
  const op = OpContext.make("lookup");
1671
1823
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
1672
1824
  return yield* coordinatedRead(op, Effect.gen(function* () {
@@ -1682,21 +1834,24 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1682
1834
  return referenceFor(child);
1683
1835
  }));
1684
1836
  }),
1685
- parent: Effect.fn("Caller.parent")(function* (input) {
1837
+ parent: Effect.fnUntraced(function* (input) {
1686
1838
  const op = OpContext.make("parent");
1687
1839
  const target = asTarget(input);
1688
1840
  return yield* coordinatedRead(op, Effect.gen(function* () {
1689
1841
  const directory = yield* resolveDirectory(target, op);
1690
1842
  yield* authorize(directory.node, identity, EXECUTE, directory.op);
1691
- return referenceFor(directory.node.parent);
1843
+ return referenceFor(directory.node.ino === boundary?.root ||
1844
+ inheritedFor(target).some((chain) => chain.at(-1) === directory.node.ino)
1845
+ ? directory.node.ino
1846
+ : directory.node.parent);
1692
1847
  }));
1693
1848
  }),
1694
- stat: Effect.fn("Caller.stat")(function* (input) {
1849
+ stat: Effect.fnUntraced(function* (input) {
1695
1850
  const op = OpContext.make("stat");
1696
1851
  const target = asTarget(input);
1697
1852
  return yield* coordinatedRead(op, Effect.map(resolveTarget(target, op), (resolved) => withMetadata(resolved.node)));
1698
1853
  }),
1699
- readDirectory: Effect.fn("Caller.readDirectory")(function* (input) {
1854
+ readDirectory: Effect.fnUntraced(function* (input) {
1700
1855
  const op = OpContext.make("readDirectory");
1701
1856
  const target = asTarget(input);
1702
1857
  return yield* accessing(op, Effect.gen(function* () {
@@ -1712,7 +1867,12 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1712
1867
  const target = asTarget(input);
1713
1868
  const prefix = Target.$is("Path")(target) ? Result.getOrUndefined(inputBytes(target.path)) : undefined;
1714
1869
  const locate = (path) => ownedPath(prefix === undefined ? path : joinPath(prefix, path));
1715
- const readable = (directory, at) => authorize(directory, identity, READ, at);
1870
+ const authorities = inheritedFor(target).map((chain) => Confinement.make(authorityView, chain));
1871
+ const readable = Effect.fnUntraced(function* (directory, at) {
1872
+ for (const authority of authorities)
1873
+ yield* authority.check(directory, at, true);
1874
+ yield* authorize(directory, identity, READ, at);
1875
+ });
1716
1876
  return Stream.unwrap(Effect.gen(function* () {
1717
1877
  const chosen = yield* decodeWalkOptions(options ?? {}).pipe(Effect.mapError((cause) => fail(Target.$is("Path")(target) ? op.at(target.path) : op, cause)));
1718
1878
  const first = coordinatedRead(op, Effect.gen(function* () {
@@ -1743,9 +1903,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1743
1903
  kind: frame.kind,
1744
1904
  depth: frame.depth
1745
1905
  }));
1746
- })).pipe(Stream.withSpan("Caller.walk"));
1906
+ }));
1747
1907
  },
1748
- readLink: Effect.fn("Caller.readLink")(function* (input) {
1908
+ readLink: Effect.fnUntraced(function* (input) {
1749
1909
  const op = OpContext.make("readLink");
1750
1910
  const target = asTarget(input);
1751
1911
  return yield* coordinatedRead(op, Effect.gen(function* () {
@@ -1755,7 +1915,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1755
1915
  return new Uint8Array(resolved.node.target);
1756
1916
  }));
1757
1917
  }),
1758
- realPath: Effect.fn("Caller.realPath")(function* (input) {
1918
+ realPath: Effect.fnUntraced(function* (input) {
1759
1919
  const op = OpContext.make("realPath");
1760
1920
  const target = asTarget(input);
1761
1921
  return yield* coordinatedRead(op, Effect.gen(function* () {
@@ -1767,7 +1927,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1767
1927
  : result.kind === "entry"
1768
1928
  ? result.parent
1769
1929
  : undefined;
1770
- const prefix = directory === undefined ? SLASH_HEX : yield* pathOfCurrent(directory.ino);
1930
+ const prefix = directory === undefined ? SLASH_HEX : yield* callerPath(directory.ino);
1771
1931
  if (prefix === undefined)
1772
1932
  return yield* pathOp.fail("NotFound");
1773
1933
  if (result.node.kind === "directory" || result.kind === "node")
@@ -1775,13 +1935,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1775
1935
  return ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + result.name));
1776
1936
  }
1777
1937
  const resolved = yield* resolveTarget(target, op);
1778
- const path = yield* pathOfCurrent(resolved.node.ino);
1938
+ const path = yield* callerPath(resolved.node.ino);
1779
1939
  if (path === undefined)
1780
1940
  return yield* resolved.op.fail("NotFound");
1781
1941
  return ownedPath(nameBytes(path));
1782
1942
  }));
1783
1943
  }),
1784
- access: Effect.fn("Caller.access")(function* (input, bits = 0) {
1944
+ access: Effect.fnUntraced(function* (input, bits = 0) {
1785
1945
  const op = OpContext.make("access");
1786
1946
  const target = asTarget(input);
1787
1947
  if (!Number.isInteger(bits) || bits < 0 || bits > (READ | WRITE | EXECUTE)) {
@@ -1801,7 +1961,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1801
1961
  return granted;
1802
1962
  }));
1803
1963
  }),
1804
- readFile: Effect.fn("Caller.readFile")(function* (input) {
1964
+ readFile: Effect.fnUntraced(function* (input) {
1805
1965
  const op = OpContext.make("readFile");
1806
1966
  const target = asTarget(input);
1807
1967
  return yield* accessing(op, Effect.gen(function* () {
@@ -1816,7 +1976,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1816
1976
  return { value: data, access: { node, now: yield* timestamp(op) } };
1817
1977
  }));
1818
1978
  }),
1819
- writeFile: Effect.fn("Caller.writeFile")(function* (input, bytes, options) {
1979
+ writeFile: Effect.fnUntraced(function* (input, bytes, options) {
1820
1980
  const op = OpContext.make("writeFile");
1821
1981
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
1822
1982
  const optionsOp = preparedOp(prepared, op);
@@ -1903,7 +2063,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1903
2063
  }));
1904
2064
  }),
1905
2065
  open,
1906
- mkdir: Effect.fn("Caller.mkdir")(function* (input, options = {}) {
2066
+ mkdir: Effect.fnUntraced(function* (input, options = {}) {
1907
2067
  const op = OpContext.make("mkdir");
1908
2068
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
1909
2069
  const optionsOp = preparedOp(prepared, op);
@@ -1919,7 +2079,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1919
2079
  return { reference: referenceFor(child), directory };
1920
2080
  }));
1921
2081
  }),
1922
- symlink: Effect.fn("Caller.symlink")(function* (target, input, options = {}) {
2082
+ symlink: Effect.fnUntraced(function* (target, input, options = {}) {
1923
2083
  const op = OpContext.make("symlink");
1924
2084
  const targetOp = op.at(target);
1925
2085
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
@@ -1938,7 +2098,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1938
2098
  return { reference: referenceFor(child), directory };
1939
2099
  }));
1940
2100
  }),
1941
- link: Effect.fn("Caller.link")(function* (sourceInput, input) {
2101
+ link: Effect.fnUntraced(function* (sourceInput, input) {
1942
2102
  const op = OpContext.make("link");
1943
2103
  const source = asTarget(sourceInput);
1944
2104
  const prepared = yield* Effect.fromResult(prepareEntry(input, op));
@@ -1954,14 +2114,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1954
2114
  return { reference: referenceFor(node.ino), directory };
1955
2115
  }));
1956
2116
  }),
1957
- unlink: Effect.fn("Caller.unlink")(function* (input) {
2117
+ unlink: Effect.fnUntraced(function* (input) {
1958
2118
  return yield* entryVerb("unlink", input, unlinkEntry);
1959
2119
  }),
1960
- rmdir: Effect.fn("Caller.rmdir")(function* (input) {
2120
+ rmdir: Effect.fnUntraced(function* (input) {
1961
2121
  return yield* entryVerb("rmdir", input, rmdirEntry);
1962
2122
  }),
1963
2123
  remove,
1964
- rename: Effect.fn("Caller.rename")(function* (fromInput, toInput) {
2124
+ rename: Effect.fnUntraced(function* (fromInput, toInput) {
1965
2125
  const op = OpContext.make("rename");
1966
2126
  const from = yield* Effect.fromResult(prepareEntry(fromInput, op));
1967
2127
  const to = yield* Effect.fromResult(prepareEntry(toInput, op));
@@ -1971,34 +2131,34 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
1971
2131
  return yield* renameEntry(source, destination, op);
1972
2132
  }));
1973
2133
  }),
1974
- chmod: Effect.fn("Caller.chmod")(function* (input, mode) {
2134
+ chmod: Effect.fnUntraced(function* (input, mode) {
1975
2135
  const op = OpContext.make("chmod");
1976
2136
  const target = asTarget(input);
1977
2137
  if (!isMode(mode))
1978
2138
  return yield* op.fail("InvalidArgument");
1979
2139
  yield* changeAttributes(() => asResolvedNode(target, op), { mode }, op);
1980
2140
  }),
1981
- chown: Effect.fn("Caller.chown")(function* (input, owner) {
2141
+ chown: Effect.fnUntraced(function* (input, owner) {
1982
2142
  const op = OpContext.make("chown");
1983
2143
  const target = asTarget(input);
1984
2144
  const decoded = yield* decodeOwnerUpdate(owner).pipe(Effect.mapError((cause) => fail(op, cause)));
1985
2145
  yield* changeAttributes(() => asResolvedNode(target, op), { owner: decoded }, op);
1986
2146
  }),
1987
- utimes: Effect.fn("Caller.utimes")(function* (input, times) {
2147
+ utimes: Effect.fnUntraced(function* (input, times) {
1988
2148
  const op = OpContext.make("utimes");
1989
2149
  const target = asTarget(input);
1990
2150
  const decoded = yield* decodeTimes(times).pipe(Effect.mapError((cause) => fail(op, cause)));
1991
2151
  yield* changeAttributes(() => asResolvedNode(target, op), { times: decoded }, op);
1992
2152
  }),
1993
2153
  // A negative length fails before the target resolves, as truncate(2) rejects it before the lookup.
1994
- truncate: Effect.fn("Caller.truncate")(function* (input, length) {
2154
+ truncate: Effect.fnUntraced(function* (input, length) {
1995
2155
  const op = OpContext.make("truncate");
1996
2156
  const target = asTarget(input);
1997
2157
  if (!isLength(length))
1998
2158
  return yield* op.fail("InvalidArgument");
1999
2159
  yield* changeAttributes(() => asResolvedNode(target, op), { size: length }, op);
2000
2160
  }),
2001
- setattr: Effect.fn("Caller.setattr")(function* (input, attributes) {
2161
+ setattr: Effect.fnUntraced(function* (input, attributes) {
2002
2162
  const op = OpContext.make("setattr");
2003
2163
  const target = asTarget(input);
2004
2164
  // A caller outside TypeScript can pass anything, so a non-object fails typed rather than as a defect;
@@ -2024,11 +2184,15 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2024
2184
  : yield* decodeExpected(attributes.expected).pipe(Effect.mapError((cause) => fail(op, cause, "expected")));
2025
2185
  yield* changeAttributes(() => asResolvedNode(target, op), { size: attributes.size, mode: attributes.mode, owner, times, expected }, op);
2026
2186
  }),
2027
- withDirectory: Effect.fn("Caller.withDirectory")(function* (input) {
2187
+ withRoot: Effect.fnUntraced(function* (input) {
2188
+ const acquired = yield* acquireDirectory(input, OpContext.make("withRoot"));
2189
+ return createCaller(acquired, identity, umask, [...roots, TokenRegistry.inode(acquired)], acquired.boundaries ?? []);
2190
+ }),
2191
+ withDirectory: Effect.fnUntraced(function* (input) {
2028
2192
  const acquired = yield* acquireDirectory(input, OpContext.make("withDirectory"));
2029
- return createCaller(acquired, identity, umask);
2193
+ return createCaller(acquired, identity, umask, acquired.namespaceRoots ?? roots, acquired.boundaries ?? []);
2030
2194
  }),
2031
- openDirectory: Effect.fn("Caller.openDirectory")(function* (input) {
2195
+ openDirectory: Effect.fnUntraced(function* (input) {
2032
2196
  const acquired = yield* acquireDirectory(input, OpContext.make("openDirectory"));
2033
2197
  const statOp = OpContext.make("stat");
2034
2198
  const handle = Object.freeze({
@@ -2036,16 +2200,18 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2036
2200
  stat: coordinatedRead(statOp, Effect.gen(function* () {
2037
2201
  const ino = TokenRegistry.inode(acquired);
2038
2202
  const node = ino === undefined ? undefined : yield* view(ino);
2203
+ if (node !== undefined && acquired.authority !== undefined)
2204
+ yield* acquired.authority(node, statOp);
2039
2205
  return yield* (node === undefined
2040
2206
  ? Effect.fail(statOp.fail("InvalidHandle"))
2041
2207
  : Effect.succeed(withMetadata(node)));
2042
- })).pipe(Effect.withSpan("DirectoryHandle.stat")),
2208
+ })),
2043
2209
  close: coordinatedCleanup(Effect.suspend(() => {
2044
2210
  if (TokenRegistry.inode(acquired) === undefined) {
2045
2211
  return Effect.fail(OpContext.make("close").fail("InvalidHandle"));
2046
2212
  }
2047
2213
  return releaseDirectory(acquired);
2048
- })).pipe(Effect.ensuring(closeReleasedScope(acquired)), Effect.withSpan("DirectoryHandle.close"))
2214
+ })).pipe(Effect.ensuring(closeReleasedScope(acquired)))
2049
2215
  });
2050
2216
  registry.registerDirectory(handle, acquired);
2051
2217
  return handle;
@@ -2059,9 +2225,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2059
2225
  identity,
2060
2226
  incarnation,
2061
2227
  limits,
2062
- usage: coordinatedRead(OpContext.make("usage"), Effect.sync(() => ({ usedBytes: state.usedBytes, entries: state.entries })))
2063
- .pipe(Effect.withSpan("Volume.usage")),
2064
- watch: Effect.fn("Volume.watch")(function* (options) {
2228
+ usage: coordinatedRead(OpContext.make("usage"), Effect.sync(() => ({ usedBytes: state.usedBytes, entries: state.entries }))),
2229
+ watch: Effect.fnUntraced(function* (options) {
2065
2230
  const op = OpContext.make("watch");
2066
2231
  const decoded = yield* Effect.fromResult(decodeConfiguration(WatchOptions, options === undefined ? {} : options, "watch"));
2067
2232
  const recursive = decoded.recursive ?? true;
@@ -2075,8 +2240,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2075
2240
  const stream = yield* admit(op, watchHub.subscribe(select, seams.afterSubscribe));
2076
2241
  return Stream.map(stream, (event) => event.change);
2077
2242
  }),
2078
- snapshot: coordinatedRead(OpContext.make("snapshot"), Effect.sync(() => Image.make(state))).pipe(Effect.withSpan("Volume.snapshot")),
2079
- referenceKey: Effect.fn("Volume.referenceKey")(function* (reference) {
2243
+ snapshot: coordinatedRead(OpContext.make("snapshot"), Effect.sync(() => Image.make(state))),
2244
+ referenceKey: Effect.fnUntraced(function* (reference) {
2080
2245
  const op = OpContext.make("referenceKey");
2081
2246
  return yield* coordinatedRead(op, Effect.gen(function* () {
2082
2247
  const node = yield* TokenRegistry.nodeOrFail(yield* registry.resolve(reference, "reference"), "reference", op);
@@ -2084,7 +2249,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2084
2249
  return { identity: identityBytes.slice(), epoch: epochBytes.slice(), ino, tag: keyTag(ino) };
2085
2250
  }));
2086
2251
  }),
2087
- resolveReferenceKey: Effect.fn("Volume.resolveReferenceKey")(function* (key) {
2252
+ resolveReferenceKey: Effect.fnUntraced(function* (key) {
2088
2253
  const op = OpContext.make("resolveReferenceKey");
2089
2254
  return yield* coordinatedRead(op, Effect.gen(function* () {
2090
2255
  if (!Schema.is(ReferenceKey)(key))
@@ -2101,7 +2266,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2101
2266
  return referenceFor(ino);
2102
2267
  }));
2103
2268
  }),
2104
- caller: Effect.fn("Volume.caller")(function* (options) {
2269
+ caller: Effect.fnUntraced(function* (options) {
2105
2270
  const decoded = yield* Effect.fromResult(decodeConfiguration(RootCallerOptions, options === undefined ? {} : options, "caller"));
2106
2271
  const chosen = decoded.identity ?? { uid: 0, gid: 0, groups: [], privileged: true };
2107
2272
  const identity = Object.freeze({ ...chosen, groups: Object.freeze([...chosen.groups]) });
@@ -2115,7 +2280,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
2115
2280
  return Object.freeze({ volume, shutdown, observe });
2116
2281
  });
2117
2282
  /** @internal */
2118
- export const make = Effect.fn("VirtualFileSystem.make")(function* (options) {
2283
+ export const make = Effect.fnUntraced(function* (options) {
2119
2284
  return (yield* makeVolume(VolumeSource.Empty(), memoryCommitProvider, options).pipe(Effect.catchIf((error) => Schema.is(VfsError)(error) && error.code !== "InvalidArgument", Effect.die))).volume;
2120
2285
  });
2121
2286
  /** @internal */
@@ -2154,22 +2319,22 @@ const changeOptions = (options) => {
2154
2319
  return Effect.fromResult(decodeConfiguration(OverlayChangesOptions, options === undefined ? {} : options, "changes"));
2155
2320
  };
2156
2321
  /** @internal */
2157
- export const fromSnapshot = Effect.fn("VirtualFileSystem.fromSnapshot")(function* (snapshot, options) {
2322
+ export const fromSnapshot = Effect.fnUntraced(function* (snapshot, options) {
2158
2323
  return (yield* makeVolume(VolumeSource.Restored({ value: yield* Image.valueOf(snapshot) }), memoryCommitProvider, options)).volume;
2159
2324
  }, Effect.mapError((error) => retargetFailure("fromSnapshot", error)));
2160
2325
  /** @internal */
2161
- export const makeOverlay = Effect.fn("VirtualFileSystem.makeOverlay")(function* (base, options) {
2326
+ export const makeOverlay = Effect.fnUntraced(function* (base, options) {
2162
2327
  const value = yield* Image.valueOf(base);
2163
2328
  const made = yield* Effect.mapError(makeVolume(VolumeSource.Restored({ value }), memoryCommitProvider, options), (error) => retargetFailure("makeOverlay", error));
2164
2329
  const baseObservation = yield* observeChanges(value);
2165
2330
  const overlay = Object.freeze({
2166
2331
  ...made.volume,
2167
- changes: Effect.fn("OverlayVolume.changes")(function* (options) {
2332
+ changes: Effect.fnUntraced(function* (options) {
2168
2333
  const selected = yield* changeOptions(options);
2169
2334
  const current = yield* made.observe.changes;
2170
2335
  return publicChanges(compareOverlay(baseObservation, current, selected.includeTimestamps ?? false));
2171
2336
  }),
2172
- capture: Effect.fn("OverlayVolume.capture")(function* (options) {
2337
+ capture: Effect.fnUntraced(function* (options) {
2173
2338
  const selected = yield* changeOptions(options);
2174
2339
  const current = yield* made.observe.capture;
2175
2340
  return Object.freeze({