@orkestrel/scaffold 0.0.24 → 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.
@@ -46,10 +46,9 @@ export declare interface AppDefinition {
46
46
  *
47
47
  * @example
48
48
  * ```ts
49
- * import type { Artifact } from '@orkestrel/scaffold'
50
- * import { applyOverrides } from '@orkestrel/scaffold'
49
+ * import { applyOverrides, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
51
50
  *
52
- * declare const artifacts: readonly Artifact[]
51
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
53
52
  *
54
53
  * applyOverrides(artifacts, [{ path: 'README.md', content: '# Title\n' }])
55
54
  * ```
@@ -71,9 +70,13 @@ export declare type Artifact = HostArtifact | HydratedArtifact | ContentArtifact
71
70
  *
72
71
  * @remarks
73
72
  * Builders in `compilers.ts` fill every varying span through
74
- * `@orkestrel/template`. Empty barrels and setup modules are intentional: the
75
- * generated workspace starts with no sample domain API, while each selected
76
- * Vitest project gets a real test that proves its barrel has no starter exports.
73
+ * `@orkestrel/template`. Empty barrels, entries, and setup modules are
74
+ * intentional: the generated workspace starts with no sample domain API, while
75
+ * each selected Vitest project gets a real test that proves its barrel has no
76
+ * starter exports. An entry starts empty for the same reason its barrel does,
77
+ * and because the vendored lint config refuses an unassigned import outside a
78
+ * stylesheet, so a starter `import './index.js'` would fail the workspace's own
79
+ * `lint:check` on the day it is written.
77
80
  *
78
81
  * @example
79
82
  * ```ts
@@ -85,7 +88,6 @@ export declare type Artifact = HostArtifact | HydratedArtifact | ContentArtifact
85
88
  export declare const ARTIFACT_TEMPLATES: Readonly<{
86
89
  source: Readonly<{
87
90
  empty: "";
88
- main: "import './index.js'\n";
89
91
  browser: "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Application</title>\n\t</head>\n\t<body>\n\t\t<script type=\"module\" src=\"/main.ts\"></script>\n\t</body>\n</html>\n";
90
92
  }>;
91
93
  tests: Readonly<{
@@ -133,12 +135,11 @@ export declare interface ArtifactBase {
133
135
  *
134
136
  * @example
135
137
  * ```ts
136
- * import type { Artifact } from '@orkestrel/scaffold'
137
- * import { artifactsToQuestions } from '@orkestrel/scaffold'
138
+ * import { artifactsToQuestions, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
138
139
  *
139
- * declare const artifacts: readonly Artifact[]
140
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
140
141
  *
141
- * artifactsToQuestions(artifacts).length === 0 // true when the draft is sound
142
+ * artifactsToQuestions(artifacts).length === 0 // true
142
143
  * ```
143
144
  */
144
145
  export declare function artifactsToQuestions(artifacts: readonly Artifact[]): readonly Question[];
@@ -149,13 +150,16 @@ export declare function artifactsToQuestions(artifacts: readonly Artifact[]): re
149
150
  * @param artifact - The planned artifact.
150
151
  * @param observed - The destination's exact bytes as hexadecimal; absent when
151
152
  * the destination holds no file.
152
- * @returns The finding, carrying `observed` exactly where bytes were read.
153
+ * @returns The finding, carrying the artifact's ownership and `observed`
154
+ * exactly where bytes were read.
153
155
  *
154
156
  * @remarks
155
157
  * The comparison itself is {@link inferDrift}'s, so ownership decides it here
156
158
  * exactly as it does everywhere else. This adds only the shape: a missing
157
159
  * destination has no bytes to record, and every other verdict records the bytes
158
160
  * it was given, which is the precondition the mutation that follows is held to.
161
+ * Ownership is copied rather than inferred from drift because aligned findings
162
+ * span all three ownership tiers.
159
163
  *
160
164
  * `foreign` is not answerable here, because it describes a path no artifact was
161
165
  * planned for.
@@ -167,7 +171,7 @@ export declare function artifactsToQuestions(artifacts: readonly Artifact[]): re
167
171
  * artifactToFinding(
168
172
  * { path: 'README.md', group: 'docs', ownership: 'content', origin: 'computed', content: 'hi\n' },
169
173
  * '6279650a',
170
- * ) // { path: 'README.md', group: 'docs', drift: 'stale', observed: '6279650a' }
174
+ * ) // { path: 'README.md', group: 'docs', ownership: 'content', drift: 'stale', observed: '6279650a' }
171
175
  * ```
172
176
  */
173
177
  export declare function artifactToFinding(artifact: Artifact, observed?: string): Finding;
@@ -205,7 +209,8 @@ export declare function artifactToHex(artifact: Artifact): string | undefined;
205
209
  * @remarks
206
210
  * A blocking question means the gate refused the blueprint, so `findings` is
207
211
  * empty and says nothing about the target. Tallies are not stored: count
208
- * `findings` by `drift`.
212
+ * `findings` by `drift` or `ownership`. The finding bound is the sum of the
213
+ * separately bounded plan artifacts and snapshot paths.
209
214
  */
210
215
  export declare interface Audit {
211
216
  readonly findings: readonly Finding[];
@@ -233,7 +238,11 @@ export declare const BIN_ENTRY_PATH = "src/bin/main.ts";
233
238
  * `bin`, `integration`, `services`, `global`, and `showcase` are structural
234
239
  * facts: each is set only when the workspace physically ships the directory or
235
240
  * exact-case file that defines it, never because of the workspace's name and
236
- * never because a sibling fact is set.
241
+ * never because a sibling fact is set. An axis-dependent fact projects nothing
242
+ * when its required axis is absent: `integration` projects only a published
243
+ * `src`, and `showcase` projects only a browser `app`. The gate answers that case
244
+ * with a non-blocking question, so a caller that set the flag learns it emitted
245
+ * nothing and the compile still completes.
237
246
  */
238
247
  export declare interface Blueprint {
239
248
  readonly name: string;
@@ -262,10 +271,9 @@ export declare interface Blueprint {
262
271
  *
263
272
  * @example
264
273
  * ```ts
265
- * import type { Blueprint } from '@orkestrel/scaffold'
266
- * import { blueprintToConfigArtifacts } from '@orkestrel/scaffold'
274
+ * import { blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
267
275
  *
268
- * declare const blueprint: Blueprint
276
+ * const blueprint = createBlueprint('router', { src: ['core'] })
269
277
  *
270
278
  * blueprintToConfigArtifacts(blueprint)[0]?.path // 'tsconfig.json'
271
279
  * ```
@@ -298,10 +306,9 @@ export declare function blueprintToConfigArtifacts(blueprint: Blueprint): readon
298
306
  *
299
307
  * @example
300
308
  * ```ts
301
- * import type { Blueprint } from '@orkestrel/scaffold'
302
- * import { blueprintToDevDependencies } from '@orkestrel/scaffold'
309
+ * import { blueprintToDevDependencies, createBlueprint } from '@orkestrel/scaffold'
303
310
  *
304
- * declare const blueprint: Blueprint
311
+ * const blueprint = createBlueprint('router', { src: ['core'] })
305
312
  *
306
313
  * blueprintToDevDependencies(blueprint).typescript // the shared TypeScript pin
307
314
  * ```
@@ -344,12 +351,11 @@ export declare function blueprintToGuideArtifacts(blueprint: Blueprint): readonl
344
351
  *
345
352
  * @example
346
353
  * ```ts
347
- * import type { Blueprint } from '@orkestrel/scaffold'
348
- * import { blueprintToMachinery } from '@orkestrel/scaffold'
354
+ * import { blueprintToMachinery, createBlueprint } from '@orkestrel/scaffold'
349
355
  *
350
- * declare const blueprint: Blueprint
356
+ * const blueprint = createBlueprint('router', { app: ['browser'] })
351
357
  *
352
- * blueprintToMachinery(blueprint).vue // true when the app declares browser
358
+ * blueprintToMachinery(blueprint).vue // true
353
359
  * ```
354
360
  */
355
361
  export declare function blueprintToMachinery(blueprint: Blueprint): ViteMachinery;
@@ -382,10 +388,9 @@ export declare function blueprintToMachinery(blueprint: Blueprint): ViteMachiner
382
388
  *
383
389
  * @example
384
390
  * ```ts
385
- * import type { Blueprint } from '@orkestrel/scaffold'
386
- * import { blueprintToManifest } from '@orkestrel/scaffold'
391
+ * import { blueprintToManifest, createBlueprint } from '@orkestrel/scaffold'
387
392
  *
388
- * declare const blueprint: Blueprint
393
+ * const blueprint = createBlueprint('router', { src: ['core'] })
389
394
  *
390
395
  * blueprintToManifest(blueprint).endsWith('}\n') // true
391
396
  * ```
@@ -409,8 +414,8 @@ export declare function blueprintToOrchestrationArtifacts(blueprint: Blueprint):
409
414
  * Measure a blueprint against every law its own fields decide.
410
415
  *
411
416
  * @param blueprint - The workspace specification.
412
- * @returns One blocking question per rejected field, in blueprint field order,
413
- * with the rules that span several fields last.
417
+ * @returns One question per rejected field, in blueprint field order, with the
418
+ * rules that span several fields last.
414
419
  *
415
420
  * @remarks
416
421
  * Only the laws a blueprint answers alone are here. The structural record and
@@ -421,19 +426,25 @@ export declare function blueprintToOrchestrationArtifacts(blueprint: Blueprint):
421
426
  * a drafted plan belong to {@link artifactsToQuestions} and
422
427
  * {@link overridesToQuestions}.
423
428
  *
424
- * Every question is blocking, because each one describes a workspace this
425
- * package cannot generate rather than one it can generate imperfectly. An
426
- * environment question carries `ENVIRONMENTS` as its candidates, so a caller
427
- * reads the accepted values from the refusal instead of from the documentation.
429
+ * A question blocks when it describes a workspace this package cannot generate.
430
+ * Three do not, because each describes a workspace it can describe honestly and
431
+ * should not create: a published axis of several environments without core, whose
432
+ * manifest names a core build the workspace never runs, and a structural flag
433
+ * whose required axis is absent, which emits nothing. Blocking those closed the
434
+ * gate for every verb, and the verbs that read an existing workspace need the
435
+ * plan the gate refused. The caller that chooses the shape refuses the advisory;
436
+ * the callers that read one report it.
437
+ *
438
+ * An environment question carries `ENVIRONMENTS` as its candidates, so a caller
439
+ * reads the accepted values from the question instead of from the documentation.
428
440
  *
429
441
  * @example
430
442
  * ```ts
431
- * import type { Blueprint } from '@orkestrel/scaffold'
432
- * import { blueprintToQuestions } from '@orkestrel/scaffold'
443
+ * import { blueprintToQuestions, createBlueprint } from '@orkestrel/scaffold'
433
444
  *
434
- * declare const blueprint: Blueprint
445
+ * const blueprint = createBlueprint('router', { src: ['core'] })
435
446
  *
436
- * blueprintToQuestions(blueprint).length === 0 // true when the gate passes
447
+ * blueprintToQuestions(blueprint).length === 0 // true
437
448
  * ```
438
449
  */
439
450
  export declare function blueprintToQuestions(blueprint: Blueprint): readonly Question[];
@@ -446,10 +457,9 @@ export declare function blueprintToQuestions(blueprint: Blueprint): readonly Que
446
457
  *
447
458
  * @example
448
459
  * ```ts
449
- * import type { Blueprint } from '@orkestrel/scaffold'
450
- * import { blueprintToRootTsconfig } from '@orkestrel/scaffold'
460
+ * import { blueprintToRootTsconfig, createBlueprint } from '@orkestrel/scaffold'
451
461
  *
452
- * declare const blueprint: Blueprint
462
+ * const blueprint = createBlueprint('router', { src: ['core'] })
453
463
  *
454
464
  * blueprintToRootTsconfig(blueprint).startsWith('{') // true
455
465
  * ```
@@ -469,10 +479,9 @@ export declare function blueprintToRootTsconfig(blueprint: Blueprint): string;
469
479
  *
470
480
  * @example
471
481
  * ```ts
472
- * import type { Blueprint } from '@orkestrel/scaffold'
473
- * import { blueprintToRootVite } from '@orkestrel/scaffold'
482
+ * import { blueprintToRootVite, createBlueprint } from '@orkestrel/scaffold'
474
483
  *
475
- * declare const blueprint: Blueprint
484
+ * const blueprint = createBlueprint('router', { src: ['core'] })
476
485
  *
477
486
  * blueprintToRootVite(blueprint).includes('defineConfig') // true
478
487
  * ```
@@ -499,10 +508,9 @@ export declare function blueprintToRootVite(blueprint: Blueprint): string;
499
508
  *
500
509
  * @example
501
510
  * ```ts
502
- * import type { Blueprint } from '@orkestrel/scaffold'
503
- * import { blueprintToScripts } from '@orkestrel/scaffold'
511
+ * import { blueprintToScripts, createBlueprint } from '@orkestrel/scaffold'
504
512
  *
505
- * declare const blueprint: Blueprint
513
+ * const blueprint = createBlueprint('router', { src: ['core'] })
506
514
  *
507
515
  * blueprintToScripts(blueprint)['format:check'] // 'oxfmt --config .oxfmtrc.json --check .'
508
516
  * ```
@@ -516,16 +524,18 @@ export declare function blueprintToScripts(blueprint: Blueprint): Readonly<Recor
516
524
  * @returns Empty published barrels, selected application entries, and the optional bin entry.
517
525
  *
518
526
  * @remarks
519
- * The barrels and bin entry intentionally export nothing. A generated sample
520
- * entity is too easy to mistake for package implementation, so the scaffold
521
- * establishes only the selected environment boundaries.
527
+ * The barrels and every runtime entry intentionally hold nothing. A generated
528
+ * sample entity is too easy to mistake for package implementation, so the
529
+ * scaffold establishes only the selected environment boundaries. An application
530
+ * entry is empty for the same reason the bin entry is, and because the vendored
531
+ * lint config refuses an unassigned import outside a stylesheet, so the entry
532
+ * cannot start by importing its barrel for effect either.
522
533
  *
523
534
  * @example
524
535
  * ```ts
525
- * import type { Blueprint } from '@orkestrel/scaffold'
526
- * import { blueprintToSourceArtifacts } from '@orkestrel/scaffold'
536
+ * import { blueprintToSourceArtifacts, createBlueprint } from '@orkestrel/scaffold'
527
537
  *
528
- * declare const blueprint: Blueprint
538
+ * const blueprint = createBlueprint('router', { src: ['core'] })
529
539
  *
530
540
  * blueprintToSourceArtifacts(blueprint).every(({ group }) => group === 'source') // true
531
541
  * ```
@@ -548,10 +558,9 @@ export declare function blueprintToSourceArtifacts(blueprint: Blueprint): readon
548
558
  *
549
559
  * @example
550
560
  * ```ts
551
- * import type { Blueprint } from '@orkestrel/scaffold'
552
- * import { blueprintToTestArtifacts } from '@orkestrel/scaffold'
561
+ * import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
553
562
  *
554
- * declare const blueprint: Blueprint
563
+ * const blueprint = createBlueprint('router', { src: ['core'] })
555
564
  *
556
565
  * blueprintToTestArtifacts(blueprint)[0]?.path // 'tests/setup.ts'
557
566
  * ```
@@ -759,12 +768,19 @@ export declare class Compiler implements CompilerInterface {
759
768
  * carries no plan, then `compile` with the whole outcome either way, so an
760
769
  * observer reads every compile from one event and the refusals from the other.
761
770
  *
771
+ * A plan says the blueprint can be built. It does not say the blueprint should
772
+ * be created, and the questions beside it are what this compiler could not
773
+ * settle. A caller creating a fresh workspace answers them first and writes
774
+ * nothing while any remains, which is the rule the `new` verb applies; a caller
775
+ * describing or repairing an existing target carries them through instead.
776
+ * Nothing downstream repeats that check, because only the caller knows which of
777
+ * the two it is.
778
+ *
762
779
  * @example
763
780
  * ```ts
764
- * import type { Blueprint } from '@orkestrel/scaffold'
765
- * import { createCompiler } from '@orkestrel/scaffold'
781
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
766
782
  *
767
- * declare const blueprint: Blueprint
783
+ * const blueprint = createBlueprint('router', { src: ['core'] })
768
784
  *
769
785
  * createCompiler().compile(blueprint, ['manifest']).plan?.artifacts.length // 1
770
786
  * ```
@@ -785,14 +801,19 @@ export declare class Compiler implements CompilerInterface {
785
801
  * reports no findings and carries the questions instead. Emits `block` in that
786
802
  * case, then `audit` with the verdict either way.
787
803
  *
804
+ * Ownership decides each verdict, not absence. A birth-owned path is never
805
+ * compared and reads `aligned` against a target holding nothing, while a
806
+ * content-owned path with no bytes to read is `missing`. An empty snapshot
807
+ * therefore produces both verdicts rather than one.
808
+ *
788
809
  * @example
789
810
  * ```ts
790
- * import type { Blueprint } from '@orkestrel/scaffold'
791
- * import { createCompiler } from '@orkestrel/scaffold'
811
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
792
812
  *
793
- * declare const blueprint: Blueprint
813
+ * const blueprint = createBlueprint('router', { src: ['core'] })
794
814
  *
795
- * createCompiler().audit(blueprint, {}).findings.every(({ drift }) => drift === 'missing') // true
815
+ * createCompiler().audit(blueprint, {}, ['manifest']).findings[0]?.drift // 'aligned'
816
+ * createCompiler().audit(blueprint, {}, ['configs']).findings[0]?.drift // 'missing'
796
817
  * ```
797
818
  */
798
819
  audit(blueprint: Blueprint, current: Snapshot, groups?: readonly Group[]): Audit;
@@ -941,7 +962,7 @@ export declare class Compiler implements CompilerInterface {
941
962
  export declare const CONFIG_TEMPLATES: Readonly<{
942
963
  root: Readonly<{
943
964
  tsconfig: "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"types\": [\"node\", \"vite/client\", \"vitest/globals\"],\n\t\t\"moduleDetection\": \"force\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\t\t\"exactOptionalPropertyTypes\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"noEmit\": true,\n\t\t\"paths\": {\n{{paths}}\n\t\t}\n\t},\n\t\"exclude\": [\"node_modules\", \"dist\", \"tmp\"]\n}\n";
944
- vite: "import type { UserConfig } from 'vite'\n{{imports}}\nimport { defineConfig, mergeConfig } from 'vitest/config'\nimport tsconfig from './tsconfig.json' with { type: 'json' }\nimport { environmentBoundary, outputBoundary } from './configs/helpers.js'\nimport { lstatSync, readdirSync, realpathSync } from 'node:fs'\nimport { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'\nimport { fileURLToPath, URL } from 'node:url'\n\nexport function resolveWorkspacePath(relativePath: string): string {\n\treturn fileURLToPath(new URL(relativePath, import.meta.url))\n}\n\n// A generated root config must classify its own fixed proof without importing\n// package source, so the exact-case check stays self-contained over Node APIs.\nfunction isExactCaseFile(path: string): boolean {\n\tconst full = resolvePath(path)\n\ttry {\n\t\tconst status = lstatSync(full)\n\t\tif (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1) return false\n\t\tconst root = parse(full).root\n\t\tconst segments = relative(root, full).split(sep)\n\t\tlet parent = root\n\t\tfor (const segment of segments) {\n\t\t\ttry {\n\t\t\t\tif (!readdirSync(parent).includes(segment)) return false\n\t\t\t} catch {\n\t\t\t\tif (basename(realpathSync.native(join(parent, segment))) !== segment) return false\n\t\t\t}\n\t\t\tparent = join(parent, segment)\n\t\t}\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst resolve = {\n\talias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {\n\t\tconst [path] = values\n\t\tif (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')\n\t\treturn Object.assign(aliases, { [key]: resolveWorkspacePath(path) })\n\t}, {}),\n}\n\n{{factories}}export default defineConfig({\n\tresolve,\n\ttest: {\n{{projects}}\n\t},\n})\n";
965
+ vite: "import type { {{viteTypes}} } from 'vite'\n{{imports}}import { defineConfig, mergeConfig } from 'vitest/config'\nimport tsconfig from './tsconfig.json' with { type: 'json' }\n{{helpers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'\nimport { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'\nimport { fileURLToPath, URL } from 'node:url'\n\nexport function resolveWorkspacePath(relativePath: string): string {\n\treturn fileURLToPath(new URL(relativePath, import.meta.url))\n}\n\n// A generated root config must classify its own fixed proof without importing\n// package source, so the exact-case check stays self-contained over Node APIs.\nfunction isExactCaseFile(path: string): boolean {\n\tconst full = resolvePath(path)\n\ttry {\n\t\tconst status = lstatSync(full)\n\t\tif (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1) return false\n\t\tconst root = parse(full).root\n\t\tconst segments = relative(root, full).split(sep)\n\t\tlet parent = root\n\t\tfor (const segment of segments) {\n\t\t\ttry {\n\t\t\t\tif (!readdirSync(parent).includes(segment)) return false\n\t\t\t} catch {\n\t\t\t\tif (basename(realpathSync.native(join(parent, segment))) !== segment) return false\n\t\t\t}\n\t\t\tparent = join(parent, segment)\n\t\t}\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst resolve = {\n\talias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {\n\t\tconst [path] = values\n\t\tif (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')\n\t\treturn Object.assign(aliases, { [key]: resolveWorkspacePath(path) })\n\t}, {}),\n}\n\n{{factories}}export default defineConfig({\n\tresolve,\n\ttest: {\n{{projects}}\n\t},\n})\n";
945
966
  }>;
946
967
  factories: Readonly<{
947
968
  src: Readonly<{
@@ -952,7 +973,7 @@ export declare class Compiler implements CompilerInterface {
952
973
  }>;
953
974
  app: Readonly<{
954
975
  core: "export const appCore = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [environmentBoundary('app/core')],\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:core', color: 'cyan' },\n\t\t\t\tinclude: ['tests/app/core/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
955
- browser: "function applicationBrowser(showcase: boolean): UserConfig {\n\tconst output = showcase ? 'dist/showcase' : 'dist/app/browser'\n\treturn {\n\t\tresolve,\n\t\tplugins: [\n\t\t\toutputBoundary(output),\n\t\t\tenvironmentBoundary('app/browser'),\n\t\t\tvue(),\n{{showcasePlugin}}\t\t],\n\t\troot: resolveWorkspacePath('app/browser'),\n\t\tpublicDir: false,\n\t\tbuild: {\n{{showcaseBuild}}\t\t\temptyOutDir: true,\n\t\t\toutDir: resolveWorkspacePath(output),\n\t\t\trolldownOptions: { input: resolveWorkspacePath('app/browser/index.html') },\n\t\t},\n\t\ttest: {\n\t\t\tname: { label: 'app:browser', color: 'blue' },\n\t\t\troot: resolveWorkspacePath('.'),\n\t\t\tdir: resolveWorkspacePath('.'),\n\t\t\tinclude: ['tests/app/browser/**/*.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n\t\t\tbrowser: {\n\t\t\t\tenabled: true,\n\t\t\t\tprovider: playwright(),\n\t\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t\t},\n\t\t\tfileParallelism: false,\n\t\t},\n\t}\n}\n\nexport function appBrowser(...options: never[]): UserConfig {\n\tif (options.length > 0) throw new Error('Browser configuration overrides are not permitted')\n\treturn applicationBrowser(false)\n}\n{{showcaseFactory}}";
976
+ browser: "function applicationBrowser(showcase: boolean): UserConfig {\n\tconst output = showcase ? 'dist/showcase' : 'dist/app/browser'\n{{showcasePlugins}}\treturn {\n\t\tresolve,\n{{plugins}}\t\troot: resolveWorkspacePath('app/browser'),\n\t\tpublicDir: false,\n\t\tbuild: {\n{{showcaseBuild}}\t\t\temptyOutDir: true,\n\t\t\toutDir: resolveWorkspacePath(output),\n\t\t\trolldownOptions: { input: resolveWorkspacePath('app/browser/index.html') },\n\t\t},\n\t\ttest: {\n\t\t\tname: { label: 'app:browser', color: 'blue' },\n\t\t\troot: resolveWorkspacePath('.'),\n\t\t\tdir: resolveWorkspacePath('.'),\n\t\t\tinclude: ['tests/app/browser/**/*.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n\t\t\tbrowser: {\n\t\t\t\tenabled: true,\n\t\t\t\tprovider: playwright(),\n\t\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t\t},\n\t\t\tfileParallelism: false,\n\t\t},\n\t}\n}\n\nexport function appBrowser(...options: never[]): UserConfig {\n\tif (options.length > 0) throw new Error('Browser configuration overrides are not permitted')\n\treturn applicationBrowser(false)\n}\n{{showcaseFactory}}";
956
977
  server: "export const appServer = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [outputBoundary('dist/app/server'), environmentBoundary('app/server')],\n\t\t\tbuild: {\n\t\t\t\temptyOutDir: true,\n\t\t\t\tlib: {\n\t\t\t\t\tentry: resolveWorkspacePath('app/server/main.ts'),\n\t\t\t\t\tformats: ['cjs'],\n\t\t\t\t\tfileName: () => 'main.cjs',\n\t\t\t\t},\n\t\t\t\toutDir: resolveWorkspacePath('dist/app/server'),\n\t\t\t\ttarget: 'node22',\n\t\t\t\trolldownOptions: { external: (id: string) => id.startsWith('node:') },\n\t\t\t},\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:server', color: 'green' },\n\t\t\t\tinclude: ['tests/app/server/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
957
978
  }>;
958
979
  policy: "export const policy = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\ttest: {\n\t\t\t\tname: { label: 'policy', color: 'white' },\n\t\t\t\tinclude: ['tests/policy.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
@@ -1171,6 +1192,21 @@ export declare class Compiler implements CompilerInterface {
1171
1192
  */
1172
1193
  export declare const ENVIRONMENTS: readonly Environment[];
1173
1194
 
1195
+ /**
1196
+ * The vendored paths a target receives with its executable bit set, frozen.
1197
+ *
1198
+ * @remarks
1199
+ * Declared rather than read from the staging host's filesystem, because that
1200
+ * reading is not portable: Windows carries no executable bit, so a host staged
1201
+ * there reports every file non-executable and every target receives hooks it
1202
+ * cannot run. Declaring the set here makes one checkout stage one manifest on
1203
+ * every host.
1204
+ *
1205
+ * Every entry is also a {@link HOST_PATHS} member or sits beneath one. A file
1206
+ * that must run when a target invokes it belongs here the moment it is vendored.
1207
+ */
1208
+ export declare const EXECUTABLE_PATHS: readonly string[];
1209
+
1174
1210
  /**
1175
1211
  * The development extra name syntax: any valid npm package name.
1176
1212
  *
@@ -1220,21 +1256,45 @@ export declare class Compiler implements CompilerInterface {
1220
1256
  * the audit showed is the one thing the destructive verb must never do. A
1221
1257
  * missing destination has no bytes to record. An aligned one may have gone
1222
1258
  * uncompared, which is what a birth-owned path always does, so it records
1223
- * bytes only where they were actually read.
1259
+ * bytes only where they were actually read. Every planned finding carries its
1260
+ * artifact's {@link Ownership}, so a consumer reads what scaffold claims at the
1261
+ * path from the finding itself. A foreign finding has no planned artifact and
1262
+ * therefore no ownership.
1263
+ *
1264
+ * Which combinations of `ownership`, `drift`, and `observed` a real audit
1265
+ * produces is {@link inferDrift}'s law, not this type's. A birth-owned path is
1266
+ * never compared and is always aligned, a presence-owned path compares existence
1267
+ * only, and bytes are recorded only where they were read. This shape therefore
1268
+ * admits a combination that law never produces — a birth-owned path reported
1269
+ * stale is the plainest one — and it admits it deliberately: restating the
1270
+ * comparison's case analysis here would be a second copy of it, able to disagree
1271
+ * with the one that decides. What closes the gap is the writer: `repair` and
1272
+ * `remove` re-derive every verdict themselves and refuse a caller's audit that
1273
+ * does not match, so a verdict the comparison could not have reached is refused
1274
+ * rather than acted on.
1224
1275
  */
1225
1276
  export declare type Finding = {
1226
1277
  readonly path: string;
1227
1278
  readonly group: Group;
1228
- readonly drift: 'stale' | 'foreign';
1279
+ readonly ownership: Ownership;
1280
+ readonly drift: 'stale';
1229
1281
  readonly observed: string;
1230
1282
  } | {
1231
1283
  readonly path: string;
1232
1284
  readonly group: Group;
1285
+ readonly ownership?: never;
1286
+ readonly drift: 'foreign';
1287
+ readonly observed: string;
1288
+ } | {
1289
+ readonly path: string;
1290
+ readonly group: Group;
1291
+ readonly ownership: Ownership;
1233
1292
  readonly drift: 'missing';
1234
1293
  readonly observed?: never;
1235
1294
  } | {
1236
1295
  readonly path: string;
1237
1296
  readonly group: Group;
1297
+ readonly ownership: Ownership;
1238
1298
  readonly drift: 'aligned';
1239
1299
  readonly observed?: string;
1240
1300
  };
@@ -1381,7 +1441,7 @@ export declare class Compiler implements CompilerInterface {
1381
1441
  /** The installed-package proof whose presence makes a workspace `integration`. */
1382
1442
  export declare const INTEGRATION_TEST_PATH = "tests/integration.test.ts";
1383
1443
 
1384
- /** Visible characters a portable path and a Markdown path cell both forbid. */
1444
+ /** Visible characters a target-relative path and a Markdown path cell both forbid. */
1385
1445
  export declare const INVALID_PATH_CHARACTER_PATTERN: RegExp;
1386
1446
 
1387
1447
  /**
@@ -1408,7 +1468,8 @@ export declare class Compiler implements CompilerInterface {
1408
1468
  *
1409
1469
  * @remarks
1410
1470
  * An audit reaches the writer and the destructive verb, so it is guarded as
1411
- * strictly as the plan beside it.
1471
+ * strictly as the plan beside it. Findings use the sum of the two producer
1472
+ * bounds: one per planned artifact, then one per unplanned snapshot path.
1412
1473
  */
1413
1474
  export declare const isAudit: Guard<Audit>;
1414
1475
 
@@ -1552,7 +1613,13 @@ export declare class Compiler implements CompilerInterface {
1552
1613
  * @remarks
1553
1614
  * `observed` is required exactly where the mutation it precedes is held to it,
1554
1615
  * absent where the destination had no bytes to record, and optional where the
1555
- * comparison may not have been made.
1616
+ * comparison may not have been made. Planned findings require `ownership`;
1617
+ * foreign findings forbid it because no artifact was planned for their path.
1618
+ *
1619
+ * That is the whole claim. This guard proves the shape a reader may destructure
1620
+ * and nothing about whether the verdict is one an audit could have reached: the
1621
+ * correlation between `ownership`, `drift`, and `observed` belongs to
1622
+ * {@link inferDrift}, and it is re-derived at the verb that acts on the finding.
1556
1623
  */
1557
1624
  export declare const isFinding: Guard<Finding>;
1558
1625
 
@@ -1617,19 +1684,20 @@ export declare class Compiler implements CompilerInterface {
1617
1684
  export declare const isOverride: Guard<Override>;
1618
1685
 
1619
1686
  /**
1620
- * Narrow a value to a portable target-relative path.
1687
+ * Narrow a value to a logical target-relative path.
1621
1688
  *
1622
1689
  * @param value - The candidate path.
1623
1690
  * @returns `true` for a bounded relative path with no traversal, empty segment,
1624
- * control character, or non-portable visible character.
1691
+ * control character, or reserved syntax character.
1625
1692
  *
1626
1693
  * @remarks
1627
1694
  * Every path this package reads or writes passes here, so one law covers a
1628
1695
  * planned artifact, an override target, an audit finding, a guide mirror, and a
1629
1696
  * snapshot key. Rejecting `..`, a leading `/`, and a backslash at the guard is
1630
1697
  * what stops a caller-supplied path from naming a destination outside the
1631
- * target, and rejecting the non-portable visible characters is what keeps a
1632
- * generated workspace checkable out on every supported filesystem.
1698
+ * target. Host-location validation is a separate server boundary: this guard
1699
+ * does not reject a device spelling, a trailing dot or space, or a segment that
1700
+ * exceeds a host filesystem's byte ceiling.
1633
1701
  *
1634
1702
  * @example
1635
1703
  * ```ts
@@ -1763,6 +1831,20 @@ export declare class Compiler implements CompilerInterface {
1763
1831
  */
1764
1832
  export declare function manifestToName(manifest: string): string | undefined;
1765
1833
 
1834
+ /**
1835
+ * Test whether {@link inferDrift} could have produced a finding for an ownership.
1836
+ *
1837
+ * @param ownership - What scaffold claims at the planned path.
1838
+ * @param finding - The audit verdict to test.
1839
+ * @returns Whether the ownership and verdict are reachable through {@link inferDrift}.
1840
+ *
1841
+ * @remarks
1842
+ * This predicate keeps the comparison law beside the reachability law it
1843
+ * restates. A mutation uses it so a refusal can distinguish an impossible
1844
+ * verdict from a target that genuinely moved after its audit.
1845
+ */
1846
+ export declare function matchesDriftReachability(ownership: Ownership, finding: Finding): boolean;
1847
+
1766
1848
  /**
1767
1849
  * Test whether a declared engines floor is at or above the supported minimum.
1768
1850
  *
@@ -1861,6 +1943,9 @@ export declare class Compiler implements CompilerInterface {
1861
1943
  /** Maximum length of the hexadecimal string carrying one artifact's bytes. */
1862
1944
  export declare const MAX_ARTIFACT_HEX_LENGTH: number;
1863
1945
 
1946
+ /** Maximum findings one audit can produce from a bounded plan and snapshot. */
1947
+ export declare const MAX_AUDIT_FINDINGS: number;
1948
+
1864
1949
  /** Maximum items accepted in one public collection. */
1865
1950
  export declare const MAX_COLLECTION_ITEMS = 1000;
1866
1951
 
@@ -2024,9 +2109,10 @@ export declare class Compiler implements CompilerInterface {
2024
2109
  *
2025
2110
  * @remarks
2026
2111
  * `content` replaces the rendered artifact at `path` and never partially
2027
- * merges it. An override that matches no planned artifact, that targets a
2028
- * host-origin artifact, or that targets the manifest is a blocking question
2029
- * rather than a silent no-op.
2112
+ * merges it. Legality is measured against every artifact the blueprint drafts,
2113
+ * before a compile narrows the returned groups. An override that matches none
2114
+ * of those artifacts, that targets a host-origin artifact, or that targets the
2115
+ * manifest is a blocking question rather than a silent no-op.
2030
2116
  */
2031
2117
  export declare interface Override {
2032
2118
  readonly path: string;
@@ -2051,10 +2137,9 @@ export declare class Compiler implements CompilerInterface {
2051
2137
  *
2052
2138
  * @example
2053
2139
  * ```ts
2054
- * import type { Artifact } from '@orkestrel/scaffold'
2055
- * import { overridesToQuestions } from '@orkestrel/scaffold'
2140
+ * import { blueprintToConfigArtifacts, createBlueprint, overridesToQuestions } from '@orkestrel/scaffold'
2056
2141
  *
2057
- * declare const artifacts: readonly Artifact[]
2142
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
2058
2143
  *
2059
2144
  * overridesToQuestions([{ path: 'package.json', content: '{}\n' }], artifacts).length // 1
2060
2145
  * ```
@@ -2224,12 +2309,11 @@ export declare class Compiler implements CompilerInterface {
2224
2309
  *
2225
2310
  * @example
2226
2311
  * ```ts
2227
- * import type { Plan } from '@orkestrel/scaffold'
2228
- * import { planToFindings } from '@orkestrel/scaffold'
2312
+ * import { createBlueprint, createCompiler, planToFindings } from '@orkestrel/scaffold'
2229
2313
  *
2230
- * declare const plan: Plan
2314
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
2231
2315
  *
2232
- * planToFindings(plan, { 'AGENTS.md': '68690a' })
2316
+ * plan === undefined ? [] : planToFindings(plan, { 'AGENTS.md': '68690a' })
2233
2317
  * ```
2234
2318
  */
2235
2319
  export declare function planToFindings(plan: Plan, current: Snapshot): readonly Finding[];
@@ -2255,12 +2339,11 @@ export declare class Compiler implements CompilerInterface {
2255
2339
  *
2256
2340
  * @example
2257
2341
  * ```ts
2258
- * import type { Plan } from '@orkestrel/scaffold'
2259
- * import { planToHash } from '@orkestrel/scaffold'
2342
+ * import { createBlueprint, createCompiler, planToHash } from '@orkestrel/scaffold'
2260
2343
  *
2261
- * declare const plan: Plan
2344
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
2262
2345
  *
2263
- * planToHash(plan)?.length // 16
2346
+ * plan === undefined ? undefined : planToHash(plan)?.length // 16
2264
2347
  * ```
2265
2348
  */
2266
2349
  export declare function planToHash(plan: Plan): string | undefined;
@@ -2331,16 +2414,20 @@ export declare class Compiler implements CompilerInterface {
2331
2414
  * The one error this package throws, carrying the coded reason it was raised.
2332
2415
  *
2333
2416
  * @remarks
2334
- * A throw is reserved for a caller the package cannot serve: `INVALID` for
2335
- * off-contract input, `DESTROYED` for any call made after teardown, `TARGET`
2336
- * for a destination that is not what the caller's observation said it was,
2337
- * `WRITE` for a mutation that could not be completed, and `FETCH` for an
2338
- * upstream read that produced no answer the caller can be given.
2339
- *
2340
- * A refused blueprint is not one of these. The gate fails closed and returns
2341
- * the questions that closed it, and `BLOCKED` is the code those stage records
2342
- * carry, so a caller reads a refusal from the value it asked for rather than
2343
- * from a stack.
2417
+ * Each code names one cause: `INVALID` for off-contract input, `DESTROYED` for
2418
+ * any call made after teardown, `TARGET` for a destination that is not what the
2419
+ * caller's observation said it was, `WRITE` for a mutation that could not be
2420
+ * completed, `FETCH` for an upstream read that produced no answer the caller can
2421
+ * be given, and `BLOCKED` for a refused blueprint.
2422
+ *
2423
+ * `BLOCKED` covers both refusals a blueprint can meet, because they are one fact
2424
+ * this blueprint will not be built and the questions say which. The compiler
2425
+ * answers its refusal rather than throwing it: the gate fails closed, returns the
2426
+ * questions that closed it, and records `BLOCKED` on its stage, so a caller reads
2427
+ * that refusal from the value it asked for. A verb that creates a workspace
2428
+ * throws it, because it chose the shape and has nothing to hand back. A blocking
2429
+ * question closed the gate; a non-blocking one is a shape this package can
2430
+ * describe and declines to create.
2344
2431
  *
2345
2432
  * `context` carries whatever the raising site can say about the failure. It is
2346
2433
  * `unknown` because nothing narrows it usefully at the catch site; read it for