@orkestrel/scaffold 0.0.4 → 0.0.6

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.
@@ -276,7 +276,13 @@ journal head as the recovery handle.
276
276
  `tmp/codex/<unit>-brief.md`, one `codex exec --json` streaming to `tmp/codex/<unit>.jsonl`
277
277
  with `--output-last-message`, foreground when it fits the shell cap, backgrounded with the
278
278
  turn ended when it may not — the harness re-invocation is the wait; placeholder loops and
279
- wait-promise reports are deviations. Recovery ladder on interruption: persisted-id
279
+ wait-promise reports are deviations. Every exec names its working directory with `-C`, and an
280
+ exec rooted outside a trusted git repository dies at launch unless `--skip-git-repo-check` is
281
+ passed, so cross-repo and fleet-container work rooted outside a checkout always passes it. A
282
+ launch is not a launch until the journal grows past its header: the bridge confirms the event
283
+ stream advanced beyond the session-configured head
284
+ before it reports the exec started, and treats an instantly-dead journal as a failed launch
285
+ whose tail is the evidence. Recovery ladder on interruption: persisted-id
280
286
  `codex-reply` re-emission → fresh CLI session with the same brief file → for an interrupted
281
287
  CLI exec, the journal survives and the Orchestrator chooses resume or fresh.
282
288
  - **The inverse bridge exists too:** Claude Code exposes `claude mcp serve`, registered in
@@ -297,9 +303,17 @@ journal head as the recovery handle.
297
303
  - At the start of each live Cloud session the user runs `codex login --device-auth` and
298
304
  completes ChatGPT approval in the browser. `scripts/codex.sh` only reports readiness; it
299
305
  never installs, authenticates, logs out, reads the auth cache, or performs a model call.
300
- - If ChatGPT device login is unavailable or expires, the Codex bench is dark. Fall back to
301
- `planner`/`reviewer` (Opus 5) and `builder`, and say so. Never substitute an API key, access
302
- token, copied `auth.json`, or another login flow unless the user changes this policy.
306
+ - A probe that finds the binary present but authentication unavailable starts recovery in the
307
+ same turn instead of recording the bench dark and waiting: the Orchestrator backgrounds
308
+ `codex login --device-auth` with its output captured to `tmp/codex/login.log`, surfaces the
309
+ verification URL and one-time code to the user the moment they appear there, arms a watcher
310
+ on completion, and re-probes `codex login status` when it fires. The bench comes live
311
+ mid-session with no restart; a session that sits dark until the user asks for the login has
312
+ failed the probe, not the bench.
313
+ - If that recovery cannot complete — device login unavailable, declined, or expired — the
314
+ Codex bench is dark. Fall back to `planner`/`reviewer` (Opus 5) and `builder`, and say so.
315
+ Never substitute an API key, access token, copied `auth.json`, or another login flow unless
316
+ the user changes this policy.
303
317
 
304
318
  Codex environment defaults:
305
319
 
@@ -81,6 +81,22 @@ shared-file edits, and tree-wide mutating gates. Return the touched files,
81
81
  diffstat, scoped validation, and deviation state for independent integration and
82
82
  review.
83
83
 
84
+ ## Routing exclusion — defensive negative-test units
85
+
86
+ The provider applies a content-safety filter that terminates a turn mid-run when
87
+ the work requires authoring or reproducing a violation construct, even when the
88
+ purpose is to prove a guard REJECTS it: sandbox escapes, resolution-bypassing
89
+ imports, boundary evasion, injection payloads, credential-handling probes. The
90
+ filter reads the construct, not the intent, so a legitimate negative test trips
91
+ it exactly like an attack would. Observed twice on one unit, at the same point in
92
+ the work, with nothing written to disk either time.
93
+
94
+ Route such a unit to the native Opus `implementer` from the start, and record the
95
+ Codex bench dark for that unit with this reason. Do not soften or obscure a brief
96
+ to slip past the filter — a bench that declines work is a routing fact, not an
97
+ obstacle. The exclusion is per unit: everything else still routes to Sol
98
+ normally, and an audit that merely READS existing negative tests is unaffected.
99
+
84
100
  ## Journals and Windows
85
101
 
86
102
  - Journals, briefs, session files, and last-message files under `tmp/codex/` are
@@ -123,14 +123,14 @@ then runs the configured scoped checks that prove environment isolation.
123
123
  - Lint is a separate complementary gate; neither lint nor root checking replaces
124
124
  environment-isolation checks.
125
125
 
