@orkestrel/scaffold 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,19 +5,21 @@ import { EmitterErrorHandler } from '@orkestrel/emitter';
5
5
  import { EmitterHooks } from '@orkestrel/emitter';
6
6
  import { EmitterInterface } from '@orkestrel/emitter';
7
7
  import { Guard } from '@orkestrel/contract';
8
+ import { HostFile } from '@orkestrel/scaffold';
9
+ import { ManifestRegionSet } from '@orkestrel/scaffold';
8
10
  import { Mirror } from '@orkestrel/scaffold';
9
11
  import { Plan } from '@orkestrel/scaffold';
10
12
  import { Release } from '@orkestrel/scaffold';
11
13
  import { Snapshot } from '@orkestrel/scaffold';
12
14
 
13
15
  /**
14
- * The Git branch syntax the guide endpoint accepts.
16
+ * The Git branch syntax the repository endpoint accepts.
15
17
  *
16
18
  * @remarks
17
19
  * A branch is caller-supplied and reaches a URL path, so it is closed to
18
20
  * alphanumerics, dot, underscore, hyphen, and the separator, must open with an
19
21
  * alphanumeric, and may carry no `..` anywhere. That last refusal is what stops
20
- * a branch from walking out of the guide directory it addresses.
22
+ * a branch from walking out of the repository path it addresses.
21
23
  */
22
24
  export declare const BRANCH_PATTERN: RegExp;
23
25
 
@@ -111,6 +113,80 @@ export declare const DIGEST_PATTERN: RegExp;
111
113
  */
112
114
  export declare const DRIVE_PATTERN: RegExp;
113
115
 
116
+ /**
117
+ * Assemble a whole vendored host from live files and the installed floor.
118
+ *
119
+ * @param files - The host-owned vendored files read from the repository, one
120
+ * row per path.
121
+ * @param floor - The installed host floor, which fixes the membership a fill
122
+ * may draw from and supplies the bytes owned by another surface.
123
+ * @returns The assembled host, or `undefined` when any row produced no answer or
124
+ * names a path the floor does not declare, or when a host-owned path is absent.
125
+ *
126
+ * @remarks
127
+ * The one place the host-owned all-or-nothing rule is decided, so no verb
128
+ * restates it. The host surface contributes one baseline: a fill carries live
129
+ * bytes for every path that surface writes, or it is nothing. A row that failed,
130
+ * went missing, names an undeclared path, or leaves a host-owned path absent
131
+ * answers `undefined`. Deferred paths are presence-only and retain the installed
132
+ * floor bytes that their catalog or mirror surface owns; repair never writes
133
+ * those floor bytes. One `Host` can therefore carry live host bytes beside floor
134
+ * bytes without mixing baselines within a surface.
135
+ *
136
+ * The emitted entries keep the release's own order and its storage and
137
+ * executable declarations, and carry digests recomputed over the bytes the fill
138
+ * actually holds. That is what lets a reader verify the value against itself,
139
+ * and it is why an undeclared path is refused rather than added: membership
140
+ * moves with a release, never with a fetch.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * import { filesToHost } from '@orkestrel/scaffold/server'
145
+ *
146
+ * filesToHost([{ path: 'AGENTS.md', lookup: 'found', hex: '23204167656e74730a' }], floor)
147
+ * // { manifest: { entries: [ … ], roots: [ … ], digest: '…' }, bytes: { 'AGENTS.md': '…' } }
148
+ * ```
149
+ */
150
+ export declare function filesToHost(files: readonly HostFile[], floor: Host): Host | undefined;
151
+
152
+ /**
153
+ * Projects exact bytes stated in hexadecimal to their SHA-256 digest.
154
+ *
155
+ * @param hex - The exact lowercase hexadecimal bytes to digest.
156
+ * @returns Sixty-four lowercase hexadecimal digits.
157
+ * @throws `ScaffoldError('INVALID', …)` when `hex` is not exact bounded
158
+ * lowercase hexadecimal text.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * import { hexToDigest } from '@orkestrel/scaffold/server'
163
+ *
164
+ * hexToDigest('68690a') // '98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4'
165
+ * ```
166
+ */
167
+ export declare function hexToDigest(hex: string): string;
168
+
169
+ /**
170
+ * A whole vendored host supplied as a value: the membership beside the bytes filling it.
171
+ *
172
+ * @remarks
173
+ * `manifest` carries the membership, the roots, and the executable declarations
174
+ * the installed release fixed, and each entry's `digest` is the digest of the
175
+ * bytes this value actually holds. `bytes` is keyed by each entry's
176
+ * `destination` and covers exactly the entries the fill holds, so an entry with
177
+ * no bytes and a byte string under no entry are each a refusal rather than a
178
+ * gap a reader has to work around.
179
+ *
180
+ * Membership never moves without a release. A fill therefore states which of
181
+ * the release's paths it carries and never introduces one the release did not
182
+ * declare, which is what keeps a value host's reach identical to the installed
183
+ * root's however the bytes were obtained.
184
+ */
185
+ export declare interface Host {
186
+ readonly manifest: HostManifest;
187
+ readonly bytes: Snapshot;
188
+ }
189
+
114
190
  /**
115
191
  * The complete vendored-host inventory.
116
192
  *
@@ -139,10 +215,10 @@ export declare interface HostManifest {
139
215
  export declare const INVALID_SEGMENT_CHARACTER_PATTERN: RegExp;
140
216
 
141
217
  /**
142
- * Narrow a value to a Git branch the guide endpoint accepts.
218
+ * Narrow a value to a Git branch the repository endpoint accepts.
143
219
  *
144
220
  * @remarks
145
- * A branch reaches the guide URL's path, so the syntax is closed rather than
221
+ * A branch reaches the repository URL's path, so the syntax is closed rather than
146
222
  * merely bounded and no `..` is admitted anywhere in it.
147
223
  *
148
224
  * @example
@@ -272,6 +348,30 @@ export declare function isExactCaseFile(path: string): boolean;
272
348
  */
273
349
  export declare function isFilesystemPath(value: unknown): value is string;
274
350
 
351
+ /**
352
+ * Narrow a value to one {@link Host}.
353
+ *
354
+ * @remarks
355
+ * A whole vendored host handed in as a value is as untrusted as one read from a
356
+ * directory a caller named, so both halves are guarded: the manifest by the same
357
+ * membership law a read root is held to, and the bytes by the core snapshot law,
358
+ * which bounds the fill and reads every key as a path and every value as exact
359
+ * lowercase hexadecimal. Whether those halves agree with each other is the
360
+ * reader's question rather than this one's, because a guard has only `false` to
361
+ * say and a mismatch has a path to name.
362
+ *
363
+ * @example
364
+ * ```ts
365
+ * import { isHost } from '@orkestrel/scaffold/server'
366
+ *
367
+ * const digest = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
368
+ *
369
+ * isHost({ manifest: { entries: [], roots: [], digest }, bytes: {} }) // true
370
+ * isHost({ manifest: { entries: [], roots: [], digest } }) // false
371
+ * ```
372
+ */
373
+ export declare const isHost: Guard<Host>;
374
+
275
375
  /**
276
376
  * Narrow a value to one {@link HostManifest}.
277
377
  *
@@ -319,11 +419,34 @@ export declare function isInventory(value: unknown): value is readonly unknown[]
319
419
  * ```ts
320
420
  * import { isManifestEntry } from '@orkestrel/scaffold/server'
321
421
  *
322
- * isManifestEntry({ storage: 'AGENTS.md', destination: 'AGENTS.md', executable: false }) // true
422
+ * isManifestEntry({
423
+ * storage: 'AGENTS.md',
424
+ * destination: 'AGENTS.md',
425
+ * executable: false,
426
+ * digest: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
427
+ * }) // true
323
428
  * ```
324
429
  */
325
430
  export declare const isManifestEntry: Guard<ManifestEntry>;
326
431
 
432
+ /**
433
+ * Narrow a value to one {@link ManifestRegionSet}.
434
+ *
435
+ * @remarks
436
+ * The whole closed record a manifest-writing method accepts, so a caller
437
+ * naming a region the writer does not carry is refused before any byte moves.
438
+ * Each region is bounded by the same collection law its own list guard applies.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * import { isManifestRegionSet } from '@orkestrel/scaffold/server'
443
+ *
444
+ * isManifestRegionSet({ pins: { runtime: [], development: [] }, scripts: [] }) // true
445
+ * isManifestRegionSet({ pins: { runtime: [], development: [] } }) // false
446
+ * ```
447
+ */
448
+ export declare const isManifestRegionSet: Guard<ManifestRegionSet>;
449
+
327
450
  /**
328
451
  * Narrow a value to the materializer's initial listener record.
329
452
  *
@@ -337,6 +460,12 @@ export declare const isMaterializerHooks: Guard<EmitterHooks<MaterializerEventMa
337
460
  /**
338
461
  * Narrow a value to {@link MaterializerOptions}.
339
462
  *
463
+ * @remarks
464
+ * `host` admits both representations of one vendored root: a directory path and
465
+ * a whole {@link Host} value. They share a key because they are one setting
466
+ * stated two ways rather than two settings, so nothing downstream has to
467
+ * reconcile a pair that could disagree.
468
+ *
340
469
  * @example
341
470
  * ```ts
342
471
  * import { isMaterializerOptions } from '@orkestrel/scaffold/server'
@@ -350,6 +479,25 @@ export declare const isMaterializerOptions: Guard<MaterializerOptions>;
350
479
  /** Narrow a value to a bounded list of fetched guide mirrors. */
