@tiphys/kernel 0.1.0 → 0.2.0

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 (94) hide show
  1. package/AGENTS.md +56 -4
  2. package/assurance-modes.yaml +23 -2
  3. package/dist/bin/tiphys.js +86 -8
  4. package/dist/src/adapters/load.d.ts +202 -0
  5. package/dist/src/adapters/load.js +440 -0
  6. package/dist/src/brief.js +27 -20
  7. package/dist/src/checks.d.ts +720 -9
  8. package/dist/src/checks.js +1874 -163
  9. package/dist/src/cli.js +11 -0
  10. package/dist/src/commands/brief.js +27 -4
  11. package/dist/src/commands/cutover.d.ts +35 -0
  12. package/dist/src/commands/cutover.js +448 -0
  13. package/dist/src/commands/doctor.d.ts +229 -0
  14. package/dist/src/commands/doctor.js +968 -27
  15. package/dist/src/commands/init.d.ts +3 -3
  16. package/dist/src/commands/init.js +57 -8
  17. package/dist/src/commands/lock.d.ts +33 -0
  18. package/dist/src/commands/lock.js +117 -6
  19. package/dist/src/commands/next.d.ts +130 -0
  20. package/dist/src/commands/next.js +597 -0
  21. package/dist/src/commands/pool.js +12 -1
  22. package/dist/src/commands/resume.d.ts +1 -0
  23. package/dist/src/commands/resume.js +88 -0
  24. package/dist/src/commands/spawn.js +51 -2
  25. package/dist/src/commands/status.d.ts +6 -4
  26. package/dist/src/commands/status.js +6 -4
  27. package/dist/src/commands/sync.d.ts +47 -0
  28. package/dist/src/commands/sync.js +341 -0
  29. package/dist/src/commands/teardown.js +10 -2
  30. package/dist/src/commands/validate.js +70 -0
  31. package/dist/src/cutover.d.ts +584 -0
  32. package/dist/src/cutover.js +1444 -0
  33. package/dist/src/exclusion.d.ts +389 -0
  34. package/dist/src/exclusion.js +843 -0
  35. package/dist/src/exec/env.d.ts +152 -2
  36. package/dist/src/exec/env.js +146 -2
  37. package/dist/src/fleet.d.ts +172 -0
  38. package/dist/src/fleet.js +219 -1
  39. package/dist/src/gates/citations.js +7 -1
  40. package/dist/src/gates/coverage.d.ts +113 -22
  41. package/dist/src/gates/coverage.js +166 -31
  42. package/dist/src/gates/credentials.d.ts +159 -0
  43. package/dist/src/gates/credentials.js +221 -2
  44. package/dist/src/gates/gate-classes.d.ts +56 -0
  45. package/dist/src/gates/gate-classes.js +633 -0
  46. package/dist/src/gates/merge-preconditions.d.ts +319 -0
  47. package/dist/src/gates/merge-preconditions.js +932 -0
  48. package/dist/src/gates/red-witness.js +105 -13
  49. package/dist/src/gates/run.d.ts +49 -1
  50. package/dist/src/gates/run.js +83 -5
  51. package/dist/src/gates/schemas/phase-declaration.schema.json +45 -0
  52. package/dist/src/gates/suite.js +48 -7
  53. package/dist/src/hooks.d.ts +55 -3
  54. package/dist/src/hooks.js +69 -6
  55. package/dist/src/index.d.ts +31 -0
  56. package/dist/src/index.js +30 -0
  57. package/dist/src/lock.d.ts +82 -4
  58. package/dist/src/lock.js +314 -22
  59. package/dist/src/model-resolution.d.ts +159 -0
  60. package/dist/src/model-resolution.js +307 -0
  61. package/dist/src/path-identity.d.ts +32 -0
  62. package/dist/src/path-identity.js +38 -0
  63. package/dist/src/pool.d.ts +197 -1
  64. package/dist/src/pool.js +289 -22
  65. package/dist/src/roles.d.ts +31 -0
  66. package/dist/src/roles.js +42 -0
  67. package/dist/src/spawn.d.ts +307 -2
  68. package/dist/src/spawn.js +690 -19
  69. package/dist/src/status.d.ts +27 -2
  70. package/dist/src/status.js +34 -5
  71. package/dist/src/task.d.ts +295 -55
  72. package/dist/src/task.js +125 -123
  73. package/dist/src/teardown.d.ts +7 -0
  74. package/dist/src/teardown.js +120 -12
  75. package/dist/src/validate.d.ts +44 -11
  76. package/dist/src/validate.js +44 -34
  77. package/dist/src/watcher.js +1 -11
  78. package/dist/src/witness/run.d.ts +32 -7
  79. package/dist/src/witness/run.js +76 -30
  80. package/dist/src/witness/spec.d.ts +168 -0
  81. package/dist/src/witness/spec.js +240 -18
  82. package/dist/tsconfig.src.tsbuildinfo +1 -1
  83. package/gate-registry.yaml +136 -0
  84. package/gates.manifest.json +63 -1
  85. package/package.json +18 -3
  86. package/roles/implementer.md +3 -0
  87. package/schemas/README.md +1 -0
  88. package/schemas/assurance-modes.schema.json +1 -1
  89. package/schemas/charter.schema.json +19 -0
  90. package/schemas/cutover-state.schema.json +64 -0
  91. package/schemas/executor-record.schema.json +36 -0
  92. package/schemas/model-resolution.schema.json +362 -0
  93. package/schemas/verdict.schema.json +9 -3
  94. package/schemas/write-bypass.schema.json +69 -0