126
- | Scope | `lib` | `types` | Permitted host globals |
127
- | ---------------------------- | --------------------------------- | ----------------- | --------------------------------------------------- |
128
- | `src:core`, `app:core` | `["ESNext"]` | `[]` | None: no DOM, Node, host crypto, console, or timers |
129
- | `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default | DOM; no Node |
130
- | `src:server`, `app:server` | `["ESNext"]` | `["node"]` | Node; no DOM |
131
- | `src:styles` | `["ESNext"]` | `["vite/client"]` | Vite SCSS module declaration only |
132
-
133
- Strict core is load-bearing. A host-dependent helper belongs in its host environment; for example, a `generateId` using host `crypto` belongs in server, not core.
126
+ | Scope | `lib` | `types` | Permitted host globals |
127
+ | ---------------------------- | --------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
128
+ | `src:core`, `app:core` | `["ESNext","WebWorker"]` | `[]` | WHATWG web interop: fetch family, streams, URL, Abort, encoders, crypto, timers, console, DOMException, structuredClone; no DOM, no Node |
129
+ | `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default | DOM; no Node |
130
+ | `src:server`, `app:server` | `["ESNext"]` | `["node"]` | Node; no DOM |
131
+ | `src:styles` | `["ESNext"]` | `["vite/client"]` | Vite SCSS module declaration only |
132
+
133
+ Strict core is load-bearing. A host-dependent helper belongs in its host environment; for example, a `generateId` reading `node:crypto` belongs in server, not core. The worker-only globals — `name`, `onrtctransform`, `close`, `postMessage`, `dispatchEvent`, `location`, `onerror`, `onlanguagechange`, `onoffline`, `ononline`, `onrejectionhandled`, `onunhandledrejection`, `self`, `importScripts`, `fonts`, `caches`, `crossOriginIsolated`, `indexedDB`, `isSecureContext`, `origin`, `scheduler`, `createImageBitmap`, `reportError`, `cancelAnimationFrame`, `requestAnimationFrame`, `onmessage`, `onmessageerror`, `addEventListener`, and `removeEventListener` — are policy-fenced out of core sources, so the `WebWorker` declarations widen the interop surface without admitting a worker host.
134
134
 
135
135
  Build/check config alignment:
136
136
 
@@ -29,7 +29,7 @@ that projection, expressed as data. Rendered defaults ship as versioned package
29
29
  here rather than a hand edit in every workspace.
30
30
 
31
31
  The module is mechanism, never product policy. The judgment calls — the name, the description, the
32
- keywords, which src and app environments, which dependencies, any template override —
32
+ keywords, which src and app environments, which dependencies, any artifact override —
33
33
  belong to the caller. What this module supplies is the closed vocabularies, the variant matrix as
34
34
  data, exact-record validation, a fail-closed gate, a deterministic pin, and lossless projections.
35
35
 
@@ -133,6 +133,7 @@ From [`types.ts`](../../src/core/types.ts).
133
133
  | `BuildFormat` | type |
134
134
  | `SrcDefinition` | interface |
135
135
  | `AppDefinition` | interface |
136
+ | `ViteMachinery` | interface |
136
137
  | `Origin` | type |
137
138
  | `Group` | type |
138
139
  | `Category` | type |
@@ -183,6 +184,12 @@ is `'INVALID' | 'BLOCKED' | 'DESTROYED' | 'TARGET' | 'WRITE' | 'FETCH'`.
183
184
  environment contributes, its test-project label, and — on the `src` axis — its `exports` subpath
184
185
  and build formats, or — on the `app` axis — its optional runtime entry.
185
186
 
187
+ `ViteMachinery` names the three host-specific pipelines a workspace's generated `vite.config.ts` may
188
+ carry: `browser` for the CSS pipeline and the Playwright-backed browser test project, `vue` for the
189
+ single-file-component, HTML, and development-server machinery an application browser environment
190
+ needs, and `output` for build-output containment. It never selects a boundary guarantee — those ship
191
+ in every shape, as the compilers section sets out.
192
+
186
193
  `Blueprint` is the closed input spec:
187
194
 