351
480
  export declare const isMirrors: Guard<readonly Mirror[]>;
352
481
 
482
+ /**
483
+ * Narrow a value to a bounded list of target-relative paths.
484
+ *
485
+ * @remarks
486
+ * Composed from the core collection and path guards rather than restated, so
487
+ * the containment law that keeps a caller-supplied path inside its target has
488
+ * exactly one home. It bounds what a caller may hand a public method, which is
489
+ * why it is not {@link isInventory}: that one bounds what a checkout may hold.
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * import { isPaths } from '@orkestrel/scaffold/server'
494
+ *
495
+ * isPaths(['AGENTS.md']) // true
496
+ * isPaths(['../secrets']) // false
497
+ * ```
498
+ */
499
+ export declare const isPaths: Guard<readonly string[]>;
500
+
353
501
  /**
354
502
  * Test whether a path is a physical directory this package will read or write into.
355
503
  *
@@ -393,25 +541,6 @@ export declare function isPhysicalDirectory(path: string): boolean;
393
541
  */
394
542
  export declare function isPhysicalFile(path: string): boolean;
395
543
 
396
- /**
397
- * Narrow a value to a {@link Repository}.
398
- *
399
- * @remarks
400
- * Both path lists are target-relative, so both are measured by the core path
401
- * law: a reported path that is not one this package could have planned is not a
402
- * path it will delete. The inventory guard bounds the lists, because a checkout
403
- * is legitimately far larger than any collection a caller hands a method.
404
- *
405
- * @example
406
- * ```ts
407
- * import { isRepository } from '@orkestrel/scaffold/server'
408
- *
409
- * isRepository({ tracked: ['AGENTS.md'], dirty: [] }) // true
410
- * isRepository({ tracked: ['../secrets'], dirty: [] }) // false
411
- * ```
412
- */
413
- export declare const isRepository: Guard<Repository>;
414
-
415
544
  /**
416
545
  * Narrow a value to a per-request timeout in milliseconds.
417
546
  *
@@ -447,7 +576,7 @@ export declare const isUpstreamHooks: Guard<EmitterHooks<UpstreamEventMap>>;
447
576
  * ```ts
448
577
  * import { isUpstreamOptions } from '@orkestrel/scaffold/server'
449
578
  *
450
- * isUpstreamOptions({ guides: { branch: 'main' }, concurrency: 4 }) // true
579
+ * isUpstreamOptions({ repository: { branch: 'main' }, concurrency: 4 }) // true
451
580
  * isUpstreamOptions({ concurrency: 0 }) // false
452
581
  * ```
453
582
  */
@@ -476,6 +605,25 @@ export declare const isUpstreamOptions: Guard<UpstreamOptions>;
476
605
  */
477
606
  export declare function isVacant(target: string): boolean;
478
607
 
608
+ /**
609
+ * Narrow a value to a {@link Worktree}.
610
+ *
611
+ * @remarks
612
+ * Both path lists are target-relative, so both are measured by the core path
613
+ * law: a reported path that is not one this package could have planned is not a
614
+ * path it will delete. The inventory guard bounds the lists, because a checkout
615
+ * is legitimately far larger than any collection a caller hands a method.
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * import { isWorktree } from '@orkestrel/scaffold/server'
620
+ *
621
+ * isWorktree({ tracked: ['AGENTS.md'], dirty: [] }) // true
622
+ * isWorktree({ tracked: ['../secrets'], dirty: [] }) // false
623
+ * ```
624
+ */
625
+ export declare const isWorktree: Guard<Worktree>;
626
+
479
627
  /**
480
628
  * List a directory's descendant directories as sorted root-relative paths.
481
629
  *
@@ -548,11 +696,19 @@ export declare function listFiles(root: string): readonly string[];
548
696
  */
549
697
  export declare const MANIFEST_NAME = "manifest.json";
550
698
 
551
- /** One file record of the vendored host's manifest. */
699
+ /**
700
+ * One file record of the vendored host's manifest.
701
+ *
702
+ * @remarks
703
+ * `digest` is the SHA-256 of the file's exact bytes. A live read compares it
704
+ * against the target's copy, and verifies host-supplied bytes against it before
705
+ * treating them as that entry.
706
+ */
552
707
  export declare interface ManifestEntry {
553
708
  readonly storage: string;
554
709
  readonly destination: string;
555
710
  readonly executable: boolean;
711
+ readonly digest: string;
556
712
  }
557
713
 
558
714
  /**
@@ -708,7 +864,7 @@ export declare function matchesPrecondition(precondition: WritePrecondition): bo
708
864
  * directories. It is the inversion the contract asks for: the candidate set
709
865
  * is re-derived from the plan and narrowed by what git tracks, and the audit
710
866
  * must agree with that derivation rather than supply the set itself.
711
- * Repository metadata is protected because losing history is not a repair,
867
+ * Git metadata is protected because losing history is not a repair,
712
868
  * and a target's own `src` and `app` trees are protected because a
713
869
  * workspace's source is the one thing scaffold never plans and never owns. A
714
870
  * plan the compiler emits never maps a protected root, so this guard exists
@@ -735,7 +891,7 @@ export declare function matchesProtectedPath(path: string): boolean;
735
891
  * The vendoring deny-list. A host root is staged from a real checkout, so the
736
892
  * refusal is stated over the path rather than over the file's content: a
737
893
  * credential is recognizable by where it sits and what it is called long before
738
- * anything reads it. Repository metadata is included through
894
+ * anything reads it. Git metadata is included through
739
895
  * {@link matchesGitPath}, so one call answers the whole question and no caller
740
896
  * has to remember to ask twice.
741
897
  *
@@ -768,6 +924,12 @@ export declare function matchesSensitivePath(path: string): boolean;
768
924
  * case, so a manifest naming `agents.md` for a stored `AGENTS.md` is refused on
769
925
  * a case-insensitive filesystem rather than silently resolved.
770
926
  *
927
+ * That host arrives as a directory path or as a whole {@link Host} value, and
928
+ * every verb reads one immutable host either way. A value is owned, verified
929
+ * against its own membership and digests, and read in memory; a write fills it
930
+ * into a private root and copies from there, so the executable declarations the
931
+ * release fixed reach the target from either representation.
932
+ *
771
933
  * What a mutation guarantees is exactly what {@link WriteTransaction}
772
934
  * guarantees, and no more: a caught failure part way through a commit rolls the
773
935
  * whole commit back, no destination ever receives half-written bytes, and a
@@ -794,18 +956,26 @@ export declare class Materializer implements MaterializerInterface {
794
956
  /**
795
957
  * Construct a materializer over one vendored host root.
796
958
  *
797
- * @param options - The vendored host root, the initial listeners, and the
798
- * listener-error handler.
959
+ * @param options - The vendored host, in either representation, the initial
960
+ * listeners, and the listener-error handler.
799
961
  * @throws {@link ScaffoldError} coded `INVALID` when `options` is present but
800
962
  * is not an option bag this materializer accepts, and `TARGET` when the host
801
- * carries a manifest that cannot be read or does not match what it stores.
963
+ * carries a manifest that cannot be read, does not match what it stores, or
964
+ * is a value that does not agree with the bytes beside it.
802
965
  *
803
966
  * @remarks
804
- * `host` defaults to this package's own vendored root, resolved from this
805
- * module's own location so it never depends on the caller's working
806
- * directory. A host carrying no manifest is read as a raw checkout and every
967
+ * A `host` path defaults to this package's own vendored root, resolved from
968
+ * this module's own location so it never depends on the caller's working
969
+ * directory. A root carrying no manifest is read as a raw checkout and every
807
970
  * artifact maps onto it one to one.
808
971
  *
972
+ * A `host` value is owned before it is read and then held immutable, so the
973
+ * bytes this check measured are the bytes every later read returns. It is
974
+ * verified the way a root is, against the same membership law: the manifest
975
+ * digest must cover the membership beside it, no two entries may claim one
976
+ * destination, and the fill must carry exactly one hashing byte string per
977
+ * declared entry.
978
+ *
809
979
  * The host is read here rather than on first use, so a broken vendored root
810
980
  * fails at construction where the caller can still act on it, and so nothing
811
981
  * has to carry a second flag recording whether the read has happened yet.
@@ -908,30 +1078,36 @@ export declare class Materializer implements MaterializerInterface {
908
1078
  */
