@orkestrel/scaffold 0.0.101 → 0.0.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +6 -14
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +41 -28
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +5 -12
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +25 -13
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +18 -25
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +11 -9
  7. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +233 -232
  8. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +95 -89
  9. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +5 -7
  10. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +1273 -0
  11. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +99 -89
  12. package/dist/agents/skills/orkestrel-publish/scripts/window.js +54 -52
  13. package/dist/agents/skills/orkestrel-scout/scripts/map.js +160 -161
  14. package/dist/bin/main.js +483 -444
  15. package/dist/bin/main.js.map +1 -1
  16. package/dist/host/AGENTS.md +1 -0
  17. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +8 -11
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +58 -28
  20. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +5 -10
  21. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +31 -12
  22. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +28 -23
  23. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +12 -10
  24. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +318 -237
  25. package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +2 -5
  26. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +7 -6
  27. package/dist/host/agents/skills/orkestrel-publish/references/release.md +12 -10
  28. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -5
  29. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +124 -82
  30. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +5 -7
  31. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1655 -0
  32. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +128 -97
  33. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +70 -55
  34. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +215 -155
  35. package/dist/host/claude/rules/architecture.md +2 -2
  36. package/dist/host/claude/rules/tests.md +8 -2
  37. package/dist/host/claude/rules/typescript.md +130 -0
  38. package/dist/host/claude/rules/workspace.md +30 -12
  39. package/dist/host/configs/helpers.ts +206 -133
  40. package/dist/host/configs/policy.ts +110 -105
  41. package/dist/host/guides/scaffold.md +244 -170
  42. package/dist/host/manifest.json +35 -29
  43. package/dist/host/tests/config.test.ts +1220 -633
  44. package/dist/host/tests/policy.test.ts +19 -0
  45. package/dist/host/tests/setupPolicy.ts +1669 -985
  46. package/dist/src/core/index.cjs +1595 -1686
  47. package/dist/src/core/index.cjs.map +1 -1
  48. package/dist/src/core/index.d.cts +202 -18
  49. package/dist/src/core/index.d.ts +202 -18
  50. package/dist/src/core/index.js +1590 -1687
  51. package/dist/src/core/index.js.map +1 -1
  52. package/dist/src/server/index.cjs +448 -392
  53. package/dist/src/server/index.cjs.map +1 -1
  54. package/dist/src/server/index.d.cts +1182 -1052
  55. package/dist/src/server/index.d.ts +1182 -1052
  56. package/dist/src/server/index.js +447 -395
  57. package/dist/src/server/index.js.map +1 -1
  58. package/package.json +2 -2
@@ -40,6 +40,57 @@ export declare interface BytesReadResult {
40
40
  readonly note: string;
41
41
  }
42
42
 
43
+ /**
44
+ * Climbs from an absolute path to its deepest existing ancestor and resolves it through every link.
45
+ *
46
+ * @param current - The absolute host path the climb reads at this depth.
47
+ * @param pending - The absent segments already climbed past, outermost first.
48
+ * @param depth - The steps already taken, from 0 through `MAX_PATH_DEPTH`.
49
+ * @returns The real path of the first existing ancestor joined with `pending` and every segment the
50
+ * climb passes, or `undefined` when `depth` is not a safe integer from 0 through `MAX_PATH_DEPTH`,
51
+ * the climb reaches the root, a link target cannot be read or carries a `..` segment, an existing
52
+ * entry is not a link, or an entry cannot be read.
53
+ *
54
+ * @remarks
55
+ * Each step reads one path and moves the climb to one other: a dangling link redirects it to the
56
+ * link's target with the same suffix, and an absent name moves it to the parent with that name added
57
+ * to the suffix. One successor per step makes the climb a bounded recursion rather than a worklist,
58
+ * and the depth argument is its bound, so an adversarial path or a link cycle answers `undefined`
59
+ * after `MAX_PATH_DEPTH` steps instead of walking indefinitely. {@link resolveRealPath} starts the
60
+ * climb at depth 0 with nothing pending.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { resolve } from 'node:path'
65
+ * import { climbRealPath } from '@orkestrel/scaffold/server'
66
+ *
67
+ * climbRealPath(resolve('./packages/new/src'), [], 0) // the real path of packages, plus new/src
68
+ * ```
69
+ */
70
+ export declare function climbRealPath(current: string, pending: readonly string[], depth: number): string | undefined;
71
+
72
+ /**
73
+ * Places each result at the index its entry names, in a fresh array.
74
+ *
75
+ * @param entries - The index and result pairs, in any order; each index a non-negative integer.
76
+ * @returns An array holding each result at its index, whose length is the highest index plus one,
77
+ * and where an index no entry names stays a hole.
78
+ *
79
+ * @remarks
80
+ * A worker pool finishes its items out of order and can skip one, so its answer is merged by
81
+ * position rather than by arrival: each index is written once into a fresh array, which needs no
82
+ * sort and keeps a skipped index absent instead of closing the gap. A repeated index keeps its last
83
+ * result.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { collectIndexed } from '@orkestrel/scaffold/server'
88
+ *
89
+ * collectIndexed([[0, 'first'], [2, 'third']]).length // 3
90
+ * ```
91
+ */
92
+ export declare function collectIndexed<TResult>(entries: ReadonlyArray<readonly [number, TResult]>): readonly TResult[];
93
+
43
94
  /**
44
95
  * Computes the SHA-256 digest of text.
45
96
  *
@@ -745,6 +796,29 @@ export declare function isVacant(target: string): boolean;
745
796
  */
746
797
  export declare const isWorktree: Guard<Worktree>;
747
798
 
799
+ /**
800
+ * Lists a path followed by each directory above it, nearest first.
801
+ *
802
+ * @param path - The host path to climb from.
803
+ * @param limit - The most paths to list; every ancestor by default.
804
+ * @returns The path, then `dirname` of each result in turn, ending at the first path that is its
805
+ * own parent (`.` for a relative path, the filesystem root for an absolute one) or at `limit`.
806
+ *
807
+ * @remarks
808
+ * The climb is derived from the path text alone and reads no filesystem, so a caller bounds a walk
809
+ * by the length it asks for rather than by a counter it reassigns. The final path of an unbounded
810
+ * list is the one `dirname` leaves unchanged.
811
+ *
812
+ * @example
813
+ * ```ts
814
+ * import { listAncestors } from '@orkestrel/scaffold/server'
815
+ *
816
+ * listAncestors('notes/entry/file.md') // ['notes/entry/file.md', 'notes/entry', 'notes', '.']
817
+ * listAncestors('notes/entry/file.md', 2) // ['notes/entry/file.md', 'notes/entry']
818
+ * ```
819
+ */
820
+ export declare function listAncestors(path: string, limit?: number): readonly string[];
821
+
748
822
  /**
749
823
  * Lists the canon paths a target holds, filtered to a plan's groups.
750
824
  *
@@ -1627,809 +1701,727 @@ export declare class Materializer implements MaterializerInterface {
1627
1701
  export declare function pruneEmptiedDirectories(target: string, removed: readonly string[]): readonly string[];
1628
1702
 
1629
1703
  /**
1630
- * Represents the byte allowance one whole upstream call spends across every read it makes.
1631
- *
1632
- * @remarks
1633
- * `remaining` is deliberately mutable, and it is the one member of this module's
1634
- * contracts that is. The carrier is what makes `budget` a bound on a call rather
1635
- * than on a request: each read subtracts what it consumed from the same object,
1636
- * so many small answers exhaust the call exactly as one oversized answer does.
1637
- * Each call constructs its own carrier from `budget`, because concurrent calls
1638
- * own separate budgets and must not spend each other's.
1639
- */
1640
- export declare interface ReadAllowance {
1641
- remaining: number;
1642
- }
1643
-
1644
- /**
1645
- * Captures one directory's physical identity.
1646
- *
1647
- * @param path - The resolved directory path to capture.
1648
- * @returns The anchor, or `undefined` when the path is not a physical directory.
1704
+ * Represents the byte allowance one upstream call spends across every read it makes.
1649
1705
  *
1650
1706
  * @remarks
1651
- * Native bigint device and inode values preserve the filesystem identity without
1652
- * numeric rounding. Device and inode rather than the path, because the path can
1653
- * be swapped underneath a write. An anchor captured before a mutation and
1654
- * checked again after it proves the directory written into sits where the
1655
- * inspected one sat, not that it is the one that was inspected.
1707
+ * Each call constructs its own allowance from the reader's budget, so concurrent
1708
+ * calls never spend each other's bytes. A read that brings the call exactly to
1709
+ * its budget leaves `remaining` at zero and is accepted; the next request is
1710
+ * refused before it is sent, and a body that passes the budget is refused at the
1711
+ * chunk that overshoots.
1656
1712
  *
1657
1713
  * @example
1658
1714
  * ```ts
1659
- * import { readAnchor } from '@orkestrel/scaffold/server'
1715
+ * import { ReadAllowance } from '@orkestrel/scaffold/server'
1660
1716
  *
1661
- * const anchor = readAnchor(process.cwd())
1662
- * anchor !== undefined && typeof anchor.device === 'bigint' && typeof anchor.inode === 'bigint' // true for a physical directory
1717
+ * const allowance = new ReadAllowance(1_024)
1718
+ * allowance.spend(1_000)
1719
+ * allowance.remaining // 24
1663
1720
  * ```
1664
1721
  */
1665
- export declare function readAnchor(path: string): WriteAnchor | undefined;
1722
+ export declare class ReadAllowance implements ReadAllowanceInterface {
1723
+ #private;
1724
+ /**
1725
+ * Constructs an allowance holding the whole budget.
1726
+ *
1727
+ * @param budget - The bytes the call may read in total.
1728
+ * @throws {@link ScaffoldError} coded `INVALID` when `budget` is not a
1729
+ * non-negative safe integer.
1730
+ */
1731
+ constructor(budget: number);
1732
+ /** The bytes the call has left; negative once a read overshoots the budget. */
1733
+ get remaining(): number;
1734
+ /**
1735
+ * Spends bytes a read consumed from the allowance.
1736
+ *
1737
+ * @param bytes - The decoded bytes the read consumed.
1738
+ * @returns Nothing.
1739
+ * @throws {@link ScaffoldError} coded `INVALID` when `bytes` is not a
1740
+ * non-negative safe integer.
1741
+ *
1742
+ * @example
1743
+ * ```ts
1744
+ * import { ReadAllowance } from '@orkestrel/scaffold/server'
1745
+ *
1746
+ * const allowance = new ReadAllowance(10)
1747
+ * allowance.spend(12)
1748
+ * allowance.remaining // -2
1749
+ * ```
1750
+ */
1751
+ spend(bytes: number): void;
1752
+ }
1666
1753
 
1667
- /**
1668
- * Captures what one destination holds before a write.
1669
- *
1670
- * @param path - The resolved destination path to capture.
1671
- * @returns The expectation, or `undefined` when the destination is a link or a
1672
- * shape this package will not write over.
1673
- *
1674
- * @remarks
1675
- * Absence is a captured state rather than a failure, because most writes expect
1676
- * exactly that. Each shape carries only the facts it supplies: a directory
1677
- * carries its identity, a file carries its identity, size, and bytes, and an
1678
- * absent destination carries nothing at all. A file past the artifact ceiling
1679
- * carries no digest and is bound by its identity, size, and modification time
1680
- * alone, which is the strongest honest claim about bytes nobody read.
1681
- *
1682
- * @example
1683
- * ```ts
1684
- * import { readExpectation } from '@orkestrel/scaffold/server'
1685
- *
1686
- * readExpectation('/tmp/project/absent.md') // { path: …, shape: 'absent' }
1687
- * ```
1688
- */
1689
- export declare function readExpectation(path: string): WriteExpectation | undefined;
1754
+ /**
1755
+ * Describes the byte allowance one upstream call spends across every read it makes.
1756
+ *
1757
+ * @remarks
1758
+ * `remaining` is the budget less every byte spent, and it goes negative once a
1759
+ * read overshoots, so a caller tells a call that has nothing left (`remaining`
1760
+ * at or below zero) from one that spent past its budget (`remaining` below zero).
1761
+ */
1762
+ export declare interface ReadAllowanceInterface {
1763
+ /** The bytes the call has left; negative once a read overshoots the budget. */
1764
+ readonly remaining: number;
1765
+ /**
1766
+ * Spends bytes a read consumed from the allowance.
1767
+ *
1768
+ * @param bytes - The decoded bytes the read consumed.
1769
+ * @returns Nothing.
1770
+ */
1771
+ spend(bytes: number): void;
1772
+ }
1690
1773
 
1691
- /**
1692
- * Reads one contained file as its exact bytes in lowercase hexadecimal.
1693
- *
1694
- * @param root - The containing host directory.
1695
- * @param path - The portable root-relative file path.
1696
- * @param limit - The most bytes this read accepts; the artifact ceiling by default.
1697
- * @returns The exact bytes as hexadecimal, or `undefined` when the file is
1698
- * absent, is not a physical readable file, is past `limit`, or moved while it
1699
- * was being read.
1700
- * @throws `ScaffoldError('INVALID', …)` when the arguments are off contract or
1701
- * `path` leaves `root`.
1702
- *
1703
- * @remarks
1704
- * Hexadecimal rather than text, because this is what a byte comparison is stated
1705
- * in everywhere in this package: a plan's artifact, an audit finding, and a
1706
- * snapshot all compare as the same digits. The file's identity and size are
1707
- * measured before and after the read, and one extra byte is requested past the
1708
- * declared size, so a file that grew or was replaced mid-read answers
1709
- * `undefined` rather than half of one file and half of another.
1710
- *
1711
- * @example
1712
- * ```ts
1713
- * import { readFileHex } from '@orkestrel/scaffold/server'
1714
- *
1715
- * readFileHex('/tmp/project', 'AGENTS.md') // '2320416765…'
1716
- * ```
1717
- */
1718
- export declare function readFileHex(root: string, path: string, limit?: number): string | undefined;
1774
+ /**
1775
+ * Captures one directory's physical identity.
1776
+ *
1777
+ * @param path - The resolved directory path to capture.
1778
+ * @returns The anchor, or `undefined` when the path is not a physical directory.
1779
+ *
1780
+ * @remarks
1781
+ * Native bigint device and inode values preserve the filesystem identity without
1782
+ * numeric rounding. Device and inode rather than the path, because the path can
1783
+ * be swapped underneath a write. An anchor captured before a mutation and
1784
+ * checked again after it proves the directory written into sits where the
1785
+ * inspected one sat, not that it is the one that was inspected.
1786
+ *
1787
+ * @example
1788
+ * ```ts
1789
+ * import { readAnchor } from '@orkestrel/scaffold/server'
1790
+ *
1791
+ * const anchor = readAnchor(process.cwd())
1792
+ * anchor !== undefined && typeof anchor.device === 'bigint' && typeof anchor.inode === 'bigint' // true for a physical directory
1793
+ * ```
1794
+ */
1795
+ export declare function readAnchor(path: string): WriteAnchor | undefined;
1719
1796
 