188
195
  ```ts
@@ -363,7 +370,9 @@ value lists behind their literal unions. `SRC_MATRIX` is the `src` environment m
363
370
  data — each environment's `configs/src` files, test-project label, `exports` subpath, and build
364
371
  formats. `APP_MATRIX` is its application sibling, adding the runtime entry where an environment produces
365
372
  one (`app/browser/index.html`, `app/server/main.ts`). `HOST_PATHS` is the ordered list of
366
- byte-copied host artifacts.
373
+ byte-copied host artifacts, and it is the staging manifest rather than the per-plan carried set:
374
+ `stageHost` vendors every path on it, while each plan carries the subset `selectHostPaths` selects
375
+ for that one workspace.
367
376
 
368
377
  The bounds are public because they are part of the contract, not implementation trivia.
369
378
  `MAX_ARTIFACT_BYTES` caps one artifact at 5 MiB and `MAX_TOTAL_ARTIFACT_BYTES` caps one blueprint,
@@ -537,7 +546,8 @@ entity allocation.
537
546
  then checks every override against the exact artifact set the plan would write. An override whose
538
547
  `path` matches no planned artifact, targets a `host`-origin artifact, or targets the
539
548
  blueprint-owned `package.json` publication boundary is a blocking question rather than a silent
540
- no-op.
549
+ no-op. An override that clears all three lands a `warnings` entry naming the path it replaces — the
550
+ declaration is accepted, and it is never accepted silently.
541
551
 
542
552
  ### Validators — server
543
553
 
@@ -729,11 +739,13 @@ From [`helpers.ts`](../../src/core/helpers.ts).
729
739
  | `contentByteLength` | function |
730
740
  | `contentToHex` | function |
731
741
  | `snapshotOf` | function |
742
+ | `selectHostPaths` | function |
732
743
  | `findPathConflict` | function |
733
744
  | `findFileConflict` | function |
734
745
  | `validateDependencyArray` | function |
735
746
  | `validateBlueprint` | function |
736
747
  | `manifestToDependencies` | function |
748
+ | `manifestToName` | function |
737
749
  | `rangeToFreshness` | function |
738
750
  | `computeHash` | function |
739
751
  | `stableStringify` | function |
@@ -779,14 +791,20 @@ report projections count with.
779
791
  `snapshotOf`, `contentToHex`, `contentToBytes`, `contentByteLength`, `contentCodePoint`, and
780
792
  `bytesToHex` are the host-independent byte leaves that make exact comparison possible without a
781
793
  host encoder or buffer; an unpaired surrogate encodes as `U+FFFD` rather than throwing.
782
- `findPathConflict` finds the first exact or case-insensitive collision in a path list, and
783
- `findFileConflict` additionally rejects a file that would sit inside another planned path.
794
+ `selectHostPaths` is the one-owner filter plan assembly applies before it carries anything: it
795
+ returns the host paths in input order minus `guides/src/<name>.md`, so a workspace never plans a
796
+ vendored mirror of the guide it writes itself. `findPathConflict` finds the first exact or
797
+ case-insensitive collision in a path list, and `findFileConflict` additionally rejects a file that
798
+ would sit inside another planned path — the loud backstop behind that selection.
784
799
 
785
800
  `validateBlueprint` and `validateDependencyArray` are the semantic pass. The array validator is
786
801
  pure — it returns its questions and the set of names it saw, so the caller can apply the
787
802
  cross-array overlap rules on top. `manifestToDependencies` reads a manifest's `dependencies`,
788
803
  `devDependencies`, and `peerDependencies` in that order, keeps only own data sections and scoped
789
- names, deduplicates, and never throws. `rangeToFreshness` applies the exact-pin comparison; the `missing` and `failed`
804
+ names, deduplicates, and never throws. `manifestToName` is its self-reading sibling over the same
805
+ text: the manifest's own string `name`, or `undefined` when the text is oversized, malformed,
806
+ rootless, or nameless — the projection that lets a target recognize itself in its own declared
807
+ dependencies. `rangeToFreshness` applies the exact-pin comparison; the `missing` and `failed`
790
808
  verdicts come from the fetch layer, never from this pure comparison.
791
809
 
792
810
  `computeHash` is a deterministic FNV-1a digest and `stableStringify` a key-order-independent
@@ -897,7 +915,9 @@ dependency-less stand-in. Environments come from `src/<environment>/` and `app/<
897
915
  scoped entries, with an optional peer recovered from `peerDependenciesMeta`; and `extras` is every
898
916
  development dependency minus the generated baseline and minus anything already declared as a
899
917
  dependency or peer, so a hand-added development dependency round-trips and stays audit-clean.
900
- `overrides` is always empty, because derivation cannot know a caller's override intent.
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.
901
921
 
902
922
  `storagePath`, `stageHost`, `readHostManifest`, `locateHostSource`, `remapArtifactPath`, and
