@orkestrel/scaffold 0.0.48 → 0.0.50

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.
@@ -3,6 +3,7 @@ let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _src_core = require("../core/index.cjs");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs = require("node:fs");
6
+ let node_os = require("node:os");
6
7
  let node_path = require("node:path");
7
8
  let node_url = require("node:url");
8
9
  let _orkestrel_emitter = require("@orkestrel/emitter");
@@ -44,13 +45,13 @@ var RESERVED_SEGMENT_PATTERN = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9]|conin\$|co
44
45
  */
45
46
  var DIGEST_PATTERN = /^[0-9a-f]{64}$/;
46
47
  /**
47
- * The Git branch syntax the guide endpoint accepts.
48
+ * The Git branch syntax the repository endpoint accepts.
48
49
  *
49
50
  * @remarks
50
51
  * A branch is caller-supplied and reaches a URL path, so it is closed to
51
52
  * alphanumerics, dot, underscore, hyphen, and the separator, must open with an
52
53
  * alphanumeric, and may carry no `..` anywhere. That last refusal is what stops
53
- * a branch from walking out of the guide directory it addresses.
54
+ * a branch from walking out of the repository path it addresses.
54
55
  */
55
56
  var BRANCH_PATTERN = /^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
56
57
  /**
@@ -84,7 +85,7 @@ var MAX_PATH_DEPTH = 64;
84
85
  var MAX_INVENTORY_PATHS = 1e5;
85
86
  /** Maximum characters one caller-supplied upstream endpoint may carry. */
86
87
  var MAX_ENDPOINT_LENGTH = 2048;
87
- /** Maximum characters one guide branch may carry. */
88
+ /** Maximum characters one repository branch may carry. */
88
89
  var MAX_BRANCH_LENGTH = 255;
89
90
  /**
90
91
  * Maximum simultaneous upstream requests.
@@ -226,10 +227,10 @@ var isEndpoint = (0, _orkestrel_contract.stringOf)({
226
227
  max: MAX_ENDPOINT_LENGTH
227
228
  });
228
229
  /**
229
- * Narrow a value to a Git branch the guide endpoint accepts.
230
+ * Narrow a value to a Git branch the repository endpoint accepts.
230
231
  *
231
232
  * @remarks
232
- * A branch reaches the guide URL's path, so the syntax is closed rather than
233
+ * A branch reaches the repository URL's path, so the syntax is closed rather than
233
234
  * merely bounded and no `..` is admitted anywhere in it.
234
235
  *
235
236
  * @example
@@ -271,8 +272,49 @@ var isTimeout = (0, _orkestrel_contract.andOf)(_orkestrel_contract.isInteger, (0
271
272
  * ```
272
273
  */
273
274
  var isDependencyNames = (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0, _orkestrel_contract.arrayOf)(_src_core.isDependencyName));
275
+ /**
276
+ * Narrow a value to a bounded list of target-relative paths.
277
+ *
278
+ * @remarks
279
+ * Composed from the core collection and path guards rather than restated, so
280
+ * the containment law that keeps a caller-supplied path inside its target has
281
+ * exactly one home. It bounds what a caller may hand a public method, which is
282
+ * why it is not {@link isInventory}: that one bounds what a checkout may hold.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * import { isPaths } from '@orkestrel/scaffold/server'
287
+ *
288
+ * isPaths(['AGENTS.md']) // true
289
+ * isPaths(['../secrets']) // false
290
+ * ```
291
+ */
292
+ var isPaths = (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0, _orkestrel_contract.arrayOf)(_src_core.isPath));
274
293
  /** Narrow a value to a bounded list of declared runtime dependencies. */
275
294
  var isDependencies = (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0, _orkestrel_contract.arrayOf)(_src_core.isDependency));
295
+ /**
296
+ * Narrow a value to one {@link ManifestRegionSet}.
297
+ *
298
+ * @remarks
299
+ * The whole closed record a manifest-writing method accepts, so a caller
300
+ * naming a region the writer does not carry is refused before any byte moves.
301
+ * Each region is bounded by the same collection law its own list guard applies.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * import { isManifestRegionSet } from '@orkestrel/scaffold/server'
306
+ *
307
+ * isManifestRegionSet({ pins: { runtime: [], development: [] }, scripts: [] }) // true
308
+ * isManifestRegionSet({ pins: { runtime: [], development: [] } }) // false
309
+ * ```
310
+ */
311
+ var isManifestRegionSet = (0, _orkestrel_contract.recordOf)({
312
+ pins: (0, _orkestrel_contract.recordOf)({
313
+ runtime: isDependencies,
314
+ development: isDependencies
315
+ }),
316
+ scripts: (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0, _orkestrel_contract.arrayOf)(_src_core.isManifestScript))
317
+ });
276
318
  /** Narrow a value to a bounded list of fetched guide mirrors. */
277
319
  var isMirrors = (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0, _orkestrel_contract.arrayOf)(_src_core.isMirror));
278
320
  /** Narrow a value to a bounded list of fleet catalog rows. */