1720
- /**
1721
- * Reads one contained file as bounded UTF-8 text.
1722
- *
1723
- * @param root - The containing host directory.
1724
- * @param path - The portable root-relative file path.
1725
- * @param limit - The most bytes this read accepts; the artifact ceiling by default.
1726
- * @returns The decoded text, or `undefined` when {@link readFileHex} answers
1727
- * nothing or the bytes are not valid UTF-8.
1728
- * @throws `ScaffoldError('INVALID', …)` when the arguments are off contract or
1729
- * `path` leaves `root`.
1730
- *
1731
- * @remarks
1732
- * Decoding is strict, so a file carrying an invalid sequence answers `undefined`
1733
- * rather than text carrying replacement characters. That matters because the
1734
- * text is parsed next: a manifest silently repaired into valid JSON by lossy
1735
- * decoding would be trusted.
1736
- *
1737
- * @example
1738
- * ```ts
1739
- * import { readFileText } from '@orkestrel/scaffold/server'
1740
- *
1741
- * readFileText('/tmp/project', 'package.json') // '{ "name": "@orkestrel/router", … }'
1742
- * ```
1743
- */
1744
- export declare function readFileText(root: string, path: string, limit?: number): string | undefined;
1797
+ /**
1798
+ * Captures what one destination holds before a write.
1799
+ *
1800
+ * @param path - The resolved destination path to capture.
1801
+ * @returns The expectation, or `undefined` when the destination is a link or a
1802
+ * shape this package will not write over.
1803
+ *
1804
+ * @remarks
1805
+ * Absence is a captured state rather than a failure, because most writes expect
1806
+ * exactly that. Each shape carries only the facts it supplies: a directory
1807
+ * carries its identity, a file carries its identity, size, and bytes, and an
1808
+ * absent destination carries nothing at all. A file past the artifact ceiling
1809
+ * carries no digest and is bound by its identity, size, and modification time
1810
+ * alone, which is the strongest honest claim about bytes nobody read.
1811
+ *
1812
+ * @example
1813
+ * ```ts
1814
+ * import { readExpectation } from '@orkestrel/scaffold/server'
1815
+ *
1816
+ * readExpectation('/tmp/project/absent.md') // { path: …, shape: 'absent' }
1817
+ * ```
1818
+ */
1819
+ export declare function readExpectation(path: string): WriteExpectation | undefined;
1745
1820
 
1746
- /**
1747
- * Reads the installed vendored host floor as a value.
1748
- *
1749
- * @param root - The vendored host root. Default: the installed package's
1750
- * vendored root, resolved from this module's location.
1751
- * @returns The verified manifest and the exact bytes of every declared entry.
1752
- * @throws `ScaffoldError('TARGET', …)` when the root is not a readable physical
1753
- * directory, its manifest is absent or unreadable, the manifest does not verify,
1754
- * or a declared file is unreadable or misses its digest.
1755
- *
1756
- * @remarks
1757
- * Reads the same default floor the {@link Materializer} uses. Each declared
1758
- * file is addressed through the manifest's storage name and retained under its
1759
- * destination, so the returned value has the same shape as the installed root.
1760
- * When this module executes from TypeScript source, the committed inventory is
1761
- * the manifest and each checkout destination supplies its bytes. The emitted
1762
- * module reads the staged `manifest.json` file and each storage path instead.
1763
- *
1764
- * @example
1765
- * ```ts
1766
- * import { readHostFloor } from '@orkestrel/scaffold/server'
1767
- *
1768
- * readHostFloor().manifest // the installed floor's verified membership
1769
- * ```
1770
- */
1771
- export declare function readHostFloor(root?: string): Host;
1821
+ /**
1822
+ * Reads one contained file as its exact bytes in lowercase hexadecimal.
1823
+ *
1824
+ * @param root - The containing host directory.
1825
+ * @param path - The portable root-relative file path.
1826
+ * @param limit - The most bytes this read accepts; the artifact ceiling by default.
1827
+ * @returns The exact bytes as hexadecimal, or `undefined` when the file is
1828
+ * absent, is not a physical readable file, is past `limit`, or moved while it
1829
+ * was being read.
1830
+ * @throws `ScaffoldError('INVALID', …)` when the arguments are off contract or
1831
+ * `path` leaves `root`.
1832
+ *
1833
+ * @remarks
1834
+ * Hexadecimal rather than text, because this is what a byte comparison is stated
1835
+ * in everywhere in this package: a plan's artifact, an audit finding, and a
1836
+ * snapshot all compare as the same digits. The file's identity and size are
1837
+ * measured before and after the read, and one extra byte is requested past the
1838
+ * declared size, so a file that grew or was replaced mid-read answers
1839
+ * `undefined` rather than half of one file and half of another.
1840
+ *
1841
+ * @example
1842
+ * ```ts
1843
+ * import { readFileHex } from '@orkestrel/scaffold/server'
1844
+ *
1845
+ * readFileHex('/tmp/project', 'AGENTS.md') // '2320416765…'
1846
+ * ```
1847
+ */
1848
+ export declare function readFileHex(root: string, path: string, limit?: number): string | undefined;
1772
1849
 
1773
- /**
1774
- * Reads a vendored host's manifest, when it carries one.
1775
- *
1776
- * @param host - The vendored host root to read.
1777
- * @param name - The root-relative manifest path. Default: `manifest.json`.
1778
- * @returns The manifest, or `undefined` when the host carries none.
1779
- * @throws `ScaffoldError('INVALID', …)` when `host` is not a host path.
1780
- * @throws `ScaffoldError('TARGET', …)` when the manifest is there but cannot be
1781
- * read, is not the declared shape, or does not match its own membership.
1782
- *
1783
- * @remarks
1784
- * The failures are held apart deliberately. A host with no manifest is a
1785
- * raw checkout, and a caller reads it by mapping each path one to one. A host
1786
- * with a manifest that does not verify is a staged host that has been edited,
1787
- * and answering `undefined` there would degrade it to that same one-to-one
1788
- * mapping — which is how an edited manifest would get a caller to read files it
1789
- * never declared. So absence answers and corruption throws.
1790
- *
1791
- * Verification here is the manifest's own self-consistency: the digest against
1792
- * the exact membership beside it. Whether that membership matches the files
1793
- * actually stored is a separate question, and it belongs to the reader that
1794
- * walks the host.
1795
- *
1796
- * @example
1797
- * ```ts
1798
- * import { readHostManifest } from '@orkestrel/scaffold/server'
1799
- *
1800
- * readHostManifest('./dist/host') // the manifest, or undefined for a raw root
1801
- * ```
1802
- */
1803
- export declare function readHostManifest(host: string, name?: string): HostManifest | undefined;
1850
+ /**
1851
+ * Reads one contained file as bounded UTF-8 text.
1852
+ *
1853
+ * @param root - The containing host directory.
1854
+ * @param path - The portable root-relative file path.
1855
+ * @param limit - The most bytes this read accepts; the artifact ceiling by default.
1856
+ * @returns The decoded text, or `undefined` when {@link readFileHex} answers
1857
+ * nothing or the bytes are not valid UTF-8.
1858
+ * @throws `ScaffoldError('INVALID', …)` when the arguments are off contract or
1859
+ * `path` leaves `root`.
1860
+ *
1861
+ * @remarks
1862
+ * Decoding is strict, so a file carrying an invalid sequence answers `undefined`
1863
+ * rather than text carrying replacement characters. That matters because the
1864
+ * text is parsed next: a manifest silently repaired into valid JSON by lossy
1865
+ * decoding would be trusted.
1866
+ *
1867
+ * @example
1868
+ * ```ts
1869
+ * import { readFileText } from '@orkestrel/scaffold/server'
1870
+ *
1871
+ * readFileText('/tmp/project', 'package.json') // '{ "name": "@orkestrel/router", … }'
1872
+ * ```
1873
+ */
1874
+ export declare function readFileText(root: string, path: string, limit?: number): string | undefined;
1804
1875
 
1805
- /**
1806
- * Derives one vendored-host manifest entry from a file in a checkout.
1807
- *
1808
- * @param destination - The target-relative path the file is written to.
1809
- * @param source - The resolved host path the bytes are read from.
1810
- * @returns The entry, or `undefined` when `source` is not a physical file this
1811
- * package will vendor or carries more bytes than one artifact may.
1812
- *
1813
- * @remarks
1814
- * The one place the declared fields are decided together, because they are
1815
- * readings of one path: {@link pathToStorage} decides where it is stored,
1816
- * the destination is the path it answers for, and {@link matchesExecutablePath}
1817
- * decides whether a target receives it executable.
1818
- *
1819
- * The bit is read from that declaration rather than from the source's mode, so
1820
- * the entry does not depend on where the package was staged. A Windows host
1821
- * reports no executable bit at all, and reading the mode there declared every
1822
- * entry non-executable and shipped consumers hooks they could not run.
1823
- *
1824
- * @example
1825
- * ```ts
1826
- * import { readManifestEntry } from '@orkestrel/scaffold/server'
1827
- *
1828
- * readManifestEntry('.gitignore', '/tmp/checkout/.gitignore')
1829
- * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false, digest: '...' }
1830
- * ```
1831
- */
1832
- export declare function readManifestEntry(destination: string, source: string): ManifestEntry | undefined;
1876
+ /**
1877
+ * Reads the installed vendored host floor as a value.
1878
+ *
1879
+ * @param root - The vendored host root. Default: the installed package's
1880
+ * vendored root, resolved from this module's location.
1881
+ * @returns The verified manifest and the exact bytes of every declared entry.
1882
+ * @throws `ScaffoldError('TARGET', …)` when the root is not a readable physical
1883
+ * directory, its manifest is absent or unreadable, the manifest does not verify,
1884
+ * or a declared file is unreadable or misses its digest.
1885
+ *
1886
+ * @remarks
1887
+ * Reads the same default floor the {@link Materializer} uses. Each declared
1888
+ * file is addressed through the manifest's storage name and retained under its
1889
+ * destination, so the returned value has the same shape as the installed root.
1890
+ * When this module executes from TypeScript source, the committed inventory is
1891
+ * the manifest and each checkout destination supplies its bytes. The emitted
1892
+ * module reads the staged `manifest.json` file and each storage path instead.
1893
+ *
1894
+ * @example
1895
+ * ```ts
1896
+ * import { readHostFloor } from '@orkestrel/scaffold/server'
1897
+ *
1898
+ * readHostFloor().manifest // the installed floor's verified membership
1899
+ * ```
1900
+ */
1901
+ export declare function readHostFloor(root?: string): Host;
1833
1902
 
1834
- /**
1835
- * Reads a target's current bytes at the paths a plan claims.
1836
- *
1837
- * @param target - The target directory to read.
1838
- * @param paths - The plan-relative paths to probe.
1839
- * @returns One entry per path that is there: a file maps to its exact bytes as
1840
- * hexadecimal and a directory maps to `''`, which records presence with no bytes
1841
- * to compare. An absent path is omitted.
1842
- * @throws `ScaffoldError('INVALID', …)` when `target` is not a host path or
1843
- * `paths` is not a bounded list of plannable paths.
1844
- * @throws `ScaffoldError('TARGET', …)` when a path that is there cannot be read,
1845
- * or when the whole read would retain more bytes than one plan may.
1846
- *
1847
- * @remarks
1848
- * The one door from a real directory into the vocabulary an audit compares in.
1849
- * Absence is omission rather than an empty value, because core reads a missing
1850
- * key as a missing destination and an empty string as a present directory;
1851
- * they are different verdicts. A path that is there but unreadable throws instead
1852
- * of being omitted, because omission would report it as missing and a repair
1853
- * would then overwrite whatever is actually sitting there.
1854
- *
1855
- * @example
1856
- * ```ts
1857
- * import { readSnapshot } from '@orkestrel/scaffold/server'
1858
- *
1859
- * readSnapshot('./packages/router', ['package.json', 'guides'])
1860
- * // { 'package.json': '7b226e…', guides: '' }
1861
- * ```
1862
- */
1863
- export declare function readSnapshot(target: string, paths: readonly string[]): Snapshot;
1903
+ /**
1904
+ * Reads a vendored host's manifest, when it carries one.
1905
+ *
1906
+ * @param host - The vendored host root to read.
1907
+ * @param name - The root-relative manifest path. Default: `manifest.json`.
1908
+ * @returns The manifest, or `undefined` when the host carries none.
1909
+ * @throws `ScaffoldError('INVALID', …)` when `host` is not a host path.
1910
+ * @throws `ScaffoldError('TARGET', …)` when the manifest is there but cannot be
1911
+ * read, is not the declared shape, or does not match its own membership.
1912
+ *
1913
+ * @remarks
1914
+ * The failures are held apart deliberately. A host with no manifest is a
1915
+ * raw checkout, and a caller reads it by mapping each path one to one. A host
1916
+ * with a manifest that does not verify is a staged host that has been edited,
1917
+ * and answering `undefined` there would degrade it to that same one-to-one
1918
+ * mapping — which is how an edited manifest would get a caller to read files it
1919
+ * never declared. So absence answers and corruption throws.
1920
+ *
1921
+ * Verification here is the manifest's own self-consistency: the digest against
1922
+ * the exact membership beside it. Whether that membership matches the files
1923
+ * actually stored is a separate question, and it belongs to the reader that
1924
+ * walks the host.
1925
+ *
1926
+ * @example
1927
+ * ```ts
1928
+ * import { readHostManifest } from '@orkestrel/scaffold/server'
1929
+ *
1930
+ * readHostManifest('./dist/host') // the manifest, or undefined for a raw root
1931
+ * ```
1932
+ */
1933
+ export declare function readHostManifest(host: string, name?: string): HostManifest | undefined;
1864
1934
 