903
923
  `hydratePlan` are the vendored-host path. `storagePath` maps a repo-relative path to its un-dotted
@@ -919,7 +939,9 @@ anything.
919
939
  `consumeCatalogAllowance` decrements the single shared entry allowance and throws `TARGET` before an
920
940
  over-budget traversal continues. `discoverPackages` requires a real, unlinked root and lists its
921
941
  immediate child directories whose bounded manifest names a scoped package, skipping anything else
922
- silently. `catalogPackages` applies one allowance across every root and directory rather than
942
+ silently. A control-bearing child directory fails closed before its manifest is read and the
943
+ untrusted name is never reflected in the diagnostic. `catalogPackages` applies one allowance across
944
+ every root and directory rather than
923
945
  resetting a per-root budget, then draws each description from the first paragraph of the first
924
946
  blockquote of that package's own bounded guide via `guideToDescription`; a missing guide, an
925
947
  unreadable or oversized one, or one with no blockquote yields an empty description rather than an
@@ -946,6 +968,7 @@ From [`compilers.ts`](../../src/core/compilers.ts).
946
968
  | `devDependenciesFor` | function |
947
969
  | `packageManifest` | function |
948
970
  | `rootTsconfig` | function |
971
+ | `viteMachinery` | function |
949
972
  | `viteHeader` | function |
950
973
  | `policyViteProject` | function |
951
974
  | `singleSrcViteConfig` | function |
@@ -986,11 +1009,40 @@ optional metadata, and engines.
986
1009
 
987
1010
  `rootTsconfig` emits the root compiler options and one path alias per declared environment;
988
1011
  `coreTsconfig`, `srcTsconfig`, and `appTsconfig` emit the scoped configurations that remove the
989
- wrong host's globals from each environment. `viteHeader` renders the shared header the alias block
990
- derived from the tsconfig paths, plus the environment-boundary plugin. `coreViteConfig`,
991
- `srcViteConfig`, and `appViteConfig` emit the thin per-target wrappers; `rootViteConfig`,
992
- `singleSrcViteConfig`, and `applicationViteConfig` emit the root configuration for a
993
- library-only, single non-core `src` environment, and application-bearing workspace respectively;
1012
+ wrong host's globals from each environment. A core scope is the interesting one: `lib` is
1013
+ `["ESNext", "WebWorker"]` and `types` stays `[]`, which declares the WHATWG surface that is
1014
+ identical across Node, browsers, and workers — `fetch` and its request/response/header types,
1015
+ streams, `URL`, `AbortController`, the text encoders, `crypto`, timers, `console`, `DOMException`,
1016
+ `structuredClone` while leaving `document`, `window`, and every `node:*` type unresolvable. That
1017
+ is one declaration set for a host-independent module, not a host. `viteHeader` renders the shared
1018
+ header — the alias block
1019
+ derived from the tsconfig paths, plus the environment-boundary plugin — and `viteMachinery` is the
1020
+ one place the header's axes are derived, read by `rootViteConfig`, `singleSrcViteConfig`,
1021
+ `applicationViteConfig`, and `configArtifacts` alike so no caller can invent a fourth answer.
1022
+
1023
+ **The boundary guarantees do not vary by blueprint.** Every generated `vite.config.ts` — a
1024
+ `core`-only library, an application of `app/core` alone, or the full six-environment workspace —
1025
+ emits `environmentBoundary`, its `resolveId` / `load` / `buildEnd` walks, the module-graph AST audit
1026
+ (`environmentAssetSources`, `parseSync`, `Visitor`), and stylesheet rejection (`isStylesheetPath`
1027
+ plus its `environmentPathError` / `environmentSourceError` clauses). Those enforce owner-independent
1028
+ laws: core stays host-independent whatever else the workspace declares, a server module never
1029
+ 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,
1031
+ along the three `ViteMachinery` axes:
1032
+
1033
+ | Machinery | Emitted when |
1034
+ | ----------------------------------------------------------------- | ------------------------------------ |
1035
+ | CSS pipeline (`ENVIRONMENT_CSS`, `preprocessCSS`, `isCSSRequest`) | a `src` or `app` browser environment |
1036
+ | Playwright provider and `hasChromium` | a `src` or `app` browser environment |
1037
+ | Vue plugin, HTML boundary, browser development server | an `app` browser environment |
1038
+ | Output containment (`outputBoundary`, `enforceOutputPath`) | anything the workspace builds |
1039
+
1040
+ An application of `app/core` alone is the sole shape that builds nothing, so it is the sole shape
1041
+ without output containment — and it still carries every boundary guarantee above.
1042
+
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;
994
1046
  `policyViteProject` emits the dedicated Node-only repository-policy test project.