@@ -1,5 +1,6 @@
1
+ import type { ChildEnvExtension } from "./exec/env.ts";
1
2
  import type { Fleet } from "./fleet.ts";
2
- import type { GuardResult, TaskMeta, TaskShape } from "./task.ts";
3
+ import type { CredentialHandoverRecord, GuardResult, PayloadClass, TaskMeta, TaskShape } from "./task.ts";
3
4
  /**
4
5
  * tiphys spawn (kernel plan v1, M1-P4 step 4): worktree, brief, turn-end
5
6
  * hook, task meta and the executor launch in ONE command.
@@ -62,7 +63,155 @@ export interface ExecutorRequest {
62
63
  * declared escape hatch; an adapter must never widen it on its own.
63
64
  */
64
65
  env: Record<string, string> | undefined;
66
+ /**
67
+ * THE ASSEMBLED BRIEF, and it is NOT optional (M4-P3 criterion 1).
68
+ *
69
+ * The brief is the agent payload's entire input, and until this phase the
70
+ * request did not carry it at all, although the call site already had it:
71
+ * `assembleBrief` returns the path and `spawnTask` pushes it onto
72
+ * `createdFiles` before the launch. So an optional `briefPath` would be an
73
+ * optionality the kernel never exercises, which is a field that cannot go
74
+ * red: every production path supplies it and no test could construct the
75
+ * absent case without inventing one.
76
+ *
77
+ * The three fields below it are `string | undefined` for the opposite
78
+ * reason: nothing in the kernel produces them, they arrive from the caller,
79
+ * and an adapter that needs one says so through `requires` rather than
80
+ * hoping.
81
+ */
82
+ briefPath: string;
83
+ /**
84
+ * The role the payload is being asked to play, verbatim as the caller named
85
+ * it. The kernel neither interprets it nor holds a vocabulary for it; the
86
+ * role briefs are a shipped artifact and the mapping from a role to a brief
87
+ * is the plugin's business.
88
+ */
89
+ role: string | undefined;
90
+ /**
91
+ * THE DECLARED TIER, VERBATIM, AND NEVER A MODEL NAME (M4-P3, the
92
+ * zero-vendor-names requirement). Whatever `role-model-config.yaml`
93
+ * declares, `strongest` or `cheaper` or anything else, crosses this seam
94
+ * unaltered. The tier-to-model mapping lives in the plugin: a mapping in
95
+ * `src/` is what would close off every harness that is not the one it names
96
+ * (delivery/plan/m4-intake.md:377).
97
+ */
98
+ declaredTier: string | undefined;
99
+ /**
100
+ * The delivery phase this task belongs to, CARRIED and never DERIVED.
101
+ *
102
+ * The scope gate derives a phase id from a branch name, and M4-D-22 leaves
103
+ * open whether that convention is the kernel's or the delivering project's.
104
+ * A kernel that derived the phase id from a branch here would settle
105
+ * M4-D-22 by accident, and a shipped constant is the hardest kind of
106
+ * decision to renumber.
107
+ */
108
+ phaseId: string | undefined;
65
109
  }
