@orkestrel/scaffold 0.0.25 → 0.0.26

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.
@@ -131,12 +131,23 @@ var MANIFEST_NAME = "manifest.json";
131
131
  * the byte ceiling. The character ceiling is read first so an oversized string is
132
132
  * refused before it is split.
133
133
  *
134
+ * The two spellings of an empty segment are answered differently. A trailing
135
+ * separator terminates a directory rather than opening a segment, and every
136
+ * supported filesystem and every Node path API reads `project/` and `project` as
137
+ * one location, so it is admitted. A doubled separator is a genuine empty
138
+ * segment, so `project//src` is refused. Nothing normalizes the argument first —
139
+ * every server entry point guards the caller's text and resolves it afterwards —
140
+ * so a directory taken from a shell completion arrives carrying the separator the
141
+ * shell appended and names the directory it appears to name.
142
+ *
134
143
  * @example
135
144
  * ```ts
136
145
  * import { isFilesystemPath } from '@orkestrel/scaffold/server'
137
146
  *
138
147
  * isFilesystemPath('C:/Users/sample/project') // true
139
148
  * isFilesystemPath('../sibling') // true
149
+ * isFilesystemPath('project/') // true
150
+ * isFilesystemPath('project//src') // false
140
151
  * isFilesystemPath('project/nul') // false
141
152
  * ```
142
153
  */