995
1047
 
996
1048
  `configArtifacts`, `sourceArtifacts`, `applicationArtifacts`, `testArtifacts`, and `guideArtifacts`
@@ -1094,8 +1146,9 @@ The public methods of each behavioral interface, one table per type.
1094
1146
  `Scaffolding`; the optional group selection scopes the plan to those artifact groups.
1095
1147
  `audit(blueprint, current, groups?)` compiles and then diffs the resulting plan against the
1096
1148
  caller-supplied current content; a gated blueprint returns `complete: false` with the gate's
1097
- blocking questions and zero findings. `destroy()` is idempotent teardown. The interface also
1098
- exposes the readonly `emitter`.
1149
+ blocking questions and zero findings, and a complete one carries the gate's advisories on that same
1150
+ `questions` field. `destroy()` is idempotent teardown. The interface also exposes the readonly
1151
+ `emitter`.
1099
1152
 
1100
1153
  #### `PlanManagerInterface`
1101
1154
 
@@ -1159,12 +1212,14 @@ in-flight request. The interface also exposes the readonly `emitter`.
1159
1212
  `compile` runs three stages in fixed order and records each as a `CompileRecord` carrying its input,
1160
1213
  its output, whether it failed, and any error text.
1161
1214
 
1162
- 1. **draft** — `blueprintToPlan` selects the covered groups, drafts each group's artifacts, appends
1163
- the host set, applies overrides, and pins the draft. A throw here records a `draft` failure coded
1215
+ 1. **draft** — `blueprintToPlan` selects the covered groups, drafts each group's artifacts, carries
1216
+ the selected host set every vendored host path except the workspace's own guide applies
1217
+ overrides, and pins the draft. A throw here records a `draft` failure coded
1164
1218
  `INVALID`, emits `error`, marks the remaining two stages skipped, and returns incomplete.
1165
1219
  2. **gate** — `validatePlan` runs the semantic pass over the blueprint and checks every override
1166
- against the drafted artifact set. Blocking questions fail the stage; a dependency outside the
1167
- vendored guide set contributes a non-blocking advisory question instead.
1220
+ against the drafted artifact set. Blocking questions fail the stage; an accepted override and a
1221
+ dependency outside the vendored guide set each contribute a non-blocking advisory question
1222
+ instead.
1168
1223
  3. **pin** — a host-origin pointer artifact is appended for each non-vendored dependency, and
1169
1224
  `pinPlan` fills `trace` and `hash` from the plan's own content.
1170
1225
 
@@ -1196,7 +1251,8 @@ how it is audited, and whether it may ever be overwritten.
1196
1251
  - **`host`** — byte-copied from the vendored data root. These are the shared files a whole fleet
1197
1252
  keeps identical: the root instruction documents and licence, the agent, rule, and skill
1198
1253
  directories, the session scripts, the repository coding-law policy module, the byte-identical root
1199
- dotfiles, and the two guide mirrors every workspace carries. `HOST_PATHS` is the exact list.
1254
+ dotfiles, and the two line guide mirrors a workspace carries for contracts other than its own.
1255
+ `HOST_PATHS` is the exact vendored list; what a given plan carries is `selectHostPaths` of it.
1200
1256
  - **`template`** — filled from a frozen template definition by a pure fill engine. These are
1201
1257
  starter files: source stubs, test stubs, the starter guide, the README.
1202
1258
  - **`computed`** — derived by this package's own combination logic. These are the structural files:
@@ -1225,18 +1281,31 @@ anything, so a mature workspace's hand-written source, tests, guides, and manife
1225
1281
  overwritten with a stub. A consequence worth stating plainly: the generated
1226
1282
  `.github/workflows/ci.yml` is a **computed** artifact, so **user-owned CI is never repaired**. Once
1227
1283
  a workspace has its own workflow, that copy stands, and any change to it is an ordinary edit in that
1228
- workspace.
1284
+ workspace. Audit still compares it because computed artifacts are content-aware canon. A legitimate
1285
+ difference that the blueprint cannot express is a canon gap: add the missing blueprint axis rather
1286
+ than forking the computed file in one repository.
1229
1287
 
1230
1288
  Overrides respect the same boundary from the other direction. `applyOverrides` never replaces a
1231
1289
  host-origin artifact and never replaces `package.json`; the gate turns either attempt — and an