909
1079
  catalog(entries: readonly CatalogEntry[], target: string): MaterializeResult;
910
1080
  /**
911
- * Rewrite the declared dependency ranges the caller names in the target's manifest.
1081
+ * Rewrite the manifest regions the caller names in the target's manifest.
912
1082
  *
913
- * @param dependencies - The names and ranges the manifest must declare.
1083
+ * @param regions - The dependency ranges and script values the manifest must declare.
914
1084
  * @param target - The directory to write into.
915
- * @returns The manifest path, written when a declared range moved and skipped otherwise.
1085
+ * @returns The manifest path, written when a named region moved and skipped otherwise.
916
1086
  * @throws {@link ScaffoldError} coded `INVALID` when an argument is not the
917
1087
  * exact shape or names a package the manifest does not declare, `TARGET` when
918
1088
  * the manifest is unreadable, `WRITE` when the write cannot be staged or
919
1089
  * committed, and `DESTROYED` after teardown.
920
1090
  *
921
1091
  * @remarks
922
- * No other part of the manifest is read back out or rewritten, so a consumer's
923
- * own description, keywords, scripts, and formatting survive the call. Only a
924
- * range already declared is rewritten: inserting a package would mean
925
- * re-serializing the whole manifest, which is exactly the edit this verb
926
- * promises not to make, so an undeclared name is refused by name instead.
1092
+ * No other part of the manifest is read back out or rewritten. The method
1093
+ * never reads or writes `peerDependencies` or `peerDependenciesMeta`. Only a
1094
+ * range already declared in its named writable section is rewritten, so an
1095
+ * undeclared name is refused instead of inserted.
1096
+ *
1097
+ * The regions refuse differently because their targets differ. A range
1098
+ * the manifest does not declare is the caller's mistake and throws. A script
1099
+ * holding a value the region does not accept is the workspace author's own
1100
+ * chain, so the script region is skipped without a byte moving and the range
1101
+ * region is still written. The advisory channel reports what the maintainer
1102
+ * must paste.
927
1103
  */
928
- declare(dependencies: readonly Dependency[], target: string): MaterializeResult;
1104
+ declare(regions: ManifestRegionSet, target: string): MaterializeResult;
929
1105
  /**
930
1106
  * Re-derive and delete the tracked files the plan does not own.
931
1107
  *
932
1108
  * @param plan - The compiled plan that decides which paths are foreign.
933
1109
  * @param audit - The preview returned by this materializer's `audit` method; it must agree with the candidate set this call re-derives.
934
- * @param repository - The target's git state; only a tracked path is ever deleted.
1110
+ * @param worktree - The target's git state; only a tracked path is ever deleted.
935
1111
  * @param target - The directory to delete from.
936
1112
  * @returns The paths removed.
937
1113
  * @throws {@link ScaffoldError} coded `INVALID` when an argument is not the
@@ -952,7 +1128,7 @@ export declare class Materializer implements MaterializerInterface {
952
1128
  * any foreign finding, including one the deletion itself would skip, because a
953
1129
  * preview stale anywhere is stale evidence.
954
1130
  */
955
- remove(plan: Plan, audit: Audit, repository: Repository, target: string): MaterializeResult;
1131
+ remove(plan: Plan, audit: Audit, worktree: Worktree, target: string): MaterializeResult;
956
1132
  /**
957
1133
  * Tear the materializer down. Every later call throws, and teardown is idempotent.
958
1134
  *
@@ -1062,23 +1238,25 @@ export declare class Materializer implements MaterializerInterface {
1062
1238
  */
1063
1239
  catalog(entries: readonly CatalogEntry[], target: string): MaterializeResult;
1064
1240
  /**
1065
- * Rewrite the declared dependency ranges the caller names in the target's manifest.
1241
+ * Rewrite the manifest regions the caller names in the target's manifest.
1066
1242
  *
1067
- * @param dependencies - The names and ranges the manifest must declare.
1243
+ * @param regions - The dependency ranges and script values the manifest must declare.
1068
1244
  * @param target - The directory to write into.
1069
- * @returns The manifest path, written when a declared range moved and skipped otherwise.
1245
+ * @returns The manifest path, written when a named region moved and skipped otherwise.
1070
1246
  *
1071
1247
  * @remarks
1072
- * No other part of the manifest is read back out or rewritten, so a
1073
- * consumer's own description, keywords, and scripts survive the call.
1248
+ * No other part of the manifest is read back out or rewritten. The method
1249
+ * never reads or writes `peerDependencies` or `peerDependenciesMeta`. A
1250
+ * script region naming a value the manifest does not accept is skipped
1251
+ * without a byte moving, and the range region is written regardless.
1074
1252
  */
1075
- declare(dependencies: readonly Dependency[], target: string): MaterializeResult;
1253
+ declare(regions: ManifestRegionSet, target: string): MaterializeResult;
1076
1254
  /**
1077
1255
  * Re-derive and delete the tracked files the plan does not own.
1078
1256
  *
1079
1257
  * @param plan - The compiled plan that decides which paths are foreign.
1080
1258
  * @param audit - The preview returned by this materializer's `audit` method; it must agree with the candidate set this call re-derives.
1081
- * @param repository - The target's git state; only a tracked path is ever deleted.
1259
+ * @param worktree - The target's git state; only a tracked path is ever deleted.
1082
1260
  * @param target - The directory to delete from.
1083
1261
  * @returns The paths removed.
1084
1262
  *
@@ -1101,7 +1279,7 @@ export declare class Materializer implements MaterializerInterface {
1101
1279
  * any foreign finding, including one the deletion itself would skip, because a
1102
1280
  * preview stale anywhere is stale evidence.
1103
1281
  */
1104
- remove(plan: Plan, audit: Audit, repository: Repository, target: string): MaterializeResult;
1282
+ remove(plan: Plan, audit: Audit, worktree: Worktree, target: string): MaterializeResult;
1105
1283
  /**
1106
1284
  * Tear the materializer down. Every later call throws, and teardown is idempotent.
1107
1285
  *
@@ -1114,19 +1292,21 @@ export declare class Materializer implements MaterializerInterface {
1114
1292
  * Options for the materializer.
1115
1293
  *
1116
1294
  * @remarks
1117
- * `host` is the vendored data root host-origin artifacts are copied from. It
1118
- * defaults to this package's own vendored root, resolved from the installed
1119
- * module's location rather than the caller's working directory. A host that
1120
- * carries no manifest beside it maps artifact paths one to one instead of
1121
- * through the manifest.
1295
+ * `host` is the vendored data root host-origin artifacts are copied from, in
1296
+ * either representation. A string is a directory: it defaults to this package's
1297
+ * own vendored root, resolved from the installed module's location rather than
1298
+ * the caller's working directory, and a directory that carries no manifest
1299
+ * beside it maps artifact paths one to one instead of through the manifest. A
1300
+ * {@link Host} is the same root supplied as a value, so a caller that already
1301
+ * holds the bytes never stages them to a directory to be read back.
1122
1302
  */
1123
1303
  export declare interface MaterializerOptions {
1124
- readonly host?: string;
1304
+ readonly host?: string | Host;
1125
1305
  readonly on?: EmitterHooks<MaterializerEventMap>;
1126
1306
  readonly error?: EmitterErrorHandler;
1127
1307
  }
1128
1308
 
1129
- /** Maximum characters one guide branch may carry. */
1309
+ /** Maximum characters one repository branch may carry. */
1130
1310
  export declare const MAX_BRANCH_LENGTH = 255;
1131
1311
 
1132
1312
  /** Maximum characters one caller-supplied upstream endpoint may carry. */