@@ -145,7 +156,8 @@ function isFilesystemPath(value) {
145
156
  if (!(0, _orkestrel_contract.isString)(value) || value.length === 0 || value.length > _src_core.MAX_PATH_LENGTH) return false;
146
157
  if (_src_core.CONTROL_CHARACTER_PATTERN.test(value)) return false;
147
158
  const normalized = value.replaceAll("\\", "/");
148
- const segments = (normalized.startsWith("//") ? normalized.slice(2) : normalized.startsWith("/") ? normalized.slice(1) : normalized).split("/");
159
+ const rooted = normalized.startsWith("//") ? normalized.slice(2) : normalized.startsWith("/") ? normalized.slice(1) : normalized;
160
+ const segments = (rooted.endsWith("/") ? rooted.slice(0, -1) : rooted).split("/");
149
161
  if (segments.length > 64) return false;
150
162
  for (const [index, segment] of segments.entries()) {
151
163
  if (segment === "." || segment === "..") continue;
@@ -510,6 +522,32 @@ function matchesSensitivePath(path) {
510
522
  return /(?:^|\/)(?:(?:\.ssh|\.aws|\.azure|\.docker|\.kube|\.gnupg|\.env(?:\.[^/]*)?)(?:\/|$)|(?:\.npmrc|\.pypirc|\.netrc|\.git-credentials|settings\.local\.json|auth\.json|credentials(?:\.json)?|application_default_credentials\.json|id_rsa|id_ed25519|kubeconfig)$|\.config\/(?:gh|gcloud)(?:\/|$)|\.local\/share\/keyrings(?:\/|$)|[^/]*service-account[^/]*\.json$|[^/]*\.(?:jks|key|p12|pem|pfx|pkcs12)$)/i.test(normalized);
511
523
  }
512
524
  /**
525
+ * Test whether a vendored path is one a target receives executable.
526
+ *
527
+ * @param path - The target-relative path to classify; either separator is read.
528
+ * @returns `true` when the path is declared in {@link EXECUTABLE_PATHS}.
529
+ *
530
+ * @remarks
531
+ * The declaration is the whole answer, and deliberately so. Reading the staging
532
+ * host's mode instead makes the manifest depend on where the package was built:
533
+ * Windows carries no executable bit, so a host staged there declares every entry
534
+ * non-executable and every target it later fills receives hooks at `0644`. One
535
+ * checkout stages one manifest on every host because this predicate never
536
+ * consults the filesystem.
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * import { matchesExecutablePath } from '@orkestrel/scaffold/server'
541
+ *
542
+ * matchesExecutablePath('scripts/codex.sh') // true
543
+ * matchesExecutablePath('scripts\\deps.sh') // true
544
+ * matchesExecutablePath('AGENTS.md') // false
545
+ * ```
546
+ */
547
+ function matchesExecutablePath(path) {
548
+ return _src_core.EXECUTABLE_PATHS.includes(path.replaceAll("\\", "/"));
549
+ }
550
+ /**
513
551
  * Project a target-relative path to the storage name a vendored host holds it under.
514
552
  *
515
553
  * @param path - The target-relative path the file is written to.
@@ -728,9 +766,10 @@ function computeFileDigest(path) {
728
766
  * Resolve a path through the real filesystem, keeping the part that does not exist yet.
729
767
  *
730
768
  * @param path - The absolute or relative host path to resolve.
731
- * @returns The path with its existing prefix resolved through every link, or
732
- * `undefined` when the text is not a host path, no bounded existing ancestor
733
- * resolves, or an ancestor cannot be read.
769
+ * @returns The lexical resolution of `path`, with its existing prefix then
770
+ * resolved through every link, or `undefined` when the text is not a host path,
771
+ * no bounded existing ancestor resolves, a link target cannot be read, a link
772
+ * target carries a `..` segment, or an ancestor cannot be read.
734
773
  *
735
774
  * @remarks
736
775
  * A containment decision has to be made about a destination that does not exist
@@ -740,6 +779,30 @@ function computeFileDigest(path) {
740
779
  * it. The climb is bounded by the path-depth ceiling, so an adversarial path
741
780
  * cannot make it walk indefinitely.
742
781
  *
782
+ * The caller's own text is collapsed first, which is what `resolve` does with a
783
+ * `..` the caller wrote: it cancels the segment before it as text, before any
784
+ * link in that segment is read. So `<root>/hop/..` answers `<root>` even where
785
+ * `hop` links elsewhere, rather than the directory holding what `hop` points at.
786
+ * The collapse only ever shortens the caller's path, so nothing reaches outside
787
+ * it by this; the answer is that lexical location resolved through links, not
788
+ * the physical location the links lead to. {@link resolveContainedPath} passes
789
+ * its `root` through here, so a root written with a parent segment is contained
790
+ * against its collapsed spelling.
791
+ *
792
+ * `realpath` answers `ENOENT` both for a name that is not there and for a link
793
+ * whose target is not there. The name is therefore inspected without following
794
+ * it: a dangling link redirects the walk to its target, while a genuinely absent
795
+ * name is retained as one segment of the unresolved suffix. A dangling link
796
+ * target containing a `..` segment is refused. Resolving that target as one
797
+ * lexical string could discard a preceding link before the filesystem gives
798
+ * `..` its physical meaning.
799
+ *
800
+ * That target is split on both separators on every host, which is the reading
801
+ * `isPath` already gives a planned path. A POSIX filename legally containing a
802
+ * backslash is therefore refused with it: `weird\..\name` is one name to the
803
+ * host and three segments here. The package keeps one separator law rather than
804
+ * a host-dependent second one, and this is the conservative side of it.
805
+ *
743
806
  * @example
744
807
  * ```ts
745
808
  * import { resolveRealPath } from '@orkestrel/scaffold/server'
@@ -759,6 +822,16 @@ function resolveRealPath(path) {
759
822
  return physical;
760
823
  }
761
824
  if (!matchesMissingPath(real.error)) return void 0;
825
+ const status = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(current));
826
+ if (status.success) {
827
+ if (!status.value.isSymbolicLink()) return void 0;
828
+ const target = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readlinkSync)(current));
829
+ if (!target.success) return void 0;
830
+ if (target.value.split(/[\\/]/u).includes("..")) return void 0;
831
+ current = (0, node_path.resolve)((0, node_path.dirname)(current), target.value);
832
+ continue;
833
+ }
834
+ if (!matchesMissingPath(status.error)) return void 0;
762
835
  const parent = (0, node_path.dirname)(current);
763
836
  if (parent === current) return void 0;
764
837
  pending.unshift((0, node_path.relative)(parent, current));
@@ -775,20 +848,31 @@ function resolveRealPath(path) {
775
848
  *
776
849
  * @remarks
777
850
  * The containment law, and the one door every read in this module goes through.
778
- * Both sides are resolved through the real filesystem before they are compared,
779
- * so a link planted inside the root cannot smuggle a destination out of it; the
780
- * answer is then the lexical join, so the caller operates on the path it named
781
- * rather than on a resolved form the target may not recognize.
851
+ * Both sides are resolved through the real filesystem before they are compared.
852
+ * A dangling link is followed only when its raw target contains no parent
853
+ * traversal. The answer is then the lexical join of `root` and `path` an
854
+ * absolute path under `root`, not a root-relative one so the caller operates
855
+ * on the path it named rather than on a resolved form the target may not
856
+ * recognize. A `root` written with a parent segment is collapsed by that
857
+ * resolution before anything is read, so containment is measured against the
858
+ * directory the caller's text names.
782
859
  *
783
860
  * Comparison is exact text, which fails closed on a case-insensitive
784
861
  * filesystem: a root and a path spelled with different case resolve to
785
862
  * different strings there and are refused, never wrongly admitted.
786
863
  *
864
+ * The answer describes the namespace this call read. The contract excludes a
865
+ * concurrent rename or link swap during the call or before the caller finishes
866
+ * using the returned path. This helper returns a string, not a filesystem
867
+ * handle, so it cannot bind its containment check to a later operation. A caller
868
+ * that admits hostile concurrent namespace mutation needs a handle-bound
869
+ * operation instead.
870
+ *
787
871
  * @example
788
872
  * ```ts
789
873
  * import { resolveContainedPath } from '@orkestrel/scaffold/server'
790
874
  *
791
- * resolveContainedPath('/tmp/project', 'guides/router.md') // '/tmp/project/guides/router.md'
875
+ * resolveContainedPath('/tmp/project', 'guides/router.md')?.endsWith('router.md') // true
792
876
  * resolveContainedPath('/tmp/project', '../secrets') // undefined
793
877
  * ```
794
878
  */
@@ -865,7 +949,7 @@ function isVacant(target) {
865
949
  * ```ts
866
950
  * import { listFiles } from '@orkestrel/scaffold/server'
867
951
  *
868
- * listFiles('./dist/host') // ['AGENTS.md', 'claude/rules/names.md', …]
952
+ * listFiles('./dist/host') // ['AGENTS.md', 'CLAUDE.md', 'LICENSE', …]
869
953
  * ```
870
954
  */
871
955
  function listFiles(root) {
@@ -1239,14 +1323,14 @@ function readHostManifest(host) {
1239
1323
  *
1240
1324
  * @remarks
1241
1325
  * The one place the three declared fields are decided together, because they are
1242
- * three readings of one file: {@link pathToStorage} decides where it is stored,
1243
- * the destination is the path it answers for, and the executable bit is read
1244
- * from the source's own mode.
1326
+ * three readings of one path: {@link pathToStorage} decides where it is stored,
1327
+ * the destination is the path it answers for, and {@link matchesExecutablePath}
1328
+ * decides whether a target receives it executable.
1245
1329
  *
1246
- * That mode is the honest limit of this reading. A Windows host reports no
1247
- * executable bit at all, so a host staged there declares every entry
1248
- * non-executable and a consumer receives scripts without it. Staging on a POSIX
1249
- * host is what carries the bit through.
1330
+ * The bit is read from that declaration rather than from the source's mode, so
1331
+ * the entry does not depend on where the package was staged. A Windows host
1332
+ * reports no executable bit at all, and reading the mode there declared every
1333
+ * entry non-executable and shipped consumers hooks they could not run.
1250
1334
  *
1251
1335
  * @example
1252
1336
  * ```ts
@@ -1263,7 +1347,7 @@ function readManifestEntry(destination, source) {
1263
1347
  return {
1264
1348
  storage: pathToStorage(destination),
1265
1349
  destination,
1266
- executable: (status.value.mode & 73) !== 0
1350
+ executable: matchesExecutablePath(destination)
1267
1351
  };
1268
1352
  }
1269
1353
  /**
@@ -1433,8 +1517,8 @@ function stageHost(checkout, host) {
1433
1517
  * @remarks
1434
1518
  * Device and inode rather than the path, because the path is the thing that can
1435
1519
  * be swapped underneath a write. An anchor captured before a mutation and
1436
- * checked again after it is what proves the directory written into is the
1437
- * directory that was inspected.
1520
+ * checked again after it proves the directory written into sits where the
1521
+ * inspected one sat, not that it is the one that was inspected.
1438
1522
  *
1439
1523
  * @example
1440
1524
  * ```ts
@@ -1460,9 +1544,13 @@ function readAnchor(path) {
1460
1544
  * device and inode.
1461
1545
  *
1462
1546
  * @remarks
1463
- * The check a write repeats between steps. A directory replaced by another
1464
- * directory of the same name answers `false` here, which is the case a path
1465
- * comparison alone cannot see.
1547
+ * This binds location rather than history. `true` means the path still resolves
1548
+ * to the same physical directory on the same device, so the next write lands
1549
+ * where the last one did. A path now holding nothing, a file, or a symlink
1550
+ * answers `false`; a directory swapped in by `rename` also answers `false`
1551
+ * because the replacement carries its own inode. A directory deleted and made
1552
+ * again under the same name can receive the old inode back and answers `true`,
1553
+ * which nothing here detects.
1466
1554
  *
1467
1555
  * @example
1468
1556
  * ```ts
@@ -1608,6 +1696,12 @@ function matchesPrecondition(precondition) {
1608
1696
  * - **No partly written destination.** Every file is written whole into the
1609
1697
  * private root and digested there before commit, so a destination never
1610
1698
  * receives bytes that were still being produced.
1699
+ * - **Containment, not continuity, of the directories it creates.** Every
1700
+ * ancestor is re-read between `mkdir` calls and again before the first
1701
+ * promotion, so an ancestor that became a file, a symlink, a directory
1702
+ * elsewhere, or nothing is refused. An ancestor deleted and recreated under
1703
+ * the same name can receive its old inode back and is indistinguishable here
1704
+ * from one that never moved.
1611
1705
  * - **No crash atomicity across destinations.** A process killed between two
1612
1706
  * promotions leaves the target holding some new files and some old ones, and
1613
1707
  * leaves the private root behind. Nothing here is a journal, and the private
@@ -1702,7 +1796,7 @@ var WriteTransaction = class {
1702
1796
  this.#stage = (0, node_path.join)(this.#root, "stage");
1703
1797
  this.#backup = (0, node_path.join)(this.#root, "backup");
1704
1798
  const opened = (0, _orkestrel_contract.attempt)(() => {
1705
- this.#created.push(...this.#establish(parent).created);
1799
+ this.#establish(parent);
1706
1800
  for (const directory of [
1707
1801
  this.#root,
1708
1802
  this.#stage,
@@ -1794,7 +1888,7 @@ var WriteTransaction = class {
1794
1888
  const staged = this.#stagePath(path);
1795
1889
  const copied = (0, _orkestrel_contract.attempt)(() => {
1796
1890
  (0, node_fs.copyFileSync)(source, staged, node_fs.constants.COPYFILE_EXCL);
1797
- if (executable) (0, node_fs.chmodSync)(staged, 493);
1891
+ (0, node_fs.chmodSync)(staged, executable ? 493 : 420);
1798
1892
  });
1799
1893
  if (!copied.success) throw new _src_core.ScaffoldError("WRITE", `The staged copy at ${path} could not be made.`, {
1800
1894
  path,
@@ -1826,8 +1920,12 @@ var WriteTransaction = class {
1826
1920
  directory(path) {
1827
1921
  this.#assertOpen();
1828
1922
  if (this.#expectation(path).shape === "file") throw new _src_core.ScaffoldError("TARGET", `The destination at ${path} holds a file.`, { path });
1829
- const result = this.#establish(this.#resolve(this.#target, path));
1830
- this.#created.push(...result.created);
1923
+ const established = (0, _orkestrel_contract.attempt)(() => this.#establish(this.#resolve(this.#target, path)));
1924
+ if (!established.success) throw new _src_core.ScaffoldError("WRITE", `The directory at ${path} could not be established.`, {
1925
+ path,
1926
+ error: established.error
1927
+ });
1928
+ const result = established.value;
1831
1929
  if (result.created.length > 0 && !this.#established.includes(path)) this.#established.push(path);
1832
1930
  return result;
1833
1931
  }
@@ -1871,14 +1969,8 @@ var WriteTransaction = class {
1871
1969
  const taken = [];
1872
1970
  const applied = (0, _orkestrel_contract.attempt)(() => {
1873
1971
  this.#preflight();
1874
- for (const path of this.#staged) {
1875
- this.#promote(path);
1876
- promoted.push(path);
1877
- }
1878
- for (const path of this.#taken) {
1879
- this.#take(path);
1880
- taken.push(path);
1881
- }
1972
+ for (const path of this.#staged) this.#promote(path, promoted);
1973
+ for (const path of this.#taken) this.#take(path, taken);
1882
1974
  });
1883
1975
  this.#open = false;
1884
1976
  if (!applied.success) {
@@ -1978,9 +2070,11 @@ var WriteTransaction = class {
1978
2070
  ancestor: anchor.path
1979
2071
  });
1980
2072
  (0, node_fs.mkdirSync)(segment);
2073
+ this.#created.push(segment);
1981
2074
  const established = readAnchor(segment);
1982
2075
  if (established === void 0) throw new _src_core.ScaffoldError("WRITE", `The directory at ${segment} changed while writing.`, { path: destination });
1983
2076
  anchor = established;
2077
+ this.#created[this.#created.length - 1] = established;
1984
2078
  created.push(established);
1985
2079
  }
1986
2080
  return {
@@ -1989,10 +2083,16 @@ var WriteTransaction = class {
1989
2083
  };
1990
2084
  }
1991
2085
  #preflight() {
1992
- for (const anchor of this.#created) if (!matchesAnchor(anchor)) throw new _src_core.ScaffoldError("WRITE", `The directory at ${anchor.path} changed while writing.`, {
1993
- target: this.#target,
1994
- path: anchor.path
1995
- });
2086
+ for (const anchor of this.#created) {
2087
+ if (typeof anchor === "string") throw new _src_core.ScaffoldError("WRITE", `The directory at ${anchor} changed while writing.`, {
2088
+ target: this.#target,
2089
+ path: anchor
2090
+ });
2091
+ if (!matchesAnchor(anchor)) throw new _src_core.ScaffoldError("WRITE", `The directory at ${anchor.path} changed while writing.`, {
2092
+ target: this.#target,
2093
+ path: anchor.path
2094
+ });
2095
+ }
1996
2096
  for (const path of [...this.#staged, ...this.#taken]) if (!matchesExpectation(this.#expectation(path))) throw new _src_core.ScaffoldError("WRITE", `The destination at ${path} moved while writing.`, {
1997
2097
  target: this.#target,
1998
2098
  path
@@ -2002,24 +2102,26 @@ var WriteTransaction = class {
2002
2102
  path
2003
2103
  });
2004
2104
  }
2005
- #promote(path) {
2105
+ #promote(path, promoted) {
2006
2106
  const expectation = this.#expectation(path);
2007
2107
  if (!matchesExpectation(expectation)) throw new _src_core.ScaffoldError("WRITE", `The destination at ${path} moved while writing.`, { path });
2008
2108
  const staged = this.#resolve(this.#stage, path);
2009
- if (expectation.shape === "absent") this.#created.push(...this.#establish((0, node_path.dirname)(expectation.path)).created);
2109
+ if (expectation.shape === "absent") this.#establish((0, node_path.dirname)(expectation.path));
2010
2110
  else {
2011
2111
  const backup = this.#resolve(this.#backup, path);
2012
2112
  (0, node_fs.mkdirSync)((0, node_path.dirname)(backup), { recursive: true });
2013
2113
  (0, node_fs.linkSync)(expectation.path, backup);
2014
2114
  }
2015
2115
  (0, node_fs.renameSync)(staged, expectation.path);
2116
+ promoted.push(path);
2016
2117
  }
2017
- #take(path) {
2118
+ #take(path, taken) {
2018
2119
  const expectation = this.#expectation(path);
2019
2120
  if (!matchesExpectation(expectation)) throw new _src_core.ScaffoldError("WRITE", `The destination at ${path} moved while writing.`, { path });
2020
2121
  const backup = this.#resolve(this.#backup, path);
2021
2122
  (0, node_fs.mkdirSync)((0, node_path.dirname)(backup), { recursive: true });
2022
2123
  (0, node_fs.renameSync)(expectation.path, backup);
2124
+ taken.push(path);
2023
2125
  }
2024
2126
  #recover(promoted, taken) {
2025
2127
  const residue = [];
@@ -2045,8 +2147,9 @@ var WriteTransaction = class {
2045
2147
  }));
2046
2148
  if (!cleared.success) residue.push(cleared.error);
2047
2149
  }
2048
- for (const anchor of [...this.#created].reverse()) {
2049
- const removed = (0, _orkestrel_contract.attempt)(() => (0, node_fs.rmdirSync)(anchor.path));
2150
+ for (const created of [...this.#created].reverse()) {
2151
+ const path = typeof created === "string" ? created : created.path;
2152
+ const removed = (0, _orkestrel_contract.attempt)(() => (0, node_fs.rmdirSync)(path));
2050
2153
  if (!removed.success && !matchesMissingPath(removed.error)) residue.push(removed.error);
2051
2154
  }
2052
2155
  return residue;
@@ -2223,10 +2326,13 @@ var Materializer = class Materializer {
2223
2326
  * The audit is a preview, not an instruction. The plan is hydrated and
2224
2327
  * compared against the target again here, and the verdicts that produces must
2225
2328
  * match the ones the audit carried for every path the plan owns; anything else
2226
- * means the target moved, and the whole call is refused. A missing destination
2227
- * is restored whatever its ownership; a stale one is replaced only where the
2228
- * artifact claims its bytes, which is what leaves a presence-owned file a
2229
- * consumer has edited exactly as it is.
2329
+ * means the target moved, and the whole call is refused. The audit is checked
2330
+ * for agreement rather than for plausibility, so a verdict the comparison could
2331
+ * not have produced a birth-owned path reported stale, which the `Finding`
2332
+ * shape admits disagrees with the derived one and is refused. A missing
2333
+ * destination is restored whatever its ownership; a stale one is replaced only
2334
+ * where the artifact claims its bytes, which is what leaves a presence-owned
2335
+ * file a consumer has edited exactly as it is.
2230
2336
  */
2231
2337
  repair(plan, audit, target) {
2232
2338
  this.#assertAlive();
@@ -2599,6 +2705,12 @@ var Materializer = class Materializer {
2599
2705
  path: finding.path
2600
2706
  });
2601
2707
  if (other.drift === finding.drift && other.observed === finding.observed) continue;
2708
+ if (!(0, _src_core.matchesDriftReachability)(finding.ownership, other)) throw this.#error("TARGET", `The path ${finding.path} carries an audit verdict this plan could not produce.`, {
2709
+ target,
2710
+ path: finding.path,
2711
+ ownership: finding.ownership,
2712
+ drift: other.drift
2713
+ });
2602
2714
  throw this.#error("TARGET", `The path ${finding.path} moved since its audit.`, {
2603
2715
  target,
2604
2716
  path: finding.path
@@ -2847,6 +2959,7 @@ var Upstream = class Upstream {
2847
2959
  static #defaultBudget = 16777216;
2848
2960
  static #scope = "orkestrel";
2849
2961
  static #unreadable = "the answer carries no readable latest version";
2962
+ static #packument = "application/vnd.npm.install-v1+json";
2850
2963
  #emitter;
2851
2964
  #guideBase;
2852
2965
  #guideBranch;
@@ -3039,7 +3152,7 @@ var Upstream = class Upstream {
3039
3152
  return parsed.href.replace(/\/+$/u, "");
3040
3153
  }
3041
3154
  async #release(dependency, allowance) {
3042
- const outcome = await this.#read(this.#registryURL(dependency.name), this.#registryTimeout, allowance);
3155
+ const outcome = await this.#read(this.#registryURL(dependency.name), this.#registryTimeout, allowance, Upstream.#packument);
3043
3156
  const latest = outcome.lookup === "found" ? this.#latest(outcome.content) : void 0;
3044
3157
  const release = latest === void 0 ? {
3045
3158
  name: dependency.name,
@@ -3076,7 +3189,7 @@ var Upstream = class Upstream {
3076
3189
  return mirror;
3077
3190
  }
3078
3191
  async #entry(name, allowance) {
3079
- const outcome = await this.#read(this.#registryURL(name), this.#registryTimeout, allowance);
3192
+ const outcome = await this.#read(this.#registryURL(name), this.#registryTimeout, allowance, Upstream.#packument);
3080
3193
  const version = outcome.lookup === "found" ? this.#latest(outcome.content) : void 0;
3081
3194
  if (version !== void 0) return {
3082
3195
  name,
@@ -3120,11 +3233,11 @@ var Upstream = class Upstream {
3120
3233
  const repository = encodeURIComponent(name.slice(name.lastIndexOf("/") + 1));
3121
3234
  return `${this.#guideBase}/${Upstream.#scope}/${repository}/refs/heads/${branch}/${(0, _src_core.nameToGuide)(name)}`;
3122
3235
  }
3123
- async #read(url, timeout, allowance) {
3236
+ async #read(url, timeout, allowance, accept) {
3124
3237
  let note = "";
3125
3238
  for (let attempt = 0; attempt <= this.#retries; attempt += 1) {
3126
3239
  this.#assertAlive();
3127
- const outcome = await this.#request(url, timeout, allowance);
3240
+ const outcome = await this.#request(url, timeout, allowance, accept);
3128
3241
  if (outcome.lookup !== "failed") return outcome;
3129
3242
  note = outcome.note;
3130
3243
  }
@@ -3138,7 +3251,7 @@ var Upstream = class Upstream {
3138
3251
  note
3139
3252
  };
3140
3253
  }
3141
- async #request(url, timeout, allowance) {
3254
+ async #request(url, timeout, allowance, accept) {
3142
3255
  if (allowance.remaining <= 0) return {
3143
3256
  lookup: "failed",
3144
3257
  content: "",
@@ -3147,7 +3260,8 @@ var Upstream = class Upstream {
3147
3260
  try {
3148
3261
  const response = await fetch(url, {
3149
3262
  signal: AbortSignal.any([this.#controller.signal, AbortSignal.timeout(timeout)]),
3150
- redirect: "manual"
3263
+ redirect: "manual",
3264
+ ...accept === void 0 ? {} : { headers: { accept } }
3151
3265
  });
3152
3266
  if (response.status === 404) {
3153
3267
  await response.body?.cancel();
@@ -3184,21 +3298,11 @@ var Upstream = class Upstream {
3184
3298
  }
3185
3299
  }
3186
3300
  async #body(response, allowance) {
3187
- const declared = response.headers.get("content-length");
3188
- const stated = declared === null ? void 0 : Number(declared);
3189
- if (stated !== void 0 && Number.isFinite(stated) && (stated > this.#limit || stated > allowance.remaining)) {
3190
- await response.body?.cancel();
3191
- return {
3192
- lookup: "failed",
3193
- content: "",
3194
- note: `the response declares ${String(stated)} bytes, past the ${String(Math.min(this.#limit, allowance.remaining))}-byte allowance`
3195
- };
3196
- }
3197
3301
  const body = response.body;
3198
3302
  if (body === null) return {
3199
- lookup: "found",
3303
+ lookup: "failed",
3200
3304
  content: "",
3201
- note: ""
3305
+ note: `HTTP ${String(response.status)}, and the answer carries no body`
3202
3306
  };
3203
3307
  const reader = body.getReader();
3204
3308
  const decoder = new TextDecoder("utf-8", { fatal: true });
@@ -3379,6 +3483,7 @@ exports.isVacant = isVacant;
3379
3483
  exports.listDirectories = listDirectories;
3380
3484
  exports.listFiles = listFiles;
3381
3485
  exports.matchesAnchor = matchesAnchor;
3486
+ exports.matchesExecutablePath = matchesExecutablePath;
3382
3487
  exports.matchesExpectation = matchesExpectation;
3383
3488
  exports.matchesGitPath = matchesGitPath;
3384
3489
  exports.matchesMissingPath = matchesMissingPath;