@orkestrel/scaffold 0.0.25 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/bin/main.js +45 -4
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +40 -11
- package/dist/host/claude/agents/orkestrel.md +63 -48
- package/dist/host/claude/rules/documentation.md +1 -1
- package/dist/host/guides/guide.md +211 -100
- package/dist/host/guides/scaffold.md +165 -10
- package/dist/host/manifest.json +5 -5
- package/dist/host/tests/config.test.ts +30 -7
- package/dist/src/core/index.cjs +242 -149
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +185 -98
- package/dist/src/core/index.d.ts +185 -98
- package/dist/src/core/index.js +240 -150
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +171 -66
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +140 -26
- package/dist/src/server/index.d.ts +140 -26
- package/dist/src/server/index.js +173 -69
- package/dist/src/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -289,12 +289,23 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
289
289
|
* the byte ceiling. The character ceiling is read first so an oversized string is
|
|
290
290
|
* refused before it is split.
|
|
291
291
|
*
|
|
292
|
+
* The two spellings of an empty segment are answered differently. A trailing
|
|
293
|
+
* separator terminates a directory rather than opening a segment, and every
|
|
294
|
+
* supported filesystem and every Node path API reads `project/` and `project` as
|
|
295
|
+
* one location, so it is admitted. A doubled separator is a genuine empty
|
|
296
|
+
* segment, so `project//src` is refused. Nothing normalizes the argument first —
|
|
297
|
+
* every server entry point guards the caller's text and resolves it afterwards —
|
|
298
|
+
* so a directory taken from a shell completion arrives carrying the separator the
|
|
299
|
+
* shell appended and names the directory it appears to name.
|
|
300
|
+
*
|
|
292
301
|
* @example
|
|
293
302
|
* ```ts
|
|
294
303
|
* import { isFilesystemPath } from '@orkestrel/scaffold/server'
|
|
295
304
|
*
|
|
296
305
|
* isFilesystemPath('C:/Users/sample/project') // true
|
|
297
306
|
* isFilesystemPath('../sibling') // true
|
|
307
|
+
* isFilesystemPath('project/') // true
|
|
308
|
+
* isFilesystemPath('project//src') // false
|
|
298
309
|
* isFilesystemPath('project/nul') // false
|
|
299
310
|
* ```
|
|
300
311
|
*/
|
|
@@ -560,7 +571,7 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
560
571
|
* ```ts
|
|
561
572
|
* import { listFiles } from '@orkestrel/scaffold/server'
|
|
562
573
|
*
|
|
563
|
-
* listFiles('./dist/host') // ['AGENTS.md', '
|
|
574
|
+
* listFiles('./dist/host') // ['AGENTS.md', 'CLAUDE.md', 'LICENSE', …]
|
|
564
575
|
* ```
|
|
565
576
|
*/
|
|
566
577
|
export declare function listFiles(root: string): readonly string[];
|
|
@@ -591,9 +602,13 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
591
602
|
* device and inode.
|
|
592
603
|
*
|
|
593
604
|
* @remarks
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
605
|
+
* This binds location rather than history. `true` means the path still resolves
|
|
606
|
+
* to the same physical directory on the same device, so the next write lands
|
|
607
|
+
* where the last one did. A path now holding nothing, a file, or a symlink
|
|
608
|
+
* answers `false`; a directory swapped in by `rename` also answers `false`
|
|
609
|
+
* because the replacement carries its own inode. A directory deleted and made
|
|
610
|
+
* again under the same name can receive the old inode back and answers `true`,
|
|
611
|
+
* which nothing here detects.
|
|
597
612
|
*
|
|
598
613
|
* @example
|
|
599
614
|
* ```ts
|
|
@@ -605,6 +620,31 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
605
620
|
*/
|
|
606
621
|
export declare function matchesAnchor(anchor: WriteAnchor): boolean;
|
|
607
622
|
|
|
623
|
+
/**
|
|
624
|
+
* Test whether a vendored path is one a target receives executable.
|
|
625
|
+
*
|
|
626
|
+
* @param path - The target-relative path to classify; either separator is read.
|
|
627
|
+
* @returns `true` when the path is declared in {@link EXECUTABLE_PATHS}.
|
|
628
|
+
*
|
|
629
|
+
* @remarks
|
|
630
|
+
* The declaration is the whole answer, and deliberately so. Reading the staging
|
|
631
|
+
* host's mode instead makes the manifest depend on where the package was built:
|
|
632
|
+
* Windows carries no executable bit, so a host staged there declares every entry
|
|
633
|
+
* non-executable and every target it later fills receives hooks at `0644`. One
|
|
634
|
+
* checkout stages one manifest on every host because this predicate never
|
|
635
|
+
* consults the filesystem.
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* import { matchesExecutablePath } from '@orkestrel/scaffold/server'
|
|
640
|
+
*
|
|
641
|
+
* matchesExecutablePath('scripts/codex.sh') // true
|
|
642
|
+
* matchesExecutablePath('scripts\\deps.sh') // true
|
|
643
|
+
* matchesExecutablePath('AGENTS.md') // false
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
export declare function matchesExecutablePath(path: string): boolean;
|
|
647
|
+
|
|
608
648
|
/**
|
|
609
649
|
* Test whether a destination still holds what was captured of it.
|
|
610
650
|
*
|
|
@@ -861,10 +901,13 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
861
901
|
* The audit is a preview, not an instruction. The plan is hydrated and
|
|
862
902
|
* compared against the target again here, and the verdicts that produces must
|
|
863
903
|
* match the ones the audit carried for every path the plan owns; anything else
|
|
864
|
-
* means the target moved, and the whole call is refused.
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
904
|
+
* means the target moved, and the whole call is refused. The audit is checked
|
|
905
|
+
* for agreement rather than for plausibility, so a verdict the comparison could
|
|
906
|
+
* not have produced — a birth-owned path reported stale, which the `Finding`
|
|
907
|
+
* shape admits — disagrees with the derived one and is refused. A missing
|
|
908
|
+
* destination is restored whatever its ownership; a stale one is replaced only
|
|
909
|
+
* where the artifact claims its bytes, which is what leaves a presence-owned
|
|
910
|
+
* file a consumer has edited exactly as it is.
|
|
868
911
|
*/
|
|
869
912
|
repair(plan: Plan, audit: Audit, target: string): MaterializeResult;
|
|
870
913
|
/**
|
|
@@ -1018,6 +1061,20 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1018
1061
|
* @param audit - The preview returned by this materializer's `audit` method.
|
|
1019
1062
|
* @param target - The directory to write into.
|
|
1020
1063
|
* @returns The paths written and skipped, each decided by its artifact's ownership.
|
|
1064
|
+
*
|
|
1065
|
+
* @remarks
|
|
1066
|
+
* The audit is checked for agreement rather than for plausibility, so a verdict
|
|
1067
|
+
* the comparison could not have produced — a birth-owned path reported stale,
|
|
1068
|
+
* which the `Finding` shape admits — disagrees with the derived one and is
|
|
1069
|
+
* refused.
|
|
1070
|
+
*
|
|
1071
|
+
* The whole audit is guarded before any of it is read, so an audit produced by
|
|
1072
|
+
* an earlier version of this package is refused at runtime rather than only at
|
|
1073
|
+
* compile time. A planned finding carries `ownership`, which findings made
|
|
1074
|
+
* before that field existed do not, and the guard refuses the call with
|
|
1075
|
+
* `INVALID`. Take a fresh audit from this materializer; a stored one is a record
|
|
1076
|
+
* of what a target looked like then, not an argument to a write now. The
|
|
1077
|
+
* refusal is deliberate at `0.0.x` and there is no migration.
|
|
1021
1078
|
*/
|
|
1022
1079
|
repair(plan: Plan, audit: Audit, target: string): MaterializeResult;
|
|
1023
1080
|
/**
|
|
@@ -1062,6 +1119,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1062
1119
|
* unlinked, so a failure part way through restores what it already took.
|
|
1063
1120
|
* The package's own source and application trees are never candidates,
|
|
1064
1121
|
* whatever the audit reports.
|
|
1122
|
+
*
|
|
1123
|
+
* The whole audit is guarded before any of it is read, so an audit produced by
|
|
1124
|
+
* an earlier version of this package is refused at runtime rather than only at
|
|
1125
|
+
* compile time. Only foreign findings are candidates and those never carried
|
|
1126
|
+
* `ownership`, but the guard reads every finding, so one planned finding made
|
|
1127
|
+
* before that field existed refuses the whole call with `INVALID`. Take a fresh
|
|
1128
|
+
* audit from this materializer. The refusal is deliberate at `0.0.x` and there
|
|
1129
|
+
* is no migration.
|
|
1065
1130
|
*/
|
|
1066
1131
|
remove(audit: Audit, repository: Repository, target: string): MaterializeResult;
|
|
1067
1132
|
/**
|
|
@@ -1175,8 +1240,8 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1175
1240
|
* @remarks
|
|
1176
1241
|
* Device and inode rather than the path, because the path is the thing that can
|
|
1177
1242
|
* be swapped underneath a write. An anchor captured before a mutation and
|
|
1178
|
-
* checked again after it
|
|
1179
|
-
*
|
|
1243
|
+
* checked again after it proves the directory written into sits where the
|
|
1244
|
+
* inspected one sat, not that it is the one that was inspected.
|
|
1180
1245
|
*
|
|
1181
1246
|
* @example
|
|
1182
1247
|
* ```ts
|
|
@@ -1307,14 +1372,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1307
1372
|
*
|
|
1308
1373
|
* @remarks
|
|
1309
1374
|
* The one place the three declared fields are decided together, because they are
|
|
1310
|
-
* three readings of one
|
|
1311
|
-
* the destination is the path it answers for, and
|
|
1312
|
-
*
|
|
1375
|
+
* three readings of one path: {@link pathToStorage} decides where it is stored,
|
|
1376
|
+
* the destination is the path it answers for, and {@link matchesExecutablePath}
|
|
1377
|
+
* decides whether a target receives it executable.
|
|
1313
1378
|
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1379
|
+
* The bit is read from that declaration rather than from the source's mode, so
|
|
1380
|
+
* the entry does not depend on where the package was staged. A Windows host
|
|
1381
|
+
* reports no executable bit at all, and reading the mode there declared every
|
|
1382
|
+
* entry non-executable and shipped consumers hooks they could not run.
|
|
1318
1383
|
*
|
|
1319
1384
|
* @example
|
|
1320
1385
|
* ```ts
|
|
@@ -1396,20 +1461,31 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1396
1461
|
*
|
|
1397
1462
|
* @remarks
|
|
1398
1463
|
* The containment law, and the one door every read in this module goes through.
|
|
1399
|
-
* Both sides are resolved through the real filesystem before they are compared
|
|
1400
|
-
*
|
|
1401
|
-
* answer is then the lexical join
|
|
1402
|
-
*
|
|
1464
|
+
* Both sides are resolved through the real filesystem before they are compared.
|
|
1465
|
+
* A dangling link is followed only when its raw target contains no parent
|
|
1466
|
+
* traversal. The answer is then the lexical join of `root` and `path` — an
|
|
1467
|
+
* absolute path under `root`, not a root-relative one — so the caller operates
|
|
1468
|
+
* on the path it named rather than on a resolved form the target may not
|
|
1469
|
+
* recognize. A `root` written with a parent segment is collapsed by that
|
|
1470
|
+
* resolution before anything is read, so containment is measured against the
|
|
1471
|
+
* directory the caller's text names.
|
|
1403
1472
|
*
|
|
1404
1473
|
* Comparison is exact text, which fails closed on a case-insensitive
|
|
1405
1474
|
* filesystem: a root and a path spelled with different case resolve to
|
|
1406
1475
|
* different strings there and are refused, never wrongly admitted.
|
|
1407
1476
|
*
|
|
1477
|
+
* The answer describes the namespace this call read. The contract excludes a
|
|
1478
|
+
* concurrent rename or link swap during the call or before the caller finishes
|
|
1479
|
+
* using the returned path. This helper returns a string, not a filesystem
|
|
1480
|
+
* handle, so it cannot bind its containment check to a later operation. A caller
|
|
1481
|
+
* that admits hostile concurrent namespace mutation needs a handle-bound
|
|
1482
|
+
* operation instead.
|
|
1483
|
+
*
|
|
1408
1484
|
* @example
|
|
1409
1485
|
* ```ts
|
|
1410
1486
|
* import { resolveContainedPath } from '@orkestrel/scaffold/server'
|
|
1411
1487
|
*
|
|
1412
|
-
* resolveContainedPath('/tmp/project', 'guides/router.md')
|
|
1488
|
+
* resolveContainedPath('/tmp/project', 'guides/router.md')?.endsWith('router.md') // true
|
|
1413
1489
|
* resolveContainedPath('/tmp/project', '../secrets') // undefined
|
|
1414
1490
|
* ```
|
|
1415
1491
|
*/
|
|
@@ -1419,9 +1495,10 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1419
1495
|
* Resolve a path through the real filesystem, keeping the part that does not exist yet.
|
|
1420
1496
|
*
|
|
1421
1497
|
* @param path - The absolute or relative host path to resolve.
|
|
1422
|
-
* @returns The path with its existing prefix
|
|
1423
|
-
* `undefined` when the text is not a host path,
|
|
1424
|
-
* resolves,
|
|
1498
|
+
* @returns The lexical resolution of `path`, with its existing prefix then
|
|
1499
|
+
* resolved through every link, or `undefined` when the text is not a host path,
|
|
1500
|
+
* no bounded existing ancestor resolves, a link target cannot be read, a link
|
|
1501
|
+
* target carries a `..` segment, or an ancestor cannot be read.
|
|
1425
1502
|
*
|
|
1426
1503
|
* @remarks
|
|
1427
1504
|
* A containment decision has to be made about a destination that does not exist
|
|
@@ -1431,6 +1508,30 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1431
1508
|
* it. The climb is bounded by the path-depth ceiling, so an adversarial path
|
|
1432
1509
|
* cannot make it walk indefinitely.
|
|
1433
1510
|
*
|
|
1511
|
+
* The caller's own text is collapsed first, which is what `resolve` does with a
|
|
1512
|
+
* `..` the caller wrote: it cancels the segment before it as text, before any
|
|
1513
|
+
* link in that segment is read. So `<root>/hop/..` answers `<root>` even where
|
|
1514
|
+
* `hop` links elsewhere, rather than the directory holding what `hop` points at.
|
|
1515
|
+
* The collapse only ever shortens the caller's path, so nothing reaches outside
|
|
1516
|
+
* it by this; the answer is that lexical location resolved through links, not
|
|
1517
|
+
* the physical location the links lead to. {@link resolveContainedPath} passes
|
|
1518
|
+
* its `root` through here, so a root written with a parent segment is contained
|
|
1519
|
+
* against its collapsed spelling.
|
|
1520
|
+
*
|
|
1521
|
+
* `realpath` answers `ENOENT` both for a name that is not there and for a link
|
|
1522
|
+
* whose target is not there. The name is therefore inspected without following
|
|
1523
|
+
* it: a dangling link redirects the walk to its target, while a genuinely absent
|
|
1524
|
+
* name is retained as one segment of the unresolved suffix. A dangling link
|
|
1525
|
+
* target containing a `..` segment is refused. Resolving that target as one
|
|
1526
|
+
* lexical string could discard a preceding link before the filesystem gives
|
|
1527
|
+
* `..` its physical meaning.
|
|
1528
|
+
*
|
|
1529
|
+
* That target is split on both separators on every host, which is the reading
|
|
1530
|
+
* `isPath` already gives a planned path. A POSIX filename legally containing a
|
|
1531
|
+
* backslash is therefore refused with it: `weird\..\name` is one name to the
|
|
1532
|
+
* host and three segments here. The package keeps one separator law rather than
|
|
1533
|
+
* a host-dependent second one, and this is the conservative side of it.
|
|
1534
|
+
*
|
|
1434
1535
|
* @example
|
|
1435
1536
|
* ```ts
|
|
1436
1537
|
* import { resolveRealPath } from '@orkestrel/scaffold/server'
|
|
@@ -1745,7 +1846,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1745
1846
|
readonly error?: EmitterErrorHandler;
|
|
1746
1847
|
}
|
|
1747
1848
|
|
|
1748
|
-
/**
|
|
1849
|
+
/**
|
|
1850
|
+
* One physical directory identity captured across a write transaction.
|
|
1851
|
+
*
|
|
1852
|
+
* @remarks
|
|
1853
|
+
* Device and inode locate the directory and do not date it. Two directories
|
|
1854
|
+
* occupying one slot at different times share an anchor, so an anchor proves
|
|
1855
|
+
* where a write lands and never that the directory was left alone.
|
|
1856
|
+
*/
|
|
1749
1857
|
export declare interface WriteAnchor {
|
|
1750
1858
|
readonly path: string;
|
|
1751
1859
|
readonly device: number;
|
|
@@ -1810,6 +1918,12 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1810
1918
|
* - **No partly written destination.** Every file is written whole into the
|
|
1811
1919
|
* private root and digested there before commit, so a destination never
|
|
1812
1920
|
* receives bytes that were still being produced.
|
|
1921
|
+
* - **Containment, not continuity, of the directories it creates.** Every
|
|
1922
|
+
* ancestor is re-read between `mkdir` calls and again before the first
|
|
1923
|
+
* promotion, so an ancestor that became a file, a symlink, a directory
|
|
1924
|
+
* elsewhere, or nothing is refused. An ancestor deleted and recreated under
|
|
1925
|
+
* the same name can receive its old inode back and is indistinguishable here
|
|
1926
|
+
* from one that never moved.
|
|
1813
1927
|
* - **No crash atomicity across destinations.** A process killed between two
|
|
1814
1928
|
* promotions leaves the target holding some new files and some old ones, and
|
|
1815
1929
|
* leaves the private root behind. Nothing here is a journal, and the private
|
|
@@ -289,12 +289,23 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
289
289
|
* the byte ceiling. The character ceiling is read first so an oversized string is
|
|
290
290
|
* refused before it is split.
|
|
291
291
|
*
|
|
292
|
+
* The two spellings of an empty segment are answered differently. A trailing
|
|
293
|
+
* separator terminates a directory rather than opening a segment, and every
|
|
294
|
+
* supported filesystem and every Node path API reads `project/` and `project` as
|
|
295
|
+
* one location, so it is admitted. A doubled separator is a genuine empty
|
|
296
|
+
* segment, so `project//src` is refused. Nothing normalizes the argument first —
|
|
297
|
+
* every server entry point guards the caller's text and resolves it afterwards —
|
|
298
|
+
* so a directory taken from a shell completion arrives carrying the separator the
|
|
299
|
+
* shell appended and names the directory it appears to name.
|
|
300
|
+
*
|
|
292
301
|
* @example
|
|
293
302
|
* ```ts
|
|
294
303
|
* import { isFilesystemPath } from '@orkestrel/scaffold/server'
|
|
295
304
|
*
|
|
296
305
|
* isFilesystemPath('C:/Users/sample/project') // true
|
|
297
306
|
* isFilesystemPath('../sibling') // true
|
|
307
|
+
* isFilesystemPath('project/') // true
|
|
308
|
+
* isFilesystemPath('project//src') // false
|
|
298
309
|
* isFilesystemPath('project/nul') // false
|
|
299
310
|
* ```
|
|
300
311
|
*/
|
|
@@ -560,7 +571,7 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
560
571
|
* ```ts
|
|
561
572
|
* import { listFiles } from '@orkestrel/scaffold/server'
|
|
562
573
|
*
|
|
563
|
-
* listFiles('./dist/host') // ['AGENTS.md', '
|
|
574
|
+
* listFiles('./dist/host') // ['AGENTS.md', 'CLAUDE.md', 'LICENSE', …]
|
|
564
575
|
* ```
|
|
565
576
|
*/
|
|
566
577
|
export declare function listFiles(root: string): readonly string[];
|
|
@@ -591,9 +602,13 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
591
602
|
* device and inode.
|
|
592
603
|
*
|
|
593
604
|
* @remarks
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
605
|
+
* This binds location rather than history. `true` means the path still resolves
|
|
606
|
+
* to the same physical directory on the same device, so the next write lands
|
|
607
|
+
* where the last one did. A path now holding nothing, a file, or a symlink
|
|
608
|
+
* answers `false`; a directory swapped in by `rename` also answers `false`
|
|
609
|
+
* because the replacement carries its own inode. A directory deleted and made
|
|
610
|
+
* again under the same name can receive the old inode back and answers `true`,
|
|
611
|
+
* which nothing here detects.
|
|
597
612
|
*
|
|
598
613
|
* @example
|
|
599
614
|
* ```ts
|
|
@@ -605,6 +620,31 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
605
620
|
*/
|
|
606
621
|
export declare function matchesAnchor(anchor: WriteAnchor): boolean;
|
|
607
622
|
|
|
623
|
+
/**
|
|
624
|
+
* Test whether a vendored path is one a target receives executable.
|
|
625
|
+
*
|
|
626
|
+
* @param path - The target-relative path to classify; either separator is read.
|
|
627
|
+
* @returns `true` when the path is declared in {@link EXECUTABLE_PATHS}.
|
|
628
|
+
*
|
|
629
|
+
* @remarks
|
|
630
|
+
* The declaration is the whole answer, and deliberately so. Reading the staging
|
|
631
|
+
* host's mode instead makes the manifest depend on where the package was built:
|
|
632
|
+
* Windows carries no executable bit, so a host staged there declares every entry
|
|
633
|
+
* non-executable and every target it later fills receives hooks at `0644`. One
|
|
634
|
+
* checkout stages one manifest on every host because this predicate never
|
|
635
|
+
* consults the filesystem.
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* import { matchesExecutablePath } from '@orkestrel/scaffold/server'
|
|
640
|
+
*
|
|
641
|
+
* matchesExecutablePath('scripts/codex.sh') // true
|
|
642
|
+
* matchesExecutablePath('scripts\\deps.sh') // true
|
|
643
|
+
* matchesExecutablePath('AGENTS.md') // false
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
export declare function matchesExecutablePath(path: string): boolean;
|
|
647
|
+
|
|
608
648
|
/**
|
|
609
649
|
* Test whether a destination still holds what was captured of it.
|
|
610
650
|
*
|
|
@@ -861,10 +901,13 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
861
901
|
* The audit is a preview, not an instruction. The plan is hydrated and
|
|
862
902
|
* compared against the target again here, and the verdicts that produces must
|
|
863
903
|
* match the ones the audit carried for every path the plan owns; anything else
|
|
864
|
-
* means the target moved, and the whole call is refused.
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
904
|
+
* means the target moved, and the whole call is refused. The audit is checked
|
|
905
|
+
* for agreement rather than for plausibility, so a verdict the comparison could
|
|
906
|
+
* not have produced — a birth-owned path reported stale, which the `Finding`
|
|
907
|
+
* shape admits — disagrees with the derived one and is refused. A missing
|
|
908
|
+
* destination is restored whatever its ownership; a stale one is replaced only
|
|
909
|
+
* where the artifact claims its bytes, which is what leaves a presence-owned
|
|
910
|
+
* file a consumer has edited exactly as it is.
|
|
868
911
|
*/
|
|
869
912
|
repair(plan: Plan, audit: Audit, target: string): MaterializeResult;
|
|
870
913
|
/**
|
|
@@ -1018,6 +1061,20 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1018
1061
|
* @param audit - The preview returned by this materializer's `audit` method.
|
|
1019
1062
|
* @param target - The directory to write into.
|
|
1020
1063
|
* @returns The paths written and skipped, each decided by its artifact's ownership.
|
|
1064
|
+
*
|
|
1065
|
+
* @remarks
|
|
1066
|
+
* The audit is checked for agreement rather than for plausibility, so a verdict
|
|
1067
|
+
* the comparison could not have produced — a birth-owned path reported stale,
|
|
1068
|
+
* which the `Finding` shape admits — disagrees with the derived one and is
|
|
1069
|
+
* refused.
|
|
1070
|
+
*
|
|
1071
|
+
* The whole audit is guarded before any of it is read, so an audit produced by
|
|
1072
|
+
* an earlier version of this package is refused at runtime rather than only at
|
|
1073
|
+
* compile time. A planned finding carries `ownership`, which findings made
|
|
1074
|
+
* before that field existed do not, and the guard refuses the call with
|
|
1075
|
+
* `INVALID`. Take a fresh audit from this materializer; a stored one is a record
|
|
1076
|
+
* of what a target looked like then, not an argument to a write now. The
|
|
1077
|
+
* refusal is deliberate at `0.0.x` and there is no migration.
|
|
1021
1078
|
*/
|
|
1022
1079
|
repair(plan: Plan, audit: Audit, target: string): MaterializeResult;
|
|
1023
1080
|
/**
|
|
@@ -1062,6 +1119,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1062
1119
|
* unlinked, so a failure part way through restores what it already took.
|
|
1063
1120
|
* The package's own source and application trees are never candidates,
|
|
1064
1121
|
* whatever the audit reports.
|
|
1122
|
+
*
|
|
1123
|
+
* The whole audit is guarded before any of it is read, so an audit produced by
|
|
1124
|
+
* an earlier version of this package is refused at runtime rather than only at
|
|
1125
|
+
* compile time. Only foreign findings are candidates and those never carried
|
|
1126
|
+
* `ownership`, but the guard reads every finding, so one planned finding made
|
|
1127
|
+
* before that field existed refuses the whole call with `INVALID`. Take a fresh
|
|
1128
|
+
* audit from this materializer. The refusal is deliberate at `0.0.x` and there
|
|
1129
|
+
* is no migration.
|
|
1065
1130
|
*/
|
|
1066
1131
|
remove(audit: Audit, repository: Repository, target: string): MaterializeResult;
|
|
1067
1132
|
/**
|
|
@@ -1175,8 +1240,8 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1175
1240
|
* @remarks
|
|
1176
1241
|
* Device and inode rather than the path, because the path is the thing that can
|
|
1177
1242
|
* be swapped underneath a write. An anchor captured before a mutation and
|
|
1178
|
-
* checked again after it
|
|
1179
|
-
*
|
|
1243
|
+
* checked again after it proves the directory written into sits where the
|
|
1244
|
+
* inspected one sat, not that it is the one that was inspected.
|
|
1180
1245
|
*
|
|
1181
1246
|
* @example
|
|
1182
1247
|
* ```ts
|
|
@@ -1307,14 +1372,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1307
1372
|
*
|
|
1308
1373
|
* @remarks
|
|
1309
1374
|
* The one place the three declared fields are decided together, because they are
|
|
1310
|
-
* three readings of one
|
|
1311
|
-
* the destination is the path it answers for, and
|
|
1312
|
-
*
|
|
1375
|
+
* three readings of one path: {@link pathToStorage} decides where it is stored,
|
|
1376
|
+
* the destination is the path it answers for, and {@link matchesExecutablePath}
|
|
1377
|
+
* decides whether a target receives it executable.
|
|
1313
1378
|
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1379
|
+
* The bit is read from that declaration rather than from the source's mode, so
|
|
1380
|
+
* the entry does not depend on where the package was staged. A Windows host
|
|
1381
|
+
* reports no executable bit at all, and reading the mode there declared every
|
|
1382
|
+
* entry non-executable and shipped consumers hooks they could not run.
|
|
1318
1383
|
*
|
|
1319
1384
|
* @example
|
|
1320
1385
|
* ```ts
|
|
@@ -1396,20 +1461,31 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1396
1461
|
*
|
|
1397
1462
|
* @remarks
|
|
1398
1463
|
* The containment law, and the one door every read in this module goes through.
|
|
1399
|
-
* Both sides are resolved through the real filesystem before they are compared
|
|
1400
|
-
*
|
|
1401
|
-
* answer is then the lexical join
|
|
1402
|
-
*
|
|
1464
|
+
* Both sides are resolved through the real filesystem before they are compared.
|
|
1465
|
+
* A dangling link is followed only when its raw target contains no parent
|
|
1466
|
+
* traversal. The answer is then the lexical join of `root` and `path` — an
|
|
1467
|
+
* absolute path under `root`, not a root-relative one — so the caller operates
|
|
1468
|
+
* on the path it named rather than on a resolved form the target may not
|
|
1469
|
+
* recognize. A `root` written with a parent segment is collapsed by that
|
|
1470
|
+
* resolution before anything is read, so containment is measured against the
|
|
1471
|
+
* directory the caller's text names.
|
|
1403
1472
|
*
|
|
1404
1473
|
* Comparison is exact text, which fails closed on a case-insensitive
|
|
1405
1474
|
* filesystem: a root and a path spelled with different case resolve to
|
|
1406
1475
|
* different strings there and are refused, never wrongly admitted.
|
|
1407
1476
|
*
|
|
1477
|
+
* The answer describes the namespace this call read. The contract excludes a
|
|
1478
|
+
* concurrent rename or link swap during the call or before the caller finishes
|
|
1479
|
+
* using the returned path. This helper returns a string, not a filesystem
|
|
1480
|
+
* handle, so it cannot bind its containment check to a later operation. A caller
|
|
1481
|
+
* that admits hostile concurrent namespace mutation needs a handle-bound
|
|
1482
|
+
* operation instead.
|
|
1483
|
+
*
|
|
1408
1484
|
* @example
|
|
1409
1485
|
* ```ts
|
|
1410
1486
|
* import { resolveContainedPath } from '@orkestrel/scaffold/server'
|
|
1411
1487
|
*
|
|
1412
|
-
* resolveContainedPath('/tmp/project', 'guides/router.md')
|
|
1488
|
+
* resolveContainedPath('/tmp/project', 'guides/router.md')?.endsWith('router.md') // true
|
|
1413
1489
|
* resolveContainedPath('/tmp/project', '../secrets') // undefined
|
|
1414
1490
|
* ```
|
|
1415
1491
|
*/
|
|
@@ -1419,9 +1495,10 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1419
1495
|
* Resolve a path through the real filesystem, keeping the part that does not exist yet.
|
|
1420
1496
|
*
|
|
1421
1497
|
* @param path - The absolute or relative host path to resolve.
|
|
1422
|
-
* @returns The path with its existing prefix
|
|
1423
|
-
* `undefined` when the text is not a host path,
|
|
1424
|
-
* resolves,
|
|
1498
|
+
* @returns The lexical resolution of `path`, with its existing prefix then
|
|
1499
|
+
* resolved through every link, or `undefined` when the text is not a host path,
|
|
1500
|
+
* no bounded existing ancestor resolves, a link target cannot be read, a link
|
|
1501
|
+
* target carries a `..` segment, or an ancestor cannot be read.
|
|
1425
1502
|
*
|
|
1426
1503
|
* @remarks
|
|
1427
1504
|
* A containment decision has to be made about a destination that does not exist
|
|
@@ -1431,6 +1508,30 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1431
1508
|
* it. The climb is bounded by the path-depth ceiling, so an adversarial path
|
|
1432
1509
|
* cannot make it walk indefinitely.
|
|
1433
1510
|
*
|
|
1511
|
+
* The caller's own text is collapsed first, which is what `resolve` does with a
|
|
1512
|
+
* `..` the caller wrote: it cancels the segment before it as text, before any
|
|
1513
|
+
* link in that segment is read. So `<root>/hop/..` answers `<root>` even where
|
|
1514
|
+
* `hop` links elsewhere, rather than the directory holding what `hop` points at.
|
|
1515
|
+
* The collapse only ever shortens the caller's path, so nothing reaches outside
|
|
1516
|
+
* it by this; the answer is that lexical location resolved through links, not
|
|
1517
|
+
* the physical location the links lead to. {@link resolveContainedPath} passes
|
|
1518
|
+
* its `root` through here, so a root written with a parent segment is contained
|
|
1519
|
+
* against its collapsed spelling.
|
|
1520
|
+
*
|
|
1521
|
+
* `realpath` answers `ENOENT` both for a name that is not there and for a link
|
|
1522
|
+
* whose target is not there. The name is therefore inspected without following
|
|
1523
|
+
* it: a dangling link redirects the walk to its target, while a genuinely absent
|
|
1524
|
+
* name is retained as one segment of the unresolved suffix. A dangling link
|
|
1525
|
+
* target containing a `..` segment is refused. Resolving that target as one
|
|
1526
|
+
* lexical string could discard a preceding link before the filesystem gives
|
|
1527
|
+
* `..` its physical meaning.
|
|
1528
|
+
*
|
|
1529
|
+
* That target is split on both separators on every host, which is the reading
|
|
1530
|
+
* `isPath` already gives a planned path. A POSIX filename legally containing a
|
|
1531
|
+
* backslash is therefore refused with it: `weird\..\name` is one name to the
|
|
1532
|
+
* host and three segments here. The package keeps one separator law rather than
|
|
1533
|
+
* a host-dependent second one, and this is the conservative side of it.
|
|
1534
|
+
*
|
|
1434
1535
|
* @example
|
|
1435
1536
|
* ```ts
|
|
1436
1537
|
* import { resolveRealPath } from '@orkestrel/scaffold/server'
|
|
@@ -1745,7 +1846,14 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1745
1846
|
readonly error?: EmitterErrorHandler;
|
|
1746
1847
|
}
|
|
1747
1848
|
|
|
1748
|
-
/**
|
|
1849
|
+
/**
|
|
1850
|
+
* One physical directory identity captured across a write transaction.
|
|
1851
|
+
*
|
|
1852
|
+
* @remarks
|
|
1853
|
+
* Device and inode locate the directory and do not date it. Two directories
|
|
1854
|
+
* occupying one slot at different times share an anchor, so an anchor proves
|
|
1855
|
+
* where a write lands and never that the directory was left alone.
|
|
1856
|
+
*/
|
|
1749
1857
|
export declare interface WriteAnchor {
|
|
1750
1858
|
readonly path: string;
|
|
1751
1859
|
readonly device: number;
|
|
@@ -1810,6 +1918,12 @@ export declare function computeManifestDigest(entries: readonly ManifestEntry[],
|
|
|
1810
1918
|
* - **No partly written destination.** Every file is written whole into the
|
|
1811
1919
|
* private root and digested there before commit, so a destination never
|
|
1812
1920
|
* receives bytes that were still being produced.
|
|
1921
|
+
* - **Containment, not continuity, of the directories it creates.** Every
|
|
1922
|
+
* ancestor is re-read between `mkdir` calls and again before the first
|
|
1923
|
+
* promotion, so an ancestor that became a file, a symlink, a directory
|
|
1924
|
+
* elsewhere, or nothing is refused. An ancestor deleted and recreated under
|
|
1925
|
+
* the same name can receive its old inode back and is indistinguishable here
|
|
1926
|
+
* from one that never moved.
|
|
1813
1927
|
* - **No crash atomicity across destinations.** A process killed between two
|
|
1814
1928
|
* promotions leaves the target holding some new files and some old ones, and
|
|
1815
1929
|
* leaves the private root behind. Nothing here is a journal, and the private
|