@@ -1304,10 +1484,38 @@ export declare class Materializer implements MaterializerInterface {
1304
1484
  */
1305
1485
  export declare function readFileText(root: string, path: string, limit?: number): string | undefined;
1306
1486
 
1487
+ /**
1488
+ * Reads the installed vendored host floor as a value.
1489
+ *
1490
+ * @param root - The vendored host root. Default: the installed package's
1491
+ * vendored root, resolved from this module's location.
1492
+ * @returns The verified manifest and the exact bytes of every declared entry.
1493
+ * @throws `ScaffoldError('TARGET', …)` when the root is not a readable physical
1494
+ * directory, its manifest is absent or unreadable, the manifest does not verify,
1495
+ * or a declared file is unreadable or misses its digest.
1496
+ *
1497
+ * @remarks
1498
+ * Reads the same default floor the {@link Materializer} uses. Each declared
1499
+ * file is addressed through the manifest's storage name and retained under its
1500
+ * destination, so the returned value has the same shape as the installed root.
1501
+ * When this module executes from TypeScript source, the committed inventory is
1502
+ * the manifest and each checkout destination supplies its bytes. The emitted
1503
+ * module reads the staged `manifest.json` file and each storage path instead.
1504
+ *
1505
+ * @example
1506
+ * ```ts
1507
+ * import { readHostFloor } from '@orkestrel/scaffold/server'
1508
+ *
1509
+ * readHostFloor().manifest // the installed floor's verified membership
1510
+ * ```
1511
+ */
1512
+ export declare function readHostFloor(root?: string): Host;
1513
+
1307
1514
  /**
1308
1515
  * Read a vendored host's manifest, when it carries one.
1309
1516
  *
1310
1517
  * @param host - The vendored host root to read.
1518
+ * @param name - The root-relative manifest path. Default: `manifest.json`.
1311
1519
  * @returns The manifest, or `undefined` when the host carries none.
1312
1520
  * @throws `ScaffoldError('INVALID', …)` when `host` is not a host path.
1313
1521
  * @throws `ScaffoldError('TARGET', …)` when the manifest is there but cannot be
@@ -1333,7 +1541,7 @@ export declare class Materializer implements MaterializerInterface {
1333
1541
  * readHostManifest('./dist/host') // the manifest, or undefined for a raw root
1334
1542
  * ```
1335
1543
  */
1336
- export declare function readHostManifest(host: string): HostManifest | undefined;
1544
+ export declare function readHostManifest(host: string, name?: string): HostManifest | undefined;
1337
1545
 
1338
1546
  /**
1339
1547
  * Derive one vendored-host manifest entry from a file in a checkout.
@@ -1359,7 +1567,7 @@ export declare class Materializer implements MaterializerInterface {
1359
1567
  * import { readManifestEntry } from '@orkestrel/scaffold/server'
1360
1568
  *
1361
1569
  * readManifestEntry('.gitignore', '/tmp/checkout/.gitignore')
1362
- * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false }
1570
+ * // { storage: 'dotfiles/gitignore', destination: '.gitignore', executable: false, digest: '...' }
1363
1571
  * ```
1364
1572
  */
1365
1573
  export declare function readManifestEntry(destination: string, source: string): ManifestEntry | undefined;
@@ -1395,25 +1603,6 @@ export declare class Materializer implements MaterializerInterface {
1395
1603
  */
1396
1604
  export declare function readSnapshot(target: string, paths: readonly string[]): Snapshot;
1397
1605
 
1398
- /**
1399
- * What git reports about a target's working tree.
1400
- *
1401
- * @remarks
1402
- * `tracked` is the only set a deletion may draw from: git does not report the
1403
- * loss of an untracked path and `git diff` cannot restore it, so an ignored
1404
- * path such as an installed dependency tree, a build output, or an editor
1405
- * directory survives every verb. `dirty` is every path carrying an uncommitted
1406
- * change, taken repo-wide rather than over a write set, because deletion makes
1407
- * the write set the whole workspace. A clean tree is an empty `dirty`. A target
1408
- * that is not a git repository yields no `Repository` at all, so the caller
1409
- * decides what to do about that rather than reading it out of an invented
1410
- * empty value.
1411
- */
1412
- export declare interface Repository {
1413
- readonly tracked: readonly string[];
1414
- readonly dirty: readonly string[];
1415
- }
1416
-
1417
1606
  /**
1418
1607
  * The Windows device names that stay reserved even when an extension follows.
1419
1608
  *
@@ -1514,6 +1703,42 @@ export declare class Materializer implements MaterializerInterface {
1514
1703
  */
1515
1704
  export declare function resolveRealPath(path: string): string | undefined;
1516
1705
 
1706
+ /**
1707
+ * Stage the named destinations of a value host into a private root.
1708
+ *
1709
+ * @param host - The host whose bytes are written, keyed by destination.
1710
+ * @param root - The private directory to fill; it must already be a directory
1711
+ * this process may write into.
1712
+ * @param destinations - The destinations to stage, each declared by `host`.
1713
+ * @returns The entry staged for each destination, in the order requested.
1714
+ * @throws `ScaffoldError('INVALID', …)` when `root` is not a host path or a
1715
+ * storage name leaves it.
1716
+ * @throws `ScaffoldError('TARGET', …)` when a destination is one the host does
1717
+ * not declare, carries no bytes, or carries bytes that miss its declared digest.
1718
+ * @throws `ScaffoldError('WRITE', …)` when a file cannot be written or does not
1719
+ * read back as the bytes it was given.
1720
+ *
1721
+ * @remarks
1722
+ * Each file lands under the storage name the manifest declares and takes the
1723
+ * executable bit that manifest records, so a root filled from a value is the
1724
+ * same shape as one staged from a checkout and a reader cannot tell them apart.
1725
+ * That is what lets a mutation copy real files with real modes from bytes a
1726
+ * caller supplied, instead of degrading them to plain text writes.
1727
+ *
1728
+ * The bytes are digested before the write and the staged file after it, so a
1729
+ * value that disagrees with its own manifest is told apart from a write that
1730
+ * did not land.
1731
+ *
1732
+ * @example
1733
+ * ```ts
1734
+ * import { stageBytes } from '@orkestrel/scaffold/server'
1735
+ *
1736
+ * stageBytes(host, '/tmp/orkestrel-host-a1b2', ['scripts/codex.sh'])
1737
+ * // [{ storage: 'scripts/codex.sh', destination: 'scripts/codex.sh', executable: true, digest: '…' }]
1738
+ * ```
1739
+ */
1740
+ export declare function stageBytes(host: Host, root: string, destinations: readonly string[]): readonly ManifestEntry[];
1741
+
1517
1742
  /**
1518
1743
  * Stage a vendored host root from a real checkout.
1519
1744
  *
@@ -1568,6 +1793,33 @@ export declare class Materializer implements MaterializerInterface {
1568
1793
  */
1569
1794
  export declare function stageHost(checkout: string, host: string): readonly ManifestEntry[];
1570
1795
 
1796
+ /**
1797
+ * Stages the committed inventory of the files a vendored host carries.
1798
+ *
1799
+ * @param checkout - The checkout whose vendored paths are inventoried.
1800
+ * @param path - The host path where the JSON inventory is written.
1801
+ * @returns The validated manifest written to `path`.
1802
+ * @throws `ScaffoldError('INVALID', …)` when `path` is not a host path.
1803
+ * @throws `ScaffoldError('WRITE', …)` when a temporary host or the inventory
1804
+ * cannot be written or removed.
1805
+ * @throws `ScaffoldError('TARGET', …)` when the staged inventory does not read
1806
+ * back through the manifest validator.
1807
+ *
1808
+ * @remarks
1809
+ * Uses {@link stageHost} as the single vendored-path expansion. The temporary
1810
+ * host supplies the same entries, roots, per-file digests, and membership
1811
+ * digest as the published host while the requested output remains one JSON
1812
+ * file.
1813
+ *
1814
+ * @example
1815
+ * ```ts
1816
+ * import { stageInventory } from '@orkestrel/scaffold/server'
1817
+ *
1818
+ * stageInventory(process.cwd(), 'host.json') // the committed host inventory
1819
+ * ```
1820
+ */
1821
+ export declare function stageInventory(checkout: string, path: string): HostManifest;
1822
+
1571
1823
  /**
1572
1824
  * The reading spine: one bounded, unauthenticated, redirect-free request per answer.
1573
1825
  *
@@ -1586,6 +1838,11 @@ export declare class Materializer implements MaterializerInterface {
1586
1838
  * there is no fleet to report, so an unreachable or malformed list is a coded
1587
1839
  * `FETCH` failure.
1588
1840
  *
1841
+ * The vendored-file inventory is the other read a whole call rests on, and it
1842
+ * fails the other way: it fails every row of its call rather than throwing, so
1843
+ * the caller receives one whole dead answer it can replace with one whole
1844
+ * baseline instead of a mixture it would have to reconcile.
1845
+ *
1589
1846
  * Requests are unauthenticated because every fleet repository is public, and
1590
1847
  * they follow no redirect, so a misconfigured or hostile endpoint cannot move a
1591
1848
  * read to another host. Each one is bounded by its endpoint's timeout and by the
@@ -1608,7 +1865,7 @@ export declare class Materializer implements MaterializerInterface {
1608
1865
  export declare class Upstream implements UpstreamInterface {
1609
1866
  #private;
1610
1867
  /**
1611
- * Construct a reader over one guide host and one registry.
1868
+ * Construct a reader over one raw content host and one registry.
1612
1869
  *
1613
1870
  * @param options - The endpoints, the request bounds, the initial
1614
1871
  * listeners, and the listener-error handler.
@@ -1684,372 +1941,447 @@ export declare class Materializer implements MaterializerInterface {
1684
1941
  */
1685
1942
  fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
1686
1943
  /**
1687
- * Catalog the published fleet from the registry's organization package list.
1944
+ * Read each named vendored file from the repository, beside the target bytes it answers for.
1688
1945
  *
1689
- * @returns One row per published package, sorted by name.
1690
- * @throws {@link ScaffoldError} coded `FETCH` when the organization package
1691
- * list is unreachable, malformed, empty, or larger than one bounded
1692
- * collection, and `DESTROYED` when the reader is torn down before or during
1693
- * the call.
1946
+ * @param paths - The target-relative vendored paths to read.
1947
+ * @param current - The target files as exact bytes, keyed by the same paths.
1948
+ * @returns One file verdict per path, in input order.
1949
+ * @throws {@link ScaffoldError} coded `INVALID` when `paths` is not a bounded
1950
+ * list of target-relative paths or `current` is not a snapshot, and
1951
+ * `DESTROYED` when the reader is torn down before or during the call.
1694
1952
  *
1695
1953
  * @remarks
1696
- * The organization list is the exact membership the registry publishes, not a
1697
- * relevance search, and it is the whole reason this answer can claim to be the
1698
- * fleet. So it fails hard where a per-package lookup fails soft: an empty or
1699
- * unreadable list would otherwise be written into a target's catalog table as
1700
- * a fleet with no packages in it. A package whose own version lookup then
1701
- * fails still keeps its row, carrying the cause instead of a version, because
1702
- * the list already proved the package is published.
1954
+ * The committed inventory is read once per call and decides every row, so a
1955
+ * path whose declared digest already matches the target's own bytes is `found`
1956
+ * without a request and the call spends nothing on it. An inventory that
1957
+ * produces no answer fails every row of the call rather than leaving some rows
1958
+ * live and some dead, which is what leaves the caller one whole baseline to
1959
+ * fall back to. A path the inventory does not name is `missing`.
1960
+ *
1961
+ * A fetched response's decoded content is verified against the digest the
1962
+ * inventory declares for that path, before any character decoding. Transport
1963
+ * encoding is transparent and does not enter the comparison, so content that
1964
+ * does not hash to the inventory's claim fails its row rather than reaching a
1965
+ * write. That is integrity against a single committed baseline, not
1966
+ * authenticity: it detects truncated, substituted, or stale content, and it
1967
+ * says nothing about who published the inventory.
1968
+ *
1969
+ * A guide mirror is never answered here whatever the caller asks for and
1970
+ * whatever the target holds, because those bytes belong to `fetch` and to the
1971
+ * mirror verb that writes them.
1703
1972
  *
1704
1973
  * @example
1705
1974
  * ```ts
1706
1975
  * import { Upstream } from '@orkestrel/scaffold/server'
1707
1976
  *
1708
1977
  * const upstream = new Upstream()
1709
- * const entries = await upstream.catalog()
1978
+ * await upstream.read(['AGENTS.md'], { 'AGENTS.md': '2320416745' })
1710
1979
  * upstream.destroy()
1711
1980
  * ```
1712
1981
  */
1713
- catalog(): Promise<readonly CatalogEntry[]>;
1982
+ read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
1714
1983
  /**
1715
- * Tear the reader down, aborting every request in flight. Teardown is idempotent.
1984
+ * Catalog the published fleet from the registry's organization package list.
1716
1985
  *
1717
- * @returns Nothing.
1986
+ * @returns One row per published package, sorted by name.
1987
+ * @throws {@link ScaffoldError} coded `FETCH` when the organization package
1988
+ * list is unreachable, malformed, empty, or larger than one bounded
1989
+ * collection, and `DESTROYED` when the reader is torn down before or during
1990
+ * the call.
1991
+ *
1992
+ * @remarks
1993
+ * The organization list is the exact membership the registry publishes, not a
1994
+ * relevance search, and it is the whole reason this answer can claim to be the
1995
+ * fleet. So it fails hard where a per-package lookup fails soft: an empty or
1996
+ * unreadable list would otherwise be written into a target's catalog table as
1997
+ * a fleet with no packages in it. A package whose own version lookup then
1998
+ * fails still keeps its row, carrying the cause instead of a version, because
1999
+ * the list already proved the package is published.
2000
+ *
2001
+ * @example
2002
+ * ```ts
2003
+ * import { Upstream } from '@orkestrel/scaffold/server'
2004
+ *
2005
+ * const upstream = new Upstream()
2006
+ * const entries = await upstream.catalog()
2007
+ * upstream.destroy()
2008
+ * ```
2009
+ */
2010
+ catalog(): Promise<readonly CatalogEntry[]>;
2011
+ /**
2012
+ * Tear the reader down, aborting every request in flight. Teardown is idempotent.
2013
+ *
2014
+ * @returns Nothing.
2015
+ *
2016
+ * @remarks
2017
+ * A call still in flight rejects with a `DESTROYED` error rather than
2018
+ * resolving to a partial answer, because half a fleet reads exactly like a
2019
+ * whole one.
2020
+ *
2021
+ * @example
2022
+ * ```ts
2023
+ * import { Upstream } from '@orkestrel/scaffold/server'
2024
+ *
2025
+ * const upstream = new Upstream()
2026
+ * upstream.destroy()
2027
+ * upstream.emitter.destroyed // true
2028
+ * ```
2029
+ */
2030
+ destroy(): void;
2031
+ }
2032
+
2033
+ /**
2034
+ * The upstream reader's observation channel.
1718
2035
  *
1719
2036
  * @remarks
1720
- * A call still in flight rejects with a `DESTROYED` error rather than
1721
- * resolving to a partial answer, because half a fleet reads exactly like a
1722
- * whole one.
2037
+ * Each verdict is published whole rather than as a name beside a summary, so a
2038
+ * listener reads the same value the call returns and a failed lookup is told
2039
+ * apart from a successful one by the verdict's own discriminant rather than by
2040
+ * which event carried it.
2041
+ */
2042
+ export declare type UpstreamEventMap = {
2043
+ readonly release: readonly [release: Release];
2044
+ readonly mirror: readonly [mirror: Mirror];
2045
+ readonly file: readonly [file: HostFile];
2046
+ readonly error: readonly [error: unknown];
2047
+ readonly destroy: readonly [];
2048
+ };
2049
+
2050
+ /**
2051
+ * The upstream contract: the package's only network reader, and it never writes.
1723
2052
  *
1724
- * @example
1725
- * ```ts
1726
- * import { Upstream } from '@orkestrel/scaffold/server'
2053
+ * @remarks
2054
+ * A per-package failure is collected as a verdict carrying its cause, not
2055
+ * thrown, so one unreachable package never costs the caller the rest of the
2056
+ * answer. The organization list is the exception: without it there is no fleet
2057
+ * to report, so an unreachable or malformed list is a coded failure.
1727
2058
  *
1728
- * const upstream = new Upstream()
1729
- * upstream.destroy()
1730
- * upstream.emitter.destroyed // true
1731
- * ```
2059
+ * The vendored-file inventory is the other whole-answer read, and it fails
2060
+ * softly: an inventory that produces no answer fails every row of that call
2061
+ * rather than throwing, which is what leaves the caller one whole baseline to
2062
+ * fall back to instead of a mixture.
1732
2063
  */
1733
- destroy(): void;
1734
- }
1735
-
1736
- /**
1737
- * The upstream reader's observation channel.
1738
- *
1739
- * @remarks
1740
- * Each verdict is published whole rather than as a name beside a summary, so a
1741
- * listener reads the same value the call returns and a failed lookup is told
1742
- * apart from a successful one by the verdict's own discriminant rather than by
1743
- * which event carried it.
1744
- */
1745
- export declare type UpstreamEventMap = {
1746
- readonly release: readonly [release: Release];
1747
- readonly mirror: readonly [mirror: Mirror];
1748
- readonly error: readonly [error: unknown];
1749
- readonly destroy: readonly [];
1750
- };
2064
+ export declare interface UpstreamInterface {
2065
+ readonly emitter: EmitterInterface<UpstreamEventMap>;
2066
+ /**
2067
+ * Look up the newest release each declared range admits.
2068
+ *
2069
+ * @param dependencies - The declared dependencies to look up.
2070
+ * @returns One release verdict per dependency, in input order.
2071
+ */
2072
+ lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
2073
+ /**
2074
+ * Fetch each named package's guide, beside the local mirror it answers for.
2075
+ *
2076
+ * @param names - The packages to fetch: the target's declared set, or the whole organization.
2077
+ * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
2078
+ * @returns One mirror verdict per name, in input order.
2079
+ */
2080
+ fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
2081
+ /**
2082
+ * Read each named vendored file from the repository, beside the target bytes it answers for.
2083
+ *
2084
+ * @param paths - The target-relative vendored paths to read.
2085
+ * @param current - The target files as exact bytes, keyed by the same paths.
2086
+ * @returns One file verdict per path, in input order.
2087
+ */
2088
+ read(paths: readonly string[], current: Snapshot): Promise<readonly HostFile[]>;
2089
+ /**
2090
+ * Catalog the published fleet from the registry's organization package list.
2091
+ *
2092
+ * @returns One row per published package, sorted by name.
2093
+ */
2094
+ catalog(): Promise<readonly CatalogEntry[]>;
2095
+ /**
2096
+ * Tear the reader down, aborting every request in flight. Teardown is idempotent.
2097
+ *
2098
+ * @returns Nothing.
2099
+ */
2100
+ destroy(): void;
2101
+ }
1751
2102
 
