@orkestrel/scaffold 0.0.6 → 0.0.8

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.
@@ -127,49 +127,51 @@ plan.artifacts.some((artifact) => artifact.path === 'app/server/main.ts') // tru
127
127
 
128
128
  From [`types.ts`](../../src/core/types.ts).
129
129
 
130
- | Name | Kind |
131
- | ---------------------- | --------- |
132
- | `Environment` | type |
133
- | `BuildFormat` | type |
134
- | `SrcDefinition` | interface |
135
- | `AppDefinition` | interface |
136
- | `ViteMachinery` | interface |
137
- | `Origin` | type |
138
- | `Group` | type |
139
- | `Category` | type |
140
- | `CatalogEntry` | interface |
141
- | `Drift` | type |
142
- | `Freshness` | type |
143
- | `CompileStage` | type |
144
- | `ScaffoldErrorCode` | type |
145
- | `Dependency` | interface |
146
- | `Override` | interface |
147
- | `Blueprint` | interface |
148
- | `Member` | interface |
149
- | `ArtifactBase` | interface |
150
- | `HostArtifact` | interface |
151
- | `ContentArtifact` | interface |
152
- | `Artifact` | type |
153
- | `Snapshot` | type |
154
- | `Plan` | interface |
155
- | `Finding` | interface |
156
- | `Audit` | interface |
157
- | `Question` | interface |
158
- | `Validation` | interface |
159
- | `GuideSync` | interface |
160
- | `VersionSync` | interface |
161
- | `SyncReport` | interface |
162
- | `PlanSummary` | interface |
163
- | `CompileRecord` | interface |
164
- | `CompileFailure` | interface |
165
- | `Scaffolding` | interface |
166
- | `PlanRecord` | interface |
167
- | `CompilerEventMap` | type |
168
- | `CompilerOptions` | interface |
169
- | `CompilerInterface` | interface |
170
- | `PlanManagerEventMap` | type |
171
- | `PlanManagerOptions` | interface |
172
- | `PlanManagerInterface` | interface |
130
+ | Name | Kind |
131
+ | ------------------------- | --------- |
132
+ | `Environment` | type |
133
+ | `BuildFormat` | type |
134
+ | `SrcDefinition` | interface |
135
+ | `AppDefinition` | interface |
136
+ | `ViteMachinery` | interface |
137
+ | `ViteAxes` | interface |
138
+ | `ViteProjectRegistration` | interface |
139
+ | `Origin` | type |
140
+ | `Group` | type |
141
+ | `Category` | type |
142
+ | `CatalogEntry` | interface |
143
+ | `Drift` | type |
144
+ | `Freshness` | type |
145
+ | `CompileStage` | type |
146
+ | `ScaffoldErrorCode` | type |
147
+ | `Dependency` | interface |
148
+ | `Override` | interface |
149
+ | `Blueprint` | interface |
150
+ | `Member` | interface |
151
+ | `ArtifactBase` | interface |
152
+ | `HostArtifact` | interface |
153
+ | `ContentArtifact` | interface |
154
+ | `Artifact` | type |
155
+ | `Snapshot` | type |
156
+ | `Plan` | interface |
157
+ | `Finding` | interface |
158
+ | `Audit` | interface |
159
+ | `Question` | interface |
160
+ | `Validation` | interface |
161
+ | `GuideSync` | interface |
162
+ | `VersionSync` | interface |
163
+ | `SyncReport` | interface |
164
+ | `PlanSummary` | interface |
165
+ | `CompileRecord` | interface |
166
+ | `CompileFailure` | interface |
167
+ | `Scaffolding` | interface |
168
+ | `PlanRecord` | interface |
169
+ | `CompilerEventMap` | type |
170
+ | `CompilerOptions` | interface |
171
+ | `CompilerInterface` | interface |
172
+ | `PlanManagerEventMap` | type |
173
+ | `PlanManagerOptions` | interface |
174
+ | `PlanManagerInterface` | interface |
173
175
 
174
176
  The closed vocabularies are small and total. `Environment` is `'core' | 'browser' | 'server'`.
175
177
  `BuildFormat` is `'es' | 'cjs'`. `Origin` is `'host' | 'template' | 'computed'`. `Group` is
@@ -190,6 +192,13 @@ single-file-component, HTML, and development-server machinery an application bro
190
192
  needs, and `output` for build-output containment. It never selects a boundary guarantee — those ship
191
193
  in every shape, as the compilers section sets out.
192
194
 
195
+ `ViteAxes` is the optional structural-project slice shared by every root Vite compiler:
196
+ `bin`, `integration`, and `service` each select their matching standalone project when `true`.
197
+
198
+ `ViteProjectRegistration` carries one generated project factory identifier and its optional browser
199
+ label. Root configuration renderers preserve that browser ownership as data through registration
200
+ instead of inferring it from a project identifier.
201
+
193
202
  `Blueprint` is the closed input spec:
194
203
 
195
204
  ```ts
@@ -205,19 +214,44 @@ interface Blueprint {
205
214
  readonly version: string
206
215
  readonly engines: string
207
216
  readonly overrides: readonly Override[]
208
- readonly engine: boolean
217
+ readonly bin: boolean
218
+ readonly integration: boolean
219
+ readonly service: boolean
209
220
  }
210
221
  ```
211
222
 