1232
1290
  override matching no planned artifact at all — into a blocking question rather than a silent no-op.
1233
-
1234
- Guide mirrors are the one place ownership is conditional. A dependency this package vendors a
1235
- byte-identical mirror for gets a real host-origin copy of `guides/src/<short>.md`. Any other
1236
- dependency gets a host-origin _pointer_ artifact plus a non-blocking question, never a fabricated
1237
- mirror; on materialization that pointer degrades to a short stub, and `scaffold pull` fetches the
1238
- real thing. That degrade is scoped exactly to guide pointers: any other missing manifest entry
1239
- means a corrupt or truncated vendored manifest, and fails closed.
1291
+ What survives those three refusals is applied and announced: the gate carries a non-blocking
1292
+ advisory naming each replaced path, and that advisory rides the `Scaffolding` and the `Audit` all
1293
+ the way through the library result.
1294
+
1295
+ Guide mirrors are the one place ownership is conditional, and the law is one owner per guide path.
1296
+ **A workspace mirrors every line guide except its own.** When the name matches the guide package
1297
+ on `guides/src/guide.md`, this package on `guides/src/scaffold.md` the workspace itself is the
1298
+ owner, keeping that path as its **template**-origin starter guide, and `selectHostPaths` drops the
1299
+ vendored mirror so the path is contributed exactly once. For every other contract the mirror is the
1300
+ owner: a dependency this package vendors a byte-identical mirror for gets a real host-origin copy of
1301
+ `guides/src/<short>.md`, contributed once whether it arrives through the host set or through the
1302
+ dependency, so a package depending on `@orkestrel/guide` plans one `guides/src/guide.md` rather than
1303
+ two. Any other dependency gets a host-origin _pointer_ artifact plus a non-blocking question, never
1304
+ a fabricated mirror; on materialization that pointer degrades to a short stub, and `scaffold pull`
1305
+ fetches the real thing. That degrade is scoped exactly to guide pointers: any other missing manifest
1306
+ entry means a corrupt or truncated vendored manifest, and fails closed. Selection is the law and
1307
+ `findFileConflict` is its backstop: two artifacts at one path refuse the plan rather than racing to
1308
+ be the last writer.
1240
1309
 
1241
1310
  ## Audit, repair, and prune
1242
1311
 
@@ -1286,10 +1355,15 @@ because there is nothing to explain.
1286
1355
  `pull` is the target-aware composition. It reads the target's declared scoped dependencies from its
1287
1356
  manifest, rejects any explicit selection the target does not declare, builds the reference map from
1288
1357
  the target's own `guides/src/<short>.md` mirrors, fetches guides and versions under one shared
1289
- allowance, and assembles a report whose `clean` flag requires both no drift and no failures. `write`
1290
- then commits only the `behind` guides never `current`, `missing`, or `failed`, none of which carry
1291
- trustworthy content under the same containment and precondition law `Materializer` enforces,
1292
- including a baseline digest check against what is actually on disk.
1358
+ allowance, and assembles a report whose `clean` flag requires both no drift and no failures. A
1359
+ target that declares itself is the one asymmetry, and it follows the same single-owner law: the
1360
+ guide pass drops the self dependency, so `pull` never fetches or writes a workspace's own contract
1361
+ guide over the copy that workspace owns, while the version pass keeps it and still reports its
1362
+ freshness. A `--live` audit reads upstream through the same two passes and applies the same
1363
+ self-exclusion, so the freshness a workspace reports about itself never depends on which verb asked.
1364
+ `write` then commits only the `behind` guides — never `current`, `missing`, or `failed`,
1365
+ none of which carry trustworthy content — under the same containment and precondition law
1366
+ `Materializer` enforces, including a baseline digest check against what is actually on disk.
1293
1367
 
1294
1368
  `catalog` builds the fleet package catalog from three reads per entry. The registry's exact
1295
1369
  organization package list is authoritative and unconditionally required. Each package's own registry
@@ -1331,10 +1405,20 @@ a fixed, interleaved order so aggregates sit immediately before their per-enviro
1331
1405
  live generated-consumer integration gate for the scaffold engine itself
1332
1406
 
1333
1407
  **Environment isolation.** Scoped TypeScript projects remove the wrong host's globals from each
