@orkestrel/scaffold 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/bin/scaffold.js +371 -140
  2. package/dist/bin/scaffold.js.map +1 -1
  3. package/dist/host/AGENTS.md +61 -42
  4. package/dist/host/CLAUDE.md +39 -408
  5. package/dist/host/agents/orchestration.md +454 -0
  6. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +25 -7
  7. package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +2 -2
  8. package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +42 -31
  9. package/dist/host/agents/skills/orkestrel-build-application/references/application.md +129 -0
  10. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +3 -3
  11. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +2 -2
  12. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +167 -0
  13. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +98 -0
  14. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +148 -0
  15. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +4 -2
  16. package/dist/host/agents/skills/orkestrel-harden-package/references/centralization.md +5 -5
  17. package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +2 -0
  18. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +11 -6
  19. package/dist/host/claude/agents/analyst.md +59 -0
  20. package/dist/host/claude/agents/application.md +1 -1
  21. package/dist/host/claude/agents/builder.md +1 -1
  22. package/dist/host/claude/agents/checker.md +5 -1
  23. package/dist/host/claude/agents/codex.md +120 -94
  24. package/dist/host/claude/agents/grok.md +44 -32
  25. package/dist/host/claude/agents/implementer.md +1 -1
  26. package/dist/host/claude/agents/orkestrel.md +5 -5
  27. package/dist/host/claude/agents/planner.md +9 -3
  28. package/dist/host/claude/agents/researcher.md +6 -5
  29. package/dist/host/claude/agents/reviewer.md +12 -6
  30. package/dist/host/claude/agents/scout.md +7 -4
  31. package/dist/host/claude/agents/verifier.md +1 -1
  32. package/dist/host/claude/rules/application.md +17 -16
  33. package/dist/host/claude/rules/architecture.md +41 -22
  34. package/dist/host/claude/rules/documentation.md +2 -1
  35. package/dist/host/claude/rules/names.md +7 -6
  36. package/dist/host/claude/rules/quality.md +65 -18
  37. package/dist/host/claude/rules/styles.md +1 -1
  38. package/dist/host/claude/rules/tests.md +12 -6
  39. package/dist/host/claude/rules/typescript.md +1 -1
  40. package/dist/host/claude/rules/workspace.md +6 -3
  41. package/dist/host/claude/settings.json +2 -0
  42. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +10 -0
  43. package/dist/host/codex/agents/analyst.toml +13 -4
  44. package/dist/host/codex/agents/checker.toml +1 -1
  45. package/dist/host/codex/agents/grok.toml +2 -1
  46. package/dist/host/codex/agents/opus.toml +1 -1
  47. package/dist/host/codex/agents/planner.toml +1 -1
  48. package/dist/host/codex/agents/researcher.toml +2 -2
  49. package/dist/host/codex/agents/reviewer.toml +1 -1
  50. package/dist/host/codex/agents/scout.toml +2 -2
  51. package/dist/host/codex/config.toml +23 -70
  52. package/dist/host/cursor/rules/orchestration.mdc +33 -0
  53. package/dist/host/guides/src/scaffold.md +339 -125
  54. package/dist/host/manifest.json +47 -1
  55. package/dist/host/tests/setupPolicy.ts +210 -3
  56. package/dist/src/core/index.cjs +477 -69
  57. package/dist/src/core/index.cjs.map +1 -1
  58. package/dist/src/core/index.d.cts +132 -43
  59. package/dist/src/core/index.d.ts +132 -43
  60. package/dist/src/core/index.js +474 -70
  61. package/dist/src/core/index.js.map +1 -1
  62. package/dist/src/server/index.cjs +83 -33
  63. package/dist/src/server/index.cjs.map +1 -1
  64. package/dist/src/server/index.d.cts +43 -27
  65. package/dist/src/server/index.d.ts +43 -27
  66. package/dist/src/server/index.js +84 -35
  67. package/dist/src/server/index.js.map +1 -1
  68. package/package.json +4 -4
@@ -226,7 +226,8 @@ application-browser projection. The root machinery selection never attaches a
226
226
  compilers section sets out.
227
227
 
228
228
  `ViteFacts` is the optional structural-fact slice shared by every root Vite compiler:
229
- `bin`, `integration`, and `service` each select their matching standalone project when `true`;
229
+ `bin` and `integration` each select their matching standalone project when `true`, while `services`
230
+ selects one standalone project for every listed vendor;
230
231
  `global` records the exact-case consumer-owned global-setup module and wires it into each eligible
231
232
  project; `showcase` records the exact-case consumer-owned showcase wrapper and selects only its
232
233
  generated browser machinery.
@@ -252,7 +253,7 @@ interface Blueprint {
252
253
  readonly overrides: readonly Override[]
253
254
  readonly bin: boolean
254
255
  readonly integration: boolean
255
- readonly service: boolean
256
+ readonly services: readonly string[]
256
257
  readonly global: boolean
257
258
  readonly showcase: boolean
258
259
  }
@@ -265,9 +266,10 @@ packages — a peer flagged `optional` also gets a `peerDependenciesMeta` entry.
265
266
  package-specific development dependencies merged over the generated baseline, and may carry any
266
267
  valid npm package name.
267
268
 
268
- `bin`, `integration`, `service`, `global`, and `showcase` are structural project facts. All five obey one law:
269
- each is `true` only when the workspace physically ships the directory or exact-case file that
270
- defines it — never because of the workspace's name, and never because a sibling fact is set.
269
+ `bin`, `integration`, `services`, `global`, and `showcase` are structural project facts. They obey
270
+ one law: each boolean is `true`, and each service name is present, only when the workspace physically
271
+ ships the directory or exact-case file that defines it — never because of the workspace's name, and
272
+ never because a sibling fact is set.
271
273
  `deriveBlueprint` probes those paths, so a fresh compile and an audit of a mature repository agree
272
274
  on what the workspace is.
273
275
 
@@ -279,10 +281,11 @@ on what the workspace is.
279
281
  workspace's own built output, outside the default run: the generated root configuration registers
280
282
  a standalone `integration` project including `tests/integration/**/*.test.ts`, and the manifest
281
283
  emits `test:integration`.
282
- - **`service`** — `tests/service/` exists. It records a slow, opt-in proof project against a foreign
283
- running process, outside the default run: a standalone `service` project including
284
- `tests/service/**/*.test.ts`, with `tests/setupService.ts` after the shared setup, and the
285
- isolated `test:service` script.
284
+ - **`services`** — each direct `tests/service/<vendor>/` directory that contains a `*.test.ts` at
285
+ any depth contributes its directory name to the sorted list. Each vendor gets a slow, opt-in
286
+ `service:<vendor>` proof project against its foreign process, including
287
+ `tests/service/<vendor>/**/*.test.ts`, and an isolated `test:service:<vendor>` script. The
288
+ aggregate `test:service` runs all vendor projects in one invocation.
286
289
  - **`global`** — the physical, exact-case `tests/setupGlobal.ts` file exists. It is the single
287
290
  governing setup-presence fact. A declared `src/browser` project runs that consumer-owned module
288
291
  as `globalSetup`; integration runs it only when `bin` and `integration` are also true.
@@ -293,12 +296,20 @@ on what the workspace is.
293
296
  consumer-only `vite-plugin-singlefile` development dependency. A directory, link, wrong-case
294
297
  name, absent wrapper, demo HTML, script, or installed dependency never implies this fact.
295
298
 
296
- A service workspace owes two companion files beside that directory, and derivation requires both
297
- physically present: `tests/setupService.ts` and `scripts/service.sh`. Either missing companion is a
298
- coded `TARGET` failure naming the missing path rather than a silent `service: false`. This package
299
- emits neither: both are consumer-owned seams, and the generated-workspace section sets out what
300
- each owes its workspace and which proof runs in which gate. Nothing here is inferred — the
301
- executable axis turns on neither proof project, and neither proof project turns on the other.
299
+ Each service vendor owes `tests/service/<vendor>/setup.ts`, whose module-load readiness check probes
300
+ and warms only that vendor. A service workspace also owes the shared `scripts/service.sh`
301
+ provisioner. Derivation fails with a coded `INVALID` question when a vendor's readiness module is
302
+ missing, when a vendor directory contains no test, or when a test uses the former flat
303
+ `tests/service/*.test.ts` layout. An absent shared provisioner is instead a repairable missing
304
+ artifact, so declaring the vendor directory does not deadlock the tool that supplies the skeleton.
305
+ The migration is to move each flat test into `tests/service/<vendor>/`, add that vendor's `setup.ts`,
306
+ and customize the repaired provisioner skeleton. Nothing here is inferred from a source or
307
+ application axis: a vendor serves both.
308
+
309
+ This is a published breaking change: `Blueprint.service` and `ViteFacts.service` were replaced by
310
+ their sorted `services` collections, the single `service` project became one project per vendor,
311
+ and the global `tests/setupService.ts` readiness seam was removed. There is no compatibility
312
+ boolean or declaration file.
302
313
 