1865
- /**
1866
- * Reads the Surface collision baseline from a committed inventory.
1867
- *
1868
- * @param root - The checkout's host path.
1869
- * @param name - The checkout-relative inventory path. Default: `host.json`.
1870
- * @returns The recorded collisions, or `undefined` when the inventory is absent.
1871
- * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or `name` leaves it.
1872
- * @throws `ScaffoldError('TARGET', …)` when an existing inventory is unreadable,
1873
- * malformed, or inconsistent with its digest.
1874
- *
1875
- * @example
1876
- * ```ts
1877
- * import { readSurfaceBaseline } from '@orkestrel/scaffold/server'
1878
- *
1879
- * readSurfaceBaseline('.') // the recorded collisions, if the inventory exists
1880
- * ```
1881
- */
1882
- export declare function readSurfaceBaseline(root: string, name?: string): readonly SurfaceCollision[] | undefined;
1935
+ /**
1936
+ * Derives one vendored-host manifest entry from a file in a checkout.
1937
+ *
1938
+ * @param destination - The target-relative path the file is written to.
1939
+ * @param source - The resolved host path the bytes are read from.
1940
+ * @returns The entry, or `undefined` when `source` is not a physical file this
1941
+ * package will vendor or carries more bytes than one artifact may.
1942
+ *
1943
+ * @remarks
1944
+ * The one place the declared fields are decided together, because they are
1945
+ * readings of one path: {@link pathToStorage} decides where it is stored,
1946
+ * the destination is the path it answers for, and {@link matchesExecutablePath}
1947
+ * decides whether a target receives it executable.
1948
+ *
1949
+ * The bit is read from that declaration rather than from the source's mode, so
1950
+ * the entry does not depend on where the package was staged. A Windows host
1951
+ * reports no executable bit at all, and reading the mode there declared every
1952
+ * entry non-executable and shipped consumers hooks they could not run.
1953
+ *
1954
+ * @example
1955
+ * ```ts
1956
+ * import { readManifestEntry } from '@orkestrel/scaffold/server'
1957
+ *
1958
+ * readManifestEntry('.gitignore', '/tmp/checkout/.gitignore')
1959
+ * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false, digest: '...' }
1960
+ * ```
1961
+ */
1962
+ export declare function readManifestEntry(destination: string, source: string): ManifestEntry | undefined;
1883
1963
 
1884
- /**
1885
- * Reads bare Surface names claimed by distinct package guides.
1886
- *
1887
- * @param root - The physical directory containing the package guides.
1888
- * @returns Colliding names and their distinct owners, sorted by name and owner.
1889
- * @throws `ScaffoldError('TARGET', …)` when the directory or a guide cannot be read.
1890
- * @throws `ScaffoldError('TARGET', …)` when `@orkestrel/guide` cannot be loaded.
1891
- *
1892
- * @remarks
1893
- * Reads immediate `.md` files except `README.md` through `createGuide().surface()`.
1894
- * Requires `@orkestrel/guide` in this module's resolution path; Scaffold declares it
1895
- * only for development. Loads it only when called, so importing the server entry
1896
- * requires no guide tooling in a production install.
1897
- *
1898
- * @example
1899
- * ```ts
1900
- * import { readSurfaceCollisions } from '@orkestrel/scaffold/server'
1901
- *
1902
- * readSurfaceCollisions('./guides').get('Shared') // the guides claiming Shared, if it collides
1903
- * ```
1904
- */
1905
- export declare function readSurfaceCollisions(root: string): ReadonlyMap<string, readonly string[]>;
1964
+ /**
1965
+ * Reads a target's current bytes at the paths a plan claims.
1966
+ *
1967
+ * @param target - The target directory to read.
1968
+ * @param paths - The plan-relative paths to probe.
1969
+ * @returns One entry per path that is there: a file maps to its exact bytes as
1970
+ * hexadecimal and a directory maps to `''`, which records presence with no bytes
1971
+ * to compare. An absent path is omitted.
1972
+ * @throws `ScaffoldError('INVALID', …)` when `target` is not a host path or
1973
+ * `paths` is not a bounded list of plannable paths.
1974
+ * @throws `ScaffoldError('TARGET', …)` when a path that is there cannot be read,
1975
+ * or when the whole read would retain more bytes than one plan may.
1976
+ *
1977
+ * @remarks
1978
+ * The one door from a real directory into the vocabulary an audit compares in.
1979
+ * Absence is omission rather than an empty value, because core reads a missing
1980
+ * key as a missing destination and an empty string as a present directory;
1981
+ * they are different verdicts. A path that is there but unreadable throws instead
1982
+ * of being omitted, because omission would report it as missing and a repair
1983
+ * would then overwrite whatever is actually sitting there.
1984
+ *
1985
+ * @example
1986
+ * ```ts
1987
+ * import { readSnapshot } from '@orkestrel/scaffold/server'
1988
+ *
1989
+ * readSnapshot('./packages/router', ['package.json', 'guides'])
1990
+ * // { 'package.json': '7b226e…', guides: '' }
1991
+ * ```
1992
+ */
1993
+ export declare function readSnapshot(target: string, paths: readonly string[]): Snapshot;
1906
1994
 
1907
- /**
1908
- * Matches the Windows device names that stay reserved even when an extension follows.
1909
- *
1910
- * @remarks
1911
- * Refused on every host rather than only on Windows. A generated workspace is
1912
- * checked out on all of them, so a directory this package writes on Linux must
1913
- * still be a name Windows can hold.
1914
- */
1915
- export declare const RESERVED_SEGMENT_PATTERN: RegExp;
1995
+ /**
1996
+ * Reads the Surface collision baseline from a committed inventory.
1997
+ *
1998
+ * @param root - The checkout's host path.
1999
+ * @param name - The checkout-relative inventory path. Default: `host.json`.
2000
+ * @returns The recorded collisions, or `undefined` when the inventory is absent.
2001
+ * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or `name` leaves it.
2002
+ * @throws `ScaffoldError('TARGET', …)` when an existing inventory is unreadable,
2003
+ * malformed, or inconsistent with its digest.
2004
+ *
2005
+ * @example
2006
+ * ```ts
2007
+ * import { readSurfaceBaseline } from '@orkestrel/scaffold/server'
2008
+ *
2009
+ * readSurfaceBaseline('.') // the recorded collisions, if the inventory exists
2010
+ * ```
2011
+ */
2012
+ export declare function readSurfaceBaseline(root: string, name?: string): readonly SurfaceCollision[] | undefined;
1916
2013
 
1917
- /**
1918
- * Resolves a root-relative path and refuses one that leaves its root.
1919
- *
1920
- * @param root - The containing host directory.
1921
- * @param path - The portable root-relative path.
1922
- * @returns The destination as this package will address it, or `undefined` when
1923
- * either argument is off contract or the destination lies outside `root`.
1924
- *
1925
- * @remarks
1926
- * The containment law, and the one door every read in this module goes through.
1927
- * Both sides are resolved through the real filesystem before they are compared.
1928
- * A dangling link is followed only when its raw target contains no parent
1929
- * traversal. The answer is then the lexical join of `root` and `path` — an
1930
- * absolute path under `root`, not a root-relative one — so the caller operates
1931
- * on the path it named rather than on a resolved form the target may not
1932
- * recognize. A `root` written with a parent segment is collapsed by that
1933
- * resolution before anything is read, so containment is measured against the
1934
- * directory the caller's text names.
1935
- *
1936
- * Comparison is exact text, which fails closed on a case-insensitive
1937
- * filesystem: a root and a path spelled with different case resolve to
1938
- * different strings there and are refused, never wrongly admitted.
1939
- *
1940
- * The answer describes the namespace this call read. The contract excludes a
1941
- * concurrent rename or link swap during the call or before the caller finishes
1942
- * using the returned path. This helper returns a string, not a filesystem
1943
- * handle, so it cannot bind its containment check to a later operation. A caller
1944
- * that admits hostile concurrent namespace mutation needs a handle-bound
1945
- * operation instead.
1946
- *
1947
- * @example
1948
- * ```ts
1949
- * import { resolveContainedPath } from '@orkestrel/scaffold/server'
1950
- *
1951
- * resolveContainedPath('/tmp/project', 'guides/router.md')?.endsWith('router.md') // true
1952
- * resolveContainedPath('/tmp/project', '../secrets') // undefined
1953
- * ```
1954
- */
1955
- export declare function resolveContainedPath(root: string, path: string): string | undefined;
2014
+ /**
2015
+ * Reads bare Surface names claimed by distinct package guides.
2016
+ *
2017
+ * @param root - The physical directory containing the package guides.
2018
+ * @returns Colliding names and their distinct owners, sorted by name and owner.
2019
+ * @throws `ScaffoldError('TARGET', …)` when the directory or a guide cannot be read.
2020
+ * @throws `ScaffoldError('TARGET', …)` when `@orkestrel/guide` cannot be loaded.
2021
+ *
2022
+ * @remarks
2023
+ * Reads immediate `.md` files except `README.md` through `createGuide().surface()`.
2024
+ * Requires `@orkestrel/guide` in this module's resolution path; Scaffold declares it
2025
+ * only for development. Loads it only when called, so importing the server entry
2026
+ * requires no guide tooling in a production install.
2027
+ *
2028
+ * @example
2029
+ * ```ts
2030
+ * import { readSurfaceCollisions } from '@orkestrel/scaffold/server'
2031
+ *
2032
+ * readSurfaceCollisions('./guides').get('Shared') // the guides claiming Shared, if it collides
2033
+ * ```
2034
+ */
2035
+ export declare function readSurfaceCollisions(root: string): ReadonlyMap<string, readonly string[]>;
1956
2036
 
1957
- /**
1958
- * Resolves a path through the real filesystem, keeping the part that does not exist yet.
1959
- *
1960
- * @param path - The absolute or relative host path to resolve.
1961
- * @returns The lexical resolution of `path`, with its existing prefix then
1962
- * resolved through every link, or `undefined` when the text is not a host path,
1963
- * no bounded existing ancestor resolves, a link target cannot be read, a link
1964
- * target carries a `..` segment, or an ancestor cannot be read.
1965
- *
1966
- * @remarks
1967
- * A containment decision has to be made about a destination that does not exist
1968
- * yet, and a lexical answer is not enough: a link anywhere in the existing
1969
- * prefix moves the destination somewhere the text never named. So the deepest
1970
- * existing ancestor is resolved and the remaining segments are re-joined onto
1971
- * it. The climb is bounded by the path-depth ceiling, so an adversarial path
1972
- * cannot make it walk indefinitely.
1973
- *
1974
- * The caller's own text is collapsed first, which is what `resolve` does with a
1975
- * `..` the caller wrote: it cancels the segment before it as text, before any
1976
- * link in that segment is read. So `<root>/hop/..` answers `<root>` even where
1977
- * `hop` links elsewhere, rather than the directory holding what `hop` points at.
1978
- * The collapse only ever shortens the caller's path, so nothing reaches outside
1979
- * it by this; the answer is that lexical location resolved through links, not
1980
- * the physical location the links lead to. {@link resolveContainedPath} passes
1981
- * its `root` through here, so a root written with a parent segment is contained
1982
- * against its collapsed spelling.
1983
- *
1984
- * `realpath` answers `ENOENT` both for a name that is not there and for a link
1985
- * whose target is not there. The name is therefore inspected without following
1986
- * it: a dangling link redirects the walk to its target, while a genuinely absent
1987
- * name is retained as one segment of the unresolved suffix. A dangling link
1988
- * target containing a `..` segment is refused. Resolving that target as one
1989
- * lexical string could discard a preceding link before the filesystem gives
1990
- * `..` its physical meaning.
1991
- *
1992
- * That target is split on both separators on every host, which is the reading
1993
- * `isPath` already gives a planned path. A POSIX filename legally containing a
1994
- * backslash is therefore refused with it: `weird\..\name` is one name to the
1995
- * host and three segments here. The package keeps one separator law rather than
1996
- * a host-dependent second one, and this is the conservative side of it.
1997
- *
1998
- * @example
1999
- * ```ts
2000
- * import { resolveRealPath } from '@orkestrel/scaffold/server'
2001
- *
2002
- * resolveRealPath('./packages/new/src') // the real path of `packages`, plus `new/src`
2003
- * ```
2004
- */
2005
- export declare function resolveRealPath(path: string): string | undefined;
2037
+ /**
2038
+ * Matches the Windows device names that stay reserved even when an extension follows.
2039
+ *
2040
+ * @remarks
2041
+ * Refused on every host rather than only on Windows. A generated workspace is
2042
+ * checked out on all of them, so a directory this package writes on Linux must
2043
+ * still be a name Windows can hold.
2044
+ */
2045
+ export declare const RESERVED_SEGMENT_PATTERN: RegExp;
2006
2046
 
2007
- /**
2008
- * Names the repository this package's own vendored files are served from.
2009
- *
2010
- * @remarks
2011
- * This package's bare name, stated rather than derived, because the reader has no
2012
- * manifest to read it out of and one raw content host serves both the fleet's
2013
- * guides and these files.
2014
- */
2015
- export declare const SCAFFOLD_REPOSITORY = "scaffold";
2047
+ /**
2048
+ * Resolves a root-relative path and refuses one that leaves its root.
2049
+ *
2050
+ * @param root - The containing host directory.
2051
+ * @param path - The portable root-relative path.
2052
+ * @returns The destination as this package will address it, or `undefined` when
2053
+ * either argument is off contract or the destination lies outside `root`.
2054
+ *
2055
+ * @remarks
2056
+ * The containment law, and the one door every read in this module goes through.
2057
+ * Both sides are resolved through the real filesystem before they are compared.
2058
+ * A dangling link is followed only when its raw target contains no parent
2059
+ * traversal. The answer is then the lexical join of `root` and `path` — an
2060
+ * absolute path under `root`, not a root-relative one — so the caller operates
2061
+ * on the path it named rather than on a resolved form the target may not
2062
+ * recognize. A `root` written with a parent segment is collapsed by that
2063
+ * resolution before anything is read, so containment is measured against the
2064
+ * directory the caller's text names.
2065
+ *
2066
+ * Comparison is exact text, which fails closed on a case-insensitive
2067
+ * filesystem: a root and a path spelled with different case resolve to
2068
+ * different strings there and are refused, never wrongly admitted.
2069
+ *
2070
+ * The answer describes the namespace this call read. The contract excludes a
2071
+ * concurrent rename or link swap during the call or before the caller finishes
2072
+ * using the returned path. This helper returns a string, not a filesystem
2073
+ * handle, so it cannot bind its containment check to a later operation. A caller
2074
+ * that admits hostile concurrent namespace mutation needs a handle-bound
2075
+ * operation instead.
2076
+ *
2077
+ * @example
2078
+ * ```ts
2079
+ * import { resolveContainedPath } from '@orkestrel/scaffold/server'
2080
+ *
2081
+ * resolveContainedPath('/tmp/project', 'guides/router.md')?.endsWith('router.md') // true
2082
+ * resolveContainedPath('/tmp/project', '../secrets') // undefined
2083
+ * ```
2084
+ */
2085
+ export declare function resolveContainedPath(root: string, path: string): string | undefined;
2016
2086
 
