superbee 0.2.1 → 0.3.0-pre.2

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.
@@ -30917,21 +30917,28 @@ function normalizeFrontmatter(data, okfVersion) {
30917
30917
  function parseMarkdown(raw, context, options = {}) {
30918
30918
  const split = splitLeadingFrontmatter(raw, context);
30919
30919
  if (!("yamlSource" in split)) return { frontmatter: {}, body: split.body };
30920
+ return { frontmatter: parseFrontmatter(split.yamlSource, context, options), body: split.body };
30921
+ }
30922
+ function parseFrontmatter(source, context, options = {}) {
30920
30923
  let parsed;
30921
30924
  try {
30922
- parsed = yamlEngine.parse(split.yamlSource);
30925
+ parsed = yamlEngine.parse(source);
30923
30926
  } catch (err) {
30924
30927
  throw new MalformedDocumentError(context, err);
30925
30928
  }
30926
- const frontmatter2 = normalizeFrontmatter(parsed, options.okfVersion);
30927
- return { frontmatter: frontmatter2, body: split.body };
30929
+ return normalizeFrontmatter(parsed, options.okfVersion);
30930
+ }
30931
+ function stringifyFrontmatter(data) {
30932
+ return import_js_yaml.default.safeDump(data).trim();
30928
30933
  }
30929
30934
  function normalizeDocumentBodyForStorage(body) {
30930
30935
  return body.endsWith("\n") ? body : `${body}
30931
30936
  `;
30932
30937
  }
30933
30938
  function stringifyWithData(data, body) {
30934
- const dumped = import_js_yaml.default.safeDump(data).trim();
30939
+ return stringifyWithSerializedFrontmatter(stringifyFrontmatter(data), body);
30940
+ }
30941
+ function stringifyWithSerializedFrontmatter(dumped, body) {
30935
30942
  const content3 = body ?? "";
30936
30943
  const newline = (value) => value.endsWith("\n") ? value : `${value}
30937
30944
  `;
@@ -30992,6 +30999,29 @@ function mutationActorFromFrontmatter(frontmatter2) {
30992
30999
 
30993
31000
  // ../core/src/paths.ts
30994
31001
  var RESERVED_FILENAMES = ["index.md", "log.md"];
31002
+ function compareStorageKeys(a, b) {
31003
+ const primary = a.localeCompare(b);
31004
+ if (primary !== 0 || a === b) return primary;
31005
+ const left = Array.from(a, (character) => character.codePointAt(0));
31006
+ const right = Array.from(b, (character) => character.codePointAt(0));
31007
+ for (let i = 0; i < Math.min(left.length, right.length); i++) {
31008
+ if (left[i] !== right[i]) return left[i] - right[i];
31009
+ }
31010
+ return left.length - right.length;
31011
+ }
31012
+ function assertPortablePath(value, label) {
31013
+ if (/[\u0000-\u001f\u007f]/u.test(value) || /^[a-z]:/i.test(value)) {
31014
+ throw new InvalidInputError(`${label} must not contain controls or start with a drive prefix.`);
31015
+ }
31016
+ if (value.includes("\\") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
31017
+ throw new InvalidInputError(`${label} must be canonical and bundle-relative.`);
31018
+ }
31019
+ }
31020
+ function assertSafeReservedFilename(name) {
31021
+ if (!RESERVED_FILENAMES.includes(name)) {
31022
+ throw new InvalidInputError("Reserved filename must be index.md or log.md.");
31023
+ }
31024
+ }
30995
31025
  function toPosix(p) {
30996
31026
  return p.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
30997
31027
  }
@@ -31011,21 +31041,8 @@ function assertSafeConceptId(id) {
31011
31041
  if (typeof id !== "string" || id.trim() === "") {
31012
31042
  throw new InvalidInputError("Concept id must be a non-empty string.");
31013
31043
  }
31014
- if (id.startsWith("/")) {
31015
- throw new InvalidInputError(`Concept id must be bundle-relative, got absolute '${id}'.`);
31016
- }
31017
- if (id.includes("\\")) {
31018
- throw new InvalidInputError(`Concept id must use forward slashes: '${id}'.`);
31019
- }
31044
+ assertPortablePath(id, "Concept id");
31020
31045
  const segments = id.split("/");
31021
- if (segments.some((seg) => seg === "..")) {
31022
- throw new InvalidInputError(`Concept id must not contain '..' segments: '${id}'.`);
31023
- }
31024
- if (segments.some((seg) => seg === "." || seg === "")) {
31025
- throw new InvalidInputError(
31026
- `Concept id must be canonical (no '.', duplicate-slash, leading './', or trailing-slash segments): '${id}'.`
31027
- );
31028
- }
31029
31046
  if (segments.slice(0, -1).some((seg) => seg.toLowerCase().endsWith(".md"))) {
31030
31047
  throw new InvalidInputError(
31031
31048
  `Concept id must not contain a non-final segment ending in '.md': '${id}' (it collides with a concept file at that path).`
@@ -31036,21 +31053,11 @@ function assertSafeBlobKey(key) {
31036
31053
  if (typeof key !== "string" || key.trim() === "") {
31037
31054
  throw new InvalidInputError("Blob key must be a non-empty string.");
31038
31055
  }
31039
- const norm = toPosix(key);
31040
- if (norm.startsWith("/")) {
31041
- throw new InvalidInputError(`Blob key must be bundle-relative, got absolute '${key}'.`);
31042
- }
31043
- const segments = norm.split("/");
31044
- if (segments.some((seg) => seg === "..")) {
31045
- throw new InvalidInputError(`Blob key must not contain '..' segments: '${key}'.`);
31046
- }
31056
+ assertPortablePath(key, "Blob key");
31057
+ const segments = key.split("/");
31047
31058
  if (segments.some((seg) => seg.startsWith("."))) {
31048
31059
  throw new InvalidInputError(`Blob key must not contain dot-prefixed segments: '${key}'.`);
31049
31060
  }
31050
- const last = segments[segments.length - 1] ?? "";
31051
- if (last === "") {
31052
- throw new InvalidInputError(`Blob key must name a file, not end with '/': '${key}'.`);
31053
- }
31054
31061
  if (segments.some((seg) => seg.toLowerCase().endsWith(".md"))) {
31055
31062
  throw new InvalidInputError(
31056
31063
  `Blob key '${key}' has a path segment ending in '.md' (checked case-insensitively, at any depth), which collides with the concept-document namespace \u2014 write it as a doc instead.`
@@ -31061,13 +31068,7 @@ function assertSafeReservedDir(dir) {
31061
31068
  if (typeof dir !== "string") {
31062
31069
  throw new InvalidInputError("Reserved-file directory must be a string.");
31063
31070
  }
31064
- const norm = toPosix(dir);
31065
- if (norm.startsWith("/")) {
31066
- throw new InvalidInputError(`Reserved-file directory must be bundle-relative, got absolute '${dir}'.`);
31067
- }
31068
- if (norm.split("/").some((seg) => seg === "..")) {
31069
- throw new InvalidInputError(`Reserved-file directory must not contain '..' segments: '${dir}'.`);
31070
- }
31071
+ if (dir !== "") assertPortablePath(dir, "Reserved-file directory");
31071
31072
  }
31072
31073
 
31073
31074
  // ../core/src/links.ts
@@ -31372,7 +31373,52 @@ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypt
31372
31373
 
31373
31374
  // ../core/src/filesystem-lock.ts
31374
31375
  import { promises as fs, realpathSync } from "node:fs";
31375
- import { homedir, hostname, userInfo } from "node:os";
31376
+ import { homedir, hostname } from "node:os";
31377
+
31378
+ // ../core/src/host-policy-snapshot.ts
31379
+ function snapshotHostPolicy(input, seen = /* @__PURE__ */ new WeakMap()) {
31380
+ const existing = seen.get(input);
31381
+ if (existing) return existing;
31382
+ const result = Array.isArray(input) ? [] : {};
31383
+ seen.set(input, result);
31384
+ const keys = /* @__PURE__ */ new Set();
31385
+ for (let owner = input; owner && owner !== Object.prototype && owner !== Array.prototype; owner = Object.getPrototypeOf(owner)) {
31386
+ for (const key of Reflect.ownKeys(owner)) {
31387
+ if (key !== "constructor" && !(Array.isArray(input) && key === "length")) keys.add(key);
31388
+ }
31389
+ }
31390
+ for (const key of keys) {
31391
+ const value = input[key];
31392
+ Object.defineProperty(result, key, {
31393
+ value: typeof value === "function" ? value.bind(result) : value && typeof value === "object" ? snapshotHostPolicy(value, seen) : value,
31394
+ enumerable: true
31395
+ });
31396
+ }
31397
+ if (Array.isArray(input)) result.length = input.length;
31398
+ return Object.freeze(result);
31399
+ }
31400
+
31401
+ // ../core/src/filesystem-host.ts
31402
+ function defaultFilesystemHostPolicy() {
31403
+ if (process.platform !== "darwin" && process.platform !== "linux") {
31404
+ throw new InvalidInputError(
31405
+ "This host requires an explicit filesystem host policy; the default filesystem supports macOS and Linux."
31406
+ );
31407
+ }
31408
+ return {
31409
+ runtimeLockParent: () => "/tmp",
31410
+ runtimeOwnerKey: () => `uid-${process.getuid()}`,
31411
+ enforcePrivateMode: true,
31412
+ isTransientOpenError: () => false,
31413
+ isReplacementConflict: () => false,
31414
+ isDirectoryContentionError: () => false
31415
+ };
31416
+ }
31417
+ function captureFilesystemHostPolicy(policy) {
31418
+ return snapshotHostPolicy(policy ?? defaultFilesystemHostPolicy());
31419
+ }
31420
+
31421
+ // ../core/src/filesystem-lock.ts
31376
31422
  import path from "node:path";
31377
31423
  import { createHash, randomUUID } from "node:crypto";
31378
31424
  var OWNER_FILE = "owner.json";
@@ -31392,16 +31438,6 @@ var FilesystemMutationLockError = class extends Error {
31392
31438
  this.malformed = details.malformed;
31393
31439
  }
31394
31440
  };
31395
- function runtimeOwnerKey() {
31396
- const uid = process.getuid?.();
31397
- if (uid !== void 0) return `uid-${uid}`;
31398
- let username = "unknown";
31399
- try {
31400
- username = userInfo().username;
31401
- } catch {
31402
- }
31403
- return `user-${createHash("sha256").update(username).digest("hex").slice(0, 16)}`;
31404
- }
31405
31441
  function canonicalExistingPath(value) {
31406
31442
  try {
31407
31443
  return realpathSync(value);
@@ -31413,11 +31449,12 @@ function pathContains(root, candidate) {
31413
31449
  const rel = path.relative(root, candidate);
31414
31450
  return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
31415
31451
  }
31416
- function filesystemMutationLockRoot(portableRoot) {
31417
- const runtimeParent = process.platform === "win32" ? path.join(homedir(), "AppData", "Local") : "/tmp";
31452
+ function filesystemMutationLockRoot(portableRoot, hostPolicy) {
31453
+ const policy = captureFilesystemHostPolicy(hostPolicy);
31454
+ const runtimeParent = policy.runtimeLockParent();
31418
31455
  const tempParent = canonicalExistingPath(runtimeParent);
31419
31456
  const homeParent = canonicalExistingPath(homedir());
31420
- const ownerKey = runtimeOwnerKey();
31457
+ const ownerKey = policy.runtimeOwnerKey();
31421
31458
  const candidates = [
31422
31459
  path.join(tempParent, `agentstate-lite-mutation-locks-${ownerKey}`),
31423
31460
  path.join(homeParent, ".agentstate", `mutation-locks-${ownerKey}`)
@@ -31457,14 +31494,32 @@ function parseFilesystemMutationLockOwner(value) {
31457
31494
  target: value.target
31458
31495
  };
31459
31496
  }
31460
- async function readOwner(lockPath) {
31497
+ async function readOwnerRecord(lockPath) {
31498
+ let raw;
31461
31499
  try {
31462
- return parseFilesystemMutationLockOwner(
31463
- JSON.parse(await fs.readFile(path.join(lockPath, OWNER_FILE), "utf8"))
31464
- );
31500
+ raw = await fs.readFile(path.join(lockPath, OWNER_FILE), "utf8");
31501
+ } catch (err) {
31502
+ const code2 = err.code;
31503
+ return code2 === "ENOENT" || code2 === "ENOTDIR" ? { state: "absent" } : { state: "unreadable", error: err };
31504
+ }
31505
+ let owner = null;
31506
+ try {
31507
+ owner = parseFilesystemMutationLockOwner(JSON.parse(raw));
31465
31508
  } catch {
31466
- return null;
31509
+ owner = null;
31467
31510
  }
31511
+ return owner === null ? { state: "absent" } : { state: "record", owner };
31512
+ }
31513
+ async function resolveOwnerRecord(lockPath, started, waitMs, pollMs) {
31514
+ while (true) {
31515
+ const record = await readOwnerRecord(lockPath);
31516
+ if (record.state !== "unreadable" || Date.now() - started >= waitMs) return record;
31517
+ await delay(pollMs);
31518
+ }
31519
+ }
31520
+ async function readOwner(lockPath) {
31521
+ const record = await readOwnerRecord(lockPath);
31522
+ return record.state === "record" ? record.owner : null;
31468
31523
  }
31469
31524
  function processExists(pid) {
31470
31525
  try {
@@ -31487,7 +31542,7 @@ async function pathExists(candidate) {
31487
31542
  throw err;
31488
31543
  }
31489
31544
  }
31490
- async function quarantineStaleLock(lockPath, owner) {
31545
+ async function quarantineStaleLock(lockPath, owner, policy) {
31491
31546
  if (owner.hostname !== hostname() || processExists(owner.pid)) return false;
31492
31547
  const quarantinePath = staleLockQuarantinePath(lockPath, owner);
31493
31548
  try {
@@ -31497,7 +31552,7 @@ async function quarantineStaleLock(lockPath, owner) {
31497
31552
  const code2 = err.code;
31498
31553
  if (code2 === "ENOENT") return false;
31499
31554
  if (await pathExists(quarantinePath)) return false;
31500
- if (process.platform === "win32" && WINDOWS_DIRECTORY_CONTENTION_CODES.has(code2 ?? "")) return false;
31555
+ if (policy.isDirectoryContentionError(err)) return false;
31501
31556
  throw err;
31502
31557
  }
31503
31558
  }
@@ -31516,7 +31571,7 @@ function isPrivateFilesystemMutationLockRoot(facts) {
31516
31571
  const unsafeMode = facts.enforcePrivateMode && (facts.mode & 511) !== 448;
31517
31572
  return facts.directory && !facts.symbolicLink && !wrongOwner && !unsafeMode;
31518
31573
  }
31519
- async function ensurePrivateLockRoot(root) {
31574
+ async function ensurePrivateLockRoot(root, policy) {
31520
31575
  try {
31521
31576
  await fs.mkdir(root, { recursive: true, mode: 448 });
31522
31577
  } catch (err) {
@@ -31530,7 +31585,7 @@ async function ensurePrivateLockRoot(root) {
31530
31585
  ownerUid: stat2.uid,
31531
31586
  expectedUid: uid,
31532
31587
  mode: stat2.mode,
31533
- enforcePrivateMode: process.platform !== "win32"
31588
+ enforcePrivateMode: policy.enforcePrivateMode
31534
31589
  })) {
31535
31590
  throw new FilesystemMutationLockError(
31536
31591
  `refusing unsafe filesystem mutation lock root '${root}'; it must be a private directory owned by this user`,
@@ -31557,50 +31612,50 @@ async function resolvedPortableRoot(portableRoot) {
31557
31612
  if (portableRoot === void 0) return void 0;
31558
31613
  return fs.realpath(portableRoot).catch(() => path.resolve(portableRoot));
31559
31614
  }
31560
- async function selectLockRoot(options) {
31615
+ async function selectLockRoot(options, policy) {
31561
31616
  const portableRoot = await resolvedPortableRoot(options.portableRoot);
31562
- const lockRoot = options.lockRoot !== void 0 ? explicitFilesystemMutationLockRoot(options.lockRoot, portableRoot) : filesystemMutationLockRoot(portableRoot);
31563
- await ensurePrivateLockRoot(lockRoot);
31617
+ const lockRoot = options.lockRoot !== void 0 ? explicitFilesystemMutationLockRoot(options.lockRoot, portableRoot) : filesystemMutationLockRoot(portableRoot, policy);
31618
+ await ensurePrivateLockRoot(lockRoot, policy);
31564
31619
  return lockRoot;
31565
31620
  }
31566
- var WINDOWS_DIRECTORY_CONTENTION_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
31567
- async function classifyLockClaimFailure(error, lockPath) {
31621
+ async function classifyLockClaimFailure(error, lockPath, policy) {
31568
31622
  const code2 = error.code;
31569
31623
  if (code2 === "EEXIST") return "contention";
31570
- if (process.platform !== "win32" || !WINDOWS_DIRECTORY_CONTENTION_CODES.has(code2 ?? "")) return "terminal";
31624
+ if (!policy.isDirectoryContentionError(error)) return "terminal";
31571
31625
  try {
31572
31626
  await fs.lstat(lockPath);
31573
31627
  return "contention";
31574
31628
  } catch (probeError) {
31575
31629
  const probeCode = probeError.code;
31576
- return probeCode === "ENOENT" || probeCode === "ENOTDIR" ? "unwitnessed-windows-sharing-error" : "terminal";
31630
+ return probeCode === "ENOENT" || probeCode === "ENOTDIR" ? "unwitnessed-contention-error" : "terminal";
31577
31631
  }
31578
31632
  }
31579
- async function claimLockPath(lockPath, owner, waitMs, pollMs) {
31633
+ async function claimLockPath(lockPath, owner, waitMs, pollMs, policy) {
31580
31634
  const started = owner.created_at_ms;
31581
- let unwitnessedWindowsRetryUsed = false;
31635
+ let unwitnessedRetryUsed = false;
31582
31636
  while (true) {
31583
31637
  try {
31584
31638
  await fs.mkdir(lockPath, { mode: 448 });
31585
31639
  } catch (err) {
31586
- const failure = await classifyLockClaimFailure(err, lockPath);
31640
+ const failure = await classifyLockClaimFailure(err, lockPath, policy);
31587
31641
  if (failure === "terminal") throw err;
31588
- if (failure === "unwitnessed-windows-sharing-error") {
31589
- if (unwitnessedWindowsRetryUsed) throw err;
31590
- unwitnessedWindowsRetryUsed = true;
31642
+ if (failure === "unwitnessed-contention-error") {
31643
+ if (unwitnessedRetryUsed) throw err;
31644
+ unwitnessedRetryUsed = true;
31591
31645
  continue;
31592
31646
  } else {
31593
- unwitnessedWindowsRetryUsed = false;
31647
+ unwitnessedRetryUsed = false;
31594
31648
  }
31595
31649
  let existingOwner = await readOwner(lockPath);
31596
31650
  if (existingOwner !== null) {
31597
- if (await quarantineStaleLock(lockPath, existingOwner)) continue;
31651
+ if (await quarantineStaleLock(lockPath, existingOwner, policy)) continue;
31598
31652
  existingOwner = await readOwner(lockPath);
31599
31653
  }
31600
31654
  if (Date.now() - started >= waitMs) throw timeoutError(lockPath, existingOwner, owner.target);
31601
31655
  await delay(pollMs);
31602
31656
  continue;
31603
31657
  }
31658
+ const claimed = await fs.lstat(lockPath, { bigint: true }).catch(() => null);
31604
31659
  try {
31605
31660
  await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
31606
31661
  `, {
@@ -31609,28 +31664,113 @@ async function claimLockPath(lockPath, owner, waitMs, pollMs) {
31609
31664
  mode: 384
31610
31665
  });
31611
31666
  } catch (err) {
31612
- await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
31667
+ const code2 = err.code;
31668
+ if (code2 === "EEXIST") continue;
31669
+ await rollBackOwnClaim(lockPath, owner, claimed, waitMs, pollMs, policy).catch(() => {
31613
31670
  });
31671
+ if (code2 === "ENOENT" || code2 === "ENOTDIR") continue;
31614
31672
  throw err;
31615
31673
  }
31616
- return async () => {
31617
- const current = await readOwner(lockPath);
31618
- if (current?.token !== owner.token) {
31619
- throw new FilesystemMutationLockError(
31620
- `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
31621
- { lockPath, owner: current, stale: false, malformed: current === null }
31622
- );
31674
+ let inFlight;
31675
+ return () => {
31676
+ if (inFlight) return inFlight;
31677
+ inFlight = (async () => {
31678
+ const started2 = Date.now();
31679
+ const current = await resolveOwnerRecord(lockPath, started2, waitMs, pollMs);
31680
+ if (current.state === "unreadable") throw unreadableOwnerRefusal(lockPath, current.error);
31681
+ if (current.state !== "record" || current.owner.token !== owner.token) {
31682
+ throw changedOwnerRefusal(lockPath, current.state === "record" ? current.owner : null);
31683
+ }
31684
+ await removeReleasedLock(lockPath, owner, started2, waitMs, pollMs, policy);
31685
+ })();
31686
+ return inFlight.finally(() => {
31687
+ inFlight = void 0;
31688
+ });
31689
+ };
31690
+ }
31691
+ }
31692
+ async function rollBackOwnClaim(lockPath, owner, claimed, waitMs, pollMs, policy) {
31693
+ const started = Date.now();
31694
+ const record = await resolveOwnerRecord(lockPath, started, waitMs, pollMs);
31695
+ if (record.state === "record") {
31696
+ if (record.owner.token === owner.token) await removeReleasedLock(lockPath, owner, started, waitMs, pollMs, policy);
31697
+ return;
31698
+ }
31699
+ if (record.state === "unreadable" || !await isClaimedDirectory(lockPath, claimed)) return;
31700
+ const remnant = releasedLockRemnantPath(lockPath, owner);
31701
+ await fs.rename(lockPath, remnant);
31702
+ await fs.rm(remnant, { recursive: true, force: true });
31703
+ }
31704
+ async function isClaimedDirectory(lockPath, claimed) {
31705
+ if (claimed === null || claimed.birthtimeNs <= 0n) return false;
31706
+ const current = await fs.lstat(lockPath, { bigint: true }).catch(() => null);
31707
+ return current !== null && current.isDirectory() && current.dev === claimed.dev && current.ino === claimed.ino && current.birthtimeNs === claimed.birthtimeNs;
31708
+ }
31709
+ function changedOwnerRefusal(lockPath, current) {
31710
+ return new FilesystemMutationLockError(
31711
+ `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
31712
+ { lockPath, owner: current, stale: false, malformed: current === null }
31713
+ );
31714
+ }
31715
+ function unreadableOwnerRefusal(lockPath, error) {
31716
+ const message = error instanceof Error ? error.message : String(error);
31717
+ return new FilesystemMutationLockError(
31718
+ `refusing to release filesystem mutation lock '${lockPath}' because its owner record could not be read within the wait budget (${message}); the mutation may have completed, inspect the lock before retrying.`,
31719
+ { lockPath, owner: null, stale: false, malformed: true }
31720
+ );
31721
+ }
31722
+ function releasedLockRemnantPath(lockPath, owner) {
31723
+ const tokenHash = createHash("sha256").update(owner.token).digest("hex");
31724
+ return `${lockPath}.released-${tokenHash}`;
31725
+ }
31726
+ function removalFailure(inspectPath, owner, err, attempts, detail) {
31727
+ const message = err instanceof Error ? err.message : String(err);
31728
+ const suffix = attempts > 1 ? ` after ${attempts} bounded attempts` : "";
31729
+ return new FilesystemMutationLockError(
31730
+ `mutation completed but ${detail}${suffix} (${message}); inspect the lock before retrying.`,
31731
+ { lockPath: inspectPath, owner, stale: false, malformed: false }
31732
+ );
31733
+ }
31734
+ async function removeReleasedLock(lockPath, owner, started, waitMs, pollMs, policy) {
31735
+ const remnant = releasedLockRemnantPath(lockPath, owner);
31736
+ let attempts = 0;
31737
+ while (true) {
31738
+ try {
31739
+ await fs.rename(lockPath, remnant);
31740
+ break;
31741
+ } catch (err) {
31742
+ attempts += 1;
31743
+ if (attempts > 1 && err.code === "ENOENT") return;
31744
+ if (!policy.isDirectoryContentionError(err) || Date.now() - started >= waitMs) {
31745
+ throw removalFailure(lockPath, owner, err, attempts, `filesystem lock '${lockPath}' could not be removed`);
31623
31746
  }
31624
- try {
31625
- await fs.rm(lockPath, { recursive: true, force: false });
31626
- } catch (err) {
31627
- const message = err instanceof Error ? err.message : String(err);
31628
- throw new FilesystemMutationLockError(
31629
- `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
31630
- { lockPath, owner: current, stale: false, malformed: false }
31747
+ }
31748
+ await delay(pollMs);
31749
+ const current = await resolveOwnerRecord(lockPath, started, waitMs, pollMs);
31750
+ if (current.state === "record" && current.owner.token === owner.token) continue;
31751
+ if (current.state === "unreadable") throw unreadableOwnerRefusal(lockPath, current.error);
31752
+ if (current.state === "absent" && !await pathExists(lockPath)) return;
31753
+ throw changedOwnerRefusal(lockPath, current.state === "record" ? current.owner : null);
31754
+ }
31755
+ attempts = 0;
31756
+ while (true) {
31757
+ try {
31758
+ await fs.rm(remnant, { recursive: true, force: false });
31759
+ return;
31760
+ } catch (err) {
31761
+ attempts += 1;
31762
+ if (err.code === "ENOENT") return;
31763
+ if (!policy.isDirectoryContentionError(err) || Date.now() - started >= waitMs) {
31764
+ throw removalFailure(
31765
+ remnant,
31766
+ owner,
31767
+ err,
31768
+ attempts,
31769
+ `filesystem lock '${lockPath}' was released yet its remnant '${remnant}' could not be removed`
31631
31770
  );
31632
31771
  }
31633
- };
31772
+ }
31773
+ await delay(pollMs);
31634
31774
  }
31635
31775
  }
31636
31776
  function newOwner(target) {
@@ -31648,21 +31788,20 @@ function assertIdentityKey(key) {
31648
31788
  }
31649
31789
  async function acquireFilesystemIdentityLock(key, identity, options = {}) {
31650
31790
  assertIdentityKey(key);
31791
+ const policy = captureFilesystemHostPolicy(options.hostPolicy);
31651
31792
  const waitMs = positiveOption(options.waitMs, DEFAULT_WAIT_MS, "waitMs");
31652
31793
  const pollMs = positiveOption(options.pollMs, DEFAULT_POLL_MS, "pollMs");
31653
31794
  const owner = newOwner(identity);
31654
- const lockRoot = await selectLockRoot(options);
31655
- return claimLockPath(path.join(lockRoot, `${key}.lock`), owner, waitMs, pollMs);
31795
+ const lockRoot = await selectLockRoot(options, policy);
31796
+ return claimLockPath(path.join(lockRoot, `${key}.lock`), owner, waitMs, pollMs, policy);
31656
31797
  }
31657
31798
 
31658
31799
  // ../core/src/filesystem-identity.ts
31659
- var WINDOWS_REPLACE_CONFLICT_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
31660
31800
  async function replaceExistingLeaf(port, tmp, target, rel) {
31661
31801
  try {
31662
31802
  await port.rename(tmp, target);
31663
31803
  } catch (err) {
31664
- const code2 = err.code ?? "";
31665
- if ((port.platform ?? process.platform) === "win32" && WINDOWS_REPLACE_CONFLICT_CODES.has(code2)) {
31804
+ if (port.isReplacementConflict?.(err)) {
31666
31805
  throw new ConcurrentReplacementError(rel, 1, 10);
31667
31806
  }
31668
31807
  throw err;
@@ -31829,7 +31968,7 @@ async function observeExact(port, root, rel, read) {
31829
31968
  opened = await port.open(target);
31830
31969
  } catch (err) {
31831
31970
  if (isAbsentPathError(err)) return { state: "absent" };
31832
- if (process.platform === "win32" && err.code === "EPERM") {
31971
+ if (port.isTransientOpenError?.(err)) {
31833
31972
  restarts = countRestart(restarts, rel);
31834
31973
  continue;
31835
31974
  }
@@ -31863,7 +32002,15 @@ async function probeExact(port, root, rel) {
31863
32002
  restarts = countRestart(restarts, rel);
31864
32003
  }
31865
32004
  }
31866
- var linkUnsupportedRoots = /* @__PURE__ */ new Set();
32005
+ var linkUnsupportedRoots = /* @__PURE__ */ new WeakMap();
32006
+ function unsupportedLinkRoots(port) {
32007
+ let roots = linkUnsupportedRoots.get(port);
32008
+ if (!roots) {
32009
+ roots = /* @__PURE__ */ new Set();
32010
+ linkUnsupportedRoots.set(port, roots);
32011
+ }
32012
+ return roots;
32013
+ }
31867
32014
  var queues = /* @__PURE__ */ new Map();
31868
32015
  function enqueue(key, fn) {
31869
32016
  const tail = queues.get(key) ?? Promise.resolve();
@@ -31945,7 +32092,7 @@ async function createExactLeaf(port, tmp, target, rel, leaf, rootResolved) {
31945
32092
  return;
31946
32093
  }
31947
32094
  if (outcome === "unsupported") {
31948
- linkUnsupportedRoots.add(rootResolved);
32095
+ unsupportedLinkRoots(port).add(rootResolved);
31949
32096
  await port.rename(tmp, target);
31950
32097
  return;
31951
32098
  }
@@ -31981,7 +32128,7 @@ async function mutateExact(port, root, rel, body) {
31981
32128
  await replaceExistingLeaf(port, tmp, target, rel);
31982
32129
  return;
31983
32130
  }
31984
- if (linkUnsupportedRoots.has(rootResolved)) {
32131
+ if (unsupportedLinkRoots(port).has(rootResolved)) {
31985
32132
  await port.rename(tmp, target);
31986
32133
  return;
31987
32134
  }
@@ -32008,80 +32155,84 @@ function kindOf(stats) {
32008
32155
  if (stats.isFile()) return "file";
32009
32156
  return "other";
32010
32157
  }
32011
- var nodeFilesystemIdentityPort = Object.freeze({
32012
- platform: process.platform,
32013
- async probe(target) {
32014
- let lstats;
32015
- try {
32016
- lstats = await fs2.lstat(target);
32017
- } catch (err) {
32018
- if (isAbsentPathError(err)) return null;
32019
- throw err;
32020
- }
32021
- return { kind: kindOf(lstats), dev: lstats.dev, ino: lstats.ino };
32022
- },
32023
- async entries(dir) {
32024
- try {
32025
- const dirents = await fs2.readdir(dir, { withFileTypes: true });
32026
- return dirents.map((dirent) => ({ name: dirent.name, kind: kindOf(dirent) }));
32027
- } catch (err) {
32028
- if (isAbsentPathError(err)) return null;
32029
- throw err;
32030
- }
32031
- },
32032
- async open(target) {
32033
- const handle = await fs2.open(target, "r");
32034
- try {
32035
- const stats = await handle.stat();
32036
- return { handle, dev: stats.dev, ino: stats.ino };
32037
- } catch (err) {
32038
- await handle.close().catch(() => {
32039
- });
32040
- throw err;
32041
- }
32042
- },
32043
- readAll(handle) {
32044
- return handle.readFile();
32045
- },
32046
- close(handle) {
32047
- return handle.close();
32048
- },
32049
- async stat(target) {
32050
- return { mtime: (await fs2.stat(target)).mtime };
32051
- },
32052
- async mkdir(dir) {
32053
- try {
32054
- await fs2.mkdir(dir);
32055
- return "created";
32056
- } catch (err) {
32057
- if (err.code === "EEXIST") return "exists";
32058
- throw err;
32059
- }
32060
- },
32061
- async writeTemp(dir, name, bytes) {
32062
- await fs2.writeFile(path2.join(dir, name), bytes);
32063
- },
32064
- async link(from, to) {
32065
- try {
32066
- await fs2.link(from, to);
32067
- return "linked";
32068
- } catch (err) {
32069
- const code2 = err.code;
32070
- if (code2 === "EEXIST") return "exists";
32071
- if (code2 === "EPERM" || code2 === "ENOTSUP" || code2 === "EOPNOTSUPP" || code2 === "EXDEV") return "unsupported";
32072
- throw err;
32158
+ function createNodeFilesystemIdentityPort(hostPolicy) {
32159
+ const policy = captureFilesystemHostPolicy(hostPolicy);
32160
+ return Object.freeze({
32161
+ isTransientOpenError: policy.isTransientOpenError,
32162
+ isReplacementConflict: policy.isReplacementConflict,
32163
+ async probe(target) {
32164
+ let lstats;
32165
+ try {
32166
+ lstats = await fs2.lstat(target);
32167
+ } catch (err) {
32168
+ if (isAbsentPathError(err)) return null;
32169
+ throw err;
32170
+ }
32171
+ return { kind: kindOf(lstats), dev: lstats.dev, ino: lstats.ino };
32172
+ },
32173
+ async entries(dir) {
32174
+ try {
32175
+ const dirents = await fs2.readdir(dir, { withFileTypes: true });
32176
+ return dirents.map((dirent) => ({ name: dirent.name, kind: kindOf(dirent) }));
32177
+ } catch (err) {
32178
+ if (isAbsentPathError(err)) return null;
32179
+ throw err;
32180
+ }
32181
+ },
32182
+ async open(target) {
32183
+ const handle = await fs2.open(target, "r");
32184
+ try {
32185
+ const stats = await handle.stat();
32186
+ return { handle, dev: stats.dev, ino: stats.ino };
32187
+ } catch (err) {
32188
+ await handle.close().catch(() => {
32189
+ });
32190
+ throw err;
32191
+ }
32192
+ },
32193
+ readAll(handle) {
32194
+ return handle.readFile();
32195
+ },
32196
+ close(handle) {
32197
+ return handle.close();
32198
+ },
32199
+ async stat(target) {
32200
+ return { mtime: (await fs2.stat(target)).mtime };
32201
+ },
32202
+ async mkdir(dir) {
32203
+ try {
32204
+ await fs2.mkdir(dir);
32205
+ return "created";
32206
+ } catch (err) {
32207
+ if (err.code === "EEXIST") return "exists";
32208
+ throw err;
32209
+ }
32210
+ },
32211
+ async writeTemp(dir, name, bytes) {
32212
+ await fs2.writeFile(path2.join(dir, name), bytes);
32213
+ },
32214
+ async link(from, to) {
32215
+ try {
32216
+ await fs2.link(from, to);
32217
+ return "linked";
32218
+ } catch (err) {
32219
+ const code2 = err.code;
32220
+ if (code2 === "EEXIST") return "exists";
32221
+ if (code2 === "EPERM" || code2 === "ENOTSUP" || code2 === "EOPNOTSUPP" || code2 === "EXDEV") return "unsupported";
32222
+ throw err;
32223
+ }
32224
+ },
32225
+ async rename(from, to) {
32226
+ await fs2.rename(from, to);
32227
+ },
32228
+ async unlink(target) {
32229
+ await fs2.unlink(target);
32230
+ },
32231
+ claim(key, identity) {
32232
+ return acquireFilesystemIdentityLock(key, `${identity.root}:${identity.rel}`, { portableRoot: identity.root, hostPolicy: policy });
32073
32233
  }
32074
- },
32075
- async rename(from, to) {
32076
- await fs2.rename(from, to);
32077
- },
32078
- async unlink(target) {
32079
- await fs2.unlink(target);
32080
- },
32081
- claim(key, identity) {
32082
- return acquireFilesystemIdentityLock(key, `${identity.root}:${identity.rel}`, { portableRoot: identity.root });
32083
- }
32084
- });
32234
+ });
32235
+ }
32085
32236
 
32086
32237
  // ../core/src/sha256.ts
32087
32238
  var K = new Uint32Array([
@@ -32240,6 +32391,9 @@ function sha256HexOfUtf8(input) {
32240
32391
  }
32241
32392
 
32242
32393
  // ../core/src/version-transport.ts
32394
+ function isContentVersion(value) {
32395
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
32396
+ }
32243
32397
  var VersionConflict = class extends Error {
32244
32398
  name = "VersionConflict";
32245
32399
  id;
@@ -32271,7 +32425,7 @@ function defaultActor() {
32271
32425
  }
32272
32426
 
32273
32427
  // ../core/src/backend.ts
32274
- var publicationRoots = /* @__PURE__ */ new WeakMap();
32428
+ var publicationContexts = /* @__PURE__ */ new WeakMap();
32275
32429
  function firstString(...vals) {
32276
32430
  for (const v of vals) {
32277
32431
  if (typeof v === "string" && v.trim() !== "") return v;
@@ -32290,22 +32444,22 @@ function notFound(root, rel) {
32290
32444
  err.path = target;
32291
32445
  return err;
32292
32446
  }
32293
- async function readBytes(handle) {
32447
+ async function readBytes(port, handle) {
32294
32448
  try {
32295
- return await nodeFilesystemIdentityPort.readAll(handle);
32449
+ return await port.readAll(handle);
32296
32450
  } catch (err) {
32297
32451
  if (isAbsentFileError(err)) return null;
32298
32452
  throw err;
32299
32453
  }
32300
32454
  }
32301
- async function walkFiles(root, keep, sub = "") {
32302
- const entries = await nodeFilesystemIdentityPort.entries(path3.join(root, sub)) ?? [];
32455
+ async function walkFiles(port, root, keep, sub = "") {
32456
+ const entries = await port.entries(path3.join(root, sub)) ?? [];
32303
32457
  const out = [];
32304
32458
  for (const entry of entries) {
32305
32459
  if (entry.name.startsWith(".")) continue;
32306
32460
  const rel = sub === "" ? entry.name : `${sub}/${entry.name}`;
32307
32461
  if (entry.kind === "directory") {
32308
- out.push(...await walkFiles(root, keep, rel));
32462
+ out.push(...await walkFiles(port, root, keep, rel));
32309
32463
  } else if (entry.kind === "file" && keep(entry.name)) {
32310
32464
  out.push(rel);
32311
32465
  }
@@ -32313,19 +32467,20 @@ async function walkFiles(root, keep, sub = "") {
32313
32467
  return out;
32314
32468
  }
32315
32469
  function reservedPath(dir, name) {
32316
- const d = toPosix(dir).replace(/^\.?\//, "").replace(/\/$/, "");
32317
- return d === "" ? name : `${d}/${name}`;
32470
+ return dir === "" ? name : `${dir}/${name}`;
32318
32471
  }
32319
32472
  var FilesystemBackend = class _FilesystemBackend {
32320
32473
  #root;
32474
+ #port;
32321
32475
  /**
32322
32476
  * The root is resolved once here: a relative root would otherwise re-resolve against the
32323
32477
  * process's current directory on every operation, so a later `chdir` would silently move the
32324
32478
  * bundle and derive a different identity key.
32325
32479
  */
32326
- constructor(root) {
32480
+ constructor(root, options = {}) {
32481
+ this.#port = createNodeFilesystemIdentityPort(options.hostPolicy);
32327
32482
  this.#root = path3.resolve(root);
32328
- publicationRoots.set(this, this.#root);
32483
+ publicationContexts.set(this, { root: this.#root, port: this.#port });
32329
32484
  }
32330
32485
  async read(id) {
32331
32486
  return this.#readWithEdition(id, () => this.#readEdition());
@@ -32341,7 +32496,7 @@ var FilesystemBackend = class _FilesystemBackend {
32341
32496
  async #readWithEdition(id, edition) {
32342
32497
  assertSafeConceptId(id);
32343
32498
  const rel = pathFromConceptId(id);
32344
- const observed = await observeExact(nodeFilesystemIdentityPort, this.#root, rel, readBytes);
32499
+ const observed = await observeExact(this.#port, this.#root, rel, (handle) => readBytes(this.#port, handle));
32345
32500
  if (observed.state === "absent") throw notFound(this.#root, rel);
32346
32501
  const raw = observed.value.toString("utf8");
32347
32502
  const { frontmatter: frontmatter2, body } = parseMarkdown(raw, rel, { okfVersion: await edition() });
@@ -32363,7 +32518,7 @@ var FilesystemBackend = class _FilesystemBackend {
32363
32518
  assertSafeConceptId(id);
32364
32519
  const raw = stringifyDoc(doc.frontmatter, doc.body ?? "");
32365
32520
  const rel = pathFromConceptId(id);
32366
- return mutateExact(nodeFilesystemIdentityPort, this.#root, rel, async (target) => {
32521
+ return mutateExact(this.#port, this.#root, rel, async (target) => {
32367
32522
  if (options.expectedVersion !== void 0) {
32368
32523
  const bytes = await target.current();
32369
32524
  const current = bytes === null ? null : versionOfBytes(bytes.toString("utf8"));
@@ -32377,7 +32532,7 @@ var FilesystemBackend = class _FilesystemBackend {
32377
32532
  }
32378
32533
  async delete(id, options = {}) {
32379
32534
  assertSafeConceptId(id);
32380
- return mutateExact(nodeFilesystemIdentityPort, this.#root, pathFromConceptId(id), async (target) => {
32535
+ return mutateExact(this.#port, this.#root, pathFromConceptId(id), async (target) => {
32381
32536
  const bytes = await target.current();
32382
32537
  if (bytes === null) return false;
32383
32538
  const current = versionOfBytes(bytes.toString("utf8"));
@@ -32391,10 +32546,10 @@ var FilesystemBackend = class _FilesystemBackend {
32391
32546
  async versions(id) {
32392
32547
  assertSafeConceptId(id);
32393
32548
  const rel = pathFromConceptId(id);
32394
- const observed = await observeExact(nodeFilesystemIdentityPort, this.#root, rel, async (handle, target) => {
32549
+ const observed = await observeExact(this.#port, this.#root, rel, async (handle, target) => {
32395
32550
  try {
32396
- const bytes = await nodeFilesystemIdentityPort.readAll(handle);
32397
- const { mtime: mtime2 } = await nodeFilesystemIdentityPort.stat(target);
32551
+ const bytes = await this.#port.readAll(handle);
32552
+ const { mtime: mtime2 } = await this.#port.stat(target);
32398
32553
  return { raw: bytes.toString("utf8"), mtime: mtime2 };
32399
32554
  } catch {
32400
32555
  return null;
@@ -32409,10 +32564,10 @@ var FilesystemBackend = class _FilesystemBackend {
32409
32564
  }
32410
32565
  async exists(id) {
32411
32566
  assertSafeConceptId(id);
32412
- return (await probeExact(nodeFilesystemIdentityPort, this.#root, pathFromConceptId(id))).state === "exact";
32567
+ return (await probeExact(this.#port, this.#root, pathFromConceptId(id))).state === "exact";
32413
32568
  }
32414
32569
  async list(prefix) {
32415
- const files = await walkFiles(this.#root, (name) => name.endsWith(".md"));
32570
+ const files = await walkFiles(this.#port, this.#root, (name) => name.endsWith(".md"));
32416
32571
  const ids = [];
32417
32572
  for (const rel of files) {
32418
32573
  if (isReservedFile(rel)) continue;
@@ -32426,20 +32581,22 @@ var FilesystemBackend = class _FilesystemBackend {
32426
32581
  if (prefix && !id.startsWith(prefix)) continue;
32427
32582
  ids.push(id);
32428
32583
  }
32429
- ids.sort((a, b) => a.localeCompare(b));
32584
+ ids.sort(compareStorageKeys);
32430
32585
  return ids;
32431
32586
  }
32432
32587
  async readReserved(dir, name) {
32433
32588
  assertSafeReservedDir(dir);
32434
- const observed = await observeExact(nodeFilesystemIdentityPort, this.#root, reservedPath(dir, name), readBytes);
32589
+ assertSafeReservedFilename(name);
32590
+ const observed = await observeExact(this.#port, this.#root, reservedPath(dir, name), (handle) => readBytes(this.#port, handle));
32435
32591
  if (observed.state === "absent") return null;
32436
32592
  const content3 = observed.value.toString("utf8");
32437
32593
  return { content: content3, version: versionOfBytes(content3) };
32438
32594
  }
32439
32595
  async writeReserved(dir, name, content3, options = {}) {
32440
32596
  assertSafeReservedDir(dir);
32597
+ assertSafeReservedFilename(name);
32441
32598
  const rel = reservedPath(dir, name);
32442
- return mutateExact(nodeFilesystemIdentityPort, this.#root, rel, async (target) => {
32599
+ return mutateExact(this.#port, this.#root, rel, async (target) => {
32443
32600
  if (options.expectedVersion !== void 0) {
32444
32601
  const bytes = await target.current();
32445
32602
  const current = bytes === null ? null : versionOfBytes(bytes.toString("utf8"));
@@ -32454,14 +32611,14 @@ var FilesystemBackend = class _FilesystemBackend {
32454
32611
  // ── blobs: opaque bytes + a content-type ──────────────────────────────────
32455
32612
  async readBlob(key) {
32456
32613
  assertSafeBlobKey(key);
32457
- const observed = await observeExact(nodeFilesystemIdentityPort, this.#root, key, readBytes);
32614
+ const observed = await observeExact(this.#port, this.#root, key, (handle) => readBytes(this.#port, handle));
32458
32615
  if (observed.state === "absent") return null;
32459
32616
  const bytes = observed.value;
32460
32617
  return { bytes, contentType: resolveContentType(key), version: blobVersion(bytes) };
32461
32618
  }
32462
32619
  async writeBlob(key, bytes, _contentType, options = {}) {
32463
32620
  assertSafeBlobKey(key);
32464
- return mutateExact(nodeFilesystemIdentityPort, this.#root, key, async (target) => {
32621
+ return mutateExact(this.#port, this.#root, key, async (target) => {
32465
32622
  if (options.expectedVersion !== void 0) {
32466
32623
  const existing = await target.current();
32467
32624
  const current = existing === null ? null : blobVersion(existing);
@@ -32475,7 +32632,7 @@ var FilesystemBackend = class _FilesystemBackend {
32475
32632
  }
32476
32633
  async deleteBlob(key, options = {}) {
32477
32634
  assertSafeBlobKey(key);
32478
- return mutateExact(nodeFilesystemIdentityPort, this.#root, key, async (target) => {
32635
+ return mutateExact(this.#port, this.#root, key, async (target) => {
32479
32636
  const existing = await target.current();
32480
32637
  if (existing === null) return false;
32481
32638
  const current = blobVersion(existing);
@@ -32488,14 +32645,14 @@ var FilesystemBackend = class _FilesystemBackend {
32488
32645
  }
32489
32646
  async existsBlob(key) {
32490
32647
  assertSafeBlobKey(key);
32491
- const observed = await probeExact(nodeFilesystemIdentityPort, this.#root, key);
32648
+ const observed = await probeExact(this.#port, this.#root, key);
32492
32649
  return observed.state === "exact" && observed.value.kind === "file";
32493
32650
  }
32494
32651
  async listBlobs(prefix) {
32495
- const files = await walkFiles(this.#root, (name) => !name.toLowerCase().endsWith(".md"));
32652
+ const files = await walkFiles(this.#port, this.#root, (name) => !name.toLowerCase().endsWith(".md"));
32496
32653
  const keys = files.map(toPosix);
32497
32654
  const filtered = prefix ? keys.filter((k) => k.startsWith(prefix)) : keys;
32498
- filtered.sort((a, b) => a.localeCompare(b));
32655
+ filtered.sort(compareStorageKeys);
32499
32656
  return filtered;
32500
32657
  }
32501
32658
  capabilities() {
@@ -32508,24 +32665,25 @@ var FilesystemBackend = class _FilesystemBackend {
32508
32665
  };
32509
32666
  }
32510
32667
  };
32511
- function publicationRoot(backend) {
32512
- const root = publicationRoots.get(backend);
32513
- if (!root) throw new InvalidInputError("Unknown filesystem backend publication source.");
32514
- return root;
32668
+ function publicationContext(backend) {
32669
+ const context = publicationContexts.get(backend);
32670
+ if (!context) throw new InvalidInputError("Unknown filesystem backend publication source.");
32671
+ return context;
32515
32672
  }
32516
32673
  async function readRawFilesystemDocument(backend, id) {
32517
32674
  assertSafeConceptId(id);
32518
- const root = publicationRoot(backend);
32675
+ const { root, port } = publicationContext(backend);
32519
32676
  const rel = pathFromConceptId(id);
32520
- const observed = await observeExact(nodeFilesystemIdentityPort, root, rel, readBytes);
32677
+ const observed = await observeExact(port, root, rel, (handle) => readBytes(port, handle));
32521
32678
  if (observed.state === "absent") throw notFound(root, rel);
32522
32679
  const raw = new TextDecoder("utf-8", { fatal: true }).decode(observed.value);
32523
32680
  return { bytes: observed.value.slice(), version: versionOfBytes(raw) };
32524
32681
  }
32525
32682
  async function readRawFilesystemReserved(backend, dir, name) {
32526
32683
  assertSafeReservedDir(dir);
32527
- const root = publicationRoot(backend);
32528
- const observed = await observeExact(nodeFilesystemIdentityPort, root, reservedPath(dir, name), readBytes);
32684
+ assertSafeReservedFilename(name);
32685
+ const { root, port } = publicationContext(backend);
32686
+ const observed = await observeExact(port, root, reservedPath(dir, name), (handle) => readBytes(port, handle));
32529
32687
  if (observed.state === "absent") return null;
32530
32688
  return {
32531
32689
  bytes: observed.value.slice(),
@@ -32533,15 +32691,16 @@ async function readRawFilesystemReserved(backend, dir, name) {
32533
32691
  };
32534
32692
  }
32535
32693
  async function listFilesystemReservedObjects(backend) {
32536
- const root = publicationRoot(backend);
32537
- const files = await walkFiles(root, (name) => name === "index.md" || name === "log.md");
32694
+ const { root, port } = publicationContext(backend);
32695
+ const files = await walkFiles(port, root, (name) => name === "index.md" || name === "log.md");
32538
32696
  return files.map((relative) => {
32539
32697
  const slash = relative.lastIndexOf("/");
32540
32698
  const dir = slash === -1 ? "" : relative.slice(0, slash);
32541
32699
  const name = relative.slice(slash + 1);
32542
32700
  assertSafeReservedDir(dir);
32701
+ assertSafeReservedFilename(name);
32543
32702
  return { dir, name };
32544
- }).sort((a, b) => reservedPath(a.dir, a.name).localeCompare(reservedPath(b.dir, b.name)));
32703
+ }).sort((a, b) => compareStorageKeys(reservedPath(a.dir, a.name), reservedPath(b.dir, b.name)));
32545
32704
  }
32546
32705
 
32547
32706
  // ../core/src/bundle-ops.ts
@@ -32581,7 +32740,7 @@ setDefaultBackendFactory((root) => new FilesystemBackend(root));
32581
32740
  var CONVENTIONS_PREFIX = "conventions/";
32582
32741
  var CONVENTION_TYPE = "Convention";
32583
32742
  function buildKindRegistry(docs, initialWarnings = [], options = {}) {
32584
- const kinds = /* @__PURE__ */ new Map();
32743
+ const accepted = /* @__PURE__ */ new Map();
32585
32744
  const warnings = [...initialWarnings];
32586
32745
  for (const doc of [...docs].sort((a, b) => a.id.localeCompare(b.id))) {
32587
32746
  const parsed = parseConventionDoc(doc, options);
@@ -32604,10 +32763,10 @@ function buildKindRegistry(docs, initialWarnings = [], options = {}) {
32604
32763
  severity: "warning"
32605
32764
  });
32606
32765
  }
32607
- if (kinds.has(kind.governs)) {
32766
+ if (accepted.has(kind.governs)) {
32608
32767
  warnings.push({
32609
32768
  code: "KIND_DUPLICATE_GOVERNS",
32610
- message: `duplicate kind convention for '${kind.governs}': '${doc.id}' ignored, keeping the first-declared '${kinds.get(kind.governs).id}'.`,
32769
+ message: `duplicate kind convention for '${kind.governs}': '${doc.id}' ignored, keeping the first-declared '${accepted.get(kind.governs).id}'.`,
32611
32770
  field: kind.governs,
32612
32771
  severity: "warning"
32613
32772
  });
@@ -32621,10 +32780,18 @@ function buildKindRegistry(docs, initialWarnings = [], options = {}) {
32621
32780
  severity: "warning"
32622
32781
  });
32623
32782
  }
32624
- kinds.set(kind.governs, kind);
32783
+ accepted.set(kind.governs, kind);
32625
32784
  }
32785
+ const kinds = /* @__PURE__ */ new Map();
32786
+ for (const kind of [...accepted.values()].sort(compareReadingOrder)) kinds.set(kind.governs, kind);
32626
32787
  return { kinds, warnings };
32627
32788
  }
32789
+ function compareReadingOrder(a, b) {
32790
+ if (a.order !== void 0 && b.order !== void 0 && a.order !== b.order) return a.order - b.order;
32791
+ if (a.order !== void 0 && b.order === void 0) return -1;
32792
+ if (a.order === void 0 && b.order !== void 0) return 1;
32793
+ return a.id.localeCompare(b.id);
32794
+ }
32628
32795
  var PROGRESS_STATUS_FIELD = "progress_status";
32629
32796
  var SUPERBEE_PROGRESS_STATUS_FIELD = "superbee_progress_status";
32630
32797
  function progressStatusStorageField(okfVersion) {
@@ -33093,6 +33260,19 @@ function parseConventionDoc(doc, options = {}) {
33093
33260
  const path6 = typeof fm.path === "string" && fm.path.trim() !== "" ? fm.path.trim() : void 0;
33094
33261
  const freshnessHorizon = typeof fm.freshness_horizon === "string" && fm.freshness_horizon.trim() !== "" ? fm.freshness_horizon.trim() : void 0;
33095
33262
  const browseCollapsed = fm.browse_collapsed === true ? true : void 0;
33263
+ let order;
33264
+ if (fm.order !== void 0) {
33265
+ if (typeof fm.order === "number" && Number.isFinite(fm.order)) {
33266
+ order = fm.order;
33267
+ } else {
33268
+ warnings.push({
33269
+ code: "KIND_CONVENTION_BAD_SHAPE",
33270
+ message: `kind convention '${doc.id}' has an invalid 'order' (${describeShape(fm.order)}; expected a finite number such as 10); ignoring it.`,
33271
+ field: "order",
33272
+ severity: "warning"
33273
+ });
33274
+ }
33275
+ }
33096
33276
  const kind = {
33097
33277
  id: doc.id,
33098
33278
  title,
@@ -33107,6 +33287,7 @@ function parseConventionDoc(doc, options = {}) {
33107
33287
  if (sections && sections.length > 0) kind.sections = sections;
33108
33288
  if (freshnessHorizon !== void 0) kind.freshnessHorizon = freshnessHorizon;
33109
33289
  if (browseCollapsed !== void 0) kind.browseCollapsed = browseCollapsed;
33290
+ if (order !== void 0) kind.order = order;
33110
33291
  if (claim !== void 0) kind.claim = claim;
33111
33292
  return {
33112
33293
  ok: true,
@@ -33257,7 +33438,7 @@ function isAnyEntryKey(entry) {
33257
33438
  return isPageEntryKey(entry) || isViewEntryKey(entry);
33258
33439
  }
33259
33440
  function isViewEntryVersion(value) {
33260
- return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
33441
+ return isContentVersion(value);
33261
33442
  }
33262
33443
  function parseRegistration(id, frontmatter2) {
33263
33444
  if (!isAnyRegistryId(id) || !isPageTypeName(frontmatter2.type) || !isAnyEntryKey(frontmatter2.entry)) return null;
@@ -42193,6 +42374,35 @@ function isBareLinkBlock(node2, fromId) {
42193
42374
  function safeLanguageClass(lang) {
42194
42375
  return typeof lang === "string" && /^[\w+-]{1,24}$/.test(lang) ? `doc-code-${lang}` : void 0;
42195
42376
  }
42377
+ function isIpLiteralHost(hostname2) {
42378
+ return hostname2.startsWith("[") || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname2);
42379
+ }
42380
+ function normalizeAllowedHost(entry) {
42381
+ if (typeof entry !== "string" || entry === "" || /[\s/?#@:\\]/.test(entry)) return null;
42382
+ let url;
42383
+ try {
42384
+ url = new URL(`https://${entry}/`);
42385
+ } catch {
42386
+ return null;
42387
+ }
42388
+ if (url.href !== `https://${url.hostname}/` || isIpLiteralHost(url.hostname)) return null;
42389
+ return url.hostname;
42390
+ }
42391
+ function admitExternalLink(raw, hosts) {
42392
+ if (!Array.isArray(hosts) || hosts.length === 0) return null;
42393
+ let url;
42394
+ try {
42395
+ url = new URL(raw);
42396
+ } catch {
42397
+ return null;
42398
+ }
42399
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "") return null;
42400
+ if (isIpLiteralHost(url.hostname)) return null;
42401
+ for (const entry of hosts) {
42402
+ if (normalizeAllowedHost(entry) === url.hostname) return url.href;
42403
+ }
42404
+ return null;
42405
+ }
42196
42406
  function documentHref(id, options) {
42197
42407
  const href = options.hrefForDoc?.(id) ?? `?view=doc&id=${encodeURIComponent(id)}`;
42198
42408
  const base = "https://renderer.invalid/";
@@ -42267,9 +42477,14 @@ function renderNode(node2, state, depth, index2) {
42267
42477
  case "break":
42268
42478
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}, index2);
42269
42479
  case "link": {
42270
- const resolved = resolveConceptId(state.options.fromId, node2.url ?? "");
42480
+ const raw = node2.url ?? "";
42481
+ const resolved = resolveConceptId(state.options.fromId, raw);
42271
42482
  const children = renderChildren(node2, state, depth);
42272
42483
  if (resolved === null) {
42484
+ const external = admitExternalLink(raw, state.options.externalLinkHosts);
42485
+ if (external !== null) {
42486
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: external, rel: "noopener noreferrer", target: "_blank", className: "doc-link-external", children }, index2);
42487
+ }
42273
42488
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
42274
42489
  "span",
42275
42490
  {
@@ -42413,8 +42628,11 @@ function renderMarkdownToStaticHtml(body, options) {
42413
42628
  );
42414
42629
  return { html, bounded: rendered.bounded, limits: rendered.limits };
42415
42630
  }
42416
- function renderDocumentToStaticHtml(document3) {
42417
- const rendered = renderMarkdownToStaticHtml(document3.body, { fromId: document3.id });
42631
+ function renderDocumentToStaticHtml(document3, options = {}) {
42632
+ const rendered = renderMarkdownToStaticHtml(document3.body, {
42633
+ fromId: document3.id,
42634
+ externalLinkHosts: options.externalLinkHosts
42635
+ });
42418
42636
  return { html: rendered.html, bounded: rendered.bounded };
42419
42637
  }
42420
42638
 
@@ -42447,9 +42665,45 @@ var MAX_QUERY_ROWS = 500;
42447
42665
  var MAX_EDGE_ROWS = 1e3;
42448
42666
  var MAX_DOCUMENT_BODY_BYTES = 1024 * 1024;
42449
42667
  var MAX_REPLY_BYTES = 2 * 1024 * 1024;
42668
+ var GRAPH_MAX_DOCUMENTS = 1e3;
42669
+ var GRAPH_MAX_RELATIONSHIPS = 1e4;
42450
42670
  var MAX_CHANGE_ROWS = 100;
42451
42671
  var MAX_CHANGE_BYTES = 256 * 1024;
42452
42672
  var MAX_SUBSCRIPTION_HEADS = 1e4;
42673
+ var MAX_HOST_CAPABILITY_BYTES = 128;
42674
+ var MAX_HOST_REQUEST_BYTES = 64 * 1024;
42675
+ var HOST_CAPABILITY_NAME = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
42676
+ var BRIDGE_SERVICE_LIMITS = Object.freeze({
42677
+ query: MAX_QUERY_ROWS,
42678
+ edges: MAX_EDGE_ROWS,
42679
+ graphDocuments: GRAPH_MAX_DOCUMENTS,
42680
+ graphRelationships: GRAPH_MAX_RELATIONSHIPS,
42681
+ replyBytes: MAX_REPLY_BYTES
42682
+ });
42683
+ var BRIDGE_HOST_CAPABILITIES = Object.freeze({
42684
+ queryKindProjection: "query.kind-projection",
42685
+ queryFieldOr: "query.field-or",
42686
+ queryOpen: "query.open",
42687
+ queryCount: "query.count",
42688
+ edges: "edges",
42689
+ renderDocument: "render-document",
42690
+ openPage: "open-page",
42691
+ subscribeDeltas: "subscribe-deltas",
42692
+ graph: "graph",
42693
+ graphModel: "graph.model",
42694
+ recordOpen: "record.open",
42695
+ frameResize: "frame.resize"
42696
+ });
42697
+ var BRIDGE_SERVICE_CAPABILITIES = Object.freeze([
42698
+ BRIDGE_HOST_CAPABILITIES.queryKindProjection,
42699
+ BRIDGE_HOST_CAPABILITIES.queryFieldOr,
42700
+ BRIDGE_HOST_CAPABILITIES.queryOpen,
42701
+ BRIDGE_HOST_CAPABILITIES.queryCount,
42702
+ BRIDGE_HOST_CAPABILITIES.edges,
42703
+ BRIDGE_HOST_CAPABILITIES.graph,
42704
+ BRIDGE_HOST_CAPABILITIES.renderDocument
42705
+ ]);
42706
+ var V0_REQUEST_TYPES = /* @__PURE__ */ new Set(["hello", "query", "read", "render-document", "edges", "graph", "subscribe", "host", "open-page"]);
42453
42707
  function isPlainRecord(value) {
42454
42708
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
42455
42709
  const prototype = Object.getPrototypeOf(value);
@@ -42582,6 +42836,45 @@ function parseBridgeRequest(value) {
42582
42836
  const params = normalizeEdgeParams(value.params);
42583
42837
  return params ? { bridge: BRIDGE_PROTOCOL, type: "edges", id, params } : null;
42584
42838
  }
42839
+ if (value.type === "graph") {
42840
+ const expected = value.includeBodies === void 0 ? ["bridge", "type", "id"] : ["bridge", "type", "id", "includeBodies"];
42841
+ if (!exactKeys(value, expected)) return null;
42842
+ if (value.includeBodies !== void 0 && typeof value.includeBodies !== "boolean") return null;
42843
+ return { bridge: BRIDGE_PROTOCOL, type: "graph", id, includeBodies: value.includeBodies === true };
42844
+ }
42845
+ if (value.type === "host") {
42846
+ const expected = value.input === void 0 ? ["bridge", "type", "id", "capability"] : ["bridge", "type", "id", "capability", "input"];
42847
+ if (!exactKeys(value, expected)) return null;
42848
+ const capability = boundedString(value.capability, MAX_HOST_CAPABILITY_BYTES);
42849
+ if (!capability || !HOST_CAPABILITY_NAME.test(capability)) return null;
42850
+ if (value.input !== void 0 && !isPlainRecord(value.input)) return null;
42851
+ let bytes;
42852
+ try {
42853
+ bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
42854
+ } catch {
42855
+ return null;
42856
+ }
42857
+ if (bytes > MAX_HOST_REQUEST_BYTES) return null;
42858
+ return {
42859
+ bridge: BRIDGE_PROTOCOL,
42860
+ type: "host",
42861
+ id,
42862
+ capability,
42863
+ ...value.input === void 0 ? {} : { input: value.input }
42864
+ };
42865
+ }
42866
+ return null;
42867
+ }
42868
+ function unsupportedRequest(value) {
42869
+ if (!isPlainRecord(value) || typeof value.type !== "string") return null;
42870
+ if (value.bridge === BRIDGE_PROTOCOL) {
42871
+ return V0_REQUEST_TYPES.has(value.type) ? null : { bridge: BRIDGE_PROTOCOL, id: requestId(value.id) ?? void 0 };
42872
+ }
42873
+ if (value.bridge === ACTION_BRIDGE_PROTOCOL) {
42874
+ if (value.type === "read-versioned") return null;
42875
+ const id = value.type === "action.propose" ? value.requestId : value.id;
42876
+ return { bridge: ACTION_BRIDGE_PROTOCOL, id: requestId(id) ?? void 0 };
42877
+ }
42585
42878
  return null;
42586
42879
  }
42587
42880
  function ok3(id, bridge, type, result) {
@@ -42606,6 +42899,17 @@ var BridgeService = class {
42606
42899
  async handle(launchId, rawRequest) {
42607
42900
  const request = parseBridgeRequest(rawRequest);
42608
42901
  if (!request) {
42902
+ const unsupported = unsupportedRequest(rawRequest);
42903
+ if (unsupported) {
42904
+ return {
42905
+ reply: fail(
42906
+ unsupported.id,
42907
+ unsupported.bridge,
42908
+ "FORBIDDEN",
42909
+ "this host does not offer the requested bridge operation"
42910
+ )
42911
+ };
42912
+ }
42609
42913
  return {
42610
42914
  reply: fail(
42611
42915
  invalidV0RequestId(rawRequest),
@@ -42615,16 +42919,6 @@ var BridgeService = class {
42615
42919
  )
42616
42920
  };
42617
42921
  }
42618
- if (request.bridge === ACTION_BRIDGE_PROTOCOL && this.options.allowActionProtocol === false) {
42619
- return {
42620
- reply: fail(
42621
- request.id,
42622
- request.bridge,
42623
- "FORBIDDEN",
42624
- "this host admits only the read-only v0 View bridge"
42625
- )
42626
- };
42627
- }
42628
42922
  const dataBearing = request.type !== "open-page";
42629
42923
  const before = await this.options.launches.resolve(launchId, dataBearing);
42630
42924
  if (!before) {
@@ -42730,6 +43024,73 @@ var BridgeService = class {
42730
43024
  [...rows].sort((a, b) => a.id.localeCompare(b.id)).map((row) => [row.id, row.version])
42731
43025
  );
42732
43026
  }
43027
+ /**
43028
+ * Whole-bundle projection for graph-shaped Views. The documents come from one head scan and
43029
+ * the relationships from `queryEdges`, which is a second full-bundle scan; the two scans are
43030
+ * accepted because the reply is bounded by the exported graph limits and the host never
43031
+ * caches bundle state on a View's behalf. No `model` or `definitions` are answered here: the
43032
+ * owner of that shape is undecided, and a host that has one declares the `graph.model`
43033
+ * capability before adding them.
43034
+ */
43035
+ async graph(launch, request) {
43036
+ const heads = await queryHeads2(this.options.bundle, {});
43037
+ if (heads.length > GRAPH_MAX_DOCUMENTS) {
43038
+ return {
43039
+ reply: fail(request.id, request.bridge, "TOO_LARGE", `the graph exceeded ${GRAPH_MAX_DOCUMENTS} documents`)
43040
+ };
43041
+ }
43042
+ const [registry, declaredOkfVersion, edges] = await Promise.all([
43043
+ loadKinds(this.options.bundle),
43044
+ readBundleOkfVersion2(this.options.bundle),
43045
+ queryEdges2(this.options.bundle, {})
43046
+ ]);
43047
+ if (edges.length > GRAPH_MAX_RELATIONSHIPS) {
43048
+ return {
43049
+ reply: fail(request.id, request.bridge, "TOO_LARGE", `the graph exceeded ${GRAPH_MAX_RELATIONSHIPS} relationships`)
43050
+ };
43051
+ }
43052
+ const okfVersion = declaredOkfVersion ?? "0.1";
43053
+ const includeBodies = request.includeBodies && (launch.capability === "bundle-read" || launch.capability === "bundle-propose");
43054
+ const documents = [];
43055
+ for (const head of heads) {
43056
+ const source = includeBodies ? await readDocVersioned2(this.options.bundle, head.id).then((result) => ({
43057
+ version: result.version,
43058
+ frontmatter: result.doc.frontmatter,
43059
+ body: result.doc.body
43060
+ })) : { version: head.version, frontmatter: head.frontmatter };
43061
+ if (source.body !== void 0 && Buffer.byteLength(source.body, "utf8") > MAX_DOCUMENT_BODY_BYTES) {
43062
+ return { reply: fail(request.id, request.bridge, "TOO_LARGE", "a document body exceeded the 1 MiB View limit") };
43063
+ }
43064
+ const kind = registry.kinds.get(String(source.frontmatter.type ?? ""));
43065
+ const frontmatter2 = kind ? projectLogicalKindFields(okfVersion, kind, source.frontmatter) : source.frontmatter;
43066
+ documents.push({
43067
+ id: head.id,
43068
+ version: source.version,
43069
+ frontmatter: frontmatter2,
43070
+ ...source.body === void 0 ? {} : { body: source.body }
43071
+ });
43072
+ }
43073
+ const relationships = edges.map(({ from, to, text: text4 }) => ({ from, to, text: text4 }));
43074
+ return {
43075
+ reply: ok3(request.id, request.bridge, request.type, {
43076
+ okfVersion,
43077
+ documents,
43078
+ relationships,
43079
+ counts: { documents: documents.length, relationships: relationships.length }
43080
+ })
43081
+ };
43082
+ }
43083
+ hostDescriptor() {
43084
+ const declared = new Set(this.options.host.capabilities);
43085
+ for (const name of Object.keys(this.options.hostHandlers ?? {})) declared.add(name);
43086
+ return {
43087
+ kind: this.options.host.kind,
43088
+ capabilities: [...declared].sort(),
43089
+ limits: { ...this.options.host.limits },
43090
+ ...this.options.host.frame ? { frame: { ...this.options.host.frame } } : {},
43091
+ ...this.options.host.theme ? { theme: { ...this.options.host.theme } } : {}
43092
+ };
43093
+ }
42733
43094
  async execute(launch, request) {
42734
43095
  if (request.type === "open-page") {
42735
43096
  if (this.options.consumeOpenPage === true) {
@@ -42753,10 +43114,25 @@ var BridgeService = class {
42753
43114
  bundle: { root: config.root, name: config.name },
42754
43115
  mode: config.mode,
42755
43116
  protocol: BRIDGE_PROTOCOL,
42756
- grant: launch.capability === "bundle-propose" ? "propose" : "read"
43117
+ grant: launch.capability === "bundle-propose" ? "propose" : "read",
43118
+ ...config.mode === "dir" && this.options.host.kind === "oss" && launch.capability === "bundle-propose" ? { actionProtocol: "v1", actions: ["document.set-field", "document.set-body", "document.update"] } : {},
43119
+ host: this.hostDescriptor()
42757
43120
  })
42758
43121
  };
42759
43122
  }
43123
+ if (request.type === "host") {
43124
+ const handlers = this.options.hostHandlers ?? {};
43125
+ if (!Object.hasOwn(handlers, request.capability)) {
43126
+ return { reply: fail(request.id, request.bridge, "FORBIDDEN", "this host does not offer the requested capability") };
43127
+ }
43128
+ const outcome = await handlers[request.capability]({
43129
+ capability: request.capability,
43130
+ input: request.input,
43131
+ launch
43132
+ });
43133
+ if (!outcome.ok) return { reply: fail(request.id, request.bridge, outcome.code, outcome.message) };
43134
+ return { reply: ok3(request.id, request.bridge, request.type, { capability: request.capability, output: outcome.output }) };
43135
+ }
42760
43136
  if (request.type === "query") {
42761
43137
  const rows = await queryHeads2(this.options.bundle, {
42762
43138
  ...request.params.type ? { type: request.params.type } : {},
@@ -42834,6 +43210,9 @@ var BridgeService = class {
42834
43210
  const projected = edges.map(({ from, to, text: text4 }) => ({ from, to, text: text4 }));
42835
43211
  return { reply: ok3(request.id, request.bridge, request.type, { edges: projected, count: projected.length }) };
42836
43212
  }
43213
+ if (request.type === "graph") {
43214
+ return this.graph(launch, request);
43215
+ }
42837
43216
  if (this.options.enablePolling) {
42838
43217
  this.subscriptions.set(launch.launchId, {
42839
43218
  baseline: await this.subscriptionSnapshot()
@@ -43291,7 +43670,7 @@ function buildSnapshot(first) {
43291
43670
  const snapshotDigest = sha256(utf8(canonicalJson(withoutDigest)));
43292
43671
  return new SnapshotHandle({ ...withoutDigest, snapshotDigest }, objects);
43293
43672
  }
43294
- async function capturePublicationSnapshot(options) {
43673
+ async function capturePublicationSnapshot(options, runtimeOptions = {}) {
43295
43674
  if (options.schema !== PUBLICATION_SNAPSHOT_V1) {
43296
43675
  throw new PublicationError("CAPABILITY_UNAVAILABLE", "the requested publication snapshot schema is unsupported");
43297
43676
  }
@@ -43302,6 +43681,7 @@ async function capturePublicationSnapshot(options) {
43302
43681
  throw new PublicationError("INVALID_OBJECT_IDENTITY", "the filesystem publication root must be absolute");
43303
43682
  }
43304
43683
  const requestedRoot = path5.resolve(options.source.root);
43684
+ const backend = new FilesystemBackend(requestedRoot, { hostPolicy: runtimeOptions.filesystemHostPolicy });
43305
43685
  let rootIdentity;
43306
43686
  try {
43307
43687
  rootIdentity = await authorizePublicationRoot(requestedRoot);
@@ -43324,7 +43704,6 @@ async function capturePublicationSnapshot(options) {
43324
43704
  }
43325
43705
  }
43326
43706
  const maxAttempts = options.maxAttempts ?? 2;
43327
- const backend = new FilesystemBackend(root);
43328
43707
  let lastError;
43329
43708
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
43330
43709
  let handle;
@@ -43466,6 +43845,10 @@ var STATIC_LAUNCH_ID = "publication-snapshot";
43466
43845
  function digest(bytes) {
43467
43846
  return `sha256:${createHash4("sha256").update(bytes).digest("hex")}`;
43468
43847
  }
43848
+ var STATIC_BRIDGE_CAPABILITIES = Object.freeze([
43849
+ ...BRIDGE_SERVICE_CAPABILITIES,
43850
+ BRIDGE_HOST_CAPABILITIES.openPage
43851
+ ]);
43469
43852
  function createPublicationBridge(options) {
43470
43853
  if (options.protocol !== PUBLICATION_BRIDGE_V0) {
43471
43854
  throw new PublicationError("CAPABILITY_UNAVAILABLE", "the requested publication bridge protocol is unsupported");
@@ -43513,7 +43896,11 @@ function createPublicationBridge(options) {
43513
43896
  if (!rendered || rendered.id !== id) throw new Error(`document '${id}' is not available`);
43514
43897
  return { html: rendered.html, bounded: rendered.bounded };
43515
43898
  },
43516
- allowActionProtocol: false,
43899
+ host: {
43900
+ kind: options.host?.kind ?? "oss",
43901
+ capabilities: options.host?.capabilities ?? STATIC_BRIDGE_CAPABILITIES,
43902
+ limits: BRIDGE_SERVICE_LIMITS
43903
+ },
43517
43904
  enablePolling: false,
43518
43905
  consumeOpenPage: false
43519
43906
  });