1752
- /**
1753
- * The upstream contract: the package's only network reader, and it never writes.
1754
- *
1755
- * @remarks
1756
- * A per-package failure is collected as a verdict carrying its cause, not
1757
- * thrown, so one unreachable package never costs the caller the rest of the
1758
- * answer. The organization list is the exception: without it there is no fleet
1759
- * to report, so an unreachable or malformed list is a coded failure.
1760
- */
1761
- export declare interface UpstreamInterface {
1762
- readonly emitter: EmitterInterface<UpstreamEventMap>;
1763
- /**
1764
- * Look up the newest release each declared range admits.
1765
- *
1766
- * @param dependencies - The declared dependencies to look up.
1767
- * @returns One release verdict per dependency, in input order.
1768
- */
1769
- lookup(dependencies: readonly Dependency[]): Promise<readonly Release[]>;
1770
- /**
1771
- * Fetch each named package's guide, beside the local mirror it answers for.
1772
- *
1773
- * @param names - The packages to fetch: the target's declared set, or the whole organization.
1774
- * @param current - The target's local mirrors as exact bytes, keyed by mirror path.
1775
- * @returns One mirror verdict per name, in input order.
1776
- */
1777
- fetch(names: readonly string[], current: Snapshot): Promise<readonly Mirror[]>;
1778
- /**
1779
- * Catalog the published fleet from the registry's organization package list.
1780
- *
1781
- * @returns One row per published package, sorted by name.
1782
- */
1783
- catalog(): Promise<readonly CatalogEntry[]>;
1784
- /**
1785
- * Tear the reader down, aborting every request in flight. Teardown is idempotent.
1786
- *
1787
- * @returns Nothing.
1788
- */
1789
- destroy(): void;
1790
- }
2103
+ /**
2104
+ * Options for the upstream reader.
2105
+ *
2106
+ * @remarks
2107
+ * The endpoints are grouped under the entity each configures: `repository`
2108
+ * takes the raw content host's `base`, its `branch`, and its `timeout`;
2109
+ * `registry` takes the registry's `base` and `timeout`. One raw content host
2110
+ * serves both the fleet's guides and this package's own vendored files, so one
2111
+ * group configures both. `concurrency` bounds requests in
2112
+ * flight and `retries` opts into per-request retry on a transport fault.
2113
+ * `limit` bounds the bytes read from one response body and `budget` bounds the
2114
+ * bytes read across a whole call, so neither one oversized answer nor many
2115
+ * small ones can exhaust the caller. Every request is unauthenticated and
2116
+ * follows no redirect.
2117
+ */
2118
+ export declare interface UpstreamOptions {
2119
+ readonly repository?: {
2120
+ readonly base?: string;
2121
+ readonly branch?: string;
2122
+ readonly timeout?: number;
2123
+ };
2124
+ readonly registry?: {
2125
+ readonly base?: string;
2126
+ readonly timeout?: number;
2127
+ };
2128
+ readonly concurrency?: number;
2129
+ readonly retries?: number;
2130
+ readonly limit?: number;
2131
+ readonly budget?: number;
2132
+ readonly on?: EmitterHooks<UpstreamEventMap>;
2133
+ readonly error?: EmitterErrorHandler;
2134
+ }
1791
2135
 