1334
- environment: core scopes carry no DOM, no Node, and no host globals at all; browser scopes carry DOM
1335
- and no Node; server scopes carry Node and no DOM. Lint restricts declared package, alias, and
1336
- conventional relative imports in the same directions. Neither replaces the other, and neither
1337
- replaces the build.
1408
+ environment: core scopes carry the WHATWG web-interop surface and no host at all no DOM, no Node,
1409
+ no `vite/client`; browser scopes carry DOM and no Node; server scopes carry Node and no DOM. The
1410
+ worker-only globals the `WebWorker` declarations would otherwise admit `name`, `onrtctransform`,
1411
+ `close`, `postMessage`, `dispatchEvent`, `location`, `onerror`, `onlanguagechange`, `onoffline`,
1412
+ `ononline`, `onrejectionhandled`, `onunhandledrejection`, `self`, `importScripts`, `fonts`, `caches`,
1413
+ `crossOriginIsolated`, `indexedDB`, `isSecureContext`, `origin`, `scheduler`, `createImageBitmap`,
1414
+ `reportError`, `cancelAnimationFrame`, `requestAnimationFrame`, `onmessage`, `onmessageerror`,
1415
+ `addEventListener`, and `removeEventListener` — are fenced out of `src/core` and `app/core` sources
1416
+ by the policy suite, so the declarations widen what a host-independent module may call without
1417
+ widening where it may run. On every TypeScript bump, derive this list from the module-scope
1418
+ global-object `declare var` and `declare function` declarations in the installed
1419
+ `lib.webworker.d.ts`, then subtract values supplied by `lib.esnext*` or current Node globals. Lint
1420
+ restricts declared package, alias, and conventional relative imports in the same directions.
1421
+ Neither replaces the other, and neither replaces the build.
1338
1422
 
1339
1423
  **The generated build boundary.** The emitted configuration carries an environment-boundary plugin
1340
1424
  that resolves the real module graph rather than re-implementing a parser. **TypeScript and
@@ -1454,7 +1538,8 @@ cannot express — that a centralized module exports every top-level declaration
1454
1538
  implementation files hold one class and no stray module-scope declaration, that no function is
1455
1539
  declared inside another function outside a directly-passed callback, that interface properties are
1456
1540
  readonly, that privacy is a runtime `#` field rather than a TypeScript modifier, that a barrel
1457
- re-exports only through `export *`, and that a computed dynamic import cannot smuggle a
1541
+ re-exports only through `export *`, that a core source never names a worker-only global the
1542
+ `WebWorker` declarations expose, and that a computed dynamic import cannot smuggle a
1458
1543
  cross-environment dependency past the declared import rules. Vue components are inspected for the
1459
1544
  same evasions. It is a complement to lint and typecheck, never a second type system, and it is not a
1460
1545
  general-purpose source analyzer. Generated workspaces receive the same exported policy module as a
@@ -1464,7 +1549,11 @@ host-origin file and run it as a dedicated Node-only `policy` test project over
1464
1549
  **Real browser capability.** Browser test projects are gated on the real executable: the generated
1465
1550
  configuration and the generated policy test both probe `existsSync(chromium.executablePath())`. A
1466
1551
  browser suite runs when a real Chromium is installed and is skipped honestly when it is not, rather
1467
- than being faked.
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.
1468
1557
 
1469
1558
  **Continuous integration.** The generated workflow runs on push and pull request, on
1470
1559
  `ubuntu-latest`, with read-only contents permission, a 60-minute timeout, and a matrix that **tests