@@ -290,13 +332,19 @@ var isCatalogEntries = (0, _orkestrel_contract.andOf)(_src_core.isCollection, (0
290
332
  * ```ts
291
333
  * import { isManifestEntry } from '@orkestrel/scaffold/server'
292
334
  *
293
- * isManifestEntry({ storage: 'AGENTS.md', destination: 'AGENTS.md', executable: false }) // true
335
+ * isManifestEntry({
336
+ * storage: 'AGENTS.md',
337
+ * destination: 'AGENTS.md',
338
+ * executable: false,
339
+ * digest: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
340
+ * }) // true
294
341
  * ```
295
342
  */
296
343
  var isManifestEntry = (0, _orkestrel_contract.recordOf)({
297
344
  storage: _src_core.isPath,
298
345
  destination: _src_core.isPath,
299
- executable: _orkestrel_contract.isBoolean
346
+ executable: _orkestrel_contract.isBoolean,
347
+ digest: isDigest
300
348
  });
301
349
  /**
302
350
  * Narrow a value to one {@link HostManifest}.
@@ -312,7 +360,33 @@ var isHostManifest = (0, _orkestrel_contract.recordOf)({
312
360
  digest: isDigest
313
361
  });
314
362
  /**
315
- * Narrow a value to a {@link Repository}.
363
+ * Narrow a value to one {@link Host}.
364
+ *
365
+ * @remarks
366
+ * A whole vendored host handed in as a value is as untrusted as one read from a
367
+ * directory a caller named, so both halves are guarded: the manifest by the same
368
+ * membership law a read root is held to, and the bytes by the core snapshot law,
369
+ * which bounds the fill and reads every key as a path and every value as exact
370
+ * lowercase hexadecimal. Whether those halves agree with each other is the
371
+ * reader's question rather than this one's, because a guard has only `false` to
372
+ * say and a mismatch has a path to name.
373
+ *
374
+ * @example
375
+ * ```ts
376
+ * import { isHost } from '@orkestrel/scaffold/server'
377
+ *
378
+ * const digest = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
379
+ *
380
+ * isHost({ manifest: { entries: [], roots: [], digest }, bytes: {} }) // true
381
+ * isHost({ manifest: { entries: [], roots: [], digest } }) // false
382
+ * ```
383
+ */
384
+ var isHost = (0, _orkestrel_contract.recordOf)({
385
+ manifest: isHostManifest,
386
+ bytes: _src_core.isSnapshot
387
+ });
388
+ /**
389
+ * Narrow a value to a {@link Worktree}.
316
390
  *
317
391
  * @remarks
318
392
  * Both path lists are target-relative, so both are measured by the core path
@@ -322,13 +396,13 @@ var isHostManifest = (0, _orkestrel_contract.recordOf)({
322
396
  *
323
397
  * @example
324
398
  * ```ts
325
- * import { isRepository } from '@orkestrel/scaffold/server'
399
+ * import { isWorktree } from '@orkestrel/scaffold/server'
326
400
  *
327
- * isRepository({ tracked: ['AGENTS.md'], dirty: [] }) // true
328
- * isRepository({ tracked: ['../secrets'], dirty: [] }) // false
401
+ * isWorktree({ tracked: ['AGENTS.md'], dirty: [] }) // true
402
+ * isWorktree({ tracked: ['../secrets'], dirty: [] }) // false
329
403
  * ```
330
404
  */
331
- var isRepository = (0, _orkestrel_contract.recordOf)({
405
+ var isWorktree = (0, _orkestrel_contract.recordOf)({
332
406
  tracked: (0, _orkestrel_contract.andOf)(isInventory, (0, _orkestrel_contract.arrayOf)(_src_core.isPath)),
333
407
  dirty: (0, _orkestrel_contract.andOf)(isInventory, (0, _orkestrel_contract.arrayOf)(_src_core.isPath))
334
408
  });
@@ -350,6 +424,12 @@ var isMaterializerHooks = (0, _orkestrel_contract.recordOf)({
350
424
  /**
351
425
  * Narrow a value to {@link MaterializerOptions}.
352
426
  *
427
+ * @remarks
428
+ * `host` admits both representations of one vendored root: a directory path and
429
+ * a whole {@link Host} value. They share a key because they are one setting
430
+ * stated two ways rather than two settings, so nothing downstream has to
431
+ * reconcile a pair that could disagree.
432
+ *
353
433
  * @example
354
434
  * ```ts
355
435
  * import { isMaterializerOptions } from '@orkestrel/scaffold/server'
@@ -359,7 +439,7 @@ var isMaterializerHooks = (0, _orkestrel_contract.recordOf)({
359
439
  * ```
360
440
  */
361
441
  var isMaterializerOptions = (0, _orkestrel_contract.recordOf)({
362
- host: isFilesystemPath,
442
+ host: (0, _orkestrel_contract.unionOf)(isFilesystemPath, isHost),
363
443
  on: isMaterializerHooks,
364
444
  error: _orkestrel_contract.isFunction
365
445
  }, true);
@@ -373,6 +453,7 @@ var isMaterializerOptions = (0, _orkestrel_contract.recordOf)({
373
453
  var isUpstreamHooks = (0, _orkestrel_contract.recordOf)({
374
454
  release: _orkestrel_contract.isFunction,
375
455
  mirror: _orkestrel_contract.isFunction,
456
+ file: _orkestrel_contract.isFunction,
376
457
  error: _orkestrel_contract.isFunction,
377
458
  destroy: _orkestrel_contract.isFunction
378
459
  }, true);
@@ -392,12 +473,12 @@ var isUpstreamHooks = (0, _orkestrel_contract.recordOf)({
392
473
  * ```ts
393
474
  * import { isUpstreamOptions } from '@orkestrel/scaffold/server'
394
475
  *
395
- * isUpstreamOptions({ guides: { branch: 'main' }, concurrency: 4 }) // true
476
+ * isUpstreamOptions({ repository: { branch: 'main' }, concurrency: 4 }) // true
396
477
  * isUpstreamOptions({ concurrency: 0 }) // false
397
478
  * ```
398
479
  */
399
480
  var isUpstreamOptions = (0, _orkestrel_contract.recordOf)({
400
- guides: (0, _orkestrel_contract.recordOf)({
481
+ repository: (0, _orkestrel_contract.recordOf)({
401
482
  base: isEndpoint,
402
483
  branch: isBranch,
403
484
  timeout: isTimeout
@@ -474,7 +555,7 @@ function matchesGitPath(path) {
474
555
  * directories. It is the inversion the contract asks for: the candidate set
475
556
  * is re-derived from the plan and narrowed by what git tracks, and the audit
476
557
  * must agree with that derivation rather than supply the set itself.
477
- * Repository metadata is protected because losing history is not a repair,
558
+ * Git metadata is protected because losing history is not a repair,
478
559
  * and a target's own `src` and `app` trees are protected because a
479
560
  * workspace's source is the one thing scaffold never plans and never owns. A
480
561
  * plan the compiler emits never maps a protected root, so this guard exists
@@ -505,7 +586,7 @@ function matchesProtectedPath(path) {
505
586
  * The vendoring deny-list. A host root is staged from a real checkout, so the
506
587
  * refusal is stated over the path rather than over the file's content: a
507
588
  * credential is recognizable by where it sits and what it is called long before
508
- * anything reads it. Repository metadata is included through
589
+ * anything reads it. Git metadata is included through
509
590
  * {@link matchesGitPath}, so one call answers the whole question and no caller
510
591
  * has to remember to ask twice.
511
592
  *
@@ -602,6 +683,25 @@ function computeDigest(content) {
602
683
  return (0, node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
603
684
  }
604
685
  /**
686
+ * Projects exact bytes stated in hexadecimal to their SHA-256 digest.
687
+ *
688
+ * @param hex - The exact lowercase hexadecimal bytes to digest.
689
+ * @returns Sixty-four lowercase hexadecimal digits.
690
+ * @throws `ScaffoldError('INVALID', …)` when `hex` is not exact bounded
691
+ * lowercase hexadecimal text.
692
+ *
693
+ * @example
694
+ * ```ts
695
+ * import { hexToDigest } from '@orkestrel/scaffold/server'
696
+ *
697
+ * hexToDigest('68690a') // '98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4'
698
+ * ```
699
+ */
700
+ function hexToDigest(hex) {
701
+ if (!(0, _src_core.isHex)(hex)) throw new _src_core.ScaffoldError("INVALID", "Digest input is not exact hexadecimal bytes", { hex });
702
+ return (0, node_crypto.createHash)("sha256").update(Buffer.from(hex, "hex")).digest("hex");
703
+ }
704
+ /**
605
705
  * Compute the digest of a vendored host's declared membership.
606
706
  *
607
707
  * @param entries - The ordered file membership declarations.
@@ -628,7 +728,8 @@ function computeManifestDigest(entries, roots) {
628
728
  entries: entries.map((entry) => ({
629
729
  storage: entry.storage,
630
730
  destination: entry.destination,
631
- executable: entry.executable
731
+ executable: entry.executable,
732
+ digest: entry.digest
632
733
  })),
633
734
  roots: [...roots]
634
735
  }));
@@ -1270,6 +1371,7 @@ function readSnapshot(target, paths) {
1270
1371
  * Read a vendored host's manifest, when it carries one.
1271
1372
  *
1272
1373
  * @param host - The vendored host root to read.
1374
+ * @param name - The root-relative manifest path. Default: `manifest.json`.
1273
1375
  * @returns The manifest, or `undefined` when the host carries none.
1274
1376
  * @throws `ScaffoldError('INVALID', …)` when `host` is not a host path.
1275
1377
  * @throws `ScaffoldError('TARGET', …)` when the manifest is there but cannot be
@@ -1295,9 +1397,8 @@ function readSnapshot(target, paths) {
1295
1397
  * readHostManifest('./dist/host') // the manifest, or undefined for a raw root
1296
1398
  * ```
1297
1399
  */
1298
- function readHostManifest(host) {
1400
+ function readHostManifest(host, name = MANIFEST_NAME) {
1299
1401
  if (!isFilesystemPath(host)) throw new _src_core.ScaffoldError("INVALID", "Host root is not a host path", { host });
1300
- const name = MANIFEST_NAME;
1301
1402
  const full = resolveContainedPath(host, name);
1302
1403
  if (full === void 0) throw new _src_core.ScaffoldError("INVALID", `Host manifest leaves its root at ${host}`, { host });
1303
1404
  const status = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(full));
@@ -1316,6 +1417,55 @@ function readHostManifest(host) {
1316
1417
  return manifest;
1317
1418
  }
1318
1419
  /**
1420
+ * Reads the installed vendored host floor as a value.
1421
+ *
1422
+ * @param root - The vendored host root. Default: the installed package's
1423
+ * vendored root, resolved from this module's location.
1424
+ * @returns The verified manifest and the exact bytes of every declared entry.
1425
+ * @throws `ScaffoldError('TARGET', …)` when the root is not a readable physical
1426
+ * directory, its manifest is absent or unreadable, the manifest does not verify,
1427
+ * or a declared file is unreadable or misses its digest.
1428
+ *
1429
+ * @remarks
1430
+ * Reads the same default floor the {@link Materializer} uses. Each declared
1431
+ * file is addressed through the manifest's storage name and retained under its
1432
+ * destination, so the returned value has the same shape as the installed root.
1433
+ * When this module executes from TypeScript source, the committed inventory is
1434
+ * the manifest and each checkout destination supplies its bytes. The emitted
1435
+ * module reads the staged `manifest.json` file and each storage path instead.
1436
+ *
1437
+ * @example
1438
+ * ```ts
1439
+ * import { readHostFloor } from '@orkestrel/scaffold/server'
1440
+ *
1441
+ * readHostFloor().manifest // the installed floor's verified membership
1442
+ * ```
1443
+ */
1444
+ function readHostFloor(root) {
1445
+ const location = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
1446
+ const module = (0, node_path.dirname)(location);
1447
+ const source = root === void 0 && (0, node_path.extname)(location) === ".ts";
1448
+ const host = root ?? (0, node_path.resolve)(module, source ? "../.." : "../../host");
1449
+ if (!isPhysicalDirectory(host)) throw new _src_core.ScaffoldError("TARGET", `The vendored host root is not readable at ${host}`, { host });
1450
+ const manifest = readHostManifest(host, source ? _src_core.HOST_INVENTORY_PATH : MANIFEST_NAME);
1451
+ if (manifest === void 0) throw new _src_core.ScaffoldError("TARGET", `The vendored host carries no manifest at ${host}`, { host });
1452
+ const bytes = {};
1453
+ for (const entry of manifest.entries) {
1454
+ const path = source ? entry.destination : entry.storage;
1455
+ const hex = readFileHex(host, path);
1456
+ if (hex === void 0 || hexToDigest(hex) !== entry.digest) throw new _src_core.ScaffoldError("TARGET", `The vendored host cannot read the declared file at ${path}`, {
1457
+ host,
1458
+ path,
1459
+ destination: entry.destination
1460
+ });
1461
+ bytes[entry.destination] = hex;
1462
+ }
1463
+ return {
1464
+ manifest,
1465
+ bytes
1466
+ };
1467
+ }
1468
+ /**
1319
1469
  * Derive one vendored-host manifest entry from a file in a checkout.
1320
1470
  *
1321
1471
  * @param destination - The target-relative path the file is written to.
@@ -1339,20 +1489,155 @@ function readHostManifest(host) {
1339
1489
  * import { readManifestEntry } from '@orkestrel/scaffold/server'
1340
1490
  *
1341
1491
  * readManifestEntry('.gitignore', '/tmp/checkout/.gitignore')
1342
- * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false }
1492
+ * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false, digest: '...' }
1343
1493
  * ```
1344
1494
  */
1345
1495
  function readManifestEntry(destination, source) {
1346
- if (!isPhysicalFile(source)) return void 0;
1347
- const status = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(source));
1348
- if (!status.success || status.value.size > _src_core.MAX_ARTIFACT_BYTES) return void 0;
1496
+ const digest = computeFileDigest(source);
1497
+ if (digest === void 0) return void 0;
1349
1498
  return {
1350
1499
  storage: pathToStorage(destination),
1351
1500
  destination,
1352
- executable: matchesExecutablePath(destination)
1501
+ executable: matchesExecutablePath(destination),
1502
+ digest
1353
1503
  };
1354
1504
  }
1355
1505
  /**
1506
+ * Assemble a whole vendored host from live files and the installed floor.
1507
+ *
1508
+ * @param files - The host-owned vendored files read from the repository, one
1509
+ * row per path.
1510
+ * @param floor - The installed host floor, which fixes the membership a fill
1511
+ * may draw from and supplies the bytes owned by another surface.
1512
+ * @returns The assembled host, or `undefined` when any row produced no answer or
1513
+ * names a path the floor does not declare, or when a host-owned path is absent.
1514
+ *
1515
+ * @remarks
1516
+ * The one place the host-owned all-or-nothing rule is decided, so no verb
1517
+ * restates it. The host surface contributes one baseline: a fill carries live
1518
+ * bytes for every path that surface writes, or it is nothing. A row that failed,
1519
+ * went missing, names an undeclared path, or leaves a host-owned path absent
1520
+ * answers `undefined`. Deferred paths are presence-only and retain the installed
1521
+ * floor bytes that their catalog or mirror surface owns; repair never writes
1522
+ * those floor bytes. One `Host` can therefore carry live host bytes beside floor
1523
+ * bytes without mixing baselines within a surface.
1524
+ *
1525
+ * The emitted entries keep the release's own order and its storage and
1526
+ * executable declarations, and carry digests recomputed over the bytes the fill
1527
+ * actually holds. That is what lets a reader verify the value against itself,
1528
+ * and it is why an undeclared path is refused rather than added: membership
1529
+ * moves with a release, never with a fetch.
1530
+ *
1531
+ * @example
1532
+ * ```ts
1533
+ * import { filesToHost } from '@orkestrel/scaffold/server'
1534
+ *
1535
+ * filesToHost([{ path: 'AGENTS.md', lookup: 'found', hex: '23204167656e74730a' }], floor)
1536
+ * // { manifest: { entries: [ … ], roots: [ … ], digest: '…' }, bytes: { 'AGENTS.md': '…' } }
1537
+ * ```
1538
+ */
1539
+ function filesToHost(files, floor) {
1540
+ const declared = new Set(floor.manifest.entries.map((entry) => entry.destination));
1541
+ const held = /* @__PURE__ */ new Map();
1542
+ for (const file of files) {
1543
+ if (file.lookup !== "found" || !declared.has(file.path)) return void 0;
1544
+ if (!(0, _src_core.isDeferredPath)(file.path)) held.set(file.path, file.hex);
1545
+ }
1546
+ const entries = [];
1547
+ const bytes = {};
1548
+ for (const entry of floor.manifest.entries) {
1549
+ const hex = (0, _src_core.isDeferredPath)(entry.destination) ? floor.bytes[entry.destination] : held.get(entry.destination);
1550
+ if (hex === void 0) return void 0;
1551
+ entries.push({
1552
+ storage: entry.storage,
1553
+ destination: entry.destination,
1554
+ executable: entry.executable,
1555
+ digest: hexToDigest(hex)
1556
+ });
1557
+ bytes[entry.destination] = hex;
1558
+ }
1559
+ return {
1560
+ manifest: {
1561
+ entries,
1562
+ roots: floor.manifest.roots,
1563
+ digest: computeManifestDigest(entries, floor.manifest.roots)
1564
+ },
1565
+ bytes
1566
+ };
1567
+ }
1568
+ /**
1569
+ * Stage the named destinations of a value host into a private root.
1570
+ *
1571
+ * @param host - The host whose bytes are written, keyed by destination.
1572
+ * @param root - The private directory to fill; it must already be a directory
1573
+ * this process may write into.
1574
+ * @param destinations - The destinations to stage, each declared by `host`.
1575
+ * @returns The entry staged for each destination, in the order requested.
1576
+ * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or a
1577
+ * storage name leaves it.
1578
+ * @throws `ScaffoldError('TARGET', …)` when a destination is one the host does
1579
+ * not declare, carries no bytes, or carries bytes that miss its declared digest.
1580
+ * @throws `ScaffoldError('WRITE', …)` when a file cannot be written or does not
1581
+ * read back as the bytes it was given.
1582
+ *
1583
+ * @remarks
1584
+ * Each file lands under the storage name the manifest declares and takes the
1585
+ * executable bit that manifest records, so a root filled from a value is the
1586
+ * same shape as one staged from a checkout and a reader cannot tell them apart.
1587
+ * That is what lets a mutation copy real files with real modes from bytes a
1588
+ * caller supplied, instead of degrading them to plain text writes.
1589
+ *
1590
+ * The bytes are digested before the write and the staged file after it, so a
1591
+ * value that disagrees with its own manifest is told apart from a write that
1592
+ * did not land.
1593
+ *
1594
+ * @example
1595
+ * ```ts
1596
+ * import { stageBytes } from '@orkestrel/scaffold/server'
1597
+ *
1598
+ * stageBytes(host, '/tmp/orkestrel-host-a1b2', ['scripts/codex.sh'])
1599
+ * // [{ storage: 'scripts/codex.sh', destination: 'scripts/codex.sh', executable: true, digest: '…' }]
1600
+ * ```
1601
+ */
1602
+ function stageBytes(host, root, destinations) {
1603
+ if (!isFilesystemPath(root)) throw new _src_core.ScaffoldError("INVALID", "Staging host root is not a host path", { host: root });
1604
+ const declared = new Map(host.manifest.entries.map((entry) => [entry.destination, entry]));
1605
+ const staged = [];
1606
+ for (const destination of destinations) {
1607
+ const entry = declared.get(destination);
1608
+ const hex = host.bytes[destination];
1609
+ if (entry === void 0 || hex === void 0) throw new _src_core.ScaffoldError("TARGET", `The host carries no bytes for ${destination}`, {
1610
+ host: root,
1611
+ destination
1612
+ });
1613
+ if (hexToDigest(hex) !== entry.digest) throw new _src_core.ScaffoldError("TARGET", `The host bytes for ${destination} miss its digest`, {
1614
+ host: root,
1615
+ destination
1616
+ });
1617
+ const full = resolveContainedPath(root, entry.storage);
1618
+ if (full === void 0) throw new _src_core.ScaffoldError("INVALID", `Host storage leaves its root at ${entry.storage}`, {
1619
+ host: root,
1620
+ storage: entry.storage
1621
+ });
1622
+ const written = (0, _orkestrel_contract.attempt)(() => {
1623
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(full), { recursive: true });
1624
+ (0, node_fs.writeFileSync)(full, Buffer.from(hex, "hex"), { flag: "wx" });
1625
+ if (entry.executable) (0, node_fs.chmodSync)(full, 493);
1626
+ });
1627
+ if (!written.success) throw new _src_core.ScaffoldError("WRITE", `Host bytes could not be staged at ${entry.storage}`, {
1628
+ host: root,
1629
+ storage: entry.storage,
1630
+ error: written.error
1631
+ });
1632
+ if (computeFileDigest(full) !== entry.digest) throw new _src_core.ScaffoldError("WRITE", `Staged host bytes at ${entry.storage} did not read back`, {
1633
+ host: root,
1634
+ storage: entry.storage
1635
+ });
1636
+ staged.push(entry);
1637
+ }
1638
+ return staged;
1639
+ }
1640
+ /**
1356
1641
  * Stage a vendored host root from a real checkout.
1357
1642
  *
1358
1643
  * @param checkout - The checkout the vendored paths are read from.
@@ -1442,7 +1727,7 @@ function stageHost(checkout, host) {
1442
1727
  missing
1443
1728
  });
1444
1729
  const stored = /* @__PURE__ */ new Set([MANIFEST_NAME]);
1445
- const entries = [];
1730
+ const candidates = [];
1446
1731
  for (const destination of vendored) {
1447
1732
  const full = resolveContainedPath(source, destination);
1448
1733
  if (full === void 0) throw new _src_core.ScaffoldError("INVALID", `Vendored path leaves its checkout at ${destination}`, {
@@ -1461,9 +1746,9 @@ function stageHost(checkout, host) {
1461
1746
  storage: entry.storage
1462
1747
  });
1463
1748
  stored.add(entry.storage);
1464
- entries.push(entry);
1749
+ candidates.push(entry);
1465
1750
  }
1466
- entries.sort((first, second) => first.storage < second.storage ? -1 : 1);
1751
+ candidates.sort((first, second) => first.storage < second.storage ? -1 : 1);
1467
1752
  roots.sort();
1468
1753
  const root = (0, node_path.resolve)(host);
1469
1754
  const established = (0, _orkestrel_contract.attempt)(() => (0, node_fs.mkdirSync)(root, { recursive: true }));
@@ -1471,7 +1756,8 @@ function stageHost(checkout, host) {
1471
1756
  host: root,
1472
1757
  ...established.success ? {} : { error: established.error }
1473
1758
  });
1474
- for (const entry of entries) {
1759
+ const entries = [];
1760
+ for (const entry of candidates) {
1475
1761
  const origin = resolveContainedPath(source, entry.destination);
1476
1762
  const destination = resolveContainedPath(root, entry.storage);
1477
1763
  if (origin === void 0 || destination === void 0) throw new _src_core.ScaffoldError("INVALID", `Vendored path leaves its root at ${entry.destination}`, {
@@ -1490,6 +1776,15 @@ function stageHost(checkout, host) {
1490
1776
  storage: entry.storage,
1491
1777
  error: copied.error
1492
1778
  });
1779
+ const digest = computeFileDigest(destination);
1780
+ if (digest === void 0) throw new _src_core.ScaffoldError("WRITE", `Vendored file could not be verified at ${entry.storage}`, {
1781
+ host: root,
1782
+ storage: entry.storage
1783
+ });
1784
+ entries.push({
1785
+ ...entry,
1786
+ digest
1787
+ });
1493
1788
  }
1494
1789
  const manifest = {
1495
1790
  entries,
@@ -1511,6 +1806,67 @@ function stageHost(checkout, host) {
1511
1806
  return entries;
1512
1807
  }
1513
1808
  /**
1809
+ * Stages the committed inventory of the files a vendored host carries.
1810
+ *
1811
+ * @param checkout - The checkout whose vendored paths are inventoried.
1812
+ * @param path - The host path where the JSON inventory is written.
1813
+ * @returns The validated manifest written to `path`.
1814
+ * @throws `ScaffoldError('INVALID', …)` when `path` is not a host path.
1815
+ * @throws `ScaffoldError('WRITE', …)` when a temporary host or the inventory
1816
+ * cannot be written or removed.
1817
+ * @throws `ScaffoldError('TARGET', …)` when the staged inventory does not read
1818
+ * back through the manifest validator.
1819
+ *
1820
+ * @remarks
1821
+ * Uses {@link stageHost} as the single vendored-path expansion. The temporary
1822
+ * host supplies the same entries, roots, per-file digests, and membership
1823
+ * digest as the published host while the requested output remains one JSON
1824
+ * file.
1825
+ *
1826
+ * @example
1827
+ * ```ts
1828
+ * import { stageInventory } from '@orkestrel/scaffold/server'
1829
+ *
1830
+ * stageInventory(process.cwd(), 'host.json') // the committed host inventory
1831
+ * ```
1832
+ */
1833
+ function stageInventory(checkout, path) {
1834
+ if (!isFilesystemPath(path)) throw new _src_core.ScaffoldError("INVALID", "Inventory destination is not a host path", { path });
1835
+ const temporary = (0, _orkestrel_contract.attempt)(() => (0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), "orkestrel-scaffold-host-")));
1836
+ if (!temporary.success) throw new _src_core.ScaffoldError("WRITE", "Inventory staging root could not be established", {
1837
+ path,
1838
+ error: temporary.error
1839
+ });
1840
+ const staged = (0, _orkestrel_contract.attempt)(() => {
1841
+ stageHost(checkout, temporary.value);
1842
+ const manifest = readHostManifest(temporary.value);
1843
+ if (manifest === void 0) throw new _src_core.ScaffoldError("TARGET", "The staged inventory carries no manifest", { path });
1844
+ const target = (0, node_path.resolve)(path);
1845
+ const published = (0, _orkestrel_contract.attempt)(() => {
1846
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
1847
+ (0, node_fs.writeFileSync)(target, `${JSON.stringify(manifest, null, " ")}\n`, "utf8");
1848
+ });
1849
+ if (!published.success) throw new _src_core.ScaffoldError("WRITE", `Inventory could not be written at ${target}`, {
1850
+ path: target,
1851
+ error: published.error
1852
+ });
1853
+ const text = readFileText((0, node_path.dirname)(target), (0, node_path.basename)(target), _src_core.MAX_MANIFEST_BYTES);
1854
+ const verified = text === void 0 ? void 0 : (0, _orkestrel_contract.parseJSONAs)(text, isHostManifest);
1855
+ if (verified === void 0 || verified.digest !== computeManifestDigest(verified.entries, verified.roots)) throw new _src_core.ScaffoldError("TARGET", `Inventory does not read back at ${target}`, { path: target });
1856
+ return verified;
1857
+ });
1858
+ const removed = (0, _orkestrel_contract.attempt)(() => (0, node_fs.rmSync)(temporary.value, {
1859
+ recursive: true,
1860
+ force: true
1861
+ }));
1862
+ if (!removed.success) throw new _src_core.ScaffoldError("WRITE", `Inventory staging root could not be removed`, {
1863
+ path: temporary.value,
1864
+ error: removed.error
1865
+ });
1866
+ if (!staged.success) throw staged.error;
1867
+ return staged.value;
1868
+ }
1869
+ /**
1514
1870
  * Capture one directory's physical identity.
1515
1871
  *
1516
1872
  * @param path - The resolved directory path to capture.
@@ -2196,6 +2552,12 @@ var WriteTransaction = class {
2196
2552
  * case, so a manifest naming `agents.md` for a stored `AGENTS.md` is refused on
2197
2553
  * a case-insensitive filesystem rather than silently resolved.
2198
2554
  *
2555
+ * That host arrives as a directory path or as a whole {@link Host} value, and
2556
+ * every verb reads one immutable host either way. A value is owned, verified
2557
+ * against its own membership and digests, and read in memory; a write fills it
2558
+ * into a private root and copies from there, so the executable declarations the
2559
+ * release fixed reach the target from either representation.
2560
+ *
2199
2561
  * What a mutation guarantees is exactly what {@link WriteTransaction}
2200
2562
  * guarantees, and no more: a caught failure part way through a commit rolls the
2201
2563
  * whole commit back, no destination ever receives half-written bytes, and a
@@ -2221,25 +2583,34 @@ var Materializer = class Materializer {
2221
2583
  static #opening = "<!-- orkestrel:catalog -->";
2222
2584
  static #closing = "<!-- /orkestrel:catalog -->";
2223
2585
  #emitter;
2224
- #host;
2586
+ #root;
2587
+ #value;
2225
2588
  #manifest;
2226
2589
  #entries;
2227
2590
  #destroyed = false;
2228
2591
  /**
2229
2592
  * Construct a materializer over one vendored host root.
2230
2593
  *
2231
- * @param options - The vendored host root, the initial listeners, and the
2232
- * listener-error handler.
2594
+ * @param options - The vendored host, in either representation, the initial
2595
+ * listeners, and the listener-error handler.
2233
2596
  * @throws {@link ScaffoldError} coded `INVALID` when `options` is present but
2234
2597
  * is not an option bag this materializer accepts, and `TARGET` when the host
2235
- * carries a manifest that cannot be read or does not match what it stores.
2598
+ * carries a manifest that cannot be read, does not match what it stores, or
2599
+ * is a value that does not agree with the bytes beside it.
2236
2600
  *
2237
2601
  * @remarks
2238
- * `host` defaults to this package's own vendored root, resolved from this
2239
- * module's own location so it never depends on the caller's working
2240
- * directory. A host carrying no manifest is read as a raw checkout and every
2602
+ * A `host` path defaults to this package's own vendored root, resolved from
2603
+ * this module's own location so it never depends on the caller's working
2604
+ * directory. A root carrying no manifest is read as a raw checkout and every
2241
2605
  * artifact maps onto it one to one.
2242
2606
  *
2607
+ * A `host` value is owned before it is read and then held immutable, so the
2608
+ * bytes this check measured are the bytes every later read returns. It is
2609
+ * verified the way a root is, against the same membership law: the manifest
2610
+ * digest must cover the membership beside it, no two entries may claim one
2611
+ * destination, and the fill must carry exactly one hashing byte string per
2612
+ * declared entry.
2613
+ *
2243
2614
  * The host is read here rather than on first use, so a broken vendored root
2244
2615
  * fails at construction where the caller can still act on it, and so nothing
2245
2616
  * has to carry a second flag recording whether the read has happened yet.
@@ -2250,15 +2621,27 @@ var Materializer = class Materializer {
2250
2621
  ...options?.on === void 0 ? {} : { on: options.on },
2251
2622
  ...options?.error === void 0 ? {} : { error: options.error }
2252
2623
  });
2253
- this.#host = options?.host ?? (0, node_path.resolve)((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)), "../../host");
2254
- const read = (0, _orkestrel_contract.attempt)(() => readHostManifest(this.#host));
2255
- if (!read.success) throw this.#error("TARGET", "The vendored host carries a manifest that cannot be read.", {
2256
- host: this.#host,
2257
- error: read.error
2258
- });
2259
- this.#manifest = read.value;
2260
- this.#entries = new Map((read.value?.entries ?? []).map((entry) => [entry.destination, entry]));
2261
- if (read.value !== void 0) this.#reconcile(read.value);
2624
+ const supplied = options?.host ?? readHostFloor();
2625
+ if (supplied !== void 0 && !isFilesystemPath(supplied)) {
2626
+ const value = this.#own(supplied);
2627
+ this.#value = value;
2628
+ this.#root = void 0;
2629
+ this.#manifest = value.manifest;
2630
+ this.#entries = new Map(value.manifest.entries.map((entry) => [entry.destination, entry]));
2631
+ this.#verify(value);
2632
+ } else {
2633
+ const root = supplied;
2634
+ this.#value = void 0;
2635
+ this.#root = root;
2636
+ const read = (0, _orkestrel_contract.attempt)(() => readHostManifest(root));
2637
+ if (!read.success) throw this.#error("TARGET", "The vendored host carries a manifest that cannot be read.", {
2638
+ host: root,
2639
+ error: read.error
2640
+ });
2641
+ this.#manifest = read.value;
2642
+ this.#entries = new Map((read.value?.entries ?? []).map((entry) => [entry.destination, entry]));
2643
+ if (read.value !== void 0) this.#reconcile(read.value, root);
2644
+ }
2262
2645
  }
2263
2646
  /** The materializer's observation channel. */
2264
2647
  get emitter() {
@@ -2426,26 +2809,32 @@ var Materializer = class Materializer {
2426
2809
  return this.#rewrite(directory, _src_core.CATALOG_AGENT_PATH, _src_core.MAX_ARTIFACT_BYTES, this.#recatalog(accepted));
2427
2810
  }
2428
2811
  /**
2429
- * Rewrite the declared dependency ranges the caller names in the target's manifest.
2812
+ * Rewrite the manifest regions the caller names in the target's manifest.
2430
2813
  *
2431
- * @param dependencies - The names and ranges the manifest must declare.
2814
+ * @param regions - The dependency ranges and script values the manifest must declare.
2432
2815
  * @param target - The directory to write into.
2433
- * @returns The manifest path, written when a declared range moved and skipped otherwise.
2816
+ * @returns The manifest path, written when a named region moved and skipped otherwise.
2434
2817
  * @throws {@link ScaffoldError} coded `INVALID` when an argument is not the
2435
2818
  * exact shape or names a package the manifest does not declare, `TARGET` when
2436
2819
  * the manifest is unreadable, `WRITE` when the write cannot be staged or
2437
2820
  * committed, and `DESTROYED` after teardown.
2438
2821
  *
2439
2822
  * @remarks
2440
- * No other part of the manifest is read back out or rewritten, so a consumer's
2441
- * own description, keywords, scripts, and formatting survive the call. Only a
2442
- * range already declared is rewritten: inserting a package would mean
2443
- * re-serializing the whole manifest, which is exactly the edit this verb
2444
- * promises not to make, so an undeclared name is refused by name instead.
2823
+ * No other part of the manifest is read back out or rewritten. The method
2824
+ * never reads or writes `peerDependencies` or `peerDependenciesMeta`. Only a
2825
+ * range already declared in its named writable section is rewritten, so an
2826
+ * undeclared name is refused instead of inserted.
2827
+ *
2828
+ * The regions refuse differently because their targets differ. A range
2829
+ * the manifest does not declare is the caller's mistake and throws. A script
2830
+ * holding a value the region does not accept is the workspace author's own
2831
+ * chain, so the script region is skipped without a byte moving and the range
2832
+ * region is still written. The advisory channel reports what the maintainer
2833
+ * must paste.
2445
2834
  */
2446
- declare(dependencies, target) {
2835
+ declare(regions, target) {
2447
2836
  this.#assertAlive();
2448
- const accepted = this.#accept(dependencies, isDependencies, "dependencies");
2837
+ const accepted = this.#accept(regions, isManifestRegionSet, "regions");
2449
2838
  const directory = this.#accept(target, isFilesystemPath, "target");
2450
2839
  return this.#rewrite(directory, "package.json", _src_core.MAX_MANIFEST_BYTES, this.#redeclare(accepted));
2451
2840
  }
@@ -2454,7 +2843,7 @@ var Materializer = class Materializer {
2454
2843
  *
2455
2844
  * @param plan - The compiled plan that decides which paths are foreign.
2456
2845
  * @param audit - The preview returned by this materializer's `audit` method; it must agree with the candidate set this call re-derives.
2457
- * @param repository - The target's git state; only a tracked path is ever deleted.
2846
+ * @param worktree - The target's git state; only a tracked path is ever deleted.
2458
2847
  * @param target - The directory to delete from.
2459
2848
  * @returns The paths removed.
2460
2849
  * @throws {@link ScaffoldError} coded `INVALID` when an argument is not the
@@ -2475,11 +2864,11 @@ var Materializer = class Materializer {
2475
2864
  * any foreign finding, including one the deletion itself would skip, because a
2476
2865
  * preview stale anywhere is stale evidence.
2477
2866
  */
2478
- remove(plan, audit, repository, target) {
2867
+ remove(plan, audit, worktree, target) {
2479
2868
  this.#assertAlive();
2480
2869
  const accepted = this.#accept(plan, _src_core.isPlan, "plan");
2481
2870
  const preview = this.#accept(audit, _src_core.isAudit, "audit");
2482
- const state = this.#accept(repository, isRepository, "repository");
2871
+ const state = this.#accept(worktree, isWorktree, "worktree");
2483
2872
  const directory = this.#accept(target, isFilesystemPath, "target");
2484
2873
  if (state.dirty.length > 0) throw this.#error("TARGET", `The target at ${directory} carries uncommitted changes.`, {
2485
2874
  target: directory,
@@ -2522,20 +2911,41 @@ var Materializer = class Materializer {
2522
2911
  this.#emitter.emit("destroy");
2523
2912
  this.#emitter.destroy();
2524
2913
  }
2525
- #reconcile(manifest) {
2526
- const walked = (0, _orkestrel_contract.attempt)(() => listFiles(this.#host));
2914
+ #own(host) {
2915
+ const owned = (0, _src_core.cloneValue)(host);
2916
+ if (isHost(owned)) return owned;
2917
+ throw this.#error("INVALID", "The host argument is not the exact shape this materializer accepts.", { field: "host" });
2918
+ }
2919
+ #verify(host) {
2920
+ const { entries, roots } = host.manifest;
2921
+ if (host.manifest.digest !== computeManifestDigest(entries, roots)) throw this.#error("TARGET", "The vendored host manifest does not cover the membership beside it.");
2922
+ if (this.#entries.size !== entries.length) throw this.#error("TARGET", "The vendored host manifest maps two files to one destination.");
2923
+ if (new Set(entries.map((entry) => entry.storage)).size !== entries.length) throw this.#error("TARGET", "The vendored host manifest maps two destinations to one file.");
2924
+ const held = Object.keys(host.bytes);
2925
+ if (held.length !== entries.length) throw this.#error("TARGET", "The vendored host does not carry what its manifest declares.", {
2926
+ held: held.length,
2927
+ declared: entries.length
2928
+ });
2929
+ for (const entry of entries) {
2930
+ const hex = host.bytes[entry.destination];
2931
+ if (hex === void 0) throw this.#error("TARGET", `The vendored host carries no bytes for ${entry.destination}.`, { destination: entry.destination });
2932
+ if (hexToDigest(hex) !== entry.digest) throw this.#error("TARGET", `The vendored host carries bytes for ${entry.destination} that miss its digest.`, { destination: entry.destination });
2933
+ }
2934
+ }
2935
+ #reconcile(manifest, root) {
2936
+ const walked = (0, _orkestrel_contract.attempt)(() => listFiles(root));
2527
2937
  if (!walked.success) throw this.#error("TARGET", "The vendored host cannot be inventoried.", {
2528
- host: this.#host,
2938
+ host: root,
2529
2939
  error: walked.error
2530
2940
  });
2531
2941
  const declared = [...manifest.entries.map((entry) => entry.storage), "manifest.json"].sort();
2532
2942
  const stored = walked.value;
2533
2943
  if (stored.length !== declared.length || stored.some((name, index) => name !== declared[index])) throw this.#error("TARGET", "The vendored host does not store what its manifest declares.", {
2534
- host: this.#host,
2944
+ host: root,
2535
2945
  stored: stored.length,
2536
2946
  declared: declared.length
2537
2947
  });
2538
- if (this.#entries.size !== manifest.entries.length) throw this.#error("TARGET", "The vendored host manifest maps two files to one destination.", { host: this.#host });
2948
+ if (this.#entries.size !== manifest.entries.length) throw this.#error("TARGET", "The vendored host manifest maps two files to one destination.", { host: root });
2539
2949
  }
2540
2950
  #hydrate(plan) {
2541
2951
  const artifacts = [];
@@ -2551,7 +2961,7 @@ var Materializer = class Materializer {
2551
2961
  artifacts.push(...expanded);
2552
2962
  }
2553
2963
  if (remaining < 0) throw this.#error("TARGET", "The hydrated plan retains more bytes than one plan may.", {
2554
- host: this.#host,
2964
+ host: this.#root,
2555
2965
  limit: _src_core.MAX_TOTAL_ARTIFACT_BYTES
2556
2966
  });
2557
2967
  return {
@@ -2585,7 +2995,8 @@ var Materializer = class Materializer {
2585
2995
  continue;
2586
2996
  }
2587
2997
  if (this.#manifest !== void 0) continue;
2588
- const directory = resolveContainedPath(this.#host, source);
2998
+ const root = this.#root;
2999
+ const directory = root === void 0 ? void 0 : resolveContainedPath(root, source);
2589
3000
  if (directory !== void 0 && isPhysicalDirectory(directory)) roots.add(artifact.path);
2590
3001
  }
2591
3002
  return [...roots];
@@ -2597,7 +3008,7 @@ var Materializer = class Materializer {
2597
3008
  const matched = manifest.entries.filter((entry) => entry.destination === source || entry.destination.startsWith(`${source}/`));
2598
3009
  const rooted = manifest.roots.some((root) => root === source || root.startsWith(`${source}/`));
2599
3010
  if (matched.length === 0 && !rooted) throw this.#error("TARGET", `The vendored host does not carry ${source}.`, {
2600
- host: this.#host,
3011
+ host: this.#root,
2601
3012
  source
2602
3013
  });
2603
3014
  const expanded = [];
@@ -2608,29 +3019,30 @@ var Materializer = class Materializer {
2608
3019
  expanded.push(this.#presence(artifact, path, entry.destination));
2609
3020
  continue;
2610
3021
  }
2611
- if (this.#deferred(path)) {
3022
+ if ((0, _src_core.isDeferredPath)(path)) {
2612
3023
  expanded.push(this.#presence(artifact, path, entry.destination));
2613
3024
  continue;
2614
3025
  }
2615
- const hex = this.#read(entry.storage, budget);
3026
+ const hex = this.#read(entry, budget);
2616
3027
  budget -= hex.length / 2;
2617
3028
  expanded.push(this.#hydrated(artifact, path, entry.destination, hex));
2618
3029
  }
2619
3030
  return expanded;
2620
3031
  }
2621
3032
  #expandRaw(artifact, source, remaining) {
2622
- const full = resolveContainedPath(this.#host, source);
3033
+ const root = this.#root;
3034
+ const full = root === void 0 ? void 0 : resolveContainedPath(root, source);
2623
3035
  if (full === void 0) throw this.#error("TARGET", `The host source at ${source} leaves its root.`, {
2624
- host: this.#host,
3036
+ host: this.#root,
2625
3037
  source
2626
3038
  });
2627
3039
  if (isPhysicalFile(full)) {
2628
3040
  if (_src_core.WORKSPACE_OWNED_PATHS.includes(artifact.path)) return [this.#presence(artifact, artifact.path, source)];
2629
- if (this.#deferred(artifact.path)) return [this.#presence(artifact, artifact.path, source)];
2630
- return [this.#hydrated(artifact, artifact.path, source, this.#read(source, remaining))];
3041
+ if ((0, _src_core.isDeferredPath)(artifact.path)) return [this.#presence(artifact, artifact.path, source)];
3042
+ return [this.#hydrated(artifact, artifact.path, source, this.#readRoot(source, remaining))];
2631
3043
  }
2632
3044
  if (!isPhysicalDirectory(full)) throw this.#error("TARGET", `The host source at ${source} is not a readable file.`, {
2633
- host: this.#host,
3045
+ host: this.#root,
2634
3046
  source
2635
3047
  });
2636
3048
  const expanded = [];
@@ -2642,11 +3054,11 @@ var Materializer = class Materializer {
2642
3054
  expanded.push(this.#presence(artifact, path, destination));
2643
3055
  continue;
2644
3056
  }
2645
- if (this.#deferred(path)) {
3057
+ if ((0, _src_core.isDeferredPath)(path)) {
2646
3058
  expanded.push(this.#presence(artifact, path, destination));
2647
3059
  continue;
2648
3060
  }
2649
- const hex = this.#read(destination, budget);
3061
+ const hex = this.#readRoot(destination, budget);
2650
3062
  budget -= hex.length / 2;
2651
3063
  expanded.push(this.#hydrated(artifact, path, destination, hex));
2652
3064
  }
@@ -2670,15 +3082,12 @@ var Materializer = class Materializer {
2670
3082
  const source = artifact.source ?? artifact.path;
2671
3083
  if (destination === source) return artifact.path;
2672
3084
  if (!destination.startsWith(`${source}/`)) throw this.#error("TARGET", `The vendored destination ${destination} is outside ${source}.`, {
2673
- host: this.#host,
3085
+ host: this.#root,
2674
3086
  source,
2675
3087
  destination
2676
3088
  });
2677
3089
  return `${artifact.path}/${destination.slice(source.length + 1)}`;
2678
3090
  }
2679
- #deferred(path) {
2680
- return path === _src_core.CATALOG_AGENT_PATH || path.startsWith("guides/") && path.endsWith(".md");
2681
- }
2682
3091
  #presence(artifact, path, destination) {
2683
3092
  return {
2684
3093
  path,
@@ -2700,10 +3109,20 @@ var Materializer = class Materializer {
2700
3109
  hex
2701
3110
  };
2702
3111
  }
2703
- #read(storage, budget) {
2704
- const hex = readFileHex(this.#host, storage, Math.max(0, Math.min(_src_core.MAX_ARTIFACT_BYTES, budget)));
3112
+ #read(entry, budget) {
3113
+ const held = this.#value?.bytes[entry.destination];
3114
+ if (held === void 0) return this.#readRoot(entry.storage, budget);
3115
+ if (held.length / 2 > Math.max(0, Math.min(_src_core.MAX_ARTIFACT_BYTES, budget))) throw this.#error("TARGET", `The vendored host cannot be read at ${entry.destination}.`, {
3116
+ destination: entry.destination,
3117
+ limit: _src_core.MAX_ARTIFACT_BYTES
3118
+ });
3119
+ return held;
3120
+ }
3121
+ #readRoot(storage, budget) {
3122
+ const root = this.#root;
3123
+ const hex = root === void 0 ? void 0 : readFileHex(root, storage, Math.max(0, Math.min(_src_core.MAX_ARTIFACT_BYTES, budget)));
2705
3124
  if (hex === void 0) throw this.#error("TARGET", `The vendored host cannot be read at ${storage}.`, {
2706
- host: this.#host,
3125
+ host: this.#root,
2707
3126
  storage
2708
3127
  });
2709
3128
  return hex;
@@ -2809,20 +3228,49 @@ var Materializer = class Materializer {
2809
3228
  skipped,
2810
3229
  removed: []
2811
3230
  });
2812
- const transaction = this.#open(target, paths, preconditions);
2813
- const staged = (0, _orkestrel_contract.attempt)(() => {
2814
- for (const artifact of writes) if (artifact.origin === "host") this.#copy(transaction, artifact);
2815
- else transaction.write(artifact.path, artifact.content);
2816
- for (const path of directories) transaction.establish(path);
2817
- });
2818
- const written = this.#close(transaction, staged, target);
2819
- for (const path of written) this.#emitter.emit("write", path);
2820
- return this.#finish({
2821
- target,
2822
- written,
2823
- skipped,
2824
- removed: []
3231
+ const filled = this.#fill(writes);
3232
+ try {
3233
+ const transaction = this.#open(target, paths, preconditions);
3234
+ const staged = (0, _orkestrel_contract.attempt)(() => {
3235
+ for (const artifact of writes) if (artifact.origin === "host") this.#copy(transaction, artifact, filled ?? this.#root);
3236
+ else transaction.write(artifact.path, artifact.content);
3237
+ for (const path of directories) transaction.establish(path);
3238
+ });
3239
+ const written = this.#close(transaction, staged, target);
3240
+ for (const path of written) this.#emitter.emit("write", path);
3241
+ return this.#finish({
3242
+ target,
3243
+ written,
3244
+ skipped,
3245
+ removed: []
3246
+ });
3247
+ } finally {
3248
+ if (filled !== void 0) (0, node_fs.rmSync)(filled, {
3249
+ recursive: true,
3250
+ force: true
3251
+ });
3252
+ }
3253
+ }
3254
+ #fill(writes) {
3255
+ const value = this.#value;
3256
+ if (value === void 0) return void 0;
3257
+ const destinations = /* @__PURE__ */ new Set();
3258
+ for (const artifact of writes) {
3259
+ if (artifact.origin !== "host") continue;
3260
+ destinations.add(artifact.source ?? artifact.path);
3261
+ }
3262
+ if (destinations.size === 0) return void 0;
3263
+ const opened = (0, _orkestrel_contract.attempt)(() => (0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), "orkestrel-scaffold-fill-")));
3264
+ if (!opened.success) throw this.#error("WRITE", "The supplied host could not be filled into a private root.", { error: opened.error });
3265
+ const root = opened.value;
3266
+ const stored = (0, _orkestrel_contract.attempt)(() => stageBytes(value, root, [...destinations]));
3267
+ if (stored.success) return root;
3268
+ (0, node_fs.rmSync)(root, {
3269
+ recursive: true,
3270
+ force: true
2825
3271
  });
3272
+ this.#emitter.emit("error", stored.error);
3273
+ throw stored.error;
2826
3274
  }
2827
3275
  #purge(target, removals, skipped, preconditions) {
2828
3276
  if (removals.length === 0) return this.#finish({
@@ -2866,13 +3314,13 @@ var Materializer = class Materializer {
2866
3314
  this.#emitter.emit("error", committed.error);
2867
3315
  throw committed.error;
2868
3316
  }
2869
- #copy(transaction, artifact) {
3317
+ #copy(transaction, artifact, root) {
2870
3318
  const destination = artifact.source ?? artifact.path;
2871
3319
  const entry = this.#entries.get(destination);
2872
3320
  const storage = entry === void 0 ? destination : entry.storage;
2873
- const source = resolveContainedPath(this.#host, storage);
3321
+ const source = root === void 0 ? void 0 : resolveContainedPath(root, storage);
2874
3322
  if (source === void 0) throw this.#error("TARGET", `The vendored source at ${storage} leaves its root.`, {
2875
- host: this.#host,
3323
+ host: root,
2876
3324
  storage
2877
3325
  });
2878
3326
  transaction.copy(artifact.path, source, entry?.executable === true);
@@ -2923,12 +3371,14 @@ var Materializer = class Materializer {
2923
3371
  #cell(note) {
2924
3372
  return note.replaceAll("|", "\\|").replaceAll(/\s+/gu, " ").trim();
2925
3373
  }
2926
- #redeclare(dependencies) {
3374
+ #redeclare(regions) {
2927
3375
  return (text) => {
2928
- const manifest = (0, _src_core.replaceManifestRanges)(text, dependencies);
2929
- if (manifest !== void 0) return manifest;
2930
- const missing = dependencies.find((dependency) => !text.includes(JSON.stringify(dependency.name)));
2931
- throw this.#error("INVALID", `The manifest does not declare ${missing?.name ?? "a requested package"}, so its range cannot be rewritten.`, missing === void 0 ? void 0 : { name: missing.name });
3376
+ const manifest = (0, _src_core.replaceManifestRanges)(text, regions.pins);
3377
+ if (manifest === void 0) {
3378
+ const missing = [...regions.pins.runtime, ...regions.pins.development].find((dependency) => !text.includes(JSON.stringify(dependency.name)));
3379
+ throw this.#error("INVALID", `The manifest does not declare ${missing?.name ?? "a requested package"}, so its range cannot be rewritten.`, missing === void 0 ? void 0 : { name: missing.name });
3380
+ }
3381
+ return (0, _src_core.replaceManifestScripts)(manifest, regions.scripts) ?? manifest;
2932
3382
  };
2933
3383
  }
2934
3384
  #finish(result) {
@@ -2969,6 +3419,11 @@ var Materializer = class Materializer {
2969
3419
  * there is no fleet to report, so an unreachable or malformed list is a coded
2970
3420
  * `FETCH` failure.
2971
3421
  *
3422
+ * The vendored-file inventory is the other read a whole call rests on, and it
3423
+ * fails the other way: it fails every row of its call rather than throwing, so
3424
+ * the caller receives one whole dead answer it can replace with one whole
3425
+ * baseline instead of a mixture it would have to reconcile.
3426
+ *
2972
3427
  * Requests are unauthenticated because every fleet repository is public, and
2973
3428
  * they follow no redirect, so a misconfigured or hostile endpoint cannot move a
2974
3429
  * read to another host. Each one is bounded by its endpoint's timeout and by the
@@ -2989,19 +3444,20 @@ var Materializer = class Materializer {
2989
3444
  * ```
2990
3445
  */
2991
3446
  var Upstream = class Upstream {
2992
- static #defaultGuide = "https://raw.githubusercontent.com";
3447
+ static #defaultRepository = "https://raw.githubusercontent.com";
2993
3448
  static #defaultRegistry = "https://registry.npmjs.org";
2994
3449
  static #defaultBranch = "main";
2995
3450
  static #defaultTimeout = 1e4;
2996
3451
  static #defaultConcurrency = 6;
2997
3452
  static #defaultRetries = 0;
2998
3453
  static #scope = "orkestrel";
3454
+ static #vendor = "scaffold";
2999
3455
  static #unreadable = "the answer carries no readable latest version";
3000
3456
  static #packument = "application/vnd.npm.install-v1+json";
3001
3457
  #emitter;
3002
- #guideBase;
3003
- #guideBranch;
3004
- #guideTimeout;
3458
+ #repositoryBase;
3459
+ #repositoryBranch;
3460
+ #repositoryTimeout;
3005
3461
  #registryBase;
3006
3462
  #registryTimeout;
3007
3463
  #concurrency;
@@ -3011,7 +3467,7 @@ var Upstream = class Upstream {
3011
3467
  #controller = new AbortController();
3012
3468
  #destroyed = false;
3013
3469
  /**
3014
- * Construct a reader over one guide host and one registry.
3470
+ * Construct a reader over one raw content host and one registry.
3015
3471
  *
3016
3472
  * @param options - The endpoints, the request bounds, the initial
3017
3473
  * listeners, and the listener-error handler.
@@ -3035,9 +3491,9 @@ var Upstream = class Upstream {
3035
3491
  ...options?.on === void 0 ? {} : { on: options.on },
3036
3492
  ...options?.error === void 0 ? {} : { error: options.error }
3037
3493
  });
3038
- this.#guideBase = this.#endpoint(options?.guides?.base ?? Upstream.#defaultGuide, "guides");
3039
- this.#guideBranch = options?.guides?.branch ?? Upstream.#defaultBranch;
3040
- this.#guideTimeout = options?.guides?.timeout ?? Upstream.#defaultTimeout;
3494
+ this.#repositoryBase = this.#endpoint(options?.repository?.base ?? Upstream.#defaultRepository, "repository");
3495
+ this.#repositoryBranch = options?.repository?.branch ?? Upstream.#defaultBranch;
3496
+ this.#repositoryTimeout = options?.repository?.timeout ?? Upstream.#defaultTimeout;
3041
3497
  this.#registryBase = this.#endpoint(options?.registry?.base ?? Upstream.#defaultRegistry, "registry");
3042
3498
  this.#registryTimeout = options?.registry?.timeout ?? Upstream.#defaultTimeout;
3043
3499
  this.#concurrency = options?.concurrency ?? Upstream.#defaultConcurrency;
@@ -3115,6 +3571,54 @@ var Upstream = class Upstream {
3115
3571
  return this.#gather(accepted, (name) => this.#mirror(name, observed, allowance));
3116
3572
  }
3117
3573
  /**
3574
+ * Read each named vendored file from the repository, beside the target bytes it answers for.
3575
+ *
3576
+ * @param paths - The target-relative vendored paths to read.
3577
+ * @param current - The target files as exact bytes, keyed by the same paths.
3578
+ * @returns One file verdict per path, in input order.
3579
+ * @throws {@link ScaffoldError} coded `INVALID` when `paths` is not a bounded
3580
+ * list of target-relative paths or `current` is not a snapshot, and
3581
+ * `DESTROYED` when the reader is torn down before or during the call.
3582
+ *
3583
+ * @remarks
3584
+ * The committed inventory is read once per call and decides every row, so a
3585
+ * path whose declared digest already matches the target's own bytes is `found`
3586
+ * without a request and the call spends nothing on it. An inventory that
3587
+ * produces no answer fails every row of the call rather than leaving some rows
3588
+ * live and some dead, which is what leaves the caller one whole baseline to
3589
+ * fall back to. A path the inventory does not name is `missing`.
3590
+ *
3591
+ * A fetched response's decoded content is verified against the digest the
3592
+ * inventory declares for that path, before any character decoding. Transport
3593
+ * encoding is transparent and does not enter the comparison, so content that
3594
+ * does not hash to the inventory's claim fails its row rather than reaching a
3595
+ * write. That is integrity against a single committed baseline, not
3596
+ * authenticity: it detects truncated, substituted, or stale content, and it
3597
+ * says nothing about who published the inventory.
3598
+ *
3599
+ * A guide mirror is never answered here whatever the caller asks for and
3600
+ * whatever the target holds, because those bytes belong to `fetch` and to the
3601
+ * mirror verb that writes them.
3602
+ *
3603
+ * @example
3604
+ * ```ts
3605
+ * import { Upstream } from '@orkestrel/scaffold/server'
3606
+ *
3607
+ * const upstream = new Upstream()
3608
+ * await upstream.read(['AGENTS.md'], { 'AGENTS.md': '2320416745' })
3609
+ * upstream.destroy()
3610
+ * ```
3611
+ */
3612
+ async read(paths, current) {
3613
+ this.#assertAlive();
3614
+ const accepted = this.#accept(paths, isPaths, "paths");
3615
+ const observed = this.#accept(current, _src_core.isSnapshot, "current");
3616
+ if (accepted.length === 0) return [];
3617
+ const allowance = { remaining: this.#budget };
3618
+ const inventory = await this.#inventory(allowance);
3619
+ return this.#gather(accepted, (path) => this.#file(path, inventory, observed, allowance));
3620
+ }
3621
+ /**
3118
3622
  * Catalog the published fleet from the registry's organization package list.
3119
3623
  *
3120
3624
  * @returns One row per published package, sorted by name.
@@ -3191,14 +3695,14 @@ var Upstream = class Upstream {
3191
3695
  return parsed.href.replace(/\/+$/u, "");
3192
3696
  }
3193
3697
  async #release(dependency, allowance) {
3194
- const outcome = await this.#read(this.#registryURL(dependency.name), this.#registryTimeout, allowance, Upstream.#packument);
3698
+ const outcome = await this.#readWithRetries(this.#registryURL(dependency.name), this.#registryTimeout, allowance, Upstream.#packument);
3195
3699
  const latest = outcome.lookup === "found" ? this.#releaseVersion(outcome.content, dependency.range) : void 0;
3196
3700
  const tagged = outcome.lookup === "found" ? this.#latest(outcome.content) : void 0;
3197
3701
  const major = tagged === void 0 ? void 0 : (0, _src_core.extractVersion)(tagged)?.[0];
3198
3702
  const release = latest === void 0 ? {
3199
3703
  name: dependency.name,
3200
3704
  range: dependency.range,
3201
- lookup: outcome.lookup === "missing" ? "missing" : "failed",
3705
+ lookup: outcome.lookup === "found" ? "unmatched" : outcome.lookup,
3202
3706
  note: outcome.lookup === "found" ? Upstream.#unreadable : outcome.note,
3203
3707
  ...major === void 0 ? {} : { major }
3204
3708
  } : {
@@ -3213,7 +3717,7 @@ var Upstream = class Upstream {
3213
3717
  }
3214
3718
  async #mirror(name, current, allowance) {
3215
3719
  const path = (0, _src_core.nameToGuide)(name);
3216
- const outcome = await this.#read(this.#guideURL(name), this.#guideTimeout, allowance);
3720
+ const outcome = await this.#readWithRetries(this.#guideURL(name), this.#repositoryTimeout, allowance);
3217
3721
  const observed = current[path];
3218
3722
  const mirror = outcome.lookup === "found" ? {
3219
3723
  name,
@@ -3231,8 +3735,102 @@ var Upstream = class Upstream {
3231
3735
  this.#emitter.emit("mirror", mirror);
3232
3736
  return mirror;
3233
3737
  }
3738
+ async #file(path, inventory, current, allowance) {
3739
+ const file = await this.#answer(path, inventory, current[path], allowance);
3740
+ this.#emitter.emit("file", file);
3741
+ return file;
3742
+ }
3743
+ async #answer(path, inventory, observed, allowance) {
3744
+ const carried = observed === void 0 ? {} : { observed };
3745
+ if (inventory.lookup !== "found") return {
3746
+ path,
3747
+ lookup: inventory.lookup,
3748
+ note: inventory.note,
3749
+ ...carried
3750
+ };
3751
+ if ((0, _src_core.inferGroup)(path) === "guides") return {
3752
+ path,
3753
+ lookup: "missing",
3754
+ note: `${path} is a guide mirror the fleet serves`,
3755
+ ...carried
3756
+ };
3757
+ if (inventory.duplicates.has(path)) return {
3758
+ path,
3759
+ lookup: "failed",
3760
+ note: `the inventory names ${path} more than once`,
3761
+ ...carried
3762
+ };
3763
+ const digest = inventory.digests.get(path);
3764
+ if (digest === void 0) return {
3765
+ path,
3766
+ lookup: "missing",
3767
+ note: `the inventory does not name ${path}`,
3768
+ ...carried
3769
+ };
3770
+ if (observed !== void 0 && hexToDigest(observed) === digest) return {
3771
+ path,
3772
+ lookup: "found",
3773
+ hex: observed,
3774
+ ...carried
3775
+ };
3776
+ const outcome = await this.#readWithRetries(this.#vendorURL(path), this.#repositoryTimeout, allowance, void 0, true);
3777
+ if (outcome.lookup !== "found") return {
3778
+ path,
3779
+ lookup: outcome.lookup,
3780
+ note: outcome.note,
3781
+ ...carried
3782
+ };
3783
+ if (hexToDigest(outcome.hex) !== digest) return {
3784
+ path,
3785
+ lookup: "failed",
3786
+ note: `the bytes served for ${path} do not match the digest the inventory declares`,
3787
+ ...carried
3788
+ };
3789
+ return {
3790
+ path,
3791
+ lookup: "found",
3792
+ hex: outcome.hex,
3793
+ ...carried
3794
+ };
3795
+ }
3796
+ async #inventory(allowance) {
3797
+ const url = this.#vendorURL(_src_core.HOST_INVENTORY_PATH);
3798
+ const empty = {
3799
+ digests: /* @__PURE__ */ new Map(),
3800
+ duplicates: /* @__PURE__ */ new Set()
3801
+ };
3802
+ const outcome = await this.#readWithRetries(url, this.#repositoryTimeout, allowance);
3803
+ if (outcome.lookup !== "found") return {
3804
+ ...empty,
3805
+ lookup: outcome.lookup,
3806
+ note: outcome.lookup === "missing" ? `the vendored inventory at ${url} is not published there` : `the vendored inventory at ${url} produced no answer: ${outcome.note}`
3807
+ };
3808
+ const manifest = (0, _orkestrel_contract.parseJSONAs)(outcome.content, isHostManifest);
3809
+ if (manifest === void 0) return {
3810
+ ...empty,
3811
+ lookup: "failed",
3812
+ note: `the vendored inventory at ${url} is not a readable manifest`
3813
+ };
3814
+ if (manifest.digest !== computeManifestDigest(manifest.entries, manifest.roots)) return {
3815
+ ...empty,
3816
+ lookup: "failed",
3817
+ note: `the vendored inventory at ${url} does not match its own membership digest`
3818
+ };
3819
+ const digests = /* @__PURE__ */ new Map();
3820
+ const duplicates = /* @__PURE__ */ new Set();
3821
+ for (const entry of manifest.entries) {
3822
+ if (digests.has(entry.destination)) duplicates.add(entry.destination);
3823
+ digests.set(entry.destination, entry.digest);
3824
+ }
3825
+ return {
3826
+ lookup: "found",
3827
+ digests,
3828
+ duplicates,
3829
+ note: ""
3830
+ };
3831
+ }
3234
3832
  async #entry(name, allowance) {
3235
- const outcome = await this.#read(this.#registryURL(name), this.#registryTimeout, allowance, Upstream.#packument);
3833
+ const outcome = await this.#readWithRetries(this.#registryURL(name), this.#registryTimeout, allowance, Upstream.#packument);
3236
3834
  const version = outcome.lookup === "found" ? this.#latest(outcome.content) : void 0;
3237
3835
  if (version !== void 0) return {
3238
3836
  name,
@@ -3242,7 +3840,7 @@ var Upstream = class Upstream {
3242
3840
  };
3243
3841
  return {
3244
3842
  name,
3245
- lookup: outcome.lookup === "missing" ? "missing" : "failed",
3843
+ lookup: outcome.lookup === "found" ? "unmatched" : outcome.lookup,
3246
3844
  note: outcome.lookup === "found" ? Upstream.#unreadable : outcome.note
3247
3845
  };
3248
3846
  }
@@ -3270,7 +3868,7 @@ var Upstream = class Upstream {
3270
3868
  }
3271
3869
  async #packages(allowance) {
3272
3870
  const url = `${this.#registryBase}/-/org/${Upstream.#scope}/package`;
3273
- const outcome = await this.#read(url, this.#registryTimeout, allowance);
3871
+ const outcome = await this.#readWithRetries(url, this.#registryTimeout, allowance);
3274
3872
  if (outcome.lookup !== "found") throw this.#error("FETCH", `The organization package list at ${url} produced no answer.`, {
3275
3873
  url,
3276
3874
  note: outcome.note
@@ -3316,15 +3914,22 @@ var Upstream = class Upstream {
3316
3914
  return `${this.#registryBase}/${encodeURIComponent(name).replaceAll("%40", "@")}`;
3317
3915
  }
3318
3916
  #guideURL(name) {
3319
- const branch = this.#guideBranch.split("/").map((segment) => encodeURIComponent(segment)).join("/");
3917
+ const branch = this.#encode(this.#repositoryBranch);
3320
3918
  const repository = encodeURIComponent(name.slice(name.lastIndexOf("/") + 1));
3321
- return `${this.#guideBase}/${Upstream.#scope}/${repository}/refs/heads/${branch}/${(0, _src_core.nameToGuide)(name)}`;
3919
+ return `${this.#repositoryBase}/${Upstream.#scope}/${repository}/refs/heads/${branch}/${(0, _src_core.nameToGuide)(name)}`;
3322
3920
  }
3323
- async #read(url, timeout, allowance, accept) {
3921
+ #vendorURL(path) {
3922
+ const branch = this.#encode(this.#repositoryBranch);
3923
+ return `${this.#repositoryBase}/${Upstream.#scope}/${Upstream.#vendor}/refs/heads/${branch}/${this.#encode(path)}`;
3924
+ }
3925
+ #encode(path) {
3926
+ return path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
3927
+ }
3928
+ async #readWithRetries(url, timeout, allowance, accept, binary = false) {
3324
3929
  let note = "";
3325
- for (let attempt = 0; attempt <= this.#retries; attempt += 1) {
3930
+ for (let round = 0; round <= this.#retries; round += 1) {
3326
3931
  this.#assertAlive();
3327
- const outcome = await this.#request(url, timeout, allowance, accept);
3932
+ const outcome = binary ? await this.#request(url, timeout, allowance, accept, true) : await this.#request(url, timeout, allowance, accept);
3328
3933
  if (outcome.lookup !== "failed") return outcome;
3329
3934
  note = outcome.note;
3330
3935
  }
@@ -3332,18 +3937,29 @@ var Upstream = class Upstream {
3332
3937
  url,
3333
3938
  note
3334
3939
  });
3335
- return {
3940
+ return binary ? {
3336
3941
  lookup: "failed",
3337
- content: "",
3942
+ hex: "",
3338
3943
  note
3339
- };
3340
- }
3341
- async #request(url, timeout, allowance, accept) {
3342
- if (allowance.remaining <= 0) return {
3944
+ } : {
3343
3945
  lookup: "failed",
3344
3946
  content: "",
3345
- note: `the call spent its ${String(this.#budget)}-byte allowance`
3947
+ note
3346
3948
  };
3949
+ }
3950
+ async #request(url, timeout, allowance, accept, binary = false) {
3951
+ if (allowance.remaining <= 0) {
3952
+ const note = `the call spent its ${String(this.#budget)}-byte allowance`;
3953
+ return binary ? {
3954
+ lookup: "failed",
3955
+ hex: "",
3956
+ note
3957
+ } : {
3958
+ lookup: "failed",
3959
+ content: "",
3960
+ note
3961
+ };
3962
+ }
3347
3963
  try {
3348
3964
  const response = await fetch(url, {
3349
3965
  signal: AbortSignal.any([this.#controller.signal, AbortSignal.timeout(timeout)]),
@@ -3352,7 +3968,11 @@ var Upstream = class Upstream {
3352
3968
  });
3353
3969
  if (response.status === 404) {
3354
3970
  await response.body?.cancel();
3355
- return {
3971
+ return binary ? {
3972
+ lookup: "missing",
3973
+ hex: "",
3974
+ note: "HTTP 404"
3975
+ } : {
3356
3976
  lookup: "missing",
3357
3977
  content: "",
3358
3978
  note: "HTTP 404"
@@ -3360,39 +3980,61 @@ var Upstream = class Upstream {
3360
3980
  }
3361
3981
  if (response.status >= 300 && response.status < 400) {
3362
3982
  await response.body?.cancel();
3363
- return {
3983
+ const note = `HTTP ${String(response.status)}, and a redirect is never followed`;
3984
+ return binary ? {
3985
+ lookup: "failed",
3986
+ hex: "",
3987
+ note
3988
+ } : {
3364
3989
  lookup: "failed",
3365
3990
  content: "",
3366
- note: `HTTP ${String(response.status)}, and a redirect is never followed`
3991
+ note
3367
3992
  };
3368
3993
  }
3369
3994
  if (!response.ok) {
3370
3995
  await response.body?.cancel();
3371
- return {
3996
+ const note = `HTTP ${String(response.status)}`;
3997
+ return binary ? {
3998
+ lookup: "failed",
3999
+ hex: "",
4000
+ note
4001
+ } : {
3372
4002
  lookup: "failed",
3373
4003
  content: "",
3374
- note: `HTTP ${String(response.status)}`
4004
+ note
3375
4005
  };
3376
4006
  }
3377
- return await this.#body(response, allowance);
4007
+ return binary ? await this.#body(response, allowance, true) : await this.#body(response, allowance);
3378
4008
  } catch (error) {
3379
4009
  this.#assertAlive();
3380
- return {
4010
+ const note = this.#note(error);
4011
+ return binary ? {
4012
+ lookup: "failed",
4013
+ hex: "",
4014
+ note
4015
+ } : {
3381
4016
  lookup: "failed",
3382
4017
  content: "",
3383
- note: this.#note(error)
4018
+ note
3384
4019
  };
3385
4020
  }
3386
4021
  }
3387
- async #body(response, allowance) {
4022
+ async #body(response, allowance, binary = false) {
3388
4023
  const body = response.body;
3389
- if (body === null) return {
3390
- lookup: "failed",
3391
- content: "",
3392
- note: `HTTP ${String(response.status)}, and the answer carries no body`
3393
- };
4024
+ if (body === null) {
4025
+ const note = `HTTP ${String(response.status)}, and the answer carries no body`;
4026
+ return binary ? {
4027
+ lookup: "failed",
4028
+ hex: "",
4029
+ note
4030
+ } : {
4031
+ lookup: "failed",
4032
+ content: "",
4033
+ note
4034
+ };
4035
+ }
3394
4036
  const reader = body.getReader();
3395
- const decoder = new TextDecoder("utf-8", { fatal: true });
4037
+ const decoder = binary ? void 0 : new TextDecoder("utf-8", { fatal: true });
3396
4038
  const chunks = [];
3397
4039
  let total = 0;
3398
4040
  try {
@@ -3403,30 +4045,44 @@ var Upstream = class Upstream {
3403
4045
  allowance.remaining -= chunk.value.byteLength;
3404
4046
  if (total > this.#limit) {
3405
4047
  await reader.cancel();
3406
- return {
4048
+ const note = `the response passed the ${String(this.#limit)}-byte response limit`;
4049
+ return binary ? {
4050
+ lookup: "failed",
4051
+ hex: "",
4052
+ note
4053
+ } : {
3407
4054
  lookup: "failed",
3408
4055
  content: "",
3409
- note: `the response passed the ${String(this.#limit)}-byte response limit`
4056
+ note
3410
4057
  };
3411
4058
  }
3412
4059
  if (allowance.remaining < 0) {
3413
4060
  await reader.cancel();
3414
- return {
4061
+ const note = `the call spent its ${String(this.#budget)}-byte allowance`;
4062
+ return binary ? {
4063
+ lookup: "failed",
4064
+ hex: "",
4065
+ note
4066
+ } : {
3415
4067
  lookup: "failed",
3416
4068
  content: "",
3417
- note: `the call spent its ${String(this.#budget)}-byte allowance`
4069
+ note
3418
4070
  };
3419
4071
  }
3420
- chunks.push(decoder.decode(chunk.value, { stream: true }));
4072
+ chunks.push(decoder === void 0 ? Buffer.from(chunk.value).toString("hex") : decoder.decode(chunk.value, { stream: true }));
3421
4073
  }
3422
- chunks.push(decoder.decode());
4074
+ if (decoder !== void 0) chunks.push(decoder.decode());
3423
4075
  } catch (error) {
3424
4076
  await reader.cancel().catch(() => void 0);
3425
4077
  throw error;
3426
4078
  } finally {
3427
4079
  reader.releaseLock();
3428
4080
  }
3429
- return {
4081
+ return binary ? {
4082
+ lookup: "found",
4083
+ hex: chunks.join(""),
4084
+ note: ""
4085
+ } : {
3430
4086
  lookup: "found",
3431
4087
  content: chunks.join(""),
3432
4088
  note: ""
@@ -3501,6 +4157,8 @@ exports.WriteTransaction = WriteTransaction;
3501
4157
  exports.computeDigest = computeDigest;
3502
4158
  exports.computeFileDigest = computeFileDigest;
3503
4159
  exports.computeManifestDigest = computeManifestDigest;
4160
+ exports.filesToHost = filesToHost;
4161
+ exports.hexToDigest = hexToDigest;
3504
4162
  exports.isBranch = isBranch;
3505
4163
  exports.isCatalogEntries = isCatalogEntries;
3506
4164
  exports.isDependencies = isDependencies;
@@ -3509,19 +4167,22 @@ exports.isDigest = isDigest;
3509
4167
  exports.isEndpoint = isEndpoint;
3510
4168
  exports.isExactCaseFile = isExactCaseFile;
3511
4169
  exports.isFilesystemPath = isFilesystemPath;
4170
+ exports.isHost = isHost;
3512
4171
  exports.isHostManifest = isHostManifest;
3513
4172
  exports.isInventory = isInventory;
3514
4173
  exports.isManifestEntry = isManifestEntry;
4174
+ exports.isManifestRegionSet = isManifestRegionSet;
3515
4175
  exports.isMaterializerHooks = isMaterializerHooks;
3516
4176
  exports.isMaterializerOptions = isMaterializerOptions;
3517
4177
  exports.isMirrors = isMirrors;
4178
+ exports.isPaths = isPaths;
3518
4179
  exports.isPhysicalDirectory = isPhysicalDirectory;
3519
4180
  exports.isPhysicalFile = isPhysicalFile;
3520
- exports.isRepository = isRepository;
3521
4181
  exports.isTimeout = isTimeout;
3522
4182
  exports.isUpstreamHooks = isUpstreamHooks;
3523
4183
  exports.isUpstreamOptions = isUpstreamOptions;
3524
4184
  exports.isVacant = isVacant;
4185
+ exports.isWorktree = isWorktree;
3525
4186
  exports.listDirectories = listDirectories;
3526
4187
  exports.listFiles = listFiles;
3527
4188
  exports.matchesAnchor = matchesAnchor;
@@ -3537,11 +4198,14 @@ exports.readAnchor = readAnchor;
3537
4198
  exports.readExpectation = readExpectation;
3538
4199
  exports.readFileHex = readFileHex;
3539
4200
  exports.readFileText = readFileText;
4201
+ exports.readHostFloor = readHostFloor;
3540
4202
  exports.readHostManifest = readHostManifest;
3541
4203
  exports.readManifestEntry = readManifestEntry;
3542
4204
  exports.readSnapshot = readSnapshot;
3543
4205
  exports.resolveContainedPath = resolveContainedPath;
3544
4206
  exports.resolveRealPath = resolveRealPath;
4207
+ exports.stageBytes = stageBytes;
3545
4208
  exports.stageHost = stageHost;
4209
+ exports.stageInventory = stageInventory;
3546
4210
 
3547
4211
  //# sourceMappingURL=index.cjs.map