1792
- /**
1793
- * Options for the upstream reader.
1794
- *
1795
- * @remarks
1796
- * The endpoints are grouped under the entity each configures: `guides`
1797
- * takes the guide host's `base`, its `branch`, and its `timeout`; `registry`
1798
- * takes the registry's `base` and `timeout`. `concurrency` bounds requests in
1799
- * flight and `retries` opts into per-request retry on a transport fault.
1800
- * `limit` bounds the bytes read from one response body and `budget` bounds the
1801
- * bytes read across a whole call, so neither one oversized answer nor many
1802
- * small ones can exhaust the caller. Every request is unauthenticated and
1803
- * follows no redirect.
1804
- */
1805
- export declare interface UpstreamOptions {
1806
- readonly guides?: {
1807
- readonly base?: string;
1808
- readonly branch?: string;
1809
- readonly timeout?: number;
1810
- };
1811
- readonly registry?: {
1812
- readonly base?: string;
1813
- readonly timeout?: number;
1814
- };
1815
- readonly concurrency?: number;
1816
- readonly retries?: number;
1817
- readonly limit?: number;
1818
- readonly budget?: number;
1819
- readonly on?: EmitterHooks<UpstreamEventMap>;
1820
- readonly error?: EmitterErrorHandler;
1821
- }
2136
+ /**
2137
+ * What git reports about a target's working tree.
2138
+ *
2139
+ * @remarks
2140
+ * `tracked` is the only set a deletion may draw from: git does not report the
2141
+ * loss of an untracked path and `git diff` cannot restore it, so an ignored
2142
+ * path such as an installed dependency tree, a build output, or an editor
2143
+ * directory survives every verb. `dirty` is every path carrying an uncommitted
2144
+ * change, taken repo-wide rather than over a write set, because deletion makes
2145
+ * the write set the whole workspace. A clean tree is an empty `dirty`. A target
2146
+ * that is not a git repository yields no `Worktree` at all, so the caller
2147
+ * decides what to do about that rather than reading it out of an invented
2148
+ * empty value.
2149
+ */
2150
+ export declare interface Worktree {
2151
+ readonly tracked: readonly string[];
2152
+ readonly dirty: readonly string[];
2153
+ }
1822
2154
 
1823
- /**
1824
- * One physical directory identity captured across a write transaction.
1825
- *
1826
- * @remarks
1827
- * Device and inode locate the directory and do not date it. Two directories
1828
- * occupying one slot at different times share an anchor, so an anchor proves
1829
- * where a write lands and never that the directory was left alone.
1830
- */
1831
- export declare interface WriteAnchor {
1832
- readonly path: string;
1833
- readonly device: number;
1834
- readonly inode: number;
1835
- }
1836
-
1837
- /** The final directory anchor of a write transaction and the subset one call created. */
1838
- export declare interface WriteDirectoryResult {
1839
- readonly anchor: WriteAnchor;
1840
- readonly created: readonly WriteAnchor[];
1841
- }
2155
+ /**
2156
+ * One physical directory identity captured across a write transaction.
2157
+ *
2158
+ * @remarks
2159
+ * Device and inode locate the directory and do not date it. Two directories
2160
+ * occupying one slot at different times share an anchor, so an anchor proves
2161
+ * where a write lands and never that the directory was left alone.
2162
+ */
2163
+ export declare interface WriteAnchor {
2164
+ readonly path: string;
2165
+ readonly device: number;
2166
+ readonly inode: number;
2167
+ }
2168
+
2169
+ /** The final directory anchor of a write transaction and the subset one call created. */
2170
+ export declare interface WriteDirectoryResult {
2171
+ readonly anchor: WriteAnchor;
2172
+ readonly created: readonly WriteAnchor[];
2173
+ }
1842
2174
 
1843
- /**
1844
- * One destination snapshot captured before a write and required to survive it.
1845
- *
1846
- * @remarks
1847
- * `device`, `inode`, `modified`, `size`, and `digest` are present only where
1848
- * the observed shape supplies them.
1849
- */
1850
- export declare interface WriteExpectation {
1851
- readonly path: string;
1852
- readonly shape: 'absent' | 'file' | 'directory';
1853
- readonly device?: number;
1854
- readonly inode?: number;
1855
- readonly modified?: number;
1856
- readonly size?: number;
1857
- readonly digest?: string;
1858
- }
1859
-
1860
- /** The narrower caller-observed destination state a write transaction must still match. */
1861
- export declare interface WritePrecondition {
1862
- readonly path: string;
1863
- readonly shape: 'absent' | 'file';
1864
- readonly digest?: string;
1865
- }
2175
+ /**
2176
+ * One destination snapshot captured before a write and required to survive it.
2177
+ *
2178
+ * @remarks
2179
+ * `device`, `inode`, `modified`, `size`, and `digest` are present only where
2180
+ * the observed shape supplies them.
2181
+ */
2182
+ export declare interface WriteExpectation {
2183
+ readonly path: string;
2184
+ readonly shape: 'absent' | 'file' | 'directory';
2185
+ readonly device?: number;
2186
+ readonly inode?: number;
2187
+ readonly modified?: number;
2188
+ readonly size?: number;
2189
+ readonly digest?: string;
2190
+ }
2191
+
2192
+ /** The narrower caller-observed destination state a write transaction must still match. */
2193
+ export declare interface WritePrecondition {
2194
+ readonly path: string;
2195
+ readonly shape: 'absent' | 'file';
2196
+ readonly digest?: string;
2197
+ }
1866
2198
 