303
314
  `Override` replaces a rendered artifact's content at a path, never partially merges it. `Member` is
304
315
  one declared public export of the scaffolded workspace, derived rather than authored.
@@ -316,7 +327,8 @@ by artifact-relative path.
316
327
  self-describing. `PlanSummary` is the dry-run tally by origin and carries both selections. `Finding` is one
317
328
  drift verdict with an optional bounded `observed` byte hex for a stale destination, and `Audit` is
318
329
  the whole diff plus its `clean` and `complete` flags, `questions`, and `drifted` / `missing` /
319
- `foreign` counts. `Question` is one validation issue; `blocking: true` fails the gate closed while
330
+ `foreign` counts.
331
+ `Question` is one validation issue; `blocking: true` fails the gate closed while
320
332
  `false` rides a complete result as an advisory. `Validation` is the semantic pass result and never
321
333
  throws.
322
334
 
@@ -374,8 +386,11 @@ instead of through the manifest.
374
386
 
375
387
  `ManifestEntry` is one vendored-host file record — its un-dotted `storage` name, its `destination`
376
388
  relative to a target, and an `executable` bit. `HostManifest` pairs the sorted file `entries` with
377
- the complete sorted directory `roots` inventory, so a destructive consumer can tell a
378
- declared-empty root from a truncated manifest.
389
+ the complete sorted directory `roots` inventory and a SHA-256 `digest` of that exact membership.
390
+ The independently persisted digest detects an entry/root membership edit that did not update the
391
+ digest, while roots distinguish a declared-empty directory. A self-consistent replacement manifest
392
+ remains structurally valid and defines its own smaller membership; authenticity of that complete
393
+ membership is outside the digest's checksum-only contract.
379
394
 
380
395
  The write-transaction shapes are the fail-closed mutation vocabulary. `WriteExpectation` is one