110
+ /** The closed set of names an adapter may name in `requires`, in field order. */
111
+ export declare function requirableRequestFields(): readonly string[];
112
+ /**
113
+ * THE ADAPTER CONTRACT CHECK (M4-P3 criterion 3): a requirement naming a
114
+ * field that does not exist is a DEFECT IN THE ADAPTER, refused when the
115
+ * adapter is taken up and before anything is created.
116
+ *
117
+ * IT IS A DIFFERENT QUESTION FROM `checkAdapterRequirements` BELOW, AND THE
118
+ * ORDER IS LOAD-BEARING. The presence test there answers "is this field
119
+ * absent", and an unknown key is absent too, so a single check written that
120
+ * way would answer a defect in the adapter with a message about a missing
121
+ * flag: an operator would go looking for a `--modelName` that the kernel has
122
+ * no field for and could never accept. Running this one FIRST is what keeps
123
+ * the two answers distinct, and `test/spawn.test.ts` asserts that they say
124
+ * structurally different things rather than merely both refusing.
125
+ */
126
+ export declare function checkAdapterContract(adapter: ExecutorAdapter): {
127
+ ok: true;
128
+ } | {
129
+ ok: false;
130
+ reason: string;
131
+ };
132
+ /**
133
+ * THE DECLARED-REQUIREMENT CHECK (M4-P3 criterion 2): a field the adapter
134
+ * declared it needs, and the spawn was not given, is a USAGE ERROR, and a
135
+ * usage error creates nothing.
136
+ *
137
+ * THE DANGEROUS STATE THIS EXISTS FOR is not "an adapter gets undefined". It
138
+ * is an adapter DISCOVERING that it got undefined, and raising, after pool
139
+ * create has already made a worktree, a branch and a pool record. That is the
140
+ * measured `--deadline` defect at src/commands/spawn.ts:81 with a new field:
141
+ * a value the kernel could not represent used to raise inside the adapter,
142
+ * after the creation, and the repair was to refuse at parse time. This is the
143
+ * same repair one field along, and it is the reason the check runs at the top
144
+ * of `spawnTask` rather than beside the launch where the request is built.
145
+ */
146
+ export declare function checkAdapterRequirements(adapter: ExecutorAdapter, options: SpawnOptions): {
147
+ ok: true;
148
+ } | {
149
+ ok: false;
150
+ reason: string;
151
+ };
152
+ /**
153
+ * THE CREDENTIAL POLICY CHECK (M4-P8 steps 2, 3 and 5), and it runs before
154
+ * ANYTHING is resolved, loaded or created.
155
+ *
156
+ * Three refusals, in this order, and the order is the fail-closed one:
157
+ *
158
+ * 1. an absent or unrecognised `payloadClass`. Checked first so that
159
+ * refusal 2 never has to reason about an unknown value: without this,
160
+ * `payloadClass !== "project"` would read an omission as permission.
161
+ * 2. the declared escape hatch asked for on a PROJECT payload. This is
162
+ * the pairing the phase exists to refuse: `allowPrCredentials` hands
163
+ * the parent environment over UNCHANGED (see ExecutorRequest.env),
164
+ * credentials and all, and M2-P8 criterion 1 wrote it for the
165
+ * orchestrator's own spawns only. Until this phase the option was
166
+ * reachable from the library seam with nothing between it and a
167
+ * project payload.
168
+ * 3. an extension entry the child must not carry, which is the same
169
+ * vocabulary check `buildChildEnv` makes, made EARLIER. The
170
+ * duplication is deliberate and is not two implementations: both call
171
+ * `refuseExtraAllowlist`. Making it here as well is what keeps a
172
+ * rejected widening from costing a worktree, a branch and a pool
173
+ * record, because `buildChildEnv` does not run until after pool
174
+ * create.
175
+ *
176
+ * It returns a reason rather than throwing, because every refusal on this
177
+ * path must be able to say "nothing was created" in the same sentence.
178
+ */
179
+ export declare function checkCredentialPolicy(options: SpawnOptions): {
180
+ ok: true;
181
+ } | {
182
+ ok: false;
183
+ reason: string;
184
+ };
185
+ /**
186
+ * Pointer evidence: what the five credential-store redirections actually
187
+ * were where the payload ran, and where that observation came from.
188
+ */
189
+ export interface RedirectionEvidence {
190
+ source: "turn-end-record" | "adapter";
191
+ /** Observed value per name; `null` for a name that was unset. */
192
+ values: Readonly<Record<string, string | null>>;
193
+ }
194
+ /**
195
+ * COMPARE THE HANDOVER (M4-P8 criterion 6, repaired for CR-B-001).
196
+ *
197
+ * `handed` is what the kernel built and passed, `reported` is the name set
198
+ * the adapter says it launched with, `pointers` is what the five
199
+ * CREDENTIAL_STORE_REDIRECTIONS actually were where the payload ran.
200
+ *
201
+ * TWO PROPERTIES, AND THE STATUS SAYS WHICH WERE CHECKED. The name-set
202
+ * comparison alone used to be written down as `compared`, which an operator
203
+ * reads as "the handover was verified"; an adapter that keeps the name set
204
+ * byte-identical and puts `HOME` and `XDG_CONFIG_HOME` back to their real
205
+ * paths defeats the M2R-004 defense entirely and was recorded as clean. See
206
+ * `CredentialHandoverRecord` for the five status values and for why the
207
+ * VALUES are still never written into the record.
208
+ *
209
+ * The pointer comparison is by value and the values are DISCARDED: only the
210
+ * names that differ survive into `changedRedirections`. A name the kernel
211
+ * never handed over is not compared, because there is no handed value to
212
+ * compare it against, and the name-set arms are what speak to that case.
213
+ */
214
+ export declare function compareHandover(handed: Record<string, string> | undefined, reported: readonly string[] | undefined, pointers?: RedirectionEvidence): CredentialHandoverRecord;
66
215
  /**
67
216
  * Launch outcomes. The distinction between a payload that never started
68
217
  * and one that did is load-bearing: only the first authorizes rollback.
@@ -70,12 +219,53 @@ export interface ExecutorRequest {
70
219
  export type LaunchOutcome = {
71
220
  kind: "completed";
72
221
  exitCode: number;
222
+ /**
223
+ * THE NAMES THE ADAPTER REPORTS IT ACTUALLY LAUNCHED WITH (M4-P8
224
+ * criterion 6). Optional, and the optionality is honest rather than
225
+ * lenient: an adapter written before this phase reports nothing, and
226
+ * a kernel that refused every silent adapter would be refusing on an
227
+ * absence of evidence. What the kernel DOES refuse is a reported set
228
+ * that DIFFERS from the one it handed over, which is the adapter
229
+ * saying, in its own record, that it widened the environment.
230
+ *
231
+ * It is NOT on `ExecutorRecord`: that document has a shipped schema
232
+ * with `additionalProperties: false`, and this value is a report to
233
+ * the kernel rather than a durable launch fact for an operator.
234
+ *
235
+ * A DISHONEST ADAPTER IS NOT CAUGHT HERE, and nothing in this field
236
+ * pretends otherwise: an adapter that widens `env` and reports the
237
+ * kernel's set is caught by the child-written probe instead, which
238
+ * is why criterion 5 asserts on a file the CHILD wrote.
239
+ */
240
+ launchedEnvNames?: readonly string[];
241
+ /**
242
+ * THE FIVE CREDENTIAL-STORE POINTERS AS THE ADAPTER LAUNCHED THEM
243
+ * (CR-B-001). Optional for the same honest reason `launchedEnvNames`
244
+ * is, and WEAKER than the kernel's own evidence: it is the adapter's
245
+ * word about its own behaviour. The kernel prefers the turn-end record
246
+ * the generated hook normally writes and falls back to this, and the
247
+ * record says which ARTIFACT it read (`redirectionSource`). That is a
248
+ * weaker claim than which PARTY observed the values, and it used to be
249
+ * spelled as the stronger one; see CredentialHandoverRecord in
250
+ * src/task.ts for the measurement that changed the word.
251
+ */
252
+ launchedRedirections?: Readonly<Record<string, string | null>>;
73
253
  } | {
74
254
  kind: "launch-failed";
75
255
  reason: string;
76
256
  } | {
77
257
  kind: "incomplete";
78
258
  reason: string;
259
+ /**
260
+ * WHAT AN ADAPTER THAT COULD NOT CONFIRM COMPLETION STILL LAUNCHED
261
+ * WITH (CR-B-003). The payload RAN on this arm, so the handover is a
262
+ * real question here and the kernel now asks it; before this round the
263
+ * comparison sat after the `incomplete` return and `meta.json` carried
264
+ * no `handover` key at all, so a widening on this arm was recorded
265
+ * nowhere. Optional for the same reason as on the `completed` arm.
266
+ */
267
+ launchedEnvNames?: readonly string[];
268
+ launchedRedirections?: Readonly<Record<string, string | null>>;
79
269
  };