212
- `src` selects published library environments under `src`; `app` selects private runtime environments under
213
- `app`. The two axes are independent, so library-only, application-only, and mixed workspaces are
214
- all first class. `dependencies` and `peers` are runtime `@orkestrel/*` packages — a peer flagged
215
- `optional` also gets a `peerDependenciesMeta` entry. `extras` are package-specific development
216
- dependencies merged over the generated baseline, and may carry any valid npm package name. `engine`
217
- is structural, never inferred from a name: it is `true` only for a workspace that ships its own
218
- `src/bin`, and it alone turns on the self-hosting extras (a `bin` field, the `scaffold` script
219
- pointed at the built executable, the bin check, test, and build scripts, `build:host`, and the
220
- `src:bin` test project).
223
+ `src` selects published library environments under `src`; `app` selects private runtime
224
+ environments under `app`. The two axes are independent, so library-only, application-only, and
225
+ mixed workspaces are all first class. `dependencies` and `peers` are runtime `@orkestrel/*`
226
+ packages — a peer flagged `optional` also gets a `peerDependenciesMeta` entry. `extras` are
227
+ package-specific development dependencies merged over the generated baseline, and may carry any
228
+ valid npm package name.
229
+
230
+ `bin`, `integration`, and `service` are the three structural axes, and they obey one law: each is
231
+ `true` only when the workspace physically ships the directory that defines it — never because of
232
+ the workspace's name, and never because a sibling axis is set. `deriveBlueprint` probes exactly
233
+ those directories, so a fresh compile and an audit of a mature repository agree on what the
234
+ workspace is.
235
+
236
+ - **`bin`** — `src/bin/` exists. It alone turns on the self-hosting extras: the manifest's `bin`
237
+ entry, the `scaffold` script pointed at the built executable, the bin check, test, and build
238
+ scripts, `build:host`, the `configs/src/tsconfig.bin.json` and `configs/src/vite.bin.config.ts`
239
+ artifacts, and the `src:bin` test project.
240
+ - **`integration`** — `tests/integration/` exists. It records a slow, opt-in proof project over the
241
+ workspace's own built output, outside the default run: the generated root configuration registers
242
+ a standalone `integration` project including `tests/integration/**/*.test.ts`, and the manifest
243
+ emits `test:integration`.
244
+ - **`service`** — `tests/service/` exists. It records a slow, opt-in proof project against a foreign
245
+ running process, outside the default run: a standalone `service` project including
246
+ `tests/service/**/*.test.ts`, with `tests/setupService.ts` after the shared setup, and the
247
+ isolated `test:service` script.
248
+
249
+ A service workspace owes two companion files beside that directory, and derivation requires both
250
+ physically present: `tests/setupService.ts` and `scripts/service.sh`. Either missing companion is a
251
+ coded `TARGET` failure naming the missing path rather than a silent `service: false`. This package
252
+ emits neither: both are consumer-owned seams, and the generated-workspace section sets out what
253
+ each owes its workspace and which proof runs in which gate. Nothing here is inferred — the
254
+ executable axis turns on neither proof project, and neither proof project turns on the other.
221
255
 
222
256
  `Override` replaces a rendered artifact's content at a path, never partially merges it. `Member` is
223
257
  one declared public export of the scaffolded workspace, derived rather than authored.
@@ -322,8 +356,10 @@ From [`constants.ts`](../../src/core/constants.ts).
322
356
  | `FRESHNESS` | const |
323
357
  | `COMPILE_STAGES` | const |
324
358
  | `SRC_MATRIX` | const |
359
+ | `BIN_CONFIGS` | const |
325
360
  | `APP_MATRIX` | const |
326
361
  | `HOST_PATHS` | const |
362
+ | `SERVICE_SCRIPT_PATH` | const |
327
363
  | `NAME_PATTERN` | const |
328
364
  | `MAX_NAME_LENGTH` | const |
329
365
  | `MAX_DEPENDENCY_NAME_LENGTH` | const |
@@ -368,11 +404,13 @@ From [`constants.ts`](../../src/core/constants.ts).
368
404
  `ENVIRONMENTS`, `ORIGINS`, `GROUPS`, `CATEGORIES`, `FRESHNESS`, and `COMPILE_STAGES` are the frozen
369
405
  value lists behind their literal unions. `SRC_MATRIX` is the `src` environment matrix as
370
406
  data — each environment's `configs/src` files, test-project label, `exports` subpath, and build
371
- formats. `APP_MATRIX` is its application sibling, adding the runtime entry where an environment produces
372
- one (`app/browser/index.html`, `app/server/main.ts`). `HOST_PATHS` is the ordered list of
373
- byte-copied host artifacts, and it is the staging manifest rather than the per-plan carried set:
407
+ formats. `APP_MATRIX` is its application sibling, adding the runtime entry where an environment
408
+ produces one (`app/browser/index.html`, `app/server/main.ts`). `BIN_CONFIGS` is the executable
409
+ axis's computed `tsconfig` and Vite wrapper pair. `HOST_PATHS` is the ordered list of byte-copied
410
+ host artifacts, and it is the staging manifest rather than the per-plan carried set:
374
411
  `stageHost` vendors every path on it, while each plan carries the subset `selectHostPaths` selects
375
- for that one workspace.
412
+ for that one workspace. `SERVICE_SCRIPT_PATH` names the consumer-owned provisioner a service
413
+ workspace's audit expects.
376
414
 
377
415
  The bounds are public because they are part of the contract, not implementation trivia.
378
416
  `MAX_ARTIFACT_BYTES` caps one artifact at 5 MiB and `MAX_TOTAL_ARTIFACT_BYTES` caps one blueprint,
@@ -826,6 +864,7 @@ From [`helpers.ts`](../../src/server/helpers.ts).
826
864
  | Name | Kind |
827
865
  | -------------------------- | -------- |
828
866
  | `isRealDirectory` | function |
867
+ | `isRealFile` | function |
829
868
  | `digestFile` | function |
830
869
  | `digestHex` | function |