1867
- /**
1868
- * One staged, reversible mutation of one target directory.
1869
- *
1870
- * @remarks
1871
- * The transaction owns a private root beside the target — a sibling directory on
1872
- * the same volume, so every promotion is a rename rather than a copy. Staging
1873
- * writes go into that root and nothing else, so a call that fails while staging
1874
- * has not touched the target at all. Commit is the only step that mutates the
1875
- * target, and it is the only step that can need rolling back.
1876
- *
1877
- * A precondition and an expectation hold a destination still. A **precondition** is what the caller
1878
- * observed earlier and is checked once, at construction, so a target that moved
1879
- * between the caller's read and this transaction fails before anything is
1880
- * created. An **expectation** is captured here, at construction, and re-checked
1881
- * at commit, so a target that moves while the write is being staged fails before
1882
- * anything is promoted.
1883
- *
1884
- * What this provides, exactly:
1885
- *
1886
- * - **Across destinations, staged-then-swapped with rollback on a caught
1887
- * failure.** A failure part way through commit restores every destination it
1888
- * already promoted, restores every file it already took, and removes every
1889
- * directory it created, then reports what recovery could not undo. This is
1890
- * measured: a promotion is driven to fail after an earlier one landed, and the
1891
- * earlier destination is read back.
1892
- * - **No partly written destination.** Every file is written whole into the
1893
- * private root and digested there before commit, so a destination never
1894
- * receives bytes that were still being produced.
1895
- * - **Containment, not continuity, of the directories it creates.** Every
1896
- * ancestor is re-read between `mkdir` calls and again before the first
1897
- * promotion, so an ancestor that became a file, a symlink, a directory
1898
- * elsewhere, or nothing is refused. An ancestor deleted and recreated under
1899
- * the same name can receive its old inode back and is indistinguishable here
1900
- * from one that never moved.
1901
- * - **No crash atomicity across destinations.** A process killed between
1902
- * promotions leaves the target holding some new files and some old ones, and
1903
- * leaves the private root behind. Nothing here is a journal, and the private
1904
- * root's name is the only record a later run could read.
1905
- *
1906
- * A destination is preserved by hard link and then replaced by a single
1907
- * `rename`, rather than moved aside and replaced, so the path continues to name
1908
- * the old file right up to the swap. That is a property of `rename` on the host,
1909
- * not one this package's tests measure: telling it apart from move-then-replace
1910
- * needs a reader observing the destination inside the swap, and no test here
1911
- * does that. Read the claim as the mechanism it describes, not as a proven
1912
- * guarantee about a concurrent reader.
1913
- *
1914
- * A path names a file this transaction writes, takes, or establishes as a
1915
- * directory; every path is target-relative and is measured by the portable-path
1916
- * law. An expectation and a precondition both name the resolved destination
1917
- * instead, because that is the path each is re-read at.
1918
- *
1919
- * @example
1920
- * ```ts
1921
- * import { WriteTransaction } from '@orkestrel/scaffold/server'
1922
- *
1923
- * const transaction = new WriteTransaction('./packages/router', ['AGENTS.md'])
1924
- * try {
1925
- * transaction.write('AGENTS.md', '# Agents\n')
1926
- * transaction.commit() // ['AGENTS.md']
1927
- * } finally {
1928
- * transaction.discard()
1929
- * }
1930
- * ```
1931
- */
1932
- export declare class WriteTransaction {
1933
- #private;
1934
- /**
1935
- * Open a transaction over one target directory.
1936
- *
1937
- * @param target - The directory every path is written beneath.
1938
- * @param paths - Every target-relative path this transaction may touch.
1939
- * @param preconditions - The caller-observed destination states the whole
1940
- * transaction is held to, each naming a resolved destination of `paths`.
1941
- * @throws {@link ScaffoldError} coded `INVALID` when an argument is off
1942
- * contract, `TARGET` when a destination is a shape this package will not write
1943
- * over or no longer matches its precondition, and `WRITE` when the private
1944
- * root cannot be established.
1945
- *
1946
- * @remarks
1947
- * Nothing is created until every destination has been inspected and every
1948
- * precondition has held, so a refused transaction leaves no residue at all.
1949
- * The private root is created last and carries a random name, so two
1950
- * transactions over one target never collide.
1951
- */
1952
- constructor(target: string, paths: readonly string[], preconditions?: readonly WritePrecondition[]);
1953
- /** The resolved directory every path is written beneath. */
1954
- get target(): string;
1955
- /** What each destination held when the transaction opened, in path order. */
1956
- get expectations(): readonly WriteExpectation[];
1957
- /** Whether the transaction can still be committed or discarded. */
1958
- get open(): boolean;
2199
+ /**
2200
+ * One staged, reversible mutation of one target directory.
2201
+ *
2202
+ * @remarks
2203
+ * The transaction owns a private root beside the target — a sibling directory on
2204
+ * the same volume, so every promotion is a rename rather than a copy. Staging
2205
+ * writes go into that root and nothing else, so a call that fails while staging
2206
+ * has not touched the target at all. Commit is the only step that mutates the
2207
+ * target, and it is the only step that can need rolling back.
2208
+ *
2209
+ * A precondition and an expectation hold a destination still. A **precondition** is what the caller
2210
+ * observed earlier and is checked once, at construction, so a target that moved
2211
+ * between the caller's read and this transaction fails before anything is
2212
+ * created. An **expectation** is captured here, at construction, and re-checked
2213
+ * at commit, so a target that moves while the write is being staged fails before
2214
+ * anything is promoted.
2215
+ *
2216
+ * What this provides, exactly:
2217
+ *
2218
+ * - **Across destinations, staged-then-swapped with rollback on a caught
2219
+ * failure.** A failure part way through commit restores every destination it
2220
+ * already promoted, restores every file it already took, and removes every
2221
+ * directory it created, then reports what recovery could not undo. This is
2222
+ * measured: a promotion is driven to fail after an earlier one landed, and the
2223
+ * earlier destination is read back.
2224
+ * - **No partly written destination.** Every file is written whole into the
2225
+ * private root and digested there before commit, so a destination never
2226
+ * receives bytes that were still being produced.
2227
+ * - **Containment, not continuity, of the directories it creates.** Every
2228
+ * ancestor is re-read between `mkdir` calls and again before the first
2229
+ * promotion, so an ancestor that became a file, a symlink, a directory
2230
+ * elsewhere, or nothing is refused. An ancestor deleted and recreated under
2231
+ * the same name can receive its old inode back and is indistinguishable here
2232
+ * from one that never moved.
2233
+ * - **No crash atomicity across destinations.** A process killed between
2234
+ * promotions leaves the target holding some new files and some old ones, and
2235
+ * leaves the private root behind. Nothing here is a journal, and the private
2236
+ * root's name is the only record a later run could read.
2237
+ *
2238
+ * A destination is preserved by hard link and then replaced by a single
2239
+ * `rename`, rather than moved aside and replaced, so the path continues to name
2240
+ * the old file right up to the swap. That is a property of `rename` on the host,
2241
+ * not one this package's tests measure: telling it apart from move-then-replace
2242
+ * needs a reader observing the destination inside the swap, and no test here
2243
+ * does that. Read the claim as the mechanism it describes, not as a proven
2244
+ * guarantee about a concurrent reader.
2245
+ *
2246
+ * A path names a file this transaction writes, takes, or establishes as a
2247
+ * directory; every path is target-relative and is measured by the portable-path
2248
+ * law. An expectation and a precondition both name the resolved destination
2249
+ * instead, because that is the path each is re-read at.
2250
+ *
2251
+ * @example
2252
+ * ```ts
2253
+ * import { WriteTransaction } from '@orkestrel/scaffold/server'
2254
+ *
2255
+ * const transaction = new WriteTransaction('./packages/router', ['AGENTS.md'])
2256
+ * try {
2257
+ * transaction.write('AGENTS.md', '# Agents\n')
2258
+ * transaction.commit() // ['AGENTS.md']
2259
+ * } finally {
2260
+ * transaction.discard()
2261
+ * }
2262
+ * ```
2263
+ */
2264
+ export declare class WriteTransaction {
2265
+ #private;
1959
2266
  /**
1960
- * Stage one text file.
2267
+ * Open a transaction over one target directory.
1961
2268
  *
1962
- * @param path - The target-relative path to write.
1963
- * @param content - The exact UTF-8 text the destination must hold.
1964
- * @returns Nothing.
1965
- * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
1966
- * transaction opened or is already staged, `TARGET` when the destination holds
1967
- * a directory, and `WRITE` when the staged file cannot be written or does not
1968
- * carry the bytes it was given.
2269
+ * @param target - The directory every path is written beneath.
2270
+ * @param paths - Every target-relative path this transaction may touch.
2271
+ * @param preconditions - The caller-observed destination states the whole
2272
+ * transaction is held to, each naming a resolved destination of `paths`.
2273
+ * @throws {@link ScaffoldError} coded `INVALID` when an argument is off
2274
+ * contract, `TARGET` when a destination is a shape this package will not write
2275
+ * over or no longer matches its precondition, and `WRITE` when the private
2276
+ * root cannot be established.
1969
2277
  *
1970
2278
  * @remarks
1971
- * The staged file is read back and digested against the text it was given, so
1972
- * a partial or interrupted write is refused here rather than promoted later.
2279
+ * Nothing is created until every destination has been inspected and every
2280
+ * precondition has held, so a refused transaction leaves no residue at all.
2281
+ * The private root is created last and carries a random name, so two
2282
+ * transactions over one target never collide.
1973
2283
  */
1974
- write(path: string, content: string): void;
2284
+ constructor(target: string, paths: readonly string[], preconditions?: readonly WritePrecondition[]);
2285
+ /** The resolved directory every path is written beneath. */
2286
+ get target(): string;
2287
+ /** What each destination held when the transaction opened, in path order. */
2288
+ get expectations(): readonly WriteExpectation[];
2289
+ /** Whether the transaction can still be committed or discarded. */
2290
+ get open(): boolean;
1975
2291
  /**
1976
- * Stage one byte-for-byte copy of a file that already exists on this host.
2292
+ * Stage one text file.
1977
2293
  *
1978
2294
  * @param path - The target-relative path to write.
1979
- * @param source - The resolved absolute path to copy the bytes from.
1980
- * @param executable - If `true`, the destination carries the executable bit; if `false`, it does not.
2295
+ * @param content - The exact UTF-8 text the destination must hold.
1981
2296
  * @returns Nothing.
1982
2297
  * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
1983
2298
  * transaction opened or is already staged, `TARGET` when the destination holds
1984
- * a directory or the source is not a physical file, and `WRITE` when the copy
1985
- * cannot be made or does not digest to the source's bytes.
2299
+ * a directory, and `WRITE` when the staged file cannot be written or does not
2300
+ * carry the bytes it was given.
1986
2301
  *
1987
2302
  * @remarks
1988
- * The source is digested before the copy and the copy is digested after it, so
1989
- * a source that changed mid-copy is refused instead of staged.
2303
+ * The staged file is read back and digested against the text it was given, so
2304
+ * a partial or interrupted write is refused here rather than promoted later.
1990
2305
  */
1991
- copy(path: string, source: string, executable: boolean): void;
2306
+ write(path: string, content: string): void;
1992
2307
  /**
1993
- * Establish one directory inside the target, one segment at a time.
2308
+ * Stage one byte-for-byte copy of a file that already exists on this host.
1994
2309
  *
1995
- * @param path - The target-relative directory to establish.
1996
- * @returns The directory's identity and every segment this call created.
2310
+ * @param path - The target-relative path to write.
2311
+ * @param source - The resolved absolute path to copy the bytes from.
2312
+ * @param executable - If `true`, the destination carries the executable bit; if `false`, it does not.
2313
+ * @returns Nothing.
1997
2314
  * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
1998
- * transaction opened, `TARGET` when the destination holds a file, and `WRITE`
1999
- * when a segment cannot be created or changed while it was being created.
2315
+ * transaction opened or is already staged, `TARGET` when the destination holds
2316
+ * a directory or the source is not a physical file, and `WRITE` when the copy
2317
+ * cannot be made or does not digest to the source's bytes.
2000
2318
  *
2001
2319
  * @remarks
2002
- * A directory is created immediately rather than staged, because creating one
2003
- * destroys nothing: rollback removes exactly the segments this call created,
2004
- * innermost first, and leaves every segment that was already there. Each
2005
- * created segment is captured by device and inode, so a segment swapped
2006
- * underneath the transaction is detected rather than written into.
2320
+ * The source is digested before the copy and the copy is digested after it, so
2321
+ * a source that changed mid-copy is refused instead of staged.
2007
2322
  */
2008
- establish(path: string): WriteDirectoryResult;
2323
+ copy(path: string, source: string, executable: boolean): void;
2009
2324
  /**
2010
- * Mark one file for deletion at commit.
2325
+ * Establish one directory inside the target, one segment at a time.
2011
2326
  *
2012
- * @param path - The target-relative file to delete.
2013
- * @returns Nothing.
2327
+ * @param path - The target-relative directory to establish.
2328
+ * @returns The directory's identity and every segment this call created.
2014
2329
  * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2015
- * transaction opened or is already claimed, and `TARGET` when the destination
2016
- * does not hold a file.
2330
+ * transaction opened, `TARGET` when the destination holds a file, and `WRITE`
2331
+ * when a segment cannot be created or changed while it was being created.
2017
2332
  *
2018
2333
  * @remarks
2019
- * Nothing moves here. Commit renames the file into the private backup rather
2020
- * than unlinking it, so a later failure in the same commit puts it back.
2334
+ * A directory is created immediately rather than staged, because creating one
2335
+ * destroys nothing: rollback removes exactly the segments this call created,
2336
+ * innermost first, and leaves every segment that was already there. Each
2337
+ * created segment is captured by device and inode, so a segment swapped
2338
+ * underneath the transaction is detected rather than written into.
2021
2339
  */
2022
- remove(path: string): void;
2340
+ establish(path: string): WriteDirectoryResult;
2023
2341
  /**
2024
- * Promote every staged file and take every marked file, or roll the whole call back.
2342
+ * Mark one file for deletion at commit.
2025
2343
  *
2026
- * @returns Every target-relative path whose destination changed: the files
2027
- * promoted, then the directories established, then the files taken.
2028
- * @throws {@link ScaffoldError} coded `WRITE` when the transaction is closed,
2029
- * when a destination moved since the transaction opened, or when the commit
2030
- * failed; a failure reports what rollback could not undo in its context.
2344
+ * @param path - The target-relative file to delete.
2345
+ * @returns Nothing.
2346
+ * @throws {@link ScaffoldError} coded `INVALID` when the path is not one this
2347
+ * transaction opened or is already claimed, and `TARGET` when the destination
2348
+ * does not hold a file.
2031
2349
  *
2032
2350
  * @remarks
2033
- * Every destination and every directory this transaction created is re-checked
2034
- * before anything moves, so the common failure moves nothing at all. That
2035
- * check runs inside the same rollback as the promotions, because a transaction
2036
- * that refuses before it starts still has a private root and created
2037
- * directories to clear. The transaction is closed either way: a committed one
2038
- * has nothing left to undo and a failed one has already been rolled back.
2351
+ * Nothing moves here. Commit renames the file into the private backup rather
2352
+ * than unlinking it, so a later failure in the same commit puts it back.
2039
2353
  */
2040
- commit(): readonly string[];
2354
+ remove(path: string): void;
2041
2355
  /**
2042
- * Abandon the transaction and remove everything it created.
2356
+ * Promote every staged file and take every marked file, or roll the whole call back.
2043
2357
  *
2044
- * @returns Nothing.
2045
- * @throws {@link ScaffoldError} coded `WRITE` when residue could not be
2046
- * removed, naming the private root that still holds it.
2358
+ * @returns Every target-relative path whose destination changed: the files
2359
+ * promoted, then the directories established, then the files taken.
2360
+ * @throws {@link ScaffoldError} coded `WRITE` when the transaction is closed,
2361
+ * when a destination moved since the transaction opened, or when the commit
2362
+ * failed; a failure reports what rollback could not undo in its context.
2047
2363
  *
2048
2364
  * @remarks
2049
- * Idempotent, and a no-op on a transaction that already committed or already
2050
- * failed, so a caller can put it in a `finally` beside the work it guards.
2365
+ * Every destination and every directory this transaction created is re-checked
2366
+ * before anything moves, so the common failure moves nothing at all. That
2367
+ * check runs inside the same rollback as the promotions, because a transaction
2368
+ * that refuses before it starts still has a private root and created
2369
+ * directories to clear. The transaction is closed either way: a committed one
2370
+ * has nothing left to undo and a failed one has already been rolled back.
2051
2371
  */
2052
- discard(): void;
2053
- }
2054
-
2055
- export { }
2372
+ commit(): readonly string[];
2373
+ /**
2374
+ * Abandon the transaction and remove everything it created.
2375
+ *
2376
+ * @returns Nothing.
2377
+ * @throws {@link ScaffoldError} coded `WRITE` when residue could not be
2378
+ * removed, naming the private root that still holds it.
2379
+ *
2380
+ * @remarks
2381
+ * Idempotent, and a no-op on a transaction that already committed or already
2382
+ * failed, so a caller can put it in a `finally` beside the work it guards.
2383
+ */
2384
+ discard(): void;
2385
+ }
2386
+
2387
+ export { }