80
270
  /**
81
271
  * The ExecutorAdapter interface (DR-0007, M1-P4 grounding). The ENTIRE
@@ -90,7 +280,40 @@ export type LaunchOutcome = {
90
280
  */
91
281
  export interface ExecutorAdapter {
92
282
  readonly name: string;
93
- launch(request: ExecutorRequest): LaunchOutcome;
283
+ /**
284
+ * WHAT THIS ADAPTER CANNOT LAUNCH WITHOUT (M4-P3 criteria 2 and 3).
285
+ *
286
+ * Every name is a field of `ExecutorRequest`. The kernel cannot know what
287
+ * a given adapter needs, so the adapter DECLARES it and the kernel refuses
288
+ * BEFORE it creates anything, rather than handing over an `undefined` that
289
+ * the adapter discovers once a worktree, a branch and a pool record exist.
290
+ *
291
+ * REQUIRED, never optional: an optional declaration would let an adapter
292
+ * omit it and get the old behaviour back silently, which is the thing this
293
+ * field exists to stop. An adapter that needs nothing declares `[]`, and
294
+ * that is a statement rather than a default.
295
+ */
296
+ readonly requires: readonly string[];
297
+ /**
298
+ * ASYNCHRONOUS since M4-P2. An agent turn is long and a subprocess is
299
+ * short: a window adapter or a cloud-session adapter cannot express
300
+ * "the turn ended" in a synchronous return, so the signature that only
301
+ * ever fitted the local subprocess case is the one that changes.
302
+ *
303
+ * This is NOT permission to background (constraint C-3). The kernel
304
+ * AWAITS this promise inside `spawnTask`, so the command still ends
305
+ * after the payload does; awaiting a call is not outliving it, and
306
+ * delivery/plan/kernel-plan-v1.md:311 already puts process ownership in
307
+ * the harness rather than the kernel.
308
+ *
309
+ * The three-armed outcome is unchanged and is still the whole contract:
310
+ * only `launch-failed` authorises rollback, because only `launch-failed`
311
+ * asserts that the payload never started. An adapter that cannot tell
312
+ * returns `incomplete` and the kernel touches nothing. What DID change
313
+ * is that `completed` is no longer taken on the adapter's word: see the
314
+ * completion precondition in `spawnTask`.
315
+ */
316
+ launch(request: ExecutorRequest): Promise<LaunchOutcome>;
94
317
  }