2017
- /**
2018
- * Stages the named destinations of a value host into a private root.
2019
- *
2020
- * @param host - The host whose bytes are written, keyed by destination.
2021
- * @param root - The private directory to fill; it must already be a directory
2022
- * this process may write into.
2023
- * @param destinations - The destinations to stage, each declared by `host`.
2024
- * @returns The entry staged for each destination, in the order requested.
2025
- * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or a
2026
- * storage name leaves it.
2027
- * @throws `ScaffoldError('TARGET', …)` when a destination is one the host does
2028
- * not declare, carries no bytes, or carries bytes that miss its declared digest.
2029
- * @throws `ScaffoldError('WRITE', …)` when a file cannot be written or does not
2030
- * read back as the bytes it was given.
2031
- *
2032
- * @remarks
2033
- * Each file lands under the storage name the manifest declares and takes the
2034
- * executable bit that manifest records, so a root filled from a value is the
2035
- * same shape as one staged from a checkout and a reader cannot tell them apart.
2036
- * That is what lets a mutation copy real files with real modes from bytes a
2037
- * caller supplied, instead of degrading them to plain text writes.
2038
- *
2039
- * The bytes are digested before the write and the staged file after it, so a
2040
- * value that disagrees with its own manifest is told apart from a write that
2041
- * did not land.
2042
- *
2043
- * @example
2044
- * ```ts
2045
- * import { stageBytes } from '@orkestrel/scaffold/server'
2046
- *
2047
- * stageBytes(host, '/tmp/orkestrel-host-a1b2', ['scripts/codex.sh'])
2048
- * // [{ storage: 'scripts/codex.sh', destination: 'scripts/codex.sh', executable: true, digest: '…' }]
2049
- * ```
2050
- */
2051
- export declare function stageBytes(host: Host, root: string, destinations: readonly string[]): readonly ManifestEntry[];
2087
+ /**
2088
+ * Resolves a path through the real filesystem, keeping the part that does not exist yet.
2089
+ *
2090
+ * @param path - The absolute or relative host path to resolve.
2091
+ * @returns The lexical resolution of `path`, with its existing prefix then
2092
+ * resolved through every link, or `undefined` when the text is not a host path,
2093
+ * no bounded existing ancestor resolves, a link target cannot be read, a link
2094
+ * target carries a `..` segment, or an ancestor cannot be read.
2095
+ *
2096
+ * @remarks
2097
+ * A containment decision has to be made about a destination that does not exist
2098
+ * yet, and a lexical answer is not enough: a link anywhere in the existing
2099
+ * prefix moves the destination somewhere the text never named. So the deepest
2100
+ * existing ancestor is resolved and the remaining segments are re-joined onto
2101
+ * it. {@link climbRealPath} performs the climb and bounds it by the path-depth
2102
+ * ceiling, so an adversarial path cannot make it walk indefinitely.
2103
+ *
2104
+ * The caller's own text is collapsed first, which is what `resolve` does with a
2105
+ * `..` the caller wrote: it cancels the segment before it as text, before any
2106
+ * link in that segment is read. So `<root>/hop/..` answers `<root>` even where
2107
+ * `hop` links elsewhere, rather than the directory holding what `hop` points at.
2108
+ * The collapse only ever shortens the caller's path, so nothing reaches outside
2109
+ * it by this; the answer is that lexical location resolved through links, not
2110
+ * the physical location the links lead to. {@link resolveContainedPath} passes
2111
+ * its `root` through here, so a root written with a parent segment is contained
2112
+ * against its collapsed spelling.
2113
+ *
2114
+ * `realpath` answers `ENOENT` both for a name that is not there and for a link
2115
+ * whose target is not there. The name is therefore inspected without following
2116
+ * it: a dangling link redirects the walk to its target, while a genuinely absent
2117
+ * name is retained as one segment of the unresolved suffix. A dangling link
2118
+ * target containing a `..` segment is refused. Resolving that target as one
2119
+ * lexical string could discard a preceding link before the filesystem gives
2120
+ * `..` its physical meaning.
2121
+ *
2122
+ * That target is split on both separators on every host, which is the reading
2123
+ * `isPath` already gives a planned path. A POSIX filename legally containing a
2124
+ * backslash is therefore refused with it: `weird\..\name` is one name to the
2125
+ * host and three segments here. The package keeps one separator law rather than
2126
+ * a host-dependent second one, and this is the conservative side of it.
2127
+ *
2128
+ * @example
2129
+ * ```ts
2130
+ * import { resolveRealPath } from '@orkestrel/scaffold/server'
2131
+ *
2132
+ * resolveRealPath('./packages/new/src') // the real path of `packages`, plus `new/src`
2133
+ * ```
2134
+ */
2135
+ export declare function resolveRealPath(path: string): string | undefined;
2052
2136
 
2053
- /**
2054
- * Stages a vendored host root from a real checkout.
2055
- *
2056
- * @param checkout - The checkout the vendored paths are read from.
2057
- * @param host - The vendored host root to fill; it must be absent or empty.
2058
- * @param options - The inventory selection, establishment switch, and reporting callback.
2059
- * Default: no reporting.
2060
- * @returns One entry per staged file, sorted by storage name.
2061
- * @throws `ScaffoldError('INVALID', …)` when either argument is not a host path
2062
- * or a vendored path leaves the checkout or the host root.
2063
- * @throws `ScaffoldError('TARGET', …)` when the checkout is not a directory, the
2064
- * host root is not vacant, the checkout does not carry every vendored path, two
2065
- * vendored files claim one storage name, a vendored file is not a plain file
2066
- * within the artifact ceiling, published guides are unreadable, a staged
2067
- * collision's owner set is not a subset of its published owner set, the inventory is absent without
2068
- * `establish: true`, or the staged manifest does not read back.
2069
- * @throws `ScaffoldError('WRITE', …)` when a staged file or the manifest cannot
2070
- * be written.
2071
- *
2072
- * @remarks
2073
- * This is the producer half of the vendored host, and it is not the mutation
2074
- * contract `MaterializerInterface` states. That contract owns **target**
2075
- * writes: it materializes a compiled plan into a consumer's workspace, binds
2076
- * every destination to what the caller observed, and rolls a failed commit back.
2077
- * This reads this package's own checkout at build time and fills its own build
2078
- * output. Different direction, different lifetime, no consumer target involved,
2079
- * so they do not overlap and neither one belongs inside the other.
2080
- *
2081
- * Staging is plain rather than transactional for the same reason. A
2082
- * `WriteTransaction` exists to hold a directory that already holds work
2083
- * still; a build output holds nothing, is deleted whole before every build, and
2084
- * has no concurrent reader. What replaces it is refusing early and ordering the
2085
- * writes: the whole membership is derived before anything is created, so a
2086
- * membership refusal leaves no host root at all, and `manifest.json` is
2087
- * written last, so a copy or Surface refusal leaves a root every
2088
- * reader treats as a raw checkout and fails loudly on.
2089
- *
2090
- * A missing vendored path is refused rather than staged around. A partial root
2091
- * is not detectably partial: it fails later, in a consumer's terminal, on
2092
- * whichever path the plan reached first. Refusing here fails the build that
2093
- * produced it, where the maintainer can act, and it names every missing path at
2094
- * once. A directory is the same case — declaring an absent directory as an empty
2095
- * root would create an empty directory in every generated workspace. A canon file
2096
- * member the checkout no longer holds is the one exception: scaffold keeps owning
2097
- * the path so a target's copy reports foreign, and the stage ships nothing for it.
2098
- *
2099
- * The walk covers `HOST_PATHS`, `CANON_PATHS`, and `REFERENCE_PATHS` together.
2100
- * A plan selects the paths a target receives; the installed package also carries
2101
- * the canon and reference files for reading. Every catalog package must have a
2102
- * guide in the discovered membership before the host is written.
2103
- * Copied guides must carry no collision whose name and exact owner set the
2104
- * committed inventory lacks. An absent inventory requires `establish: true`;
2105
- * an inventory without a valid recorded Surface is refused.
2106
- * Everything downstream — the missing-path refusal, the
2107
- * storage collision guard, the sort, the digests, and the root inventory — reads
2108
- * the union, so every staged path follows the same law.
2109
- *
2110
- * The vendoring deny-list applies to what the walk discovers beneath a staged
2111
- * directory, where a maintainer's local credential can legitimately sit, and
2112
- * such a path is skipped. A path a staging list names itself is curated data
2113
- * rather than discovery, so it is staged or the stage is refused.
2114
- *
2115
- * @example Vendored data root
2116
- * ```ts
2117
- * import { stageHost } from '@orkestrel/scaffold/server'
2118
- *
2119
- * stageHost(process.cwd(), 'dist/host') // one ManifestEntry per file staged
2120
- * ```
2121
- */
2122
- export declare function stageHost(checkout: string, host: string, options?: HostStageOptions): readonly ManifestEntry[];
2137
+ /**
2138
+ * Names the repository this package's own vendored files are served from.
2139
+ *
2140
+ * @remarks
2141
+ * This package's bare name, stated rather than derived, because the reader has no
2142
+ * manifest to read it out of and one raw content host serves both the fleet's
2143
+ * guides and these files.
2144
+ */
2145
+ export declare const SCAFFOLD_REPOSITORY = "scaffold";
2123
2146
 
2124
- /**
2125
- * Stages the committed inventory of the files a vendored host carries.
2126
- *
2127
- * @param checkout - The checkout whose vendored paths are inventoried.
2128
- * @param path - The host path where the JSON inventory is written.
2129
- * @returns The validated manifest written to `path`.
2130
- * @throws `ScaffoldError('INVALID', …)` when `path` is not a host path.
2131
- * @throws `ScaffoldError('WRITE', …)` when a temporary host or the inventory
2132
- * cannot be written or removed.
2133
- * @throws `ScaffoldError('TARGET', …)` when the staged inventory does not read
2134
- * back through the manifest validator.
2135
- *
2136
- * @remarks
2137
- * Uses {@link stageHost} as the single vendored-path expansion. The temporary
2138
- * host supplies the same entries, roots, per-file digests, and membership
2139
- * digest as the published host while the requested output remains one JSON
2140
- * file.
2141
- *
2142
- * @example
2143
- * ```ts
2144
- * import { stageInventory } from '@orkestrel/scaffold/server'
2145
- *
2146
- * stageInventory(process.cwd(), 'host.json') // the committed host inventory
2147
- * ```
2148
- */
2149
- export declare function stageInventory(checkout: string, path?: string): HostManifest;
2147
+ /**
2148
+ * Stages the named destinations of a value host into a private root.
2149
+ *
2150
+ * @param host - The host whose bytes are written, keyed by destination.
2151
+ * @param root - The private directory to fill; it must already be a directory
2152
+ * this process may write into.
2153
+ * @param destinations - The destinations to stage, each declared by `host`.
2154
+ * @returns The entry staged for each destination, in the order requested.
2155
+ * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or a
2156
+ * storage name leaves it.
2157
+ * @throws `ScaffoldError('TARGET', …)` when a destination is one the host does
2158
+ * not declare, carries no bytes, or carries bytes that miss its declared digest.
2159
+ * @throws `ScaffoldError('WRITE', …)` when a file cannot be written or does not
2160
+ * read back as the bytes it was given.
2161
+ *
2162
+ * @remarks
2163
+ * Each file lands under the storage name the manifest declares and takes the
2164
+ * executable bit that manifest records, so a root filled from a value is the
2165
+ * same shape as one staged from a checkout and a reader cannot tell them apart.
2166
+ * That is what lets a mutation copy real files with real modes from bytes a
2167
+ * caller supplied, instead of degrading them to plain text writes.
2168
+ *
2169
+ * The bytes are digested before the write and the staged file after it, so a
2170
+ * value that disagrees with its own manifest is told apart from a write that
2171
+ * did not land.
2172
+ *
2173
+ * @example
2174
+ * ```ts
2175
+ * import { stageBytes } from '@orkestrel/scaffold/server'
2176
+ *
2177
+ * stageBytes(host, '/tmp/orkestrel-host-a1b2', ['scripts/codex.sh'])
2178
+ * // [{ storage: 'scripts/codex.sh', destination: 'scripts/codex.sh', executable: true, digest: '…' }]
2179
+ * ```
2180
+ */
2181
+ export declare function stageBytes(host: Host, root: string, destinations: readonly string[]): readonly ManifestEntry[];
2150
2182
 