@@ -1872,6 +1961,7 @@ import {
1872
1961
  hostGroup,
1873
1962
  packageManifest,
1874
1963
  paritySpecifiers,
1964
+ selectHostPaths,
1875
1965
  sourceArtifacts,
1876
1966
  srcVariant,
1877
1967
  testArtifacts,
@@ -1881,6 +1971,7 @@ const spec = blueprint('router', { src: ['core'], app: ['core', 'server'] })
1881
1971
  const members = blueprintToMembers(spec)
1882
1972
 
1883
1973
  hostGroup('AGENTS.md') // 'docs'
1974
+ selectHostPaths(['guides/src/router.md', 'LICENSE'], spec.name) // ['LICENSE'] — never its own guide
1884
1975
  srcVariant(['core', 'server']) // 'multi'
1885
1976
  entryFields(['browser']).main // './dist/src/browser/index.js'
1886
1977
  dualCondition('./dist/src/core/index')
@@ -1924,6 +2015,7 @@ import {
1924
2015
  srcTsconfig,
1925
2016
  srcViteConfig,
1926
2017
  viteHeader,
2018
+ viteMachinery,
1927
2019
  } from '@orkestrel/scaffold'
1928
2020
 
1929
2021
  rootTsconfig(['core'], ['core', 'server'])
@@ -1931,7 +2023,9 @@ coreTsconfig()
1931
2023
  srcTsconfig('server')
1932
2024
  appTsconfig('browser', true)
1933
2025
 
1934
- viteHeader(true, true) // the shared header, with browser and Vue support
2026
+ viteMachinery(['core']) // { browser: false, vue: false, output: true }
2027
+ viteMachinery([], ['core', 'browser']) // { browser: true, vue: true, output: true }
2028
+ viteHeader(viteMachinery([], ['core', 'browser'])) // the shared header, with browser and Vue support
1935
2029
  coreViteConfig()
1936
2030
  srcViteConfig('browser')
1937
2031
  appViteConfig('server')
@@ -1945,7 +2039,12 @@ applicationViteConfig([], ['core', 'server']).includes('appServer') // true
1945
2039
  ### Reading declared dependencies and comparing freshness
1946
2040
 
1947
2041
  ```ts
1948
- import { isBehind, manifestToDependencies, rangeToFreshness } from '@orkestrel/scaffold'
2042
+ import {
2043
+ isBehind,
2044
+ manifestToDependencies,
2045
+ manifestToName,
2046
+ rangeToFreshness,
2047
+ } from '@orkestrel/scaffold'
1949
2048
  import {
1950
2049
  guideStub,
1951
2050
  packageShortName,
@@ -1954,6 +2053,7 @@ import {
1954
2053
  } from '@orkestrel/scaffold/server'
1955
2054
 
1956
2055
  manifestToDependencies('{"dependencies":{"@orkestrel/contract":"^0.0.7"}}')
2056
+ manifestToName('{"name":"@orkestrel/router"}') // '@orkestrel/router' — the target's own name
1957
2057
  rangeToFreshness('^0.0.7', '0.0.7') // 'current'
1958
2058
  isBehind(rangeToFreshness('^0.0.7', '0.0.9')) // true
1959
2059
 
@@ -2186,7 +2286,8 @@ isMissingPathError(caught) // true only for an ENOENT error
2186
2286
  ## Tests
2187
2287
 
2188
2288
  - [`tests/src/core/helpers.test.ts`](../../tests/src/core/helpers.test.ts) — the pure leaves: table
2189
- alignment, byte encoding, snapshots, conflicts, projections, hashing, and format-stable JSON.
2289
+ alignment, byte encoding, snapshots, host selection, conflicts, projections, hashing, and
2290
+ format-stable JSON.
2190
2291
  - [`tests/src/core/builders.test.ts`](../../tests/src/core/builders.test.ts) — the blueprint,
2191
2292
  dependency, override, and member builders, including optional-field omission.
2192
2293
  - [`tests/src/core/validators.test.ts`](../../tests/src/core/validators.test.ts) — every guard and
@@ -2194,7 +2295,8 @@ isMissingPathError(caught) // true only for an ENOENT error
2194
2295
  - [`tests/src/core/shapers.test.ts`](../../tests/src/core/shapers.test.ts) — per-shape guard
2195
2296
  exactness, schema essentials, seeded generation, and parse round-trips.
2196
2297
  - [`tests/src/core/compilers.test.ts`](../../tests/src/core/compilers.test.ts) — every drafted
2197
- group, the manifest and exports combination rules, and the emitted configuration text.
2298
+ group, the manifest and exports combination rules, the one-owner guide law for a workspace that
2299
+ names a line guide, and the emitted configuration text.
2198
2300
  - [`tests/src/core/Compiler.test.ts`](../../tests/src/core/Compiler.test.ts) — the three-stage
2199
2301
  pipeline, the fail-closed gate, the emission sequences, and post-destroy behavior.
2200
2302
  - [`tests/src/core/PlanManager.test.ts`](../../tests/src/core/PlanManager.test.ts) — content-hash
@@ -32,8 +32,10 @@ valid_effort() {
32
32
 
33
33
  if codex login status >/dev/null 2>&1; then
34
34
  auth="authentication ready"
35
+ recovery=""
35
36
  else
36
37
  auth="authentication unavailable"
38
+ recovery='codex login --device-auth'
37
39
  fi
38
40
 
39
41
  if valid_model "$analyst_model" &&
@@ -46,4 +48,7 @@ else
46
48
  fi
47
49
 
48
50
  echo "codex.sh: bench detected; ${auth}; ${routes}."
51
+ if [ -n "$recovery" ]; then
52
+ echo "codex.sh: to recover, run \`${recovery}\`."
53
+ fi
49
54
  exit 0