95
318
  /** The launch record (JSON per DR-0006, shape per PR-207). */
96
319
  export interface ExecutorRecord {
@@ -106,6 +329,25 @@ export interface ExecutorRecord {
106
329
  * choice, recorded rather than assumed.
107
330
  */
108
331
  deadline?: string;
332
+ /**
333
+ * WHAT WAS REQUESTED, NOT WHAT WAS RESOLVED (M4-P3 criterion 6).
334
+ *
335
+ * The declared tier the request carried, copied verbatim. Optional in the
336
+ * same sense `deadline` is: present exactly when the request carried one,
337
+ * absent otherwise, never the string "undefined".
338
+ *
339
+ * THERE IS NO RESOLVED MODEL HERE, and the absence is a decision rather
340
+ * than an omission. This record is written BEFORE the payload starts,
341
+ * which is the whole basis of the launch-failed-versus-incomplete
342
+ * distinction, while a harness that requests one model and is served
343
+ * another resolves mid-turn. A resolved model in a launch record would
344
+ * therefore be a value nobody could have observed at the moment it was
345
+ * written. M4-P7 carries the resolved half, at turn end, where it can be
346
+ * true.
347
+ */
348
+ requestedTier?: string;
349
+ /** The role the request carried, copied verbatim. See `requestedTier`. */
350
+ requestedRole?: string;
109
351
  }
110
352
  /**
111
353
  * The local subprocess adapter, the one M1 ships (and the one the exit
@@ -115,6 +357,13 @@ export interface ExecutorRecord {
115
357
  * auto-backgrounds anything (plan constraint C-3, FM-054), so there is
116
358
  * no daemonize path here to forget to guard.
117
359
  *
360
+ * `async` since M4-P2, and its BODY IS UNCHANGED: every statement below
361
+ * is still synchronous, `spawnSync` is still what runs both children, and
362
+ * the promise this now returns is already settled by the time the first
363
+ * `await` on it runs. The keyword is there because the INTERFACE is async
364
+ * for the adapters that need it, not because this adapter gained a
365
+ * concurrency path to get wrong.
366
+ *
118
367
  * It runs without a shell on purpose. Under a shell a missing payload
119
368
  * binary arrives as an ordinary exit code 127, indistinguishable from a
120
369
  * payload that ran and failed, and spawn's rollback rule turns on
@@ -150,7 +399,63 @@ export interface SpawnOptions {
150
399
  * payload; default is false and the scrub is on.
151
400
  */
152
401
  allowPrCredentials?: boolean;
402
+ /**
403
+ * WHOSE AUTHORITY THIS PAYLOAD RUNS UNDER (M4-P8 step 2). REQUIRED, and
404
+ * there is NO DEFAULT anywhere on this path, in the type or at runtime.
405
+ *
406
+ * A default would be the whole defect: `allowPrCredentials` is reachable
407
+ * from the library seam, which is where the plugin sits, and the pairing
408
+ * this field exists to refuse is the escape hatch on a project payload.
409
+ * If omission meant "orchestrator", a caller would acquire the
410
+ * orchestrator's authority by leaving a field out, which is the quietest
411
+ * way there is to reach a credential. `spawnTask` therefore refuses an
412
+ * absent or unrecognised value before it creates or loads anything,
413
+ * rather than trusting the type: TypeScript is a compile-time promise and
414
+ * the consumer that matters here is a JavaScript plugin.
415
+ */
416
+ payloadClass: PayloadClass;
417
+ /**
418
+ * PER-INVOCATION ALLOWLIST EXTENSIONS, EACH WITH THE REASON IT WAS
419
+ * GRANTED (M4-P8 step 3). Absent means none, which is the measured
420
+ * minimum for model authentication in the probed container
421
+ * (delivery/verification/m4-prototype-probes.md:33).
422
+ *
423
+ * The reason is DATA and a blank one is refused, so a widening cannot be
424
+ * granted without leaving behind something a later reader can check; the
425
+ * granted set is copied into meta.json verbatim. The kernel refuses any
426
+ * entry naming a variable in the walked gh-token or dangerous vocabulary,
427
+ * so this field cannot be used to re-admit a credential.
428
+ */
429
+ extraAllowlist?: readonly ChildEnvExtension[];
430
+ /**
431
+ * The three caller-supplied request fields (M4-P3 criterion 1, M4-D-05).
432
+ * Each is `string | undefined` because nothing in the kernel produces one;
433
+ * `briefPath` is not here because `assembleBrief` does produce it.
434
+ */
435
+ role: string | undefined;
436
+ declaredTier: string | undefined;
437
+ phaseId: string | undefined;
438
+ /**
439
+ * AN ADAPTER OBJECT SUPPLIED DIRECTLY, which only the kernel's own tests
440
+ * do. It outranks `adapterSpecifier` because it is not a request to
441
+ * resolve anything: there is nothing to root, nothing to load, and no
442
+ * trust boundary to cross.
443
+ */
153
444
  adapter?: ExecutorAdapter;
445
+ /**
446
+ * THE `--adapter` SPECIFIER, verbatim as the operator typed it (M4-P4
447
+ * criterion 1). `undefined` means the flag was absent, which is NOT the
448
+ * same as an empty one: an absent flag falls through to the fleet home's
449
+ * declared default and then to the built-in adapter, and each of those
450
+ * three outcomes is named in the launch record rather than being silent
451
+ * (criterion 6).
452
+ *
453
+ * The specifier is not resolved here. It is handed to `selectAdapter`,
454
+ * which roots Node module resolution at the FLEET HOME and never at the
455
+ * project clone; see src/adapters/load.ts for why that root is the whole
456
+ * security property of this phase.
457
+ */
458
+ adapterSpecifier?: string;
154
459
  }
155
460
  export interface SpawnSuccess {
156
461
  meta: TaskMeta;