831
870
  | `digestText` | function |
@@ -888,7 +927,7 @@ so a file swapped mid-read is a failure rather than a silent wrong digest. `read
888
927
  strictly, rejecting invalid UTF-8. Manifest reads stop at `MAX_MANIFEST_BYTES`; catalog guide reads
889
928
  stop at `MAX_GUIDE_BYTES`. `listFiles` and `listDirectories` walk a real, unlinked root under the
890
929
  entry and depth bounds, returning sorted POSIX-relative paths and `[]` for an absent root.
891
- `isRealDirectory` is the physical-directory predicate they all lean on.
930
+ `isRealDirectory` and `isRealFile` are the physical path predicates they all lean on.
892
931
 
893
932
  The write-transaction helpers are the fail-closed mutation path. `createWriteDirectory` establishes
894
933
  a directory one segment at a time behind captured identities; `validateWriteAnchor`,
@@ -910,14 +949,17 @@ omitting an absent path entirely. `readManifest` reads `package.json` text, and
910
949
 
911
950
  `deriveBlueprint` is the faithful inverse an audit needs: it reconstructs a blueprint from an
912
951
  existing workspace so a mature package is diffed against its own would-be scaffold rather than a
913
- dependency-less stand-in. Environments come from `src/<environment>/` and `app/<environment>/`;
914
- `engine` is `true` only when `src/bin/` exists; dependencies and peers come from the manifest's
915
- scoped entries, with an optional peer recovered from `peerDependenciesMeta`; and `extras` is every
916
- development dependency minus the generated baseline and minus anything already declared as a
917
- dependency or peer, so a hand-added development dependency round-trips and stays audit-clean.
918
- Derivation yields no `overrides`: they are caller-time inputs, not repository state. A computed
919
- artifact that must differ reveals a gap in the canon; the blueprint grows an axis for that
920
- distinction rather than the repository forking the file.
952
+ dependency-less stand-in. Environments come from `src/<environment>/` and `app/<environment>/`, and
953
+ the three structural axes from the directory probes and the service companion law the blueprint
954
+ section states every one of them a reading of the filesystem, never of the name. Dependencies and
955
+ peers come from the manifest's scoped entries, with an optional peer recovered from
956
+ `peerDependenciesMeta`; and `extras` is every development dependency minus the complete set
957
+ `devDependenciesFor` emits for those environments and structural axes, and minus anything already
958
+ declared as a dependency or peer. An axis-emitted dependency is therefore never double-counted,
959
+ while a hand-added development dependency round-trips and stays audit-clean. Derivation yields no
960
+ `overrides`: they are caller-time inputs, not repository state. A computed artifact that must differ
961
+ reveals a gap in the canon; the blueprint grows an axis for that distinction rather than the
962
+ repository forking the file.
921
963
 
922
964
  `storagePath`, `stageHost`, `readHostManifest`, `locateHostSource`, `remapArtifactPath`, and
923
965
  `hydratePlan` are the vendored-host path. `storagePath` maps a repo-relative path to its un-dotted
@@ -956,43 +998,52 @@ ordered guide and version outcomes.
956
998
 
957
999
  From [`compilers.ts`](../../src/core/compilers.ts).
958
1000
 
959
- | Name | Kind |
960
- | ----------------------- | -------- |
961
- | `hostGroup` | function |
962
- | `fillArtifact` | function |
963
- | `srcVariant` | function |
964
- | `entryFields` | function |
965
- | `dualCondition` | function |
966
- | `exportsMap` | function |
967
- | `compareCodeUnit` | function |
968
- | `devDependenciesFor` | function |
969
- | `packageManifest` | function |
970
- | `rootTsconfig` | function |
971
- | `viteMachinery` | function |
972
- | `viteHeader` | function |
973
- | `policyViteProject` | function |
974
- | `singleSrcViteConfig` | function |
975
- | `rootViteConfig` | function |
976
- | `applicationViteConfig` | function |
977
- | `coreTsconfig` | function |
978
- | `coreViteConfig` | function |
979
- | `srcTsconfig` | function |
980
- | `srcViteConfig` | function |
981
- | `appTsconfig` | function |
982
- | `appViteConfig` | function |
983
- | `ciWorkflow` | function |
984
- | `configArtifacts` | function |
985
- | `sourceArtifacts` | function |
986
- | `applicationArtifacts` | function |
987
- | `paritySpecifiers` | function |
988
- | `testArtifacts` | function |
989
- | `guideMemberTable` | function |
990
- | `guideUsage` | function |
991
- | `guideMethods` | function |
992
- | `guideTests` | function |
993
- | `guideArtifacts` | function |
994
- | `applyOverrides` | function |
995
- | `blueprintToPlan` | function |
1001
+ | Name | Kind |
1002
+ | -------------------------- | -------- |
1003
+ | `hostGroup` | function |
1004
+ | `fillArtifact` | function |
1005
+ | `srcVariant` | function |
1006
+ | `entryFields` | function |
1007
+ | `dualCondition` | function |
1008
+ | `exportsMap` | function |
1009
+ | `compareCodeUnit` | function |
1010
+ | `devDependenciesFor` | function |
1011
+ | `packageManifest` | function |
1012
+ | `rootTsconfig` | function |
1013
+ | `viteMachinery` | function |
1014
+ | `renderViteTest` | function |
1015
+ | `viteHeader` | function |
1016
+ | `policyViteProject` | function |
1017
+ | `guidesViteProject` | function |
1018
+ | `binViteProject` | function |
1019
+ | `integrationViteProject` | function |
1020
+ | `serviceViteProject` | function |
1021
+ | `viteProjectRegistrations` | function |
1022
+ | `viteProjectDefinitions` | function |
1023
+ | `singleSrcViteConfig` | function |
1024
+ | `rootViteConfig` | function |
1025
+ | `applicationViteConfig` | function |
1026
+ | `coreTsconfig` | function |
1027
+ | `coreViteConfig` | function |
1028
+ | `srcTsconfig` | function |
1029
+ | `srcViteConfig` | function |
1030
+ | `binTsconfig` | function |
1031
+ | `binViteConfig` | function |
1032
+ | `appTsconfig` | function |
1033
+ | `appViteConfig` | function |
1034
+ | `ciWorkflow` | function |
1035
+ | `configArtifacts` | function |
1036
+ | `sourceArtifacts` | function |
1037
+ | `applicationArtifacts` | function |
1038
+ | `paritySpecifiers` | function |
1039
+ | `testArtifacts` | function |
1040
+ | `guideMemberTable` | function |
1041
+ | `guideUsage` | function |
1042
+ | `guideMethods` | function |
1043
+ | `guideTests` | function |
1044
+ | `guideArtifacts` | function |
1045
+ | `applyOverrides` | function |
1046
+ | `blueprintToPlan` | function |
996
1047
 
997
1048
  `blueprintToPlan` is the whole pure compilation: draft each selected group's artifacts, append the
998
1049
  host set, apply overrides, and pin. Everything above it is an exported leaf of that drafting, each
@@ -1002,10 +1053,11 @@ independently callable and independently tested.
1002
1053
  several. `entryFields`, `dualCondition`, and `exportsMap` build the manifest entry fields and the
1003
1054
  `exports` map from that variant; a browser-only package exports a single module condition, while
1004
1055
  core and server src get dual import and require conditions with matching declaration files.
1005
- `devDependenciesFor` merges a blueprint's extras over the shared baseline, extras winning a name
1006
- collision, sorted by `compareCodeUnit` so ordering is stable across locales. `packageManifest`
1007
- assembles the whole file name, publication mode, files, scripts, dependencies, peers and their
1008
- optional metadata, and engines.
1056
+ `devDependenciesFor` emits the blueprint's complete development dependency set: the shared
1057
+ baseline, package extras, dev-installed peers, selected browser toolchains, and the bin axis's
1058
+ browser test provider. Extras and peers are sorted by `compareCodeUnit` so ordering is stable across
1059
+ locales. `packageManifest` assembles the whole file — name, publication mode, files, scripts,
1060
+ dependencies, peers and their optional metadata, and engines.
1009
1061
 
1010
1062
  `rootTsconfig` emits the root compiler options and one path alias per declared environment;
1011
1063
  `coreTsconfig`, `srcTsconfig`, and `appTsconfig` emit the scoped configurations that remove the
@@ -1027,7 +1079,8 @@ emits `environmentBoundary`, its `resolveId` / `load` / `buildEnd` walks, the mo
1027
1079
  plus its `environmentPathError` / `environmentSourceError` clauses). Those enforce owner-independent
1028
1080
  laws: core stays host-independent whatever else the workspace declares, a server module never
1029
1081
  imports a stylesheet, and a `@vite-ignore` dynamic import — which `resolveId` never sees and the
1030
- module graph never records — has no other enforcement point. Only host-specific pipelines vary,
1082
+ module graph never records — has no other enforcement point in workspace-owned source. Dependency
1083
+ and toolchain modules are outside that ownership boundary. Only host-specific pipelines vary,
1031
1084
  along the three `ViteMachinery` axes:
1032
1085
 
1033
1086
  | Machinery | Emitted when |
@@ -1040,13 +1093,36 @@ along the three `ViteMachinery` axes:
1040
1093
  An application of `app/core` alone is the sole shape that builds nothing, so it is the sole shape
1041
1094
  without output containment — and it still carries every boundary guarantee above.
1042
1095
 
1043
- `coreViteConfig`, `srcViteConfig`, and `appViteConfig` emit the thin per-target wrappers;
1044
- `rootViteConfig`, `singleSrcViteConfig`, and `applicationViteConfig` emit the root configuration for
1045
- a library-only, single non-core `src` environment, and application-bearing workspace respectively;
1046
- `policyViteProject` emits the dedicated Node-only repository-policy test project.
1096
+ `renderViteTest` is the single root-project renderer. It consumes ordered `ViteProjectRegistration`
1097
+ data and emits either the plain project list or the browser gate, keeping source and application
1098
+ root configurations byte-consistent without reconstructing browser ownership.
1099
+ `viteProjectRegistrations` is the one registration derivation every root shape consumes: it derives
1100
+ the selected source and application projects from the canonical environment order, then appends
1101
+ `policy`, `guides`, and the optional `srcBin`, `integration`, and `service` projects.
1102
+ `viteProjectDefinitions` renders the standalone proof and structural-axis definitions in that same
1103
+ order with one blank line between declarations. Both consume `ViteAxes`, so each optional project is
1104
+ controlled only by its matching `bin`, `integration`, or `service` blueprint axis.
1105
+
1106
+ `coreViteConfig`, `srcViteConfig`, `binViteConfig`, and `appViteConfig` emit the thin per-target
1107
+ wrappers, while `binTsconfig` emits the executable declaration scope; `rootViteConfig`,
1108
+ `singleSrcViteConfig`, and `applicationViteConfig` emit the root configuration for a library-only,
1109
+ single non-core `src` environment, and application-bearing workspace respectively; and
1110
+ `policyViteProject`, `guidesViteProject`, `integrationViteProject`, and `serviceViteProject` emit
1111
+ the standalone Node proof projects, with `binViteProject` the single executable-project emitter. A
1112
+ proof project is structurally derived from the directory holding its tests and never wraps a source
1113
+ or application environment project. The guides project therefore uses only `tests/setup.ts`, never
1114
+ `setupServer.ts`, `setupBrowser.ts`, or `setupService.ts`; and its `tests/src/**/*.test.ts` and
1115
+ `tests/app/**/*.test.ts` exclude rows are uniform across all root shapes by design, including
1116
+ core-only workspaces where one row cannot currently match. Integration and service use 120-second
1117
+ test and hook timeouts with file parallelism disabled, and service alone layers
1118
+ `tests/setupService.ts` onto the shared setup. Where a bin workspace also ships the integration
1119
+ project, that project wires `tests/setupIntegration.ts` as its global setup for the shared
1120
+ template-registry harness; bin-less integration workspaces do not.
1047
1121
 
1048
1122
  `configArtifacts`, `sourceArtifacts`, `applicationArtifacts`, `testArtifacts`, and `guideArtifacts`
1049
- are the per-group drafters. `paritySpecifiers` computes the self-specifier and module map the
1123
+ are the per-group drafters. When `bin` is selected, `configArtifacts` includes
1124
+ `configs/src/tsconfig.bin.json` and `configs/src/vite.bin.config.ts` beside the declared environment
1125
+ configuration pairs. `paritySpecifiers` computes the self-specifier and module map the
1050
1126
  generated parity suite resolves fence imports through. `guideMemberTable`, `guideUsage`,
1051
1127
  `guideMethods`, and `guideTests` render the generated guide's member tables, usage examples, method
1052
1128
  contract, and test inventory. `fillArtifact` fills one template entry into a `template`-origin
@@ -1275,13 +1351,13 @@ Audit semantics follow directly from that.
1275
1351
  - A target file the plan does not own is `foreign`, and `inferGroup` classifies it by its leading
1276
1352
  path segment.
1277
1353
 
1278
- The same ownership boundary is what makes mutation safe. **`repair` and `fleet` mutate host-origin
1279
- artifacts only.** Both scope the compiled plan to host origin before hydrating, diffing, or applying
1280
- anything, so a mature workspace's hand-written source, tests, guides, and manifest are never
1281
- overwritten with a stub. A consequence worth stating plainly: the generated
1282
- `.github/workflows/ci.yml` is a **computed** artifact, so **user-owned CI is never repaired**. Once
1283
- a workspace has its own workflow, that copy stands, and any change to it is an ordinary edit in that
1284
- workspace. Audit still compares it because computed artifacts are content-aware canon. A legitimate
1354
+ The same ownership boundary is what makes mutation safe. **`fleet` and default `repair` both scope
1355
+ the compiled plan to host origin before hydrating, diffing, or applying.** `--generated` widens that
1356
+ scoped plan to generated canon except `package.json`; template artifacts remain birth-only in
1357
+ either mode. A mature workspace's hand-written source, tests, guides, and manifest are therefore
1358
+ never overwritten with a stub. The generated `.github/workflows/ci.yml` is a **computed** artifact,
1359
+ so user-owned CI stands by default but is intentionally restored when `--generated` is passed.
1360
+ Audit always compares it because computed artifacts are content-aware canon. A legitimate
1285
1361
  difference that the blueprint cannot express is a canon gap: add the missing blueprint axis rather
1286
1362
  than forking the computed file in one repository.
1287
1363
 
@@ -1313,12 +1389,20 @@ An audit is a pure function of a plan and a snapshot, so the same engine that cr
1313
1389
  checks one. `readTarget` supplies the snapshot as exact bytes; `diffPlan` returns findings as data;
1314
1390
  `auditToReview` renders them for a human. Nothing in that path writes.
1315
1391
 
1392
+ The executable's physical unexpected-file scan treats exactly `scripts/service.sh` as an expected
1393
+ consumer-owned seam when the derived blueprint has `service: true`. That exclusion is warranted
1394
+ because a service blueprint cannot derive without the physical file: the companion-file law raises
1395
+ a `TARGET` failure first, so the scan removes a false positive and can never mask an absent
1396
+ provisioner. A non-service workspace still reports the same path as foreign.
1397
+
1316
1398
  `repair` turns those findings back into the narrowest possible write. It re-reads the target,
1317
1399
  re-diffs it, and refuses to proceed if the findings changed since the preview it was given — a
1318
1400
  target that moved under the caller is a `TARGET` failure, not a race to win. It then derives a write
1319
1401
  precondition per artifact from the audit itself: a `missing` finding requires the destination to
1320
1402
  still be absent, a `stale` finding requires it to still carry exactly the bytes that were observed.
1321
1403
  Those preconditions are checked again inside the write transaction before any promotion.
1404
+ An interactive audit repair hand-off forwards `--generated` into the repair invocation when the
1405
+ flag was present on `audit`.
1322
1406
 
1323
1407
  `prune` is the deletion arm, and it is deliberately narrow. Its candidate set comes from
1324
1408
  `pruneTargets`, which is also what the executable's audit and preview read, so what is reported and
@@ -1389,20 +1473,65 @@ configuration, and ships `dist/src` plus its README. An application-only workspa
1389
1473
  that builds its own executable additionally ships `dist/bin` and `dist/host`. Scripts are emitted in
1390
1474
  a fixed, interleaved order so aggregates sit immediately before their per-environment members:
1391
1475
 
1392
- - `clean`, `copy`, `scaffold`
1393
- - `lint` and `lint:check`, `format` and `format:check`
1476
+ - `clean`, `copy`, `scaffold`, `lint`
1394
1477
  - `check`, then `check:src` with one `check:src:<environment>` per published environment, then
1395
1478
  `check:app` with one `check:app:<environment>` per app environment — the browser app scope uses the
1396
1479
  Vue typechecker, every other scope uses plain `tsc`
1397
- - `test`, then `test:src` and its per-environment scopes, `test:app` and its per-environment scopes,
1398
- `test:policy`, and `test:guides`; an engine also receives the deliberately non-default
1399
- `test:integration` live installed-consumer gate
1480
+ - `format`, `format:check`, `lint:check`
1481
+ - `test`, then `test:src` and its per-environment scopes, the optional `test:integration`,
1482
+ `test:equivalence`, and `test:service` proofs, `test:app` and its per-environment scopes, then
1483
+ `test:policy` and `test:guides`
1400
1484
  - `build`, then `build:src` and its per-environment targets, `build:app` and its runtime targets, and
1401
- `build:host` for an engine workspace
1485
+ `build:host` for a bin workspace
1402
1486
  - `dev` when a browser application is selected; `serve` and `serve:build` when a server application
1403
1487
  is selected
1404
- - `prepublishOnly` chaining `format:check → lint:check → check → build → test`, followed by the
1405
- live generated-consumer integration gate for the scaffold engine itself
1488
+ - `prepublishOnly` chaining `format:check → lint:check → check → build → test`, followed by
1489
+ `test:integration` when the integration axis is selected
1490
+
1491
+ **Proof gating.** The opt-in proofs are predictable from the axes alone. `test:integration` rides
1492
+ the `integration` axis and `test:service` the `service` axis, while `test:equivalence` is emitted
1493
+ only where `bin` and `integration` are both set:
1494
+
1495
+ | Proof | `npm test` | `prepublishOnly` | CI |
1496
+ | ------------------ | ---------- | ---------------- | -------------------------- |
1497
+ | `test:integration` | no | yes, last | after the standard gates |
1498
+ | `test:equivalence` | no | no | no |
1499
+ | `test:service` | no | never | after `scripts/service.sh` |
1500
+
1501
+ No proof joins the default chain: `npm test` runs the source, application, policy, and guide
1502
+ projects, and nothing there needs a build artifact or a foreign process. Publication is the one
1503
+ asymmetry — `prepublishOnly` appends `test:integration`, because a package about to be published
1504
+ should prove itself against its own built output, while `test:service` is never in that chain.
1505
+ Neither default testing nor publication starts or requires a foreign process.
1506
+
1507
+ When a prerequisite is absent the proof fails rather than skipping. `test:integration` reads the
1508
+ workspace's own built output, so it belongs after `build` — which is exactly where `prepublishOnly`
1509
+ and CI put it. `test:service` refuses to start against an unprovisioned service: its setup throws at
1510
+ module load, which is why CI runs `bash scripts/service.sh` immediately before it. And a script the
1511
+ axes do not emit is simply not there: `test:equivalence` in a workspace that is not both `bin` and
1512
+ `integration` is an unknown script rather than a quietly passing one.
1513
+
1514
+ The equivalence proof is a dual-path re-run rather than a separate suite. Run
1515
+ `npm run test:equivalence` after changing the persistent boundary build driver; it invokes the
1516
+ integration project in dual-path mode and proves each programmatic driver verdict against the
1517
+ spawned npm-script reference. Ordinary integration runs keep the faster driver-only path.
1518
+
1519
+ **Consumer-owned service seams.** The service axis is the one place canon stops at the boundary:
1520
+ there is no template for a proof project and neither companion path is on `HOST_PATHS`, so a
1521
+ service workspace owns both of its seams outright. They come as a pair.
1522
+
1523
+ - `tests/setupService.ts` is the readiness seam. It probes the foreign process and warms it before
1524
+ any test runs, and throws at module load — naming the `service` project — when that process is
1525
+ unreachable, so an unprovisioned run fails loudly instead of passing an empty suite. Only the
1526
+ `service` project loads it.
1527
+ - `scripts/service.sh` is the provisioning seam, named once by `SERVICE_SCRIPT_PATH`. It brings that
1528
+ process up idempotently — a second run against an already-provisioned service is a no-op rather
1529
+ than a second instance — and exits nonzero when it cannot, which is what makes CI's
1530
+ `bash scripts/service.sh` step a gate rather than a hint.
1531
+
1532
+ The audit expects the script rather than reporting it foreign, on the derive-time warrant the audit
1533
+ section gives. Repair pruning applies the same exclusion, so it never proposes or removes that
1534
+ required consumer-owned provisioner.
1406
1535
 
1407
1536
  **Environment isolation.** Scoped TypeScript projects remove the wrong host's globals from each
1408
1537
  environment: core scopes carry the WHATWG web-interop surface and no host at all — no DOM, no Node,
@@ -1451,10 +1580,10 @@ application's module graph through `import.meta.env` instead.
1451
1580
  Asset URLs that force `?inline` are rejected before Vite can read them outside that auditable output
1452
1581
  graph. Dynamic imports must use a static quoted string or expression-free template string; even
1453
1582
  `/* @vite-ignore */` static values repeat the same environment and containment checks inside the
1454
- transform boundary, including inline HTML proxy modules and trusted dependency modules. Trusted
1455
- dependency modules also pass through a bounded, no-follow, identity-checked load inspection before
1456
- Vite transforms or tree-shakes their raw source, so dependency-side asset references cannot escape
1457
- the physical package root by disappearing from the later graph.
1583
+ transform boundary, including inline HTML proxy modules. The transform, load, resolution, emitted
1584
+ asset, and finished-module-graph passes apply that law only to workspace-owned `src/*` and `app/*`
1585
+ modules. Resolved ids under any `node_modules` segment, Vite/Vitest virtual ids, and tooling client
1586
+ injections remain owned by their toolchain and are exempt.
1458
1587
 
1459
1588
  Browser application scripts are modules. Vite's parsed HTML asset callback rejects a classic
1460
1589
  external `<script src>` before resolution and directs the author to `type="module"`. A module
@@ -1469,9 +1598,9 @@ path remains Vite-owned and passes through the environment resolver, which rejec
1469
1598
  ASCII control range and every non-Node URL scheme before loading or output. No second HTML parser or global
1470
1599
  reference rewrite is involved, so comments, text, non-script attributes, and entity-spelled asset
1471
1600
  filenames retain Vite's native parsing and resolution behavior.
1472
- The resolver leaves NUL-prefixed Rolldown/Vite virtual module IDs to the tool that owns that
1473
- namespace; author module and asset URLs are extracted and validated before they reach that resolver
1474
- exception.
1601
+ The resolver leaves NUL-prefixed and `virtual:` Rolldown/Vite module IDs, tooling client injections,
1602
+ and every resolved `node_modules` module to the tool that owns that namespace; author module and
1603
+ asset URLs are extracted and validated before they reach those resolver exceptions.
1475
1604
  SVG script `href` and `xlink:href` attributes are parsed too and rejected as classic script loads.
1476
1605
  Inline module scripts enter Vite's HTML proxy graph and receive the same Oxc boundary analysis as
1477
1606
  module files. Classic inline scripts cannot enter that graph, so the required security prologue places
@@ -1511,11 +1640,7 @@ What it rejects is equally deliberate:
1511
1640
  browser or server package subpath;
1512
1641
  - a browser module reaching a Node builtin or a server subpath;
1513
1642
  - a server module reaching a stylesheet, Vue, or a browser subpath;
1514
- - a workspace-relative import that resolves outside the workspace, or a dependency import that
1515
- escapes the exact physical package root established by the nearest bounded, unlinked
1516
- `package.json` whose own `name` exactly matches the resolved dependency;
1517
- - a package `#imports` mapping that resolves outside both the declaring package and another exact
1518
- physical package root;
1643
+ - a workspace-relative import that resolves outside the workspace;
1519
1644
  - an HTML reference carrying `vite-ignore` that violates the same environment or containment law
1520
1645
  as an ordinary reference, a Vite `%ENV%` HTML substitution, a classic external script, or a
1521
1646
  computed dynamic import in the module graph that would bypass graph resolution;
@@ -1549,11 +1674,14 @@ host-origin file and run it as a dedicated Node-only `policy` test project over
1549
1674
  **Real browser capability.** Browser test projects are gated on the real executable: the generated
1550
1675
  configuration and the generated policy test both probe `existsSync(chromium.executablePath())`. A
1551
1676
  browser suite runs when a real Chromium is installed and is skipped honestly when it is not, rather
1552
- than being faked. The gate is applied at registration, not only inside the project: without a
1553
- Chromium the browser project is left out of the emitted `projects` list entirely, so the runner
1554
- never has to reconcile a registered project whose include set resolves to nothing, and one printed
1555
- warning names every omitted project label. A machine with a browser runs the browser suite; a
1556
- machine without one runs the remaining projects and says so.
1677
+ than being faked. The gate is applied at registration, not inside the real browser project: without
1678
+ Chromium, each browser factory is replaced by a same-label Node/no-test placeholder, so generated
1679
+ `--project <label>` and `--project=<label>` filters still resolve while no browser code runs. The
1680
+ root permits an empty run only when every recognized exact project filter names one of those gated
1681
+ placeholders; an unreadable or mixed filter keeps the ordinary no-test failure semantics for its
1682
+ Node projects. One printed warning names every gated project label. A machine with a browser
1683
+ registers and runs the real browser suites unchanged; a machine without one runs the remaining
1684
+ projects and says so.
1557
1685
 
1558
1686
  **Continuous integration.** The generated workflow runs on push and pull request, on
1559
1687
  `ubuntu-latest`, with read-only contents permission, a 60-minute timeout, and a matrix that **tests
@@ -1561,7 +1689,8 @@ Node `22.12.0` and `26`** with fail-fast disabled. Checkout and Node setup are p
1561
1689
  action commits, and checkout does not persist credentials. Dependencies install with
1562
1690
  `npm ci --ignore-scripts`; Chromium is installed only when the workspace selects a browser environment
1563
1691
  or builds its own executable. The gates then run in order: `format:check`, `lint:check`, `check`,
1564
- `build`, `test`; engine workspaces then run the separate live installed-consumer integration gate.
1692
+ `build`, `test`, and the workspace's selected proofs follow as their own named steps, in the order
1693
+ the proof-gating table gives them.
1565
1694
 
1566
1695
  **Agent orchestration files.** The session hooks in the generated `.claude/settings.json` run the
1567
1696
  dependency, model, and external-tool readiness scripts at session start. The **`Stop` hook runs only
@@ -1590,7 +1719,7 @@ no module API of its own. Six verbs:
1590
1719
  | `new` | scaffold a workspace into `./<name>` |
1591
1720
  | `pull` | refresh vendored guides and versions, report drift |
1592
1721
  | `audit` | whole-plan conformance report |
1593
- | `repair` | restore the shared host-owned set |
1722
+ | `repair` | restore host-owned files and optional generated canon |
1594
1723
  | `fleet` | audit or repair every workspace under the cwd's children |
1595
1724
  | `catalog` | regenerate the fleet package-catalog table |
1596
1725
 
@@ -1611,8 +1740,10 @@ declared dependency mirror is considered.
1611
1740
  `--groups a,b` scopes an audit to artifact groups. `--live` adds an upstream freshness check to an
1612
1741
  audit. `--strict` makes a pull throw on a network fault. `--offline` restricts a catalog to local
1613
1742
  sources. `--prune` opts a repair or fleet run into deleting unexpected files under the three prune
1614
- directories. `--json` emits one machine-readable value. `--apply` writes, `--yes` skips the
1615
- confirmation, and `-h` or `--help` prints usage.
1743
+ directories. `--generated` opts a repair or fleet run into restoring generated canon except
1744
+ `package.json`; on `audit`, it is inherited if the interactive repair hand-off is accepted.
1745
+ `--json` emits one machine-readable value. `--apply` writes, `--yes` skips the confirmation, and
1746
+ `-h` or `--help` prints usage.
1616
1747
 
1617
1748
  **Safety model.** Every verb is a dry run by default. On a terminal a write asks for confirmation
1618
1749
  first, defaulting to no; in a script, `--apply` writes and `--yes` skips the question. Every write is
@@ -1622,8 +1753,9 @@ non-interactive session without `--apply` or `--yes` skips pruning rather than g
1622
1753
  operates on the immediate children of the working directory and never on the directory itself, and
1623
1754
  it has no root flag at all — `repair` is the single-workspace tool.
1624
1755
 
1625
- Both `repair` and `fleet` are scoped to host-origin artifacts, and the executable states that scope
1626
- in its own output: starter and generated files are never touched.
1756
+ `fleet` and default `repair` are scoped to host-origin artifacts. `repair` states its selected scope
1757
+ in the output; `--generated` widens both verbs to generated files while still excluding starter
1758
+ files and `package.json`.
1627
1759
 
1628
1760
  **Catalog markers.** `catalog` rewrites the block between `<!-- catalog:start -->` and
1629
1761
  `<!-- catalog:end -->` in `.claude/agents/orkestrel.md`. **Ambiguous markers fail before any
@@ -1976,7 +2108,7 @@ srcVariant(['core', 'server']) // 'multi'
1976
2108
  entryFields(['browser']).main // './dist/src/browser/index.js'
1977
2109
  dualCondition('./dist/src/core/index')
1978
2110
  exportsMap(['core'])['.']
1979
- devDependenciesFor(spec.extras).typescript
2111
+ devDependenciesFor(spec).typescript
1980
2112
  packageManifest(spec) // the whole manifest, newline-terminated
1981
2113
 
1982
2114
  configArtifacts(spec).length
@@ -2006,16 +2138,23 @@ import {
2006
2138
  appTsconfig,
2007
2139
  appViteConfig,
2008
2140
  applicationViteConfig,
2141
+ binViteProject,
2009
2142
  coreTsconfig,
2010
2143
  coreViteConfig,
2144
+ guidesViteProject,
2145
+ integrationViteProject,
2011
2146
  policyViteProject,
2147
+ renderViteTest,
2012
2148
  rootTsconfig,
2013
2149
  rootViteConfig,
2150
+ serviceViteProject,
2014
2151
  singleSrcViteConfig,
2015
2152
  srcTsconfig,
2016
2153
  srcViteConfig,
2017
2154
  viteHeader,
2018
2155
  viteMachinery,
2156
+ viteProjectDefinitions,
2157
+ viteProjectRegistrations,
2019
2158
  } from '@orkestrel/scaffold'
2020
2159
 
2021
2160
  rootTsconfig(['core'], ['core', 'server'])
@@ -2025,13 +2164,21 @@ appTsconfig('browser', true)
2025
2164
 
2026
2165
  viteMachinery(['core']) // { browser: false, vue: false, output: true }
2027
2166
  viteMachinery([], ['core', 'browser']) // { browser: true, vue: true, output: true }
2167
+ renderViteTest([{ project: 'srcCore' }], false).includes('projects: [srcCore]') // true
2028
2168
  viteHeader(viteMachinery([], ['core', 'browser'])) // the shared header, with browser and Vue support
2029
2169
  coreViteConfig()
2030
2170
  srcViteConfig('browser')
2031
2171
  appViteConfig('server')
2032
2172
  policyViteProject()
2033
-
2034
- rootViteConfig(['core', 'server'])
2173
+ guidesViteProject()
2174
+ binViteProject()
2175
+ integrationViteProject({ bin: true, integration: true })
2176
+ serviceViteProject()
2177
+ viteProjectDefinitions({ integration: true }).includes('export const integration =') // true
2178
+ viteProjectRegistrations(['core'], [], { integration: true }).map(({ project }) => project)
2179
+ // ['srcCore', 'policy', 'guides', 'integration']
2180
+
2181
+ rootViteConfig(['core', 'server'], { bin: true })
2035
2182
  singleSrcViteConfig('server').includes('srcServer') // true
2036
2183
  applicationViteConfig([], ['core', 'server']).includes('appServer') // true
2037
2184
  ```
@@ -2136,6 +2283,7 @@ import {
2136
2283
  discoverPackages,
2137
2284
  guideToDescription,
2138
2285
  isRealDirectory,
2286
+ isRealFile,
2139
2287
  listDirectories,
2140
2288
  listFiles,
2141
2289
  pruneTargets,
@@ -2150,6 +2298,7 @@ deriveBlueprint('./packages/router') // the faithful inverse an audit diffs agai
2150
2298
  selectOrkestrelEntries({ '@orkestrel/contract': '^0.0.7', vite: '^8.1.5' })
2151
2299
 
2152
2300
  isRealDirectory('./packages/router')
2301
+ isRealFile('./packages/router/package.json')
2153
2302
  listFiles('./packages/router/.claude/agents')
2154
2303
  listDirectories('./packages/router/.claude')
2155
2304