381
396
  destination snapshot captured before mutation (`absent`, `file`, or `directory`, with device,
@@ -410,9 +425,12 @@ From [`constants.ts`](../../src/core/constants.ts).
410
425
  | `BIN_CONFIGS` | const |
411
426
  | `APP_MATRIX` | const |
412
427
  | `HOST_PATHS` | const |
428
+ | `ORCHESTRATION_PATH_PREFIXES` | const |
429
+ | `ORCHESTRATION_PATH_NAMES` | const |
413
430
  | `SERVICE_SCRIPT_PATH` | const |
414
431
  | `GLOBAL_SETUP_PATH` | const |
415
432
  | `SHOWCASE_CONFIG_PATH` | const |
433
+ | `CATALOG_AGENT_PATH` | const |
416
434
  | `NAME_PATTERN` | const |
417
435
  | `MAX_NAME_LENGTH` | const |
418
436
  | `MAX_DEPENDENCY_NAME_LENGTH` | const |
@@ -464,10 +482,38 @@ produces one (`app/browser/index.html`, `app/server/main.ts`). `BIN_CONFIGS` is
464
482
  axis's computed `tsconfig` and Vite wrapper pair. `HOST_PATHS` is the ordered list of byte-copied
465
483
  host artifacts, and it is the staging manifest rather than the per-plan carried set:
466
484
  `stageHost` vendors every path on it, while each plan carries the subset `selectHostPaths` selects
467
- for that one workspace. `SERVICE_SCRIPT_PATH` names the consumer-owned provisioner a service
468
- workspace's audit expects, and `GLOBAL_SETUP_PATH` names the consumer-owned Vitest global-setup
485
+ for that one workspace. `ORCHESTRATION_PATH_PREFIXES` and `ORCHESTRATION_PATH_NAMES` are the one
486
+ membership rule behind both group classifiers: `inferGroup` reads them for a foreign target path and
487
+ `hostGroup` for a `HOST_PATHS` entry, so a new harness directory is admitted once rather than twice.
488
+ `SERVICE_SCRIPT_PATH` names the generated provisioner skeleton a service
489
+ workspace must replace with its idempotent vendor provisioning. It is birth-only while present and
490
+ repairable while absent. `GLOBAL_SETUP_PATH` names the consumer-owned Vitest global-setup
469
491
  module that independently selected projects can load. `SHOWCASE_CONFIG_PATH` names the sole
470
492
  consumer-owned regular file whose exact physical presence enables the optional app showcase.
493
+ `CATALOG_AGENT_PATH` names the one artifact `diffPlan` compares by presence even after hydration,
494
+ so a consumer can name the file the catalog operation owns rather than rediscovering it from a
495
+ finding:
496
+
497
+ ```ts
498
+ import type { Plan } from '@orkestrel/scaffold'
499
+ import { blueprint, CATALOG_AGENT_PATH, contentToHex, diffPlan } from '@orkestrel/scaffold'
500
+
501
+ const plan: Plan = {
502
+ blueprint: blueprint('router', { src: ['core'] }),
503
+ groups: ['orchestration'],
504
+ artifacts: [
505
+ {
506
+ path: CATALOG_AGENT_PATH,
507
+ group: 'orchestration',
508
+ origin: 'host',
509
+ hex: contentToHex('vendored catalog\n'),
510
+ },
511
+ ],
512
+ }
513
+
514
+ diffPlan(plan, { [CATALOG_AGENT_PATH]: contentToHex('a newer fleet table\n') }).clean // true
515
+ diffPlan(plan, {}).missing // 1 — restorable while absent, never replaced while present
516
+ ```
471
517
 
472
518
  The bounds are public because they are part of the contract, not implementation trivia.
473
519
  `MAX_ARTIFACT_BYTES` caps one artifact at 5 MiB and `MAX_TOTAL_ARTIFACT_BYTES` caps one blueprint,
@@ -548,6 +594,14 @@ From [`constants.ts`](../../src/server/constants.ts).
548
594
  `PRUNE_DIRECTORIES` is the closed set of prune-owned directories — `.claude/agents`,
549
595
  `.codex/agents`, and `scripts`. Nothing outside those roots is ever a deletion candidate, which is
550
596
  why project-owned skills under `.agents/skills` and `.claude/skills` are structurally safe.
597
+ `.cursor/rules` is vendored but deliberately not pruned, for the same reason: a workspace owns
598
+ project-specific Cursor rules beside the vendored bridge, and pruning would delete them. That
599
+ choice has a cost, and it is accepted rather than avoided: a rule file dropped from `HOST_PATHS`
600
+ stays in every consumer that already received it, no `audit` run reports it — the executable audit
601
+ reads only planned paths, and nothing outside `PRUNE_DIRECTORIES` is ever a `foreign` finding — and
602
+ a Cursor rule carrying `alwaysApply: true` keeps instructing agents there indefinitely. Retiring a
603
+ vendored rule therefore needs a deliberate consumer-side removal, not a scaffold run.
604
+ `.claude/rules` and `.claude/skills` carry the identical exposure for the identical reason.
551
605
  `HOST_MANIFEST_PATH` is the reserved `manifest.json` written at the root of every staged host.
552
606
  `SENSITIVE_HOST_PATH_PATTERN` rejects credential-like, key-store, certificate-key, and
553
607
  local-configuration paths at the staging boundary. `RESERVED_TARGET_PATH_PATTERN` protects `.git`
@@ -838,6 +892,7 @@ From [`helpers.ts`](../../src/core/helpers.ts).
838
892
  | `syncToReview` | function |
839
893
  | `catalogToBlock` | function |
840
894
  | `inferGroup` | function |
895
+ | `matchesOrchestrationPath` | function |
841
896
  | `diffPlan` | function |
842
897
  | `bytesToHex` | function |
843
898
  | `contentCodePoint` | function |
@@ -899,6 +954,14 @@ that block enters agent instruction context. `isBehind` is the shared freshness
899
954
  report projections count with.
900
955
 
901
956
  `diffPlan` is the audit engine, and `inferGroup` classifies a target file the plan does not own.
957
+ `matchesOrchestrationPath` is the shared membership test both classifiers use to decide whether a
958
+ path instructs or wires an agent rather than configuring the toolchain.
959
+ A host artifact without canonical `hex` is presence-owned: present is `aligned`, absent is
960
+ `missing`. The server face attaches `hex` to every readable vendored source before executable
961
+ audits, except the dependency-guide pointers hydration deliberately marks presence-owned. The
962
+ hydrated `CATALOG_AGENT_PATH` artifact remains presence-owned even with vendored bytes because
963
+ `catalog` owns its bounded marker region. The same engine governs `Materializer.repair`'s preview
964
+ recheck and direct library consumers without a call-site plan rewrite.
902
965
  `snapshotOf`, `contentToHex`, `contentToBytes`, `contentByteLength`, `contentCodePoint`, and
903
966
  `bytesToHex` are the host-independent byte leaves that make exact comparison possible without a
904
967
  host encoder or buffer; an unpaired surrogate encodes as `U+FFFD` rather than throwing.
@@ -946,6 +1009,7 @@ From [`helpers.ts`](../../src/server/helpers.ts).
946
1009
  | `digestFile` | function |
947
1010
  | `digestHex` | function |
948
1011
  | `digestText` | function |
1012
+ | `digestHostManifest` | function |
949
1013
  | `guideStub` | function |
950
1014
  | `packageShortName` | function |
951
1015
  | `readGuideReferences` | function |
@@ -998,7 +1062,9 @@ directory. All three reject malformed paths before filesystem access. Containmen
998
1062
  realpath-aware rather than merely lexical, so a symlinked subdirectory planted inside an otherwise
999
1063
  legitimate root cannot smuggle a write or a read outside it.
1000
1064
 
1001
- `digestFile`, `digestHex`, and `digestText` are the three SHA-256 leaves. The file digest is
1065
+ `digestFile`, `digestHex`, and `digestText` are the byte and text SHA-256 leaves;
1066
+ `digestHostManifest` hashes the canonical entry/root membership independently of the stored digest
1067
+ field. The file digest is
1002
1068
  bounded-memory and revalidates device, inode, size, and modification time before and after reading,
1003
1069
  so a file swapped mid-read is a failure rather than a silent wrong digest. `readFileHex` and
1004
1070
  `readFileText` read one contained file under the same revalidation, and the text reader decodes
@@ -1030,8 +1096,9 @@ existing workspace so a mature package is diffed against its own would-be scaffo
1030
1096
  dependency-less stand-in. Environments come from `src/<environment>/` and `app/<environment>/`, the
1031
1097
  three directory-shaped structural project facts from their directory probes, `global` from the
1032
1098
  physical exact-case `tests/setupGlobal.ts` file, and `showcase` from the physical exact-case regular
1033
- file `configs/app/vite.showcase.config.ts`; the service companion law remains the one the blueprint
1034
- section states every fact is a reading of the filesystem, never of the name. Dependencies and peers come
1099
+ file `configs/app/vite.showcase.config.ts`; service names come from the direct vendor directories
1100
+ under `tests/service/`, subject to the companion law in the blueprint section. Every fact is a
1101
+ reading of the filesystem, never of the package name. Dependencies and peers come
1035
1102
  from the manifest's scoped entries, with an optional peer recovered from
1036
1103
  `peerDependenciesMeta`; and `extras` is every development dependency minus the complete set
1037
1104
  `devDependenciesFor` emits for those environments and structural axes, and minus anything already
@@ -1045,7 +1112,8 @@ repository forking the file.
1045
1112
  `hydratePlan` are the vendored-host path. `storagePath` maps a repo-relative path to its un-dotted
1046
1113
  storage name, `stageHost` copies the vendored set into an output directory behind a full preflight
1047
1114
  and an atomic swap, and `readHostManifest` reads and validates the resulting `manifest.json`,
1048
- returning `undefined` when a host has none — the raw-repository-root fallback that maps sources 1:1.
1115
+ including its independently stored membership digest, returning `undefined` when a host has none —
1116
+ the raw-repository-root fallback that maps sources 1:1.
1049
1117
  `locateHostSource` resolves one source to its storage file, `remapArtifactPath` maps a manifest
1050
1118
  destination back onto an artifact's target prefix, and `hydratePlan` rehydrates a plan's host
1051
1119
  artifacts with their exact bytes, expanding a directory-shaped host artifact into one artifact per
@@ -1182,17 +1250,26 @@ formatter's 100-column fixed point: a complete registration-array line, includin
1182
1250
  its trailing comma, stays collapsed when it fits and expands one entry per line otherwise.
1183
1251
  `viteProjectRegistrations` is the one registration derivation every root shape consumes: it derives
1184
1252
  the selected source and application projects from the canonical environment order, then appends
1185
- `policy`, `config`, `guides`, and the optional `srcBin`, `integration`, and `service` projects.
1253
+ `policy`, `config`, `guides`, the optional `srcBin` and `integration` projects, and one
1254
+ `service<Vendor>` project for every selected service.
1186
1255
  `viteProjectDefinitions` renders the standalone proof and structural-fact definitions in that same
1187
1256
  order with one blank line between declarations. Both consume `ViteFacts`, so each optional project
1188
- is controlled only by its matching `bin`, `integration`, or `service` blueprint fact; the same
1257
+ is controlled only by its matching `bin`, `integration`, or `services` blueprint fact; the same
1189
1258
  slice carries `global` to integration and the source-browser compiler, and `showcase` to the
1190
1259
  application-browser compiler, without adding another test project.
1191
1260
 
1192
1261
  `coreViteConfig`, `srcViteConfig`, `binViteConfig`, and `appViteConfig` emit the thin per-target
1193
1262
  wrappers. `coreViteConfig()` is parameterless and never imports or attaches browser CSS machinery;
1194
1263
  the root `srcCore` factory and its wrapper stay host-independent even when the workspace also owns a
1195
- browser target. `binTsconfig` emits the executable declaration scope; `rootViteConfig`,
1264
+ browser target. `srcViteConfig` takes the environment plus the blueprint's `name` and `src`, because
1265
+ its face declaration has to reach `src/core` through a specifier the published tarball carries.
1266
+ `bundleTypes` rolls the face up through API Extractor, which leaves each core re-export behind a
1267
+ relative `../core/index.ts` path no consumer can resolve; a workspace declaring `core` therefore
1268
+ emits a `beforeWriteFile` rewrite turning that path into `@orkestrel/<name>`, its own published root
1269
+ export. The rewrite matches the final face roll-up only — applying it to the intermediate
1270
+ declarations makes API Extractor analyse `src/core`'s source and abort — and a workspace with no
1271
+ `core` emits no rewrite at all. `binTsconfig` emits the executable declaration scope;
1272
+ `rootViteConfig`,
1196
1273
  `singleSrcViteConfig`, and `applicationViteConfig` emit the root configuration for a library-only,
1197
1274
  single non-core `src` environment, and application-bearing workspace respectively; and
1198
1275
  `policyViteProject`, `configViteProject`, `guidesViteProject`, `integrationViteProject`, and
@@ -1200,11 +1277,12 @@ single non-core `src` environment, and application-bearing workspace respectivel
1200
1277
  executable-project emitter. A
1201
1278
  proof project is structurally derived from the directory holding its tests and never wraps a source
1202
1279
  or application environment project. The guides project therefore uses only `tests/setup.ts`, never
1203
- `setupServer.ts`, `setupBrowser.ts`, or `setupService.ts`; and its `tests/src/**/*.test.ts` and
1280
+ `setupServer.ts`, `setupBrowser.ts`, or a vendor readiness module; and its `tests/src/**/*.test.ts` and
1204
1281
  `tests/app/**/*.test.ts` exclude rows are uniform across all root shapes by design, including
1205
1282
  core-only workspaces where one row cannot currently match. Integration and service use 120-second
1206
- test and hook timeouts with file parallelism disabled, and service alone layers
1207
- `tests/setupService.ts` onto the shared setup. The integration project wires
1283
+ test and hook timeouts with file parallelism disabled. Each service project layers
1284
+ `tests/setupServer.ts` and `tests/service/<vendor>/setup.ts` onto the shared setup, carries the
1285
+ server environment boundary, and may exercise either the `src` or `app` axis. The integration project wires
1208
1286
  `tests/setupGlobal.ts` for the shared template-registry harness exactly when `bin`, `integration`,
1209
1287
  and `global` are all true. Independently, a `global` source-browser project places
1210
1288
  `globalSetup: ['./tests/setupGlobal.ts']` immediately before its ordinary `setupFiles` row (and
@@ -1221,7 +1299,9 @@ generated parity suite resolves fence imports through. `guideMemberTable`, `guid
1221
1299
  `guideMethods`, and `guideTests` render the generated guide's member tables, usage examples, method
1222
1300
  contract, and test inventory. `fillArtifact` fills one template entry into a `template`-origin
1223
1301
  artifact with missing placeholders treated as an error, and `hostGroup` resolves which group a
1224
- byte-copied host path belongs to. `applyOverrides` replaces a matching artifact's content in place
1302
+ byte-copied host path belongs to splitting by what a path governs rather than where it sits, so
1303
+ that both MCP registrations (`.mcp.json` and `.cursor/mcp.json`) group with the harness bridges as
1304
+ `orchestration` rather than with the root dotfiles they sit beside. `applyOverrides` replaces a matching artifact's content in place
1225
1305
  and deliberately leaves an unmatched, host-owned, or `package.json` override unapplied, because the
1226
1306
  gate reports it as a blocking question. `ciWorkflow` renders the generated workflow.
1227
1307
 
@@ -1317,7 +1397,10 @@ The public methods of each behavioral interface, one table per type.
1317
1397
  `audit(blueprint, current, groups?)` compiles and then diffs the resulting plan against the
1318
1398
  caller-supplied current content; a gated blueprint returns `complete: false` with the gate's
1319
1399
  blocking questions and zero findings, and a complete one carries the gate's advisories on that same
1320
- `questions` field. `destroy()` is idempotent teardown. The interface also exposes the readonly
1400
+ `questions` field. Because this core-only method performs no host I/O, its compiled host artifacts
1401
+ have no `hex` and are audited by presence. Callers that need host-byte verdicts hydrate the compiled
1402
+ plan through the server face and call `diffPlan`, which is the path every executable audit uses.
1403
+ `destroy()` is idempotent teardown. The interface also exposes the readonly
1321
1404
  `emitter`.
1322
1405
 
1323
1406
  #### `PlanManagerInterface`
@@ -1347,13 +1430,14 @@ interface also exposes the readonly `emitter` and `size` properties.
1347
1430
  | `destroy` | `void` |
1348
1431
 
1349
1432
  `materialize(plan, target)` is green-field: it refuses any target `isVacant` rejects, then copies
1350
- each host artifact and writes each template and computed artifact. `repair(plan, audit, target)` is
1351
- into-existing: it skips the vacancy check, re-verifies that the target still matches the audit
1352
- preview, and writes only the missing and stale artifacts that audit names, leaving aligned ones
1353
- untouched and reporting them as `skipped`. `prune(target, expected)` deletes exactly the unexpected
1354
- files the vendored host no longer declares under the prune directories, and only after the observed
1355
- bytes still match the `expected` snapshot it was previewed with. `destroy()` is idempotent teardown.
1356
- The interface also exposes the readonly `emitter`.
1433
+ each host artifact and writes each template and computed artifact. `repair(plan, audit, target,
1434
+ replace?)` is into-existing: it skips the vacancy check, re-verifies that the target still matches
1435
+ the audit preview, and writes missing artifacts. Stale artifacts are report-only and returned as
1436
+ `skipped` by default; passing `true` for `replace` explicitly replaces their bytes and discards their
1437
+ local changes. Aligned artifacts are always `skipped`. `prune(target, expected)` deletes exactly the
1438
+ unexpected files the vendored host no longer declares under the prune directories, and only after
1439
+ the observed bytes still match the `expected` snapshot it was previewed with. `destroy()` is
1440
+ idempotent teardown. The interface also exposes the readonly `emitter`.
1357
1441
 
1358
1442
  #### `SyncInterface`
1359
1443
 
@@ -1424,10 +1508,11 @@ produce the same digest — and a `PlanManager` id is that digest.
1424
1508
  how it is audited, and whether it may ever be overwritten.
1425
1509
 
1426
1510
  - **`host`** — byte-copied from the vendored data root. These are the shared files a whole fleet
1427
- keeps identical: the root instruction documents and licence, the agent, rule, and skill
1428
- directories, the session scripts, the repository coding-law policy module, the byte-identical root
1429
- dotfiles, and the two line guide mirrors a workspace carries for contracts other than its own.
1430
- `HOST_PATHS` is the exact vendored list; what a given plan carries is `selectHostPaths` of it.
1511
+ keeps identical: the root instruction documents and licence, the canonical orchestration contract
1512
+ and the three harness bridges that point at it, the agent, rule, and skill directories, the
1513
+ session scripts, the repository coding-law policy module, the byte-identical root dotfiles, and
1514
+ the two line guide mirrors a workspace carries for contracts other than its own. `HOST_PATHS` is
1515
+ the exact vendored list; what a given plan carries is `selectHostPaths` of it.
1431
1516
  - **`template`** — filled from a frozen template definition by a pure fill engine. These are
1432
1517
  starter files: source stubs, test stubs, the starter guide, the README.
1433
1518
  - **`computed`** — derived by this package's own combination logic. These are the structural files:
@@ -1443,19 +1528,32 @@ Audit semantics follow directly from that.
1443
1528
  missing, or clean tallies.
1444
1529
  - A **computed** artifact is content-aware canon: `missing`, `aligned`, or `stale`, and it gates the
1445
1530
  audit like any other drift.
1446
- - A **host** artifact is audited by presence alone `missing` or `aligned`, never `stale` — unless
1447
- it has been hydrated with its real host bytes, in which case it is content-compared exactly like a
1448
- computed artifact and can be `stale`. Hydration also expands a directory-shaped host artifact into
1449
- one artifact per file, so agent configuration and skills are audited file by file.
1450
- - A target file the plan does not own is `foreign`, and `inferGroup` classifies it by its leading
1451
- path segment.
1531
+ - A **host** artifact with canonical `hex` is content-compared exactly like a computed artifact and
1532
+ can be `stale`. Without `hex`, it is presence-owned: present is `aligned`, absent is `missing`.
1533
+ The catalog agent is explicitly presence-owned because `catalog` is its sole content writer.
1534
+ Hydration expands a directory-shaped host artifact into one artifact per declared file and verifies
1535
+ that the manifest digest matches the manifest's current membership before expansion. That detects
1536
+ stale-digest truncation; a self-consistently rewritten manifest defines a smaller valid inventory,
1537
+ so the digest alone cannot authenticate omitted membership.
1538
+ - In a caller-supplied snapshot, a path the plan does not own is `foreign`, and `inferGroup`
1539
+ classifies it by its leading path segment. The executable supplies unexpected paths only from
1540
+ `.claude/agents`, `.codex/agents`, and `scripts`, because those prune-owned directories are the
1541
+ only regions scaffold has authority to delete from; unplanned files elsewhere are not reported
1542
+ as foreign.
1452
1543
 
1453
1544
  The same ownership boundary is what makes mutation safe. **`fleet` and default `repair` both scope
1454
- the compiled plan to host origin before hydrating, diffing, or applying.** `--generated` widens that
1455
- scoped plan to generated canon except `package.json`; template artifacts remain birth-only in
1456
- either mode. A mature workspace's hand-written source, tests, guides, and manifest are therefore
1457
- never overwritten with a stub. The generated `.github/workflows/ci.yml` is a **computed** artifact,
1458
- so user-owned CI stands by default but is intentionally restored when `--generated` is passed.
1545
+ the compiled plan to host origin before hydrating, diffing, or applying.** Missing files in that
1546
+ scope are restored, but stale files are report-only unless `--replace` explicitly authorizes byte
1547
+ replacement. `--generated` widens the selected ownership scope to generated canon. It keeps the
1548
+ `package.json` publication boundary protected except for the generated service-script keys needed
1549
+ when the derived service set changes; it composes with `--replace` and does not itself authorize
1550
+ replacement. Template
1551
+ artifacts remain birth-only in either scope, except that an absent service provisioner and absent
1552
+ service conformance test are promoted to missing-file repair artifacts. A present customized copy
1553
+ is never compared or replaced. A mature workspace's hand-written source, tests, and guides are
1554
+ therefore never overwritten with a stub. The generated
1555
+ `.github/workflows/ci.yml` is a **computed** artifact, so user-owned CI stands by default and is
1556
+ restored only when both `--generated` and `--replace` are passed.
1459
1557
  Audit always compares it because computed artifacts are content-aware canon. A legitimate
1460
1558
  difference that the blueprint cannot express is a canon gap: add the missing blueprint axis rather
1461
1559
  than forking the computed file in one repository.
@@ -1477,8 +1575,11 @@ owner: a dependency this package vendors a byte-identical mirror for gets a real
1477
1575
  dependency, so a package depending on `@orkestrel/guide` plans one `guides/src/guide.md` rather than
1478
1576
  two. Any other dependency gets a host-origin _pointer_ artifact plus a non-blocking question, never
1479
1577
  a fabricated mirror; on materialization that pointer degrades to a short stub, and `scaffold pull`
1480
- fetches the real thing. That degrade is scoped exactly to guide pointers: any other missing manifest
1481
- entry means a corrupt or truncated vendored manifest, and fails closed. Selection is the law and
1578
+ fetches the real thing. Hydration marks that permanent pointer state presence-owned, so both the
1579
+ birth stub and a later pulled guide audit clean while present; `pull` refreshes content but is not a
1580
+ remedy for an audit state. That degrade is scoped exactly to guide pointers. A manifest whose
1581
+ membership changes without a matching digest is rejected, while any other undeclared or unreadable
1582
+ source is rejected with a coded `TARGET` failure. Selection is the law and
1482
1583
  `findFileConflict` is its backstop: two artifacts at one path refuse the plan rather than racing to
1483
1584
  be the last writer.
1484
1585
 
@@ -1489,23 +1590,63 @@ checks one. `readTarget` supplies the snapshot as exact bytes; `diffPlan` return
1489
1590
  `auditToReview` renders them for a human. Nothing in that path writes.
1490
1591
 
1491
1592
  The executable's physical unexpected-file scan treats exactly `scripts/service.sh` as an expected
1492
- consumer-owned seam when the derived blueprint has `service: true`. That exclusion is warranted
1493
- because a service blueprint cannot derive without the physical file: the companion-file law raises
1494
- a `TARGET` failure first, so the scan removes a false positive and can never mask an absent
1495
- provisioner. A non-service workspace still reports the same path as foreign.
1593
+ workspace-owned seam when the derived blueprint has at least one service. That exclusion is
1594
+ warranted because the promoted plan reports an absent file as missing while a present file is
1595
+ consumer-owned. A workspace with no services still reports the same path as foreign.
1496
1596
 
1497
1597
  `repair` turns those findings back into the narrowest possible write. It re-reads the target,
1498
1598
  re-diffs it, and refuses to proceed if the findings changed since the preview it was given — a
1499
1599
  target that moved under the caller is a `TARGET` failure, not a race to win. It then derives a write
1500
1600
  precondition per artifact from the audit itself: a `missing` finding requires the destination to
1501
- still be absent, a `stale` finding requires it to still carry exactly the bytes that were observed.
1502
- Those preconditions are checked again inside the write transaction before any promotion.
1503
- An interactive audit repair hand-off forwards `--generated` into the repair invocation when the
1504
- flag was present on `audit`.
1601
+ still be absent. A `stale` finding remains untouched and is reported as skipped unless the caller
1602
+ passes `replace`; an authorized stale replacement requires the destination to still carry exactly
1603
+ the bytes that were observed. Those preconditions are checked again inside the write transaction
1604
+ before any promotion. A skipped stale path keeps the executable at exit `1`, because the selected
1605
+ workspace remains drifted; a clean run and a run that fully applies its findings exit `0`. An
1606
+ interactive audit repair hand-off forwards both `--generated` and `--replace` when those flags were
1607
+ present on `audit`.
1608
+
1609
+ That boundary governs the executable's words too. Every drift line states what a command will do
1610
+ rather than how a file came to differ: the executable cannot know whether a generated file was
1611
+ hand-edited, and a consumer whose blueprint cannot yet express what it needs legitimately edits one.
1612
+ Every cost is stated where it can still be declined, and nowhere else: a run with nothing to write
1613
+ states no boundary it is not about to act on, because a warning attached to a no-op only trains
1614
+ operators to ignore warnings. `repair` states its scope when the audit found something to repair;
1615
+ `fleet` states its scope, its repository count, and the same replacement cost once `--apply` has
1616
+ authorized a write; neither states it over a dry run. Each run closes on the tally of what it did,
1617
+ including a run that writes nothing — and a drifted file left alone is counted apart from an
1618
+ aligned one, because `unchanged` is already the audit table's word for a file that matches canon.
1619
+
1620
+ **Four paths discard content a consumer may own, and each names its cost before it acts.**
1621
+
1622
+ | Path | What it discards | Ownership boundary |
1623
+ | --------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
1624
+ | `--replace` on repair or fleet | The local bytes of a drifted file the report named | Host-owned artifacts, widened to generated canon by `--generated`; never a present starter, never `package.json` |
1625
+ | `--prune --apply` | Whole unexpected files, quarantined first and reported by exact path | Only `.claude/agents`, `.codex/agents`, and `scripts`, and only paths the vendored host does not declare |
1626
+ | `catalog --apply` | Everything between the two catalog markers, local additions included | Exactly the one bounded marker region of `CATALOG_AGENT_PATH`; the rest of that file is never touched |
1627
+ | `pull --apply` / `mirror --apply` | A locally edited vendored guide mirror, which reads as `behind` | Only `guides/src/<name>.md` mirrors of other packages; never the guide this workspace owns |
1628
+
1629
+ `--replace` is the only one of the four that is an opt-in modifier rather than a verb, so it is the
1630
+ one whose cost is repeated in every line that offers it: the scope line, the repair verdict, the
1631
+ audit's drift guidance, and the hand-off question itself. `--apply` authorizes all four and nothing
1632
+ else does — `--yes` only skips a confirmation it can no longer stand in for, and an unexpected-file
1633
+ hint that recommended `repair --prune` without it would name a command that deletes nothing. `new`
1634
+ is absent from the table on purpose: it refuses any target `isVacant` rejects, so it has no local
1635
+ content to discard.
1636
+
1637
+ The catalog agent has a narrower ownership exception in `diffPlan` itself.
1638
+ `CATALOG_AGENT_PATH` remains presence-owned after host hydration: repair can restore the
1639
+ absent file, but audit, repair, fleet, and direct library consumers never compare or replace its
1640
+ existing bytes, even under `--replace`. `catalog` is the sole content writer and continues to
1641
+ replace only the uniquely bounded marker region. Thus
1642
+ `repair` → `catalog` → `repair` converges without restoring a stale embedded catalog snapshot over
1643
+ the current fleet table.
1505
1644
 
1506
1645
  `prune` is the deletion arm, and it is deliberately narrow. Its candidate set comes from
1507
- `pruneTargets`, which is also what the executable's audit and preview read, so what is reported and
1508
- what is deleted cannot diverge. Only the three prune directories are in scope; the allowlist must be
1646
+ `pruneTargets`, which is also what the executable's merged report and preview read. Repair's
1647
+ optimistic-concurrency recheck receives the raw plan audit, while the separate foreign findings
1648
+ remain attached to reporting and exit status; after that recheck succeeds, the same preview snapshot
1649
+ drives deletion. Only the three prune directories are in scope; the allowlist must be
1509
1650
  positively established from the vendored host, or the call fails closed rather than treating an
1510
1651
  unresolved host as "vendors nothing" and proposing to delete everything. Each candidate is verified
1511
1652
  as a plain physical file whose bytes still match the preview, moved into a private quarantine rather
@@ -1574,11 +1715,14 @@ fleet refresh is never partial. Files outside the discovered guide set remain un
1574
1715
 
1575
1716
  A generated workspace is not a folder of suggestions; it is a working, gated project.
1576
1717
 
1577
- **Manifest and scripts.** A published workspace is scoped and carries an `exports` map, publish
1578
- configuration, and ships `dist/src` plus its README. An application-only workspace is unscoped and
1579
- `private: true`, with no export map and no publish configuration, and ships `dist/app`. A workspace
1580
- that builds its own executable additionally ships `dist/bin` and `dist/host`. Scripts are emitted in
1581
- a fixed, interleaved order so aggregates sit immediately before their per-environment members:
1718
+ **Manifest and scripts.** A published workspace is scoped, carries the Orkestrel GitHub homepage,
1719
+ issues, and repository identity, carries an `exports` map and publish configuration, and ships
1720
+ `dist/src` plus its README. An application-only workspace is unscoped and `private: true`, with no
1721
+ export map, publish configuration, or invented GitHub identity, and ships `dist/app`. Both carry
1722
+ `license: "MIT"` because every generated workspace receives the same host-owned MIT `LICENSE`.
1723
+ A workspace that builds its own executable additionally ships `dist/bin` and `dist/host`. Scripts
1724
+ are emitted in a fixed, interleaved order so aggregates sit immediately before their per-environment
1725
+ members:
1582
1726
 
1583
1727
  - `clean`, `copy`, `scaffold`, `lint`
1584
1728
  - `check`, then `check:src` with one `check:src:<environment>` per published environment, then
@@ -1586,7 +1730,8 @@ a fixed, interleaved order so aggregates sit immediately before their per-enviro
1586
1730
  Vue typechecker, every other scope uses plain `tsc`
1587
1731
  - `format`, `format:check`, `lint:check`
1588
1732
  - `test`, then `test:src` and its per-environment scopes, the optional `test:integration`,
1589
- `test:equivalence`, and `test:service` proofs, `test:app` and its per-environment scopes, then
1733
+ `test:equivalence`, and `test:service` aggregate followed by its sorted per-vendor proofs,
1734
+ `test:app` and its per-environment scopes, then
1590
1735
  `test:policy`, `test:config`, and `test:guides`
1591
1736
  - `build`, then `build:src` and its per-environment targets, `build:app` and its runtime targets, and
1592
1737
  `build:host` for a bin workspace
@@ -1595,24 +1740,24 @@ a fixed, interleaved order so aggregates sit immediately before their per-enviro
1595
1740
  - `showcase`, `build:showcase`, and `show` only when the physical showcase wrapper is present;
1596
1741
  `show` formats, then builds, then copies `dist/showcase/index.html` to `demo/showcase.html`
1597
1742
  - `prepublishOnly` chaining `format:check → lint:check → check → build → test`, followed by
1598
- `test:integration` when the integration axis is selected
1743
+ `test:integration` when selected and finally `test:service` when any service is selected
1599
1744
 
1600
1745
  **Proof gating.** The opt-in proofs are predictable from the axes alone. `test:integration` rides
1601
- the `integration` axis and `test:service` the `service` axis, while `test:equivalence` is emitted
1746
+ the `integration` axis and `test:service` a nonempty `services` axis, while `test:equivalence` is emitted
1602
1747
  only where `bin` and `integration` are both set:
1603
1748
 
1604
- | Proof | `npm test` | `prepublishOnly` | CI |
1605
- | ------------------ | ---------- | ---------------- | -------------------------- |
1606
- | `test:integration` | no | yes, last | after the standard gates |
1607
- | `test:equivalence` | no | no | no |
1608
- | `test:service` | no | never | after `scripts/service.sh` |
1749
+ | Proof | `npm test` | `prepublishOnly` | CI |
1750
+ | ------------------ | ---------- | ------------------- | -------------------------- |
1751
+ | `test:integration` | no | yes, before service | after the standard gates |
1752
+ | `test:equivalence` | no | no | no |
1753
+ | `test:service` | no | yes, last | after `scripts/service.sh` |
1609
1754
 
1610
1755
  No opt-in proof joins the default chain: `npm test` runs the source, application, policy,
1611
1756
  configuration, and guide projects, and nothing there needs a build artifact or a foreign process.
1612
- Publication is the one
1613
- asymmetry `prepublishOnly` appends `test:integration`, because a package about to be published
1614
- should prove itself against its own built output, while `test:service` is never in that chain.
1615
- Neither default testing nor publication starts or requires a foreign process.
1757
+ Publication is the one asymmetry: `prepublishOnly` appends integration and then service proofs.
1758
+ A package that claims to drive a vendor has not proved that claim unless publishing runs against
1759
+ it, despite the provisioning cost. Neither default testing nor publication starts a foreign
1760
+ process; publication requires the caller to provision one first.
1616
1761
  The showcase is likewise outside `build`, `test`, and `prepublishOnly`; it is an explicit projection
1617
1762
  of `app/browser`, not an environment, test-project row, or source/demo artifact.
1618
1763
  Its copied `demo/showcase.html` is generated and minified, so the mirrored `.prettierignore` keeps it
@@ -1630,22 +1775,29 @@ The equivalence proof is a dual-path re-run rather than a separate suite. Run
1630
1775
  integration project in dual-path mode and proves each programmatic driver verdict against the
1631
1776
  spawned npm-script reference. Ordinary integration runs keep the faster driver-only path.
1632
1777
 
1633
- **Consumer-owned service seams.** The service axis is the one place canon stops at the boundary:
1634
- there is no template for a proof project and neither companion path is on `HOST_PATHS`, so a
1635
- service workspace owns both of its seams outright. They come as a pair.
1636
-
1637
- - `tests/setupService.ts` is the readiness seam. It probes the foreign process and warms it before
1638
- any test runs, and throws at module load — naming the `service` project when that process is
1639
- unreachable, so an unprovisioned run fails loudly instead of passing an empty suite. Only the
1640
- `service` project loads it.
1641
- - `scripts/service.sh` is the provisioning seam, named once by `SERVICE_SCRIPT_PATH`. It brings that
1642
- process up idempotently a second run against an already-provisioned service is a no-op rather
1643
- than a second instance and exits nonzero when it cannot, which is what makes CI's
1644
- `bash scripts/service.sh` step a gate rather than a hint.
1778
+ **Consumer-owned service seams.** Each vendor owns its readiness module at
1779
+ `tests/service/<vendor>/setup.ts`. It probes and warms that vendor at module load, throwing a clear
1780
+ error when unavailable so only that vendor project fails readiness. The scaffold never generates
1781
+ these modules because an inert readiness check would be a false proof.
1782
+
1783
+ `scripts/service.sh`, named once by `SERVICE_SCRIPT_PATH`, is shared provisioning for every vendor.
1784
+ The scaffold emits a template skeleton that exits nonzero until the workspace replaces it with
1785
+ idempotent provisioning; an already-provisioned vendor must be a no-op, and any vendor that cannot
1786
+ be prepared must make the script fail. The skeleton is written at birth when services are already
1787
+ declared, or by repair when a post-birth vendor declaration makes it newly absent. Once present it
1788
+ is consumer-owned and never replaced. CI invokes it once before the aggregate project run.
1789
+
1790
+ The configuration conformance test lives in the ordinary `config` project, so `npm test` checks the
1791
+ directory names, readiness files, project declarations, scripts, default-test omission, and
1792
+ publication suffix without contacting a vendor. Like the provisioner it is repaired only when
1793
+ absent, then remains workspace-owned and audit-exempt. Service adoption under `--generated`
1794
+ regenerates the Vite and CI canon and merges only `test:service`, the per-vendor service scripts,
1795
+ and the `prepublishOnly` service suffix into `package.json`; publication metadata and unrelated
1796
+ scripts retain their existing values.
1645
1797
 
1646
1798
  The audit expects the script rather than reporting it foreign, on the derive-time warrant the audit
1647
1799
  section gives. Repair pruning applies the same exclusion, so it never proposes or removes that
1648
- required consumer-owned provisioner.
1800
+ required workspace-owned provisioner.
1649
1801
 
1650
1802
  **Environment isolation.** Scoped TypeScript projects remove the wrong host's globals from each
1651
1803
  environment: core scopes carry the WHATWG web-interop surface and no host at all — no DOM, no Node,
@@ -1826,6 +1978,34 @@ it is not a general-purpose source analyzer. Generated workspaces receive the sa
1826
1978
  module as a host-origin file and run it as a dedicated Node-only `policy` test project over
1827
1979
  `tests/policy.test.ts`.
1828
1980
 
1981
+ **Fleet policy purity.** Both policy files are fleet-owned: scaffold copies its own
1982
+ [`tests/setupPolicy.ts`](../../tests/setupPolicy.ts) into every workspace verbatim and regenerates
1983
+ [`tests/policy.test.ts`](../../tests/policy.test.ts) from the shipped template, so a local addition
1984
+ to either is discarded at the next bump. A second pass therefore guards them against accumulating
1985
+ any one package's architecture. It derives the forbidden identifier tokens from the consuming
1986
+ workspace's own declared package name — the short name's upper-snake and Pascal spellings, deduped —
1987
+ so the pass states no package literal and can never report itself. It reports every identifier that
1988
+ begins with one of those tokens, and it rejects any string or template literal naming a
1989
+ source-environment path under the `src/` prefix. Policy names an environment without that prefix
1990
+ when it must name one at all, so the rule does not fight the files' real needs. The generated test
1991
+ sweeps both files and plants a violation built from the same derived token, so a clean sweep is
1992
+ evidence rather than an instrument that has never fired.
1993
+
1994
+ **Why the token match is a prefix.** A package's architecture reaches these files as identifiers
1995
+ named for the package — `MCP_PATH`, `RouterPlan` — so the identifier must _begin_ with the token.
1996
+ A word that merely holds the token somewhere inside it is fleet vocabulary, not one package's
1997
+ architecture, and the pass leaves it alone. That is the rule on its own terms, and it also settles
1998
+ the collision the fleet actually has: `@orkestrel/contract` derives `Contract`, which
1999
+ [`tests/setupPolicy.ts`](../../tests/setupPolicy.ts) holds inside `isContractProperty`, and that
2000
+ workspace now adopts the pass unchanged. The match stays case-sensitive, so `mcpValue` is not the
2001
+ `MCP` token.
2002
+
2003
+ One collision stays open, and its report is correct rather than a false positive. `@orkestrel/policy`
2004
+ derives `POLICY`, which both files use as an identifier prefix many times over — the pass would be
2005
+ reporting the vocabulary it is built from. A workspace cannot be named for that and adopt this pass;
2006
+ it renames or omits the pass. Every other name in the line derives tokens no policy identifier
2007
+ begins with.
2008
+
1829
2009
  **The configuration suite.** Policy reads source, the `config` project exercises the root
1830
2010
  configuration, and integration builds for real. Every generated workspace therefore receives a
1831
2011
  universal Node-only
@@ -1881,8 +2061,13 @@ the proof-gating table gives them.
1881
2061
  dependency, model, and external-tool readiness scripts at session start. The **`Stop` hook runs only
1882
2062
  `git diff --check`** — a whitespace and conflict-marker check over the working tree, nothing more.
1883
2063
  Bash invocation and sensitive reads are controlled by the **settings permission list, not by a guard
1884
- script**: every Bash command requires explicit approval, including commands Claude Code otherwise
1885
- classifies as read-only. Read-only reviewer, checker, and ecosystem roles carry no Bash tool; the
2064
+ script**. The allow list is closed and holds exactly two entries — `Bash(codex --version)` and
2065
+ `Bash(codex login *)` because the orchestration contract requires a bench-liveness probe and a
2066
+ device-login recovery at session start, and prompting for those would stall every session before
2067
+ planning. Every other Bash command requires explicit approval, including commands Claude Code
2068
+ otherwise classifies as read-only. That list is inherited by every workspace in the line, so a
2069
+ machine-local grant belongs in `settings.local.json`, which `SENSITIVE_HOST_PATH_PATTERN` keeps out
2070
+ of every vendored host. Read-only reviewer, checker, and ecosystem roles carry no Bash tool; the
1886
2071
  orchestrator supplies their diff and status evidence. Bridge, writer, and verifier roles request
1887
2072
  approval when their bounded shell work is needed. Read patterns covering environment files,
1888
2073
  package-manager credentials, credential stores, private keys, key stores, SSH, cloud credentials,
@@ -1905,7 +2090,7 @@ no module API of its own. Seven verbs:
1905
2090
  | `pull` | refresh vendored guides and versions, report drift |
1906
2091
  | `mirror` | refresh every published Orkestrel package guide |
1907
2092
  | `audit` | whole-plan conformance report |
1908
- | `repair` | restore host-owned files and optional generated canon |
2093
+ | `repair` | restore missing canon; optionally replace drifted bytes |
1909
2094
  | `fleet` | audit or repair every workspace under the cwd's children |
1910
2095
  | `catalog` | regenerate the fleet package-catalog table |
1911
2096
 
@@ -1919,8 +2104,10 @@ Orkestrel short name. Other npm packages are not a creation-time flag — add th
1919
2104
  manifest's development dependencies afterwards, and they round-trip through `deriveBlueprint`'s
1920
2105
  extras so the workspace stays audit-clean.
1921
2106
 
1922
- **Other flags.** `--target <path>` selects the directory a verb operates on. `--from <path>` is
1923
- repeatable and points at a local template or catalog source instead of the bundled one.
2107
+ **Other flags.** `--target <path>` selects the directory a single-workspace verb operates on;
2108
+ `fleet --target` is a usage error because fleet's root is always the current directory.
2109
+ `--from <path>` points at a local template source instead of the bundled one and may be passed once
2110
+ to those verbs. It is repeatable only for `catalog`, where each occurrence adds one catalog source.
1924
2111
  On `pull`, `--deps x,y` limits refresh to those declared Orkestrel dependencies; without it, every
1925
2112
  declared dependency mirror is considered.
1926
2113
  `mirror` accepts no dependency selection: its exact npm organization discovery is the operation's
@@ -1928,25 +2115,34 @@ scope, and it fetches guides without registry version or packument requests.
1928
2115
  `--groups a,b` scopes an audit to artifact groups. `--live` adds an upstream freshness check to an
1929
2116
  audit. `--strict` makes a pull or mirror throw on a network fault. `--offline` restricts a catalog to local
1930
2117
  sources. `--prune` opts a repair or fleet run into deleting unexpected files under the three prune
1931
- directories. `--generated` opts a repair or fleet run into restoring generated canon except
1932
- `package.json`; on `audit`, it is inherited if the interactive repair hand-off is accepted.
2118
+ directories. `--generated` opts a repair or fleet run into including generated canon while
2119
+ protecting `package.json` outside its generated service-script keys; on `audit`, it is inherited if
2120
+ the interactive repair hand-off is accepted.
2121
+ `--replace` authorizes repair to discard local changes in the drifted files named by its report; it
2122
+ composes with `--generated`, and is likewise inherited by an accepted audit hand-off.
1933
2123
  `--json` emits one machine-readable value. `--apply` writes, `--yes` skips the confirmation, and
1934
2124
  `-h` or `--help` prints usage.
1935
2125
 
1936
- **Safety model.** Every verb is a dry run by default. On a terminal a write asks for confirmation
1937
- first, defaulting to no; in a script, `--apply` writes and `--yes` skips the question. Every write is
2126
+ **Safety model.** Every verb is a dry run by default. `--apply` is the sole write authorization;
2127
+ `--yes` only skips a confirmation and never authorizes a write or deletion by itself. On a terminal
2128
+ an authorized write asks for confirmation first, defaulting to no; scripts do not prompt. Every write is
1938
2129
  confined to the working directory, so the instruction is to change into it first rather than to pass
1939
2130
  a root. `repair` asks a second, separately defaulted question before deleting anything, and a
1940
- non-interactive session without `--apply` or `--yes` skips pruning rather than guessing. `fleet`
1941
- operates on the immediate children of the working directory and never on the directory itself, and
1942
- it has no root flag at all `repair` is the single-workspace tool.
1943
-
1944
- `fleet` and default `repair` are scoped to host-origin artifacts. `repair` states its selected scope
1945
- in the output; `--generated` widens both verbs to generated files while still excluding starter
1946
- files and `package.json`.
2131
+ session without `--apply` skips pruning regardless of `--yes`. `fleet` operates on the immediate
2132
+ children of the working directory and never on the directory itself. It has no root flag at all:
2133
+ passing `--target` is rejected with exit `2` instead of being silently ignored. `repair` is the
2134
+ single-workspace tool.
2135
+
2136
+ `fleet` and default `repair` are scoped to host-origin artifacts plus absent service-owned starter
2137
+ seams. Both state that selected scope in the output before they act — `repair` once its audit found
2138
+ something to repair, `fleet` once `--apply` authorized a write, naming the number of repositories
2139
+ that write covers. `--generated` widens both verbs to generated
2140
+ files and the manifest's generated service-script keys while still excluding present starter files
2141
+ and package publication metadata. Within either scope, missing files are safe to restore, stale
2142
+ files are report-only by default, and `--replace` is the explicit destructive opt-in.
1947
2143
 
1948
2144
  **Catalog markers.** `catalog` rewrites the block between `<!-- catalog:start -->` and
1949
- `<!-- catalog:end -->` in `.claude/agents/orkestrel.md`. **Ambiguous markers fail before any
2145
+ `<!-- catalog:end -->` in `CATALOG_AGENT_PATH`. **Ambiguous markers fail before any
1950
2146
  mutation**: the file must contain exactly one ordered pair. A missing marker, a reversed pair, or a
1951
2147
  repeated marker of either kind is a coded `TARGET` failure raised before the file is touched, and
1952
2148
  the run reports the drift and any row-count shrink rather than rewriting a file it cannot bound.
@@ -1958,10 +2154,16 @@ The check is a feature detection: **earlier supported Node 22 releases simply us
1958
2154
  roots**. It only ever adds trusted issuers — nothing disables verification — and a failure is a
1959
2155
  silent no-op rather than a crash. Custom PEMs are added through the standard environment variable.
1960
2156
 
1961
- **Exit codes.** `0` is clean or successful, `1` is drift or failure, `2` is a usage error. An audit
1962
- exits non-zero on any drift, foreign files included, which makes it usable directly as a CI gate. A
1963
- pull exits non-zero on any drift or failure whether or not `--strict` was passed; `--strict`
1964
- additionally throws on a network fault. Every unknown verb is a usage error and gets a nearest-match
2157
+ **Exit codes.** `0` is clean or successful, `1` is drift or failure, `2` is a usage error. Repair
2158
+ and fleet use the same dirty-repository predicate: selected-scope drift or any full-plan finding
2159
+ outside that scope keeps exit `1`. A repair that skips stale files therefore exits `1`; a repair
2160
+ exits `0` only when its selected audit and its reported outside scope are both clean. An audit exits
2161
+ non-zero on any drift, foreign files included, which makes it usable directly as a CI gate.
2162
+ `repair --json` carries that same terminal audit after any authorized write, while its `result`
2163
+ records the files the write copied, wrote, skipped, and removed. A pull exits non-zero on any drift
2164
+ or failure whether or not `--strict` was passed, including when
2165
+ other entries were applied successfully; `--strict` additionally throws on a network fault. Every
2166
+ unknown verb is a usage error and gets a nearest-match
1965
2167
  suggestion when one is sufficiently close.
1966
2168
 
1967
2169
  ## Package contents
@@ -1972,7 +2174,8 @@ files, plus `./package.json`. The `scaffold` binary maps to the built executable
1972
2174
 
1973
2175
  The published file set is exactly `dist/src`, `dist/bin`, `dist/host`, and `README.md`. `dist/host`
1974
2176
  is the vendored data root: the byte-preserved host files plus the `manifest.json` recording their
1975
- storage names, destinations, and executable bits. Storage names are un-dotted, because a leading dot
2177
+ storage names, destinations, executable bits, directory roots, and membership digest. Storage names
2178
+ are un-dotted, because a leading dot
1976
2179
  does not survive packaging intact; the manifest is what maps a storage name back to its real
1977
2180
  destination. That is also why the default host is resolved from the installed module's own
1978
2181
  location — the package carries its host data with itself, and a caller-supplied raw repository root
@@ -2361,17 +2564,21 @@ viteMachinery([], ['core', 'browser']) // { browser: true, vue: true, output: tr
2361
2564
  renderViteTest([{ project: 'srcCore' }], false).includes('projects: [srcCore]') // true
2362
2565
  viteHeader(viteMachinery([], ['core', 'browser'])) // the shared header, with browser and Vue support
2363
2566
  coreViteConfig()
2364
- srcViteConfig('browser')
2567
+ srcViteConfig('browser', { name: 'router', src: ['core', 'browser'] })
2365
2568
  appViteConfig('server')
2366
2569
  policyViteProject()
2367
2570
  configViteProject()
2368
2571
  guidesViteProject()
2369
2572
  binViteProject()
2370
2573
  integrationViteProject({ bin: true, integration: true, global: true })
2371
- serviceViteProject()
2372
- viteProjectDefinitions({ integration: true }).includes('export const integration =') // true
2373
- viteProjectRegistrations(['core'], [], { integration: true }).map(({ project }) => project)
2374
- // ['srcCore', 'policy', 'config', 'guides', 'integration']
2574
+ serviceViteProject('claude')
2575
+ viteProjectDefinitions({ integration: true, services: ['claude'] }).includes(
2576
+ 'export const serviceClaude =',
2577
+ ) // true
2578
+ viteProjectRegistrations(['core'], [], { integration: true, services: ['claude'] }).map(
2579
+ ({ project }) => project,
2580
+ )
2581
+ // ['srcCore', 'policy', 'config', 'guides', 'integration', 'serviceClaude']
2375
2582
 
2376
2583
  rootViteConfig(['core', 'server'], { bin: true })
2377
2584
  singleSrcViteConfig('server').includes('srcServer') // true
@@ -2411,6 +2618,7 @@ syncReportOf('./packages/router', [], []) // { clean: true, failed: 0, … }
2411
2618
  import { blueprint, blueprintToPlan, diffPlan } from '@orkestrel/scaffold'
2412
2619
  import {
2413
2620
  createMaterializer,
2621
+ digestHostManifest,
2414
2622
  hostRoot,
2415
2623
  hydratePlan,
2416
2624
  isVacant,
@@ -2424,6 +2632,7 @@ import {
2424
2632
  } from '@orkestrel/scaffold/server'
2425
2633
 
2426
2634
  const host = hostRoot()
2635
+ digestHostManifest([], []) // exact empty manifest membership digest
2427
2636
  readHostManifest(host) // the vendored manifest, or undefined for a raw root
2428
2637
  storagePath('.claude/agents/reviewer.md') // 'claude/agents/reviewer.md'
2429
2638
  locateHostSource(undefined, 'package.json', host)
@@ -2443,7 +2652,8 @@ const current = readTarget(
2443
2652
  './packages/router',
2444
2653
  plan.artifacts.map((artifact) => artifact.path),
2445
2654
  )
2446
- materializer.repair(plan, diffPlan(plan, current), './packages/router')
2655
+ materializer.repair(plan, diffPlan(plan, current), './packages/router') // missing only; stale is skipped
2656
+ materializer.repair(plan, diffPlan(plan, current), './packages/router', true) // replace stale bytes
2447
2657
  materializer.prune('./packages/router', {})
2448
2658
  materializer.destroy()
2449
2659
 
@@ -2638,7 +2848,11 @@ hasOnlyDataProperties({ a: 1 }) // true
2638
2848
  isDenseDataArray(['a'], 10, isPortablePath) // true
2639
2849
  isWritePrecondition({ path: 'package.json', shape: 'absent' }) // true
2640
2850
  isManifestEntry({ storage: 'AGENTS.md', destination: 'AGENTS.md', executable: false }) // true
2641
- isHostManifest({ entries: [], roots: [] }) // true
2851
+ isHostManifest({
2852
+ entries: [],
2853
+ roots: [],
2854
+ digest: 'f98e1531d9fd8fab7e301d1cc944249913d93f48c918a11a753048b877211679',
2855
+ }) // true
2642
2856
  isSyncEventHooks({ done: () => undefined }) // true
2643
2857
  isMaterializerEventHooks({ done: () => undefined }) // true
2644
2858
  isEmitterErrorHandler(() => undefined) // true