2151
- /** Represents a Surface name claimed by distinct package guides. */
2152
- export declare interface SurfaceCollision {
2153
- readonly name: string;
2154
- readonly owners: readonly string[];
2155
- }
2183
+ /**
2184
+ * Stages a vendored host root from a real checkout.
2185
+ *
2186
+ * @param checkout - The checkout the vendored paths are read from.
2187
+ * @param host - The vendored host root to fill; it must be absent or empty.
2188
+ * @param options - The inventory selection, establishment switch, and reporting callback.
2189
+ * Default: no reporting.
2190
+ * @returns One entry per staged file, sorted by storage name.
2191
+ * @throws `ScaffoldError('INVALID', …)` when either argument is not a host path
2192
+ * or a vendored path leaves the checkout or the host root.
2193
+ * @throws `ScaffoldError('TARGET', …)` when the checkout is not a directory, the
2194
+ * host root is not vacant, the checkout does not carry every vendored path, two
2195
+ * vendored files claim one storage name, a vendored file is not a plain file
2196
+ * within the artifact ceiling, published guides are unreadable, a staged
2197
+ * collision's owner set is not a subset of its published owner set, the inventory is absent without
2198
+ * `establish: true`, or the staged manifest does not read back.
2199
+ * @throws `ScaffoldError('WRITE', …)` when a staged file or the manifest cannot
2200
+ * be written.
2201
+ *
2202
+ * @remarks
2203
+ * This is the producer half of the vendored host, and it is not the mutation
2204
+ * contract `MaterializerInterface` states. That contract owns **target**
2205
+ * writes: it materializes a compiled plan into a consumer's workspace, binds
2206
+ * every destination to what the caller observed, and rolls a failed commit back.
2207
+ * This reads this package's own checkout at build time and fills its own build
2208
+ * output. Different direction, different lifetime, no consumer target involved,
2209
+ * so they do not overlap and neither one belongs inside the other.
2210
+ *
2211
+ * Staging is plain rather than transactional for the same reason. A
2212
+ * `WriteTransaction` exists to hold a directory that already holds work
2213
+ * still; a build output holds nothing, is deleted whole before every build, and
2214
+ * has no concurrent reader. What replaces it is refusing early and ordering the
2215
+ * writes: the whole membership is derived before anything is created, so a
2216
+ * membership refusal leaves no host root at all, and `manifest.json` is
2217
+ * written last, so a copy or Surface refusal leaves a root every
2218
+ * reader treats as a raw checkout and fails loudly on.
2219
+ *
2220
+ * A missing vendored path is refused rather than staged around. A partial root
2221
+ * is not detectably partial: it fails later, in a consumer's terminal, on
2222
+ * whichever path the plan reached first. Refusing here fails the build that
2223
+ * produced it, where the maintainer can act, and it names every missing path at
2224
+ * once. A directory is the same case — declaring an absent directory as an empty
2225
+ * root would create an empty directory in every generated workspace. A canon file
2226
+ * member the checkout no longer holds is the one exception: scaffold keeps owning
2227
+ * the path so a target's copy reports foreign, and the stage ships nothing for it.
2228
+ *
2229
+ * The walk covers `HOST_PATHS`, `CANON_PATHS`, and `REFERENCE_PATHS` together.
2230
+ * A plan selects the paths a target receives; the installed package also carries
2231
+ * the canon and reference files for reading. Every catalog package must have a
2232
+ * guide in the discovered membership before the host is written.
2233
+ * Copied guides must carry no collision whose name and exact owner set the
2234
+ * committed inventory lacks. An absent inventory requires `establish: true`;
2235
+ * an inventory without a valid recorded Surface is refused.
2236
+ * Everything downstream — the missing-path refusal, the
2237
+ * storage collision guard, the sort, the digests, and the root inventory — reads
2238
+ * the union, so every staged path follows the same law.
2239
+ *
2240
+ * The vendoring deny-list applies to what the walk discovers beneath a staged
2241
+ * directory, where a maintainer's local credential can legitimately sit, and
2242
+ * such a path is skipped. A path a staging list names itself is curated data
2243
+ * rather than discovery, so it is staged or the stage is refused.
2244
+ *
2245
+ * @example Vendored data root
2246
+ * ```ts
2247
+ * import { stageHost } from '@orkestrel/scaffold/server'
2248
+ *
2249
+ * stageHost(process.cwd(), 'dist/host') // one ManifestEntry per file staged
2250
+ * ```
2251
+ */
2252
+ export declare function stageHost(checkout: string, host: string, options?: HostStageOptions): readonly ManifestEntry[];
2156
2253
 
2157
- /**
2158
- * Reports the outcome of one bounded read whose body is taken as text.
2159
- *
2160
- * @remarks
2161
- * `content` carries the body only when `lookup` is `found`; otherwise it is
2162
- * empty and `note` states what happened. A read that is not `found` is an answer
2163
- * rather than a throw, which is what lets one dead row sit beside live ones.
2164
- */
2165
- export declare interface TextReadResult {
2166
- readonly lookup: Lookup;
2167
- readonly content: string;
2168
- readonly note: string;
2169
- }
2254
+ /**
2255
+ * Stages the committed inventory of the files a vendored host carries.
2256
+ *
2257
+ * @param checkout - The checkout whose vendored paths are inventoried.
2258
+ * @param path - The host path where the JSON inventory is written.
2259
+ * @returns The validated manifest written to `path`.
2260
+ * @throws `ScaffoldError('INVALID', …)` when `path` is not a host path.
2261
+ * @throws `ScaffoldError('WRITE', …)` when a temporary host or the inventory
2262
+ * cannot be written or removed.
2263
+ * @throws `ScaffoldError('TARGET', …)` when the staged inventory does not read
2264
+ * back through the manifest validator.
2265
+ *
2266
+ * @remarks
2267
+ * Uses {@link stageHost} as the single vendored-path expansion. The temporary
2268
+ * host supplies the same entries, roots, per-file digests, and membership
2269
+ * digest as the published host while the requested output remains one JSON
2270
+ * file.
2271
+ *
2272
+ * @example
2273
+ * ```ts
2274
+ * import { stageInventory } from '@orkestrel/scaffold/server'
2275
+ *
2276
+ * stageInventory(process.cwd(), 'host.json') // the committed host inventory
2277
+ * ```
2278
+ */
2279
+ export declare function stageInventory(checkout: string, path?: string): HostManifest;
2170
2280
 
2171
- /** Holds the note a release carries when its packument names no readable latest version. */
2172
- export declare const UNREADABLE_VERSION_NOTE = "the answer carries no readable latest version";
2281
+ /** Represents a Surface name claimed by distinct package guides. */
2282
+ export declare interface SurfaceCollision {
2283
+ readonly name: string;
2284
+ readonly owners: readonly string[];
2285
+ }
2173
2286
 
2174
- /**
2175
- * Represents the reading spine: one bounded, unauthenticated, redirect-free request per answer.
2176
- *
2177
- * @remarks
2178
- * This is the package's only network reader, and it never writes. Every call
2179
- * opens one byte allowance and spends it across every read the call makes, so a
2180
- * caller is bounded twice over: `limit` refuses one oversized answer and
2181
- * `budget` refuses many small ones. They are separate exhaustion routes and
2182
- * neither bound covers the other.
2183
- *
2184
- * A per-package failure never escapes as a throw. It is projected into the
2185
- * verdict that package's row already carries — `missing` for an upstream `404`,
2186
- * which is a definite answer, and `failed` for a transport fault, which is no
2187
- * answer at all — so one unreachable package never costs the caller the rest of
2188
- * the answer. The organization package list is the one exception: without it
2189
- * there is no fleet to report, so an unreachable or malformed list is a coded
2190
- * `FETCH` failure.
2191
- *
2192
- * The vendored-file inventory is the other read a whole call rests on, and it
2193
- * fails the other way: it fails every row of its call rather than throwing, so
2194
- * the caller receives one whole dead answer it can replace with one whole
2195
- * baseline instead of a mixture it would have to reconcile.
2196
- *
2197
- * Requests are unauthenticated because every fleet repository is public, and
2198
- * they follow no redirect, so a misconfigured or hostile endpoint cannot move a
2199
- * read to another host. Each one is bounded by its endpoint's timeout and by the
2200
- * reader's own abort signal, so {@link Upstream.destroy} cancels what is in
2201
- * flight instead of waiting for it.
2202
- *
2203
- * The allowance is threaded through the private reads as a mutable
2204
- * {@link ReadAllowance} carrier rather than held on the instance, because
2205
- * concurrent calls each own their own budget and must not spend each other's.
2206
- *
2207
- * @example
2208
- * ```ts
2209
- * import { Upstream } from '@orkestrel/scaffold/server'
2210
- *
2211
- * const upstream = new Upstream({ registry: { timeout: 5_000 } })
2212
- * const releases = await upstream.lookup([{ name: '@orkestrel/emitter', range: '^0.0.5' }])
2213
- *
2214
- * releases.filter((release) => release.lookup === 'found')
2215
- * upstream.destroy()
2216
- * ```
2217
- */
2218
- export declare class Upstream implements UpstreamInterface {
2219
- #private;
2220
- /**
2221
- * Constructs a reader over one raw content host and one registry.
2222
- *
2223
- * @param options - The endpoints, the request bounds, the initial
2224
- * listeners, and the listener-error handler.
2225
- * @throws {@link ScaffoldError} coded `INVALID` when `options` is present but
2226
- * is not an option bag this reader accepts, or when either endpoint names a
2227
- * scheme, host, or form this reader will not request.
2228
- *
2229
- * @remarks
2230
- * `isEndpoint` bounds an endpoint's length and nothing else, so the scheme and
2231
- * host law is settled here, where a refusal can say which endpoint was refused
2232
- * and why. An endpoint must be HTTPS, or HTTP to a loopback host — the one
2233
- * place an unencrypted request has no network between its ends. That
2234
- * refuses `file:`, `data:`, and plain HTTP to a real host, and it is what
2235
- * keeps a fixture reachable without weakening transport security anywhere a
2236
- * real request goes. An endpoint carrying credentials, a query, or a fragment
2237
- * is refused too: this reader authenticates nothing and appends its own path.
2238
- */
2239
- constructor(options?: UpstreamOptions);
2240
- /** Exposes the upstream reader's observation channel. */
2241
- get emitter(): EmitterInterface<UpstreamEventMap>;
2242
2287
  /**
2243
- * Looks up the newest release each declared range admits.
2288
+ * Reports the outcome of one bounded read whose body is taken as text.
2244
2289
  *
2245
- * @param dependencies - The declared dependencies to look up.
2246
- * @returns One release verdict per dependency, in input order.
2247
- * @throws {@link ScaffoldError} coded `INVALID` when `dependencies` is not a
2248
- * bounded list of declared dependencies, and `DESTROYED` when the reader is
2249
- * torn down before or during the call.
2250
- *
2251
- * @remarks
2252
- * The reader selects across the packument's stable version map before any
2253
- * collection bound can truncate it. It falls back to `dist-tags.latest` only
2254
- * when that version satisfies the declared range. `*` requests the newest
2255
- * stable version outright. The verdict's `major` field carries the stable
2256
- * major from the latest tag when that tag can be read.
2257
- *
2258
- * @example
2259
- * ```ts
2260
- * import { Upstream } from '@orkestrel/scaffold/server'
2290
+ * @remarks
2291
+ * `content` carries the body only when `lookup` is `found`; otherwise it is
2292
+ * empty and `note` states what happened. A read that is not `found` is an answer
2293
+ * rather than a throw, which is what lets one dead row sit beside live ones.
2294
+ */
2295
+ export declare interface TextReadResult {
2296
+ readonly lookup: Lookup;
2297
+ readonly content: string;
2298
+ readonly note: string;
2299
+ }
2300
+
2301
+ /** Holds the note a release carries when its packument names no readable latest version. */
2302
+ export declare const UNREADABLE_VERSION_NOTE = "the answer carries no readable latest version";
2303
+
2304
+ /**
2305
+ * Represents the reading spine: one bounded, unauthenticated, redirect-free request per answer.
2306
+ *
2307
+ * @remarks
2308
+ * This is the package's only network reader, and it never writes. Every call
2309
+ * opens one byte allowance and spends it across every read the call makes, so a
2310
+ * caller is bounded twice over: `limit` refuses one oversized answer and
2311
+ * `budget` refuses many small ones. They are separate exhaustion routes and
2312
+ * neither bound covers the other.
2313
+ *
2314
+ * A per-package failure never escapes as a throw. It is projected into the
2315
+ * verdict that package's row already carries — `missing` for an upstream `404`,
2316
+ * which is a definite answer, and `failed` for a transport fault, which is no
2317
+ * answer at all — so one unreachable package never costs the caller the rest of
2318
+ * the answer. The organization package list is the one exception: without it
2319
+ * there is no fleet to report, so an unreachable or malformed list is a coded
2320
+ * `FETCH` failure.
2321
+ *
2322
+ * The vendored-file inventory is the other read a whole call rests on, and it
2323
+ * fails the other way: it fails every row of its call rather than throwing, so
2324
+ * the caller receives one whole dead answer it can replace with one whole
2325
+ * baseline instead of a mixture it would have to reconcile.
2326
+ *
2327
+ * Requests are unauthenticated because every fleet repository is public, and
2328
+ * they follow no redirect, so a misconfigured or hostile endpoint cannot move a
2329
+ * read to another host. Each one is bounded by its endpoint's timeout and by the
2330
+ * reader's own abort signal, so {@link Upstream.destroy} cancels what is in
2331
+ * flight instead of waiting for it.
2332
+ *
2333
+ * Each call constructs its own {@link ReadAllowance} from the reader's budget and passes it
2334
+ * through the private reads rather than holding it on the instance, because concurrent calls
2335
+ * each own a budget and must not spend each other's.
2336
+ *
2337
+ * @example
2338
+ * ```ts
2339
+ * import { Upstream } from '@orkestrel/scaffold/server'
2340
+ *
2341
+ * const upstream = new Upstream({ registry: { timeout: 5_000 } })
2342
+ * const releases = await upstream.lookup([{ name: '@orkestrel/emitter', range: '^0.0.5' }])
2343
+ *
2344
+ * releases.filter((release) => release.lookup === 'found')
2345
+ * upstream.destroy()
2346
+ * ```
2347
+ */
2348
+ export declare class Upstream implements UpstreamInterface {
2349
+ #private;
2350
+ /**
2351
+ * Constructs a reader over one raw content host and one registry.
2261
2352
  *
2262
- * const upstream = new Upstream()
2263
- * await upstream.lookup([{ name: '@orkestrel/router', range: '^0.0.8' }])
2264
- * upstream.destroy()
2265
- * ```
2266
- */
2267
- lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
2268
- /**
2269
- * Fetches each named package's guide, beside the local mirror it answers for.
2270
- *
2271
- * @param names - The packages to fetch: the target's declared set, or the whole organization.
2272
- * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
2273
- * @returns One mirror verdict per name, in input order.
2274
- * @throws {@link ScaffoldError} coded `INVALID` when `names` is not a bounded
2275
- * list of fleet package names or `current` is not a snapshot, and `DESTROYED`
2276
- * when the reader is torn down before or during the call.
2277
- *
2278
- * @remarks
2279
- * The mirror path is derived from the package name by the same helper the plan
2280
- * derives it with, and the fetched URL ends in that exact path, so a verdict
2281
- * always answers for the file it names. `observed` is carried through from
2282
- * `current` whatever the lookup produced, because it is the precondition the
2283
- * later write is held to and a failed fetch does not change what the target
2284
- * holds.
2285
- *
2286
- * @example
2287
- * ```ts
2288
- * import { Upstream } from '@orkestrel/scaffold/server'
2353
+ * @param options - The endpoints, the request bounds, the initial
2354
+ * listeners, and the listener-error handler.
2355
+ * @throws {@link ScaffoldError} coded `INVALID` when `options` is present but
2356
+ * is not an option bag this reader accepts, or when either endpoint names a
2357
+ * scheme, host, or form this reader will not request.
2358
+ *
2359
+ * @remarks
2360
+ * `isEndpoint` bounds an endpoint's length and nothing else, so the scheme and
2361
+ * host law is settled here, where a refusal can say which endpoint was refused
2362
+ * and why. An endpoint must be HTTPS, or HTTP to a loopback host — the one
2363
+ * place an unencrypted request has no network between its ends. That
2364
+ * refuses `file:`, `data:`, and plain HTTP to a real host, and it is what
2365
+ * keeps a fixture reachable without weakening transport security anywhere a
2366
+ * real request goes. An endpoint carrying credentials, a query, or a fragment
2367
+ * is refused too: this reader authenticates nothing and appends its own path.
2368
+ */
2369
+ constructor(options?: UpstreamOptions);
2370
+ /** Exposes the upstream reader's observation channel. */
2371
+ get emitter(): EmitterInterface<UpstreamEventMap>;
2372
+ /**
2373
+ * Looks up the newest release each declared range admits.
2289
2374
  *
2290
- * const upstream = new Upstream()
2291
- * await upstream.fetch(['@orkestrel/router'], { 'guides/router.md': '2320526f75746572' })
2292
- * upstream.destroy()
2293
- * ```
2294
- */
2295
- fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
2296
- /**
2297
- * Reads each named vendored file from the repository, beside the target bytes it answers for.
2298
- *
2299
- * @param paths - The target-relative vendored paths to read.
2300
- * @param current - The target files as exact bytes, keyed by the same paths.
2301
- * @returns One file verdict per path, in input order.
2302
- * @throws {@link ScaffoldError} coded `INVALID` when `paths` is not a bounded
2303
- * list of target-relative paths or `current` is not a snapshot, and
2304
- * `DESTROYED` when the reader is torn down before or during the call.
2305
- *
2306
- * @remarks
2307
- * The committed inventory is read once per call and decides every row, so a
2308
- * path whose declared digest already matches the target's own bytes is `found`
2309
- * without a request and the call spends nothing on it. An inventory that
2310
- * produces no answer fails every row of the call rather than leaving some rows
2311
- * live and some dead, which is what leaves the caller one whole baseline to
2312
- * fall back to. A path the inventory does not name is `missing`.
2313
- *
2314
- * A fetched response's decoded content is verified against the digest the
2315
- * inventory declares for that path, before any character decoding. Transport
2316
- * encoding is transparent and does not enter the comparison, so content that
2317
- * does not hash to the inventory's claim fails its row rather than reaching a
2318
- * write. That is integrity against a single committed baseline, not
2319
- * authenticity: it detects truncated, substituted, or stale content, and it
2320
- * says nothing about who published the inventory.
2321
- *
2322
- * A guide mirror is never answered here whatever the caller asks for and
2323
- * whatever the target holds, because those bytes belong to `fetch` and to the
2324
- * mirror verb that writes them.
2325
- *
2326
- * @example
2327
- * ```ts
2328
- * import { Upstream } from '@orkestrel/scaffold/server'
2375
+ * @param dependencies - The declared dependencies to look up.
2376
+ * @returns One release verdict per dependency, in input order.
2377
+ * @throws {@link ScaffoldError} coded `INVALID` when `dependencies` is not a
2378
+ * bounded list of declared dependencies, and `DESTROYED` when the reader is
2379
+ * torn down before or during the call.
2380
+ *
2381
+ * @remarks
2382
+ * The reader selects across the packument's stable version map before any
2383
+ * collection bound can truncate it. It falls back to `dist-tags.latest` only
2384
+ * when that version satisfies the declared range. `*` requests the newest
2385
+ * stable version outright. The verdict's `major` field carries the stable
2386
+ * major from the latest tag when that tag can be read.
2387
+ *
2388
+ * @example
2389
+ * ```ts
2390
+ * import { Upstream } from '@orkestrel/scaffold/server'
2391
+ *
2392
+ * const upstream = new Upstream()
2393
+ * await upstream.lookup([{ name: '@orkestrel/router', range: '^0.0.8' }])
2394
+ * upstream.destroy()
2395
+ * ```
2396
+ */
2397
+ lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
2398
+ /**
2399
+ * Fetches each named package's guide, beside the local mirror it answers for.
2329
2400
  *
2330
- * const upstream = new Upstream()
2331
- * await upstream.read(['AGENTS.md'], { 'AGENTS.md': '2320416745' })
2332
- * upstream.destroy()
2333
- * ```
2334
- */
2335
- read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
2336
- /**
2337
- * Catalogs the published fleet from the registry's organization package list.
2338
- *
2339
- * @returns One row per published package, sorted by name.
2340
- * @throws {@link ScaffoldError} coded `FETCH` when the organization package
2341
- * list is unreachable, malformed, empty, or larger than one bounded
2342
- * collection, and `DESTROYED` when the reader is torn down before or during
2343
- * the call.
2344
- *
2345
- * @remarks
2346
- * The organization list is the exact membership the registry publishes, not a
2347
- * relevance search, and it is the whole reason this answer can claim to be the
2348
- * fleet. So it fails hard where a per-package lookup fails soft: an empty or
2349
- * unreadable list would otherwise be written into a target's catalog table as
2350
- * a fleet with no packages in it. A package whose own version lookup then
2351
- * fails still keeps its row, carrying the cause instead of a version, because
2352
- * the list already proved the package is published.
2353
- *
2354
- * @example
2355
- * ```ts
2356
- * import { Upstream } from '@orkestrel/scaffold/server'
2357
- *
2358
- * const upstream = new Upstream()
2359
- * const entries = await upstream.catalog()
2360
- * upstream.destroy()
2361
- * ```
2362
- */
2363
- catalog(): Promise<readonly CatalogEntry[]>;
2364
- /**
2365
- * Tears the reader down, aborting every request in flight. Teardown is idempotent.
2366
- *
2367
- * @returns Nothing.
2368
- *
2369
- * @remarks
2370
- * A call still in flight rejects with a `DESTROYED` error rather than
2371
- * resolving to a partial answer, because half a fleet reads exactly like a
2372
- * whole one.
2373
- *
2374
- * @example
2375
- * ```ts
2376
- * import { Upstream } from '@orkestrel/scaffold/server'
2377
- *
2378
- * const upstream = new Upstream()
2379
- * upstream.destroy()
2380
- * upstream.emitter.destroyed // true
2381
- * ```
2382
- */
2383
- destroy(): void;
2384
- }
2385
-
2386
- /**
2387
- * Represents the upstream reader's observation channel.
2388
- *
2389
- * @remarks
2390
- * Each verdict is published whole rather than as a name beside a summary, so a
2391
- * listener reads the same value the call returns and a failed lookup is told
2392
- * apart from a successful one by the verdict's own discriminant rather than by
2393
- * which event carried it.
2394
- */
2395
- export declare type UpstreamEventMap = {
2396
- readonly release: readonly [release: Release];
2397
- readonly mirror: readonly [mirror: Mirror];
2398
- readonly file: readonly [file: HostFile];
2399
- readonly error: readonly [error: unknown];
2400
- readonly destroy: readonly [];
2401
- };
2402
-
2403
- /**
2404
- * Describes the upstream contract: the package's only network reader, and it never writes.
2405
- *
2406
- * @remarks
2407
- * A per-package failure is collected as a verdict carrying its cause, not
2408
- * thrown, so one unreachable package never costs the caller the rest of the
2409
- * answer. The organization list is the exception: without it there is no fleet
2410
- * to report, so an unreachable or malformed list is a coded failure.
2411
- *
2412
- * The vendored-file inventory is the other whole-answer read, and it fails
2413
- * softly: an inventory that produces no answer fails every row of that call
2414
- * rather than throwing, which is what leaves the caller one whole baseline to
2415
- * fall back to instead of a mixture.
2416
- */
2417
- export declare interface UpstreamInterface {
2418
- readonly emitter: EmitterInterface<UpstreamEventMap>;
2419
- /**
2420
- * Looks up the newest release each declared range admits.
2421
- *
2422
- * @param dependencies - The declared dependencies to look up.
2423
- * @returns One release verdict per dependency, in input order.
2424
- */
2425
- lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
2426
- /**
2427
- * Fetches each named package's guide, beside the local mirror it answers for.
2428
- *
2429
- * @param names - The packages to fetch: the target's declared set, or the whole organization.
2430
- * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
2431
- * @returns One mirror verdict per name, in input order.
2432
- */
2401
+ * @param names - The packages to fetch: the target's declared set, or the whole organization.
2402
+ * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
2403
+ * @returns One mirror verdict per name, in input order.
2404
+ * @throws {@link ScaffoldError} coded `INVALID` when `names` is not a bounded
2405
+ * list of fleet package names or `current` is not a snapshot, and `DESTROYED`
2406
+ * when the reader is torn down before or during the call.
2407
+ *
2408
+ * @remarks
2409
+ * The mirror path is derived from the package name by the same helper the plan
2410
+ * derives it with, and the fetched URL ends in that exact path, so a verdict
2411
+ * always answers for the file it names. `observed` is carried through from
2412
+ * `current` whatever the lookup produced, because it is the precondition the
2413
+ * later write is held to and a failed fetch does not change what the target
2414
+ * holds.
2415
+ *
2416
+ * @example
2417
+ * ```ts
2418
+ * import { Upstream } from '@orkestrel/scaffold/server'
2419
+ *
2420
+ * const upstream = new Upstream()
2421
+ * await upstream.fetch(['@orkestrel/router'], { 'guides/router.md': '2320526f75746572' })
2422
+ * upstream.destroy()
2423
+ * ```
2424
+ */
2433
2425
  fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
2434
2426
  /**
2435
2427
  * Reads each named vendored file from the repository, beside the target bytes it answers for.
@@ -2437,307 +2429,445 @@ export declare class Materializer implements MaterializerInterface {
2437
2429
  * @param paths - The target-relative vendored paths to read.
2438
2430
  * @param current - The target files as exact bytes, keyed by the same paths.
2439
2431
  * @returns One file verdict per path, in input order.
2440
- */
2441
- read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
2442
- /**
2443
- * Catalogs the published fleet from the registry's organization package list.
2444
- *
2445
- * @returns One row per published package, sorted by name.
2446
- */
2447
- catalog(): Promise<readonly CatalogEntry[]>;
2448
- /**
2449
- * Tears the reader down, aborting every request in flight. Teardown is idempotent.
2450
- *
2451
- * @returns Nothing.
2452
- */
2453
- destroy(): void;
2454
- }
2455
-
2456
- /**
2457
- * Represents the options for the upstream reader.
2458
- *
2459
- * @remarks
2460
- * The endpoints are grouped under the entity each configures: `repository`
2461
- * takes the raw content host's `base`, its `branch`, and its `timeout`;
2462
- * `registry` takes the registry's `base` and `timeout`. One raw content host
2463
- * serves both the fleet's guides and this package's own vendored files, so one
2464
- * group configures both. `concurrency` bounds requests in
2465
- * flight and `retries` opts into per-request retry on a transport fault.
2466
- * `limit` bounds the bytes read from one response body and `budget` bounds the
2467
- * bytes read across a whole call, so neither one oversized answer nor many
2468
- * small ones can exhaust the caller. Every request is unauthenticated and
2469
- * follows no redirect.
2470
- */
2471
- export declare interface UpstreamOptions {
2472
- readonly repository?: {
2473
- readonly base?: string;
2474
- readonly branch?: string;
2475
- readonly timeout?: number;
2476
- };
2477
- readonly registry?: {
2478
- readonly base?: string;
2479
- readonly timeout?: number;
2480
- };
2481
- readonly concurrency?: number;
2482
- readonly retries?: number;
2483
- readonly limit?: number;
2484
- readonly budget?: number;
2485
- readonly on?: EmitterHooks<UpstreamEventMap>;
2486
- readonly error?: EmitterErrorHandler;
2487
- }
2488
-
2489
- /**
2490
- * Describes what git reports about a target's working tree.
2491
- *
2492
- * @remarks
2493
- * `tracked` is the only set a deletion may draw from: git does not report the
2494
- * loss of an untracked path and `git diff` cannot restore it, so an ignored
2495
- * path such as an installed dependency tree, a build output, or an editor
2496
- * directory survives every verb. `dirty` is every path carrying an uncommitted
2497
- * change, taken repo-wide rather than over a write set, because deletion makes
2498
- * the write set the whole workspace. A clean tree is an empty `dirty`. A target
2499
- * that is not a git repository yields no `Worktree` at all, so the caller
2500
- * decides what to do about that rather than reading it out of an invented
2501
- * empty value.
2502
- */
2503
- export declare interface Worktree {
2504
- readonly tracked: readonly string[];
2505
- readonly dirty: readonly string[];
2506
- }
2507
-
2508
- /**
2509
- * Represents one physical directory identity captured across a write transaction.
2510
- *
2511
- * @remarks
2512
- * Native bigint device and inode values locate the directory and do not date it.
2513
- * Two directories occupying one slot at different times share an anchor, so an anchor proves
2514
- * where a write lands and never that the directory was left alone.
2515
- */
2516
- export declare interface WriteAnchor {
2517
- readonly path: string;
2518
- readonly device: bigint;
2519
- readonly inode: bigint;
2520
- }
2521
-
2522
- /** Reports the final directory anchor of a write transaction and the subset one call created. */
2523
- export declare interface WriteDirectoryResult {
2524
- readonly anchor: WriteAnchor;
2525
- readonly created: readonly WriteAnchor[];
2526
- }
2527
-
2528
- /**
2529
- * Represents one destination snapshot captured before a write and required to survive it.
2530
- *
2531
- * @remarks
2532
- * `device`, `inode`, `modified`, `size`, and `digest` are present only where
2533
- * the observed shape supplies them.
2534
- */
2535
- export declare interface WriteExpectation {
2536
- readonly path: string;
2537
- readonly shape: 'absent' | 'file' | 'directory';
2538
- readonly device?: number;
2539
- readonly inode?: number;
2540
- readonly modified?: number;
2541
- readonly size?: number;
2542
- readonly digest?: string;
2543
- }
2544
-
2545
- /**
2546
- * Describes the narrower caller-observed destination state a write transaction must
2547
- * still match.
2548
- */
2549
- export declare interface WritePrecondition {
2550
- readonly path: string;
2551
- readonly shape: 'absent' | 'file';
2552
- readonly digest?: string;
2553
- }
2554
-
2555
- /**
2556
- * Represents one staged, reversible mutation of one target directory.
2557
- *
2558
- * @remarks
2559
- * The transaction owns a private root beside the target — a sibling directory on
2560
- * the same volume, so every promotion is a rename rather than a copy. Staging
2561
- * writes go into that root and nothing else, so a call that fails while staging
2562
- * has not touched the target at all. Commit is the only step that mutates the
2563
- * target, and it is the only step that can need rolling back.
2564
- *
2565
- * A precondition and an expectation hold a destination still. A **precondition** is what the caller
2566
- * observed earlier and is checked once, at construction, so a target that moved
2567
- * between the caller's read and this transaction fails before anything is
2568
- * created. An **expectation** is captured here, at construction, and re-checked
2569
- * at commit, so a target that moves while the write is being staged fails before
2570
- * anything is promoted.
2571
- *
2572
- * What this provides, exactly:
2573
- *
2574
- * - **Across destinations, staged-then-swapped with rollback on a caught
2575
- * failure.** A failure part way through commit restores every destination it
2576
- * already promoted, restores every file it already took, and removes every
2577
- * directory it created, then reports what recovery could not undo. This is
2578
- * measured: a promotion is driven to fail after an earlier one landed, and the
2579
- * earlier destination is read back.
2580
- * - **No partly written destination.** Every file is written whole into the
2581
- * private root and digested there before commit, so a destination never
2582
- * receives bytes that were still being produced.
2583
- * - **Containment, not continuity, of the directories it creates.** Every
2584
- * ancestor is re-read between `mkdir` calls and again before the first
2585
- * promotion, so an ancestor that became a file, a symlink, a directory
2586
- * elsewhere, or nothing is refused. An ancestor deleted and recreated under
2587
- * the same name can receive its old inode back and is indistinguishable here
2588
- * from one that never moved.
2589
- * - **No crash atomicity across destinations.** A process killed between
2590
- * promotions leaves the target holding some new files and some old ones, and
2591
- * leaves the private root behind. Nothing here is a journal, and the private
2592
- * root's name is the only record a later run could read.
2593
- *
2594
- * A destination is preserved by hard link and then replaced by a single
2595
- * `rename`, rather than moved aside and replaced, so the path continues to name
2596
- * the old file right up to the swap. That is a property of `rename` on the host,
2597
- * not one this package's tests measure: telling it apart from move-then-replace
2598
- * needs a reader observing the destination inside the swap, and no test here
2599
- * does that. Read the claim as the mechanism it describes, not as a proven
2600
- * guarantee about a concurrent reader.
2601
- *
2602
- * A path names a file this transaction writes, takes, or establishes as a
2603
- * directory; every path is target-relative and is measured by the portable-path
2604
- * law. An expectation and a precondition both name the resolved destination
2605
- * instead, because that is the path each is re-read at.
2606
- *
2607
- * @example
2608
- * ```ts
2609
- * import { WriteTransaction } from '@orkestrel/scaffold/server'
2610
- *
2611
- * const transaction = new WriteTransaction('./packages/router', ['AGENTS.md'])
2612
- * try {
2613
- * transaction.write('AGENTS.md', '# Agents\n')
2614
- * transaction.commit() // ['AGENTS.md']
2615
- * } finally {
2616
- * transaction.discard()
2617
- * }
2618
- * ```
2619
- */
2620
- export declare class WriteTransaction {
2621
- #private;
2622
- /**
2623
- * Opens a transaction over one target directory.
2624
- *
2625
- * @param target - The directory every path is written beneath.
2626
- * @param paths - Every target-relative path this transaction may touch.
2627
- * @param preconditions - The caller-observed destination states the whole
2628
- * transaction is held to, each naming a resolved destination of `paths`.
2629
- * @throws {@link ScaffoldError} coded `INVALID` when an argument is off
2630
- * contract, `TARGET` when a destination is a shape this package will not write
2631
- * over or no longer matches its precondition, and `WRITE` when the private
2632
- * root cannot be established.
2432
+ * @throws {@link ScaffoldError} coded `INVALID` when `paths` is not a bounded
2433
+ * list of target-relative paths or `current` is not a snapshot, and
2434
+ * `DESTROYED` when the reader is torn down before or during the call.
2633
2435
  *
2634
2436
  * @remarks
2635
- * Nothing is created until every destination has been inspected and every
2636
- * precondition has held, so a refused transaction leaves no residue at all.
2637
- * The private root is created last and carries a random name, so two
2638
- * transactions over one target never collide.
2437
+ * The committed inventory is read once per call and decides every row, so a
2438
+ * path whose declared digest already matches the target's own bytes is `found`
2439
+ * without a request and the call spends nothing on it. An inventory that
2440
+ * produces no answer fails every row of the call rather than leaving some rows
2441
+ * live and some dead, which is what leaves the caller one whole baseline to
2442
+ * fall back to. A path the inventory does not name is `missing`.
2443
+ *
2444
+ * A fetched response's decoded content is verified against the digest the
2445
+ * inventory declares for that path, before any character decoding. Transport
2446
+ * encoding is transparent and does not enter the comparison, so content that
2447
+ * does not hash to the inventory's claim fails its row rather than reaching a
2448
+ * write. That is integrity against a single committed baseline, not
2449
+ * authenticity: it detects truncated, substituted, or stale content, and it
2450
+ * says nothing about who published the inventory.
2451
+ *
2452
+ * A guide mirror is never answered here whatever the caller asks for and
2453
+ * whatever the target holds, because those bytes belong to `fetch` and to the
2454
+ * mirror verb that writes them.
2455
+ *
2456
+ * @example
2457
+ * ```ts
2458
+ * import { Upstream } from '@orkestrel/scaffold/server'
2459
+ *
2460
+ * const upstream = new Upstream()
2461
+ * await upstream.read(['AGENTS.md'], { 'AGENTS.md': '2320416745' })
2462
+ * upstream.destroy()
2463
+ * ```
2639
2464
  */
2640
- constructor(target: string, paths: readonly string[], preconditions?: readonly WritePrecondition[]);
2641
- /** Names the resolved directory every path is written beneath. */
2642
- get target(): string;
2643
- /** Reports what each destination held when the transaction opened, in path order. */
2644
- get expectations(): readonly WriteExpectation[];
2645
- /** Reports whether the transaction can still be committed or discarded. */
2646
- get open(): boolean;
2465
+ read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
2647
2466
  /**
2648
- * Stages one text file.
2467
+ * Catalogs the published fleet from the registry's organization package list.
2649
2468
  *
2650
- * @param path - The target-relative path to write.
2651
- * @param content - The exact UTF-8 text the destination must hold.
2652
- * @returns Nothing.
2653
- * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2654
- * transaction opened or is already staged, `TARGET` when the destination holds
2655
- * a directory, and `WRITE` when the staged file cannot be written or does not
2656
- * carry the bytes it was given.
2469
+ * @returns One row per published package, sorted by name.
2470
+ * @throws {@link ScaffoldError} coded `FETCH` when the organization package
2471
+ * list is unreachable, malformed, empty, or larger than one bounded
2472
+ * collection, and `DESTROYED` when the reader is torn down before or during
2473
+ * the call.
2657
2474
  *
2658
2475
  * @remarks
2659
- * The staged file is read back and digested against the text it was given, so
2660
- * a partial or interrupted write is refused here rather than promoted later.
2476
+ * The organization list is the exact membership the registry publishes, not a
2477
+ * relevance search, and it is the whole reason this answer can claim to be the
2478
+ * fleet. So it fails hard where a per-package lookup fails soft: an empty or
2479
+ * unreadable list would otherwise be written into a target's catalog table as
2480
+ * a fleet with no packages in it. A package whose own version lookup then
2481
+ * fails still keeps its row, carrying the cause instead of a version, because
2482
+ * the list already proved the package is published.
2483
+ *
2484
+ * @example
2485
+ * ```ts
2486
+ * import { Upstream } from '@orkestrel/scaffold/server'
2487
+ *
2488
+ * const upstream = new Upstream()
2489
+ * const entries = await upstream.catalog()
2490
+ * upstream.destroy()
2491
+ * ```
2661
2492
  */
2662
- write(path: string, content: string): void;
2493
+ catalog(): Promise<readonly CatalogEntry[]>;
2663
2494
  /**
2664
- * Stages one byte-for-byte copy of a file that already exists on this host.
2495
+ * Tears the reader down, aborting every request in flight. Teardown is idempotent.
2665
2496
  *
2666
- * @param path - The target-relative path to write.
2667
- * @param source - The resolved absolute path to copy the bytes from.
2668
- * @param executable - If `true`, the destination carries the executable bit; if `false`, it does not.
2669
2497
  * @returns Nothing.
2670
- * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2671
- * transaction opened or is already staged, `TARGET` when the destination holds
2672
- * a directory or the source is not a physical file, and `WRITE` when the copy
2673
- * cannot be made or does not digest to the source's bytes.
2674
- *
2675
- * @remarks
2676
- * The source is digested before the copy and the copy is digested after it, so
2677
- * a source that changed mid-copy is refused instead of staged.
2678
- */
2679
- copy(path: string, source: string, executable: boolean): void;
2680
- /**
2681
- * Establishes one directory inside the target, one segment at a time.
2682
- *
2683
- * @param path - The target-relative directory to establish.
2684
- * @returns The directory's identity and every segment this call created.
2685
- * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2686
- * transaction opened, `TARGET` when the destination holds a file, and `WRITE`
2687
- * when a segment cannot be created or changed while it was being created.
2688
- *
2689
- * @remarks
2690
- * A directory is created immediately rather than staged, because creating one
2691
- * destroys nothing: rollback removes exactly the segments this call created,
2692
- * innermost first, and leaves every segment that was already there. Each
2693
- * created segment is captured by device and inode, so a segment swapped
2694
- * underneath the transaction is detected rather than written into.
2695
- */
2696
- establish(path: string): WriteDirectoryResult;
2697
- /**
2698
- * Marks one file for deletion at commit.
2699
- *
2700
- * @param path - The target-relative file to delete.
2701
- * @returns Nothing.
2702
- * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2703
- * transaction opened or is already claimed, and `TARGET` when the destination
2704
- * does not hold a file.
2705
- *
2706
- * @remarks
2707
- * Nothing moves here. Commit renames the file into the private backup rather
2708
- * than unlinking it, so a later failure in the same commit puts it back.
2709
- */
2710
- remove(path: string): void;
2498
+ *
2499
+ * @remarks
2500
+ * A call still in flight rejects with a `DESTROYED` error rather than
2501
+ * resolving to a partial answer, because half a fleet reads exactly like a
2502
+ * whole one.
2503
+ *
2504
+ * @example
2505
+ * ```ts
2506
+ * import { Upstream } from '@orkestrel/scaffold/server'
2507
+ *
2508
+ * const upstream = new Upstream()
2509
+ * upstream.destroy()
2510
+ * upstream.emitter.destroyed // true
2511
+ * ```
2512
+ */
2513
+ destroy(): void;
2514
+ }
2515
+
2516
+ /**
2517
+ * Represents the upstream reader's observation channel.
2518
+ *
2519
+ * @remarks
2520
+ * Each verdict is published whole rather than as a name beside a summary, so a
2521
+ * listener reads the same value the call returns and a failed lookup is told
2522
+ * apart from a successful one by the verdict's own discriminant rather than by
2523
+ * which event carried it.
2524
+ */
2525
+ export declare type UpstreamEventMap = {
2526
+ readonly release: readonly [release: Release];
2527
+ readonly mirror: readonly [mirror: Mirror];
2528
+ readonly file: readonly [file: HostFile];
2529
+ readonly error: readonly [error: unknown];
2530
+ readonly destroy: readonly [];
2531
+ };
2532
+
2533
+ /**
2534
+ * Describes the upstream contract: the package's only network reader, and it never writes.
2535
+ *
2536
+ * @remarks
2537
+ * A per-package failure is collected as a verdict carrying its cause, not
2538
+ * thrown, so one unreachable package never costs the caller the rest of the
2539
+ * answer. The organization list is the exception: without it there is no fleet
2540
+ * to report, so an unreachable or malformed list is a coded failure.
2541
+ *
2542
+ * The vendored-file inventory is the other whole-answer read, and it fails
2543
+ * softly: an inventory that produces no answer fails every row of that call
2544
+ * rather than throwing, which is what leaves the caller one whole baseline to
2545
+ * fall back to instead of a mixture.
2546
+ */
2547
+ export declare interface UpstreamInterface {
2548
+ readonly emitter: EmitterInterface<UpstreamEventMap>;
2549
+ /**
2550
+ * Looks up the newest release each declared range admits.
2551
+ *
2552
+ * @param dependencies - The declared dependencies to look up.
2553
+ * @returns One release verdict per dependency, in input order.
2554
+ */
2555
+ lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
2556
+ /**
2557
+ * Fetches each named package's guide, beside the local mirror it answers for.
2558
+ *
2559
+ * @param names - The packages to fetch: the target's declared set, or the whole organization.
2560
+ * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
2561
+ * @returns One mirror verdict per name, in input order.
2562
+ */
2563
+ fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
2564
+ /**
2565
+ * Reads each named vendored file from the repository, beside the target bytes it answers for.
2566
+ *
2567
+ * @param paths - The target-relative vendored paths to read.
2568
+ * @param current - The target files as exact bytes, keyed by the same paths.
2569
+ * @returns One file verdict per path, in input order.
2570
+ */
2571
+ read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
2572
+ /**
2573
+ * Catalogs the published fleet from the registry's organization package list.
2574
+ *
2575
+ * @returns One row per published package, sorted by name.
2576
+ */
2577
+ catalog(): Promise<readonly CatalogEntry[]>;
2578
+ /**
2579
+ * Tears the reader down, aborting every request in flight. Teardown is idempotent.
2580
+ *
2581
+ * @returns Nothing.
2582
+ */
2583
+ destroy(): void;
2584
+ }
2585
+
2586
+ /**
2587
+ * Represents the options for the upstream reader.
2588
+ *
2589
+ * @remarks
2590
+ * The endpoints are grouped under the entity each configures: `repository`
2591
+ * takes the raw content host's `base`, its `branch`, and its `timeout`;
2592
+ * `registry` takes the registry's `base` and `timeout`. One raw content host
2593
+ * serves both the fleet's guides and this package's own vendored files, so one
2594
+ * group configures both. `concurrency` bounds requests in
2595
+ * flight and `retries` opts into per-request retry on a transport fault.
2596
+ * `limit` bounds the bytes read from one response body and `budget` bounds the
2597
+ * bytes read across a whole call, so neither one oversized answer nor many
2598
+ * small ones can exhaust the caller. Every request is unauthenticated and
2599
+ * follows no redirect.
2600
+ */
2601
+ export declare interface UpstreamOptions {
2602
+ readonly repository?: {
2603
+ readonly base?: string;
2604
+ readonly branch?: string;
2605
+ readonly timeout?: number;
2606
+ };
2607
+ readonly registry?: {
2608
+ readonly base?: string;
2609
+ readonly timeout?: number;
2610
+ };
2611
+ readonly concurrency?: number;
2612
+ readonly retries?: number;
2613
+ readonly limit?: number;
2614
+ readonly budget?: number;
2615
+ readonly on?: EmitterHooks<UpstreamEventMap>;
2616
+ readonly error?: EmitterErrorHandler;
2617
+ }
2618
+
2619
+ /**
2620
+ * Describes what git reports about a target's working tree.
2621
+ *
2622
+ * @remarks
2623
+ * `tracked` is the only set a deletion may draw from: git does not report the
2624
+ * loss of an untracked path and `git diff` cannot restore it, so an ignored
2625
+ * path such as an installed dependency tree, a build output, or an editor
2626
+ * directory survives every verb. `dirty` is every path carrying an uncommitted
2627
+ * change, taken repo-wide rather than over a write set, because deletion makes
2628
+ * the write set the whole workspace. A clean tree is an empty `dirty`. A target
2629
+ * that is not a git repository yields no `Worktree` at all, so the caller
2630
+ * decides what to do about that rather than reading it out of an invented
2631
+ * empty value.
2632
+ */
2633
+ export declare interface Worktree {
2634
+ readonly tracked: readonly string[];
2635
+ readonly dirty: readonly string[];
2636
+ }
2637
+
2638
+ /**
2639
+ * Represents one physical directory identity captured across a write transaction.
2640
+ *
2641
+ * @remarks
2642
+ * Native bigint device and inode values locate the directory and do not date it.
2643
+ * Two directories occupying one slot at different times share an anchor, so an anchor proves
2644
+ * where a write lands and never that the directory was left alone.
2645
+ */
2646
+ export declare interface WriteAnchor {
2647
+ readonly path: string;
2648
+ readonly device: bigint;
2649
+ readonly inode: bigint;
2650
+ }
2651
+
2652
+ /** Reports the final directory anchor of a write transaction and the subset one call created. */
2653
+ export declare interface WriteDirectoryResult {
2654
+ readonly anchor: WriteAnchor;
2655
+ readonly created: readonly WriteAnchor[];
2656
+ }
2657
+
2658
+ /**
2659
+ * Represents one destination snapshot captured before a write and required to survive it.
2660
+ *
2661
+ * @remarks
2662
+ * `device`, `inode`, `modified`, `size`, and `digest` are present only where
2663
+ * the observed shape supplies them.
2664
+ */
2665
+ export declare interface WriteExpectation {
2666
+ readonly path: string;
2667
+ readonly shape: 'absent' | 'file' | 'directory';
2668
+ readonly device?: number;
2669
+ readonly inode?: number;
2670
+ readonly modified?: number;
2671
+ readonly size?: number;
2672
+ readonly digest?: string;
2673
+ }
2674
+
2675
+ /**
2676
+ * Describes the narrower caller-observed destination state a write transaction must
2677
+ * still match.
2678
+ */
2679
+ export declare interface WritePrecondition {
2680
+ readonly path: string;
2681
+ readonly shape: 'absent' | 'file';
2682
+ readonly digest?: string;
2683
+ }
2684
+
2685
+ /**
2686
+ * Represents one staged, reversible mutation of one target directory.
2687
+ *
2688
+ * @remarks
2689
+ * The transaction owns a private root beside the target — a sibling directory on
2690
+ * the same volume, so every promotion is a rename rather than a copy. Staging
2691
+ * writes go into that root and nothing else, so a call that fails while staging
2692
+ * has not touched the target at all. Commit is the only step that mutates the
2693
+ * target, and it is the only step that can need rolling back.
2694
+ *
2695
+ * A precondition and an expectation hold a destination still. A **precondition** is what the caller
2696
+ * observed earlier and is checked once, at construction, so a target that moved
2697
+ * between the caller's read and this transaction fails before anything is
2698
+ * created. An **expectation** is captured here, at construction, and re-checked
2699
+ * at commit, so a target that moves while the write is being staged fails before
2700
+ * anything is promoted.
2701
+ *
2702
+ * What this provides, exactly:
2703
+ *
2704
+ * - **Across destinations, staged-then-swapped with rollback on a caught
2705
+ * failure.** A failure part way through commit restores every destination it
2706
+ * already promoted, restores every file it already took, and removes every
2707
+ * directory it created, then reports what recovery could not undo. This is
2708
+ * measured: a promotion is driven to fail after an earlier one landed, and the
2709
+ * earlier destination is read back.
2710
+ * - **No partly written destination.** Every file is written whole into the
2711
+ * private root and digested there before commit, so a destination never
2712
+ * receives bytes that were still being produced.
2713
+ * - **Containment, not continuity, of the directories it creates.** Every
2714
+ * ancestor is re-read between `mkdir` calls and again before the first
2715
+ * promotion, so an ancestor that became a file, a symlink, a directory
2716
+ * elsewhere, or nothing is refused. An ancestor deleted and recreated under
2717
+ * the same name can receive its old inode back and is indistinguishable here
2718
+ * from one that never moved.
2719
+ * - **No crash atomicity across destinations.** A process killed between
2720
+ * promotions leaves the target holding some new files and some old ones, and
2721
+ * leaves the private root behind. Nothing here is a journal, and the private
2722
+ * root's name is the only record a later run could read.
2723
+ *
2724
+ * A destination is preserved by hard link and then replaced by a single
2725
+ * `rename`, rather than moved aside and replaced, so the path continues to name
2726
+ * the old file right up to the swap. That is a property of `rename` on the host,
2727
+ * not one this package's tests measure: telling it apart from move-then-replace
2728
+ * needs a reader observing the destination inside the swap, and no test here
2729
+ * does that. Read the claim as the mechanism it describes, not as a proven
2730
+ * guarantee about a concurrent reader.
2731
+ *
2732
+ * A path names a file this transaction writes, takes, or establishes as a
2733
+ * directory; every path is target-relative and is measured by the portable-path
2734
+ * law. An expectation and a precondition both name the resolved destination
2735
+ * instead, because that is the path each is re-read at.
2736
+ *
2737
+ * @example
2738
+ * ```ts
2739
+ * import { WriteTransaction } from '@orkestrel/scaffold/server'
2740
+ *
2741
+ * const transaction = new WriteTransaction('./packages/router', ['AGENTS.md'])
2742
+ * try {
2743
+ * transaction.write('AGENTS.md', '# Agents\n')
2744
+ * transaction.commit() // ['AGENTS.md']
2745
+ * } finally {
2746
+ * transaction.discard()
2747
+ * }
2748
+ * ```
2749
+ */
2750
+ export declare class WriteTransaction {
2751
+ #private;
2711
2752
  /**
2712
- * Promotes every staged file and takes every marked file, or rolls the whole call back.
2753
+ * Opens a transaction over one target directory.
2713
2754
  *
2714
- * @returns Every target-relative path whose destination changed: the files
2715
- * promoted, then the directories established, then the files taken.
2716
- * @throws {@link ScaffoldError} coded `WRITE` when the transaction is closed,
2717
- * when a destination moved since the transaction opened, or when the commit
2718
- * failed; a failure reports what rollback could not undo in its context.
2755
+ * @param target - The directory every path is written beneath.
2756
+ * @param paths - Every target-relative path this transaction may touch.
2757
+ * @param preconditions - The caller-observed destination states the whole
2758
+ * transaction is held to, each naming a resolved destination of `paths`.
2759
+ * @throws {@link ScaffoldError} coded `INVALID` when an argument is off
2760
+ * contract, `TARGET` when a destination is a shape this package will not write
2761
+ * over or no longer matches its precondition, and `WRITE` when the private
2762
+ * root cannot be established.
2719
2763
  *
2720
2764
  * @remarks
2721
- * Every destination and every directory this transaction created is re-checked
2722
- * before anything moves, so the common failure moves nothing at all. That
2723
- * check runs inside the same rollback as the promotions, because a transaction
2724
- * that refuses before it starts still has a private root and created
2725
- * directories to clear. The transaction is closed either way: a committed one
2726
- * has nothing left to undo and a failed one has already been rolled back.
2765
+ * Nothing is created until every destination has been inspected and every
2766
+ * precondition has held, so a refused transaction leaves no residue at all.
2767
+ * The private root is created last and carries a random name, so two
2768
+ * transactions over one target never collide.
2727
2769
  */
2728
- commit(): readonly string[];
2770
+ constructor(target: string, paths: readonly string[], preconditions?: readonly WritePrecondition[]);
2771
+ /** Names the resolved directory every path is written beneath. */
2772
+ get target(): string;
2773
+ /** Reports what each destination held when the transaction opened, in path order. */
2774
+ get expectations(): readonly WriteExpectation[];
2775
+ /** Reports whether the transaction can still be committed or discarded. */
2776
+ get open(): boolean;
2729
2777
  /**
2730
- * Abandons the transaction and removes everything it created.
2778
+ * Stages one text file.
2731
2779
  *
2780
+ * @param path - The target-relative path to write.
2781
+ * @param content - The exact UTF-8 text the destination must hold.
2732
2782
  * @returns Nothing.
2733
- * @throws {@link ScaffoldError} coded `WRITE` when residue could not be
2734
- * removed, naming the private root that still holds it.
2783
+ * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2784
+ * transaction opened or is already staged, `TARGET` when the destination holds
2785
+ * a directory, and `WRITE` when the staged file cannot be written or does not
2786
+ * carry the bytes it was given.
2735
2787
  *
2736
2788
  * @remarks
2737
- * Idempotent, and a no-op on a transaction that already committed or already
2738
- * failed, so a caller can put it in a `finally` beside the work it guards.
2789
+ * The staged file is read back and digested against the text it was given, so
2790
+ * a partial or interrupted write is refused here rather than promoted later.
2739
2791
  */
2740
- discard(): void;
2741
- }
2742
-
2743
- export { }
2792
+ write(path: string, content: string): void;
2793
+ /**
2794
+ * Stages one byte-for-byte copy of a file that already exists on this host.
2795
+ *
2796
+ * @param path - The target-relative path to write.
2797
+ * @param source - The resolved absolute path to copy the bytes from.
2798
+ * @param executable - If `true`, the destination carries the executable bit; if `false`, it does not.
2799
+ * @returns Nothing.
2800
+ * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2801
+ * transaction opened or is already staged, `TARGET` when the destination holds
2802
+ * a directory or the source is not a physical file, and `WRITE` when the copy
2803
+ * cannot be made or does not digest to the source's bytes.
2804
+ *
2805
+ * @remarks
2806
+ * The source is digested before the copy and the copy is digested after it, so
2807
+ * a source that changed mid-copy is refused instead of staged.
2808
+ */
2809
+ copy(path: string, source: string, executable: boolean): void;
2810
+ /**
2811
+ * Establishes one directory inside the target, one segment at a time.
2812
+ *
2813
+ * @param path - The target-relative directory to establish.
2814
+ * @returns The directory's identity and every segment this call created.
2815
+ * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2816
+ * transaction opened, `TARGET` when the destination holds a file, and `WRITE`
2817
+ * when a segment cannot be created or changed while it was being created.
2818
+ *
2819
+ * @remarks
2820
+ * A directory is created immediately rather than staged, because creating one
2821
+ * destroys nothing: rollback removes exactly the segments this call created,
2822
+ * innermost first, and leaves every segment that was already there. Each
2823
+ * created segment is captured by device and inode, so a segment swapped
2824
+ * underneath the transaction is detected rather than written into.
2825
+ */
2826
+ establish(path: string): WriteDirectoryResult;
2827
+ /**
2828
+ * Marks one file for deletion at commit.
2829
+ *
2830
+ * @param path - The target-relative file to delete.
2831
+ * @returns Nothing.
2832
+ * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2833
+ * transaction opened or is already claimed, and `TARGET` when the destination
2834
+ * does not hold a file.
2835
+ *
2836
+ * @remarks
2837
+ * Nothing moves here. Commit renames the file into the private backup rather
2838
+ * than unlinking it, so a later failure in the same commit puts it back.
2839
+ */
2840
+ remove(path: string): void;
2841
+ /**
2842
+ * Promotes every staged file and takes every marked file, or rolls the whole call back.
2843
+ *
2844
+ * @returns Every target-relative path whose destination changed: the files
2845
+ * promoted, then the directories established, then the files taken.
2846
+ * @throws {@link ScaffoldError} coded `WRITE` when the transaction is closed,
2847
+ * when a destination moved since the transaction opened, or when the commit
2848
+ * failed; a failure reports what rollback could not undo in its context.
2849
+ *
2850
+ * @remarks
2851
+ * Every destination and every directory this transaction created is re-checked
2852
+ * before anything moves, so the common failure moves nothing at all. That
2853
+ * check runs inside the same rollback as the promotions, because a transaction
2854
+ * that refuses before it starts still has a private root and created
2855
+ * directories to clear. The transaction is closed either way: a committed one
2856
+ * has nothing left to undo and a failed one has already been rolled back.
2857
+ */
2858
+ commit(): readonly string[];
2859
+ /**
2860
+ * Abandons the transaction and removes everything it created.
2861
+ *
2862
+ * @returns Nothing.
2863
+ * @throws {@link ScaffoldError} coded `WRITE` when residue could not be
2864
+ * removed, naming the private root that still holds it.
2865
+ *
2866
+ * @remarks
2867
+ * Idempotent, and a no-op on a transaction that already committed or already
2868
+ * failed, so a caller can put it in a `finally` beside the work it guards.
2869
+ */
2870
+ discard(): void;
2871
+ }
2872
+
2873
+ export { }