@b00dea/cockpit 0.1.0 → 0.1.1

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 (196) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +3 -0
  3. package/cockpit.schema.json +73 -3
  4. package/dist/autonomy.d.ts +3 -2
  5. package/dist/autonomy.js +30 -13
  6. package/dist/browser/commands.d.ts +57 -2
  7. package/dist/browser/commands.js +114 -10
  8. package/dist/browser/runtime.d.ts +1 -0
  9. package/dist/browser/runtime.js +4 -1
  10. package/dist/browser/surfaces.d.ts +7 -7
  11. package/dist/browser/surfaces.js +5 -0
  12. package/dist/canonical-json.d.ts +2 -0
  13. package/dist/canonical-json.js +5 -0
  14. package/dist/cli-main.d.ts +40 -3
  15. package/dist/cli-main.js +241 -62
  16. package/dist/command-table.d.ts +19 -11
  17. package/dist/command-table.js +190 -38
  18. package/dist/config/load.d.ts +16 -4
  19. package/dist/config/load.js +25 -3
  20. package/dist/config/unconfirmed-toml.d.ts +9 -0
  21. package/dist/config/unconfirmed-toml.js +29 -0
  22. package/dist/diagnostics.js +10 -1
  23. package/dist/docs/check.d.ts +16 -0
  24. package/dist/docs/check.js +197 -0
  25. package/dist/docs/decision-record.d.ts +34 -0
  26. package/dist/docs/decision-record.js +135 -0
  27. package/dist/docs/decisions.d.ts +16 -0
  28. package/dist/docs/decisions.js +103 -0
  29. package/dist/docs/markdown.d.ts +36 -0
  30. package/dist/docs/markdown.js +88 -0
  31. package/dist/docs/references.d.ts +32 -0
  32. package/dist/docs/references.js +148 -0
  33. package/dist/environment/application/confirm.d.ts +35 -0
  34. package/dist/environment/application/confirm.js +122 -0
  35. package/dist/environment/application/destructive.js +6 -2
  36. package/dist/environment/application/doctor.d.ts +12 -7
  37. package/dist/environment/application/doctor.js +20 -8
  38. package/dist/environment/application/guard.d.ts +12 -6
  39. package/dist/environment/application/guard.js +87 -32
  40. package/dist/environment/application/health.d.ts +14 -5
  41. package/dist/environment/application/health.js +8 -2
  42. package/dist/environment/application/lifecycle-context.d.ts +10 -3
  43. package/dist/environment/application/lifecycle-state.js +4 -3
  44. package/dist/environment/application/lifecycle.d.ts +2 -1
  45. package/dist/environment/application/lifecycle.js +11 -3
  46. package/dist/environment/application/services.d.ts +60 -0
  47. package/dist/environment/application/services.js +89 -0
  48. package/dist/environment/application/siblings.d.ts +43 -0
  49. package/dist/environment/application/siblings.js +269 -0
  50. package/dist/environment/claims.d.ts +10 -10
  51. package/dist/environment/claims.js +3 -11
  52. package/dist/environment/compose-topology.d.ts +15 -7
  53. package/dist/environment/compose-topology.js +100 -23
  54. package/dist/environment/contract.d.ts +42 -9
  55. package/dist/environment/contract.js +33 -0
  56. package/dist/environment/executable.d.ts +12 -0
  57. package/dist/environment/executable.js +46 -23
  58. package/dist/environment/generated-application/check.js +2 -0
  59. package/dist/environment/generated-application/prepare.js +2 -0
  60. package/dist/environment/generated-application/shared.d.ts +10 -2
  61. package/dist/environment/generated-application/shared.js +6 -1
  62. package/dist/environment/generated-application/surface-outputs.js +23 -12
  63. package/dist/environment/generated-application/surfaces.js +16 -11
  64. package/dist/environment/generated-application/sync.js +19 -4
  65. package/dist/environment/git-observation.d.ts +25 -6
  66. package/dist/environment/git-observation.js +37 -28
  67. package/dist/environment/health-composition.d.ts +3 -2
  68. package/dist/environment/health-composition.js +19 -2
  69. package/dist/environment/init-application.d.ts +39 -14
  70. package/dist/environment/init-application.js +39 -10
  71. package/dist/environment/lifecycle-composition.d.ts +4 -2
  72. package/dist/environment/lifecycle-composition.js +84 -17
  73. package/dist/environment/lifecycle-stage.d.ts +8 -3
  74. package/dist/environment/lifecycle-stage.js +11 -1
  75. package/dist/environment/logs.d.ts +1 -1
  76. package/dist/environment/map-limit.d.ts +2 -0
  77. package/dist/environment/map-limit.js +13 -0
  78. package/dist/environment/mcp-observation.d.ts +9 -0
  79. package/dist/environment/mcp-observation.js +46 -0
  80. package/dist/environment/observations.d.ts +1 -1
  81. package/dist/environment/observations.js +2 -0
  82. package/dist/environment/operator-application.d.ts +35 -6
  83. package/dist/environment/operator-application.js +42 -0
  84. package/dist/environment/presence.d.ts +4 -0
  85. package/dist/environment/presence.js +26 -0
  86. package/dist/environment/read-composition.d.ts +8 -3
  87. package/dist/environment/read-composition.js +53 -8
  88. package/dist/environment/readiness-composition.d.ts +20 -1
  89. package/dist/environment/readiness-composition.js +23 -2
  90. package/dist/environment/reclaim.d.ts +56 -19
  91. package/dist/environment/reclaim.js +147 -42
  92. package/dist/environment/runtime.d.ts +64 -18
  93. package/dist/environment/runtime.js +73 -3
  94. package/dist/environment/served-checkout-grants.d.ts +53 -0
  95. package/dist/environment/served-checkout-grants.js +182 -0
  96. package/dist/environment/sibling-checkouts.d.ts +119 -0
  97. package/dist/environment/sibling-checkouts.js +254 -0
  98. package/dist/environment/sibling-observation.d.ts +27 -0
  99. package/dist/environment/sibling-observation.js +45 -0
  100. package/dist/environment/sibling-pull.d.ts +23 -0
  101. package/dist/environment/sibling-pull.js +95 -0
  102. package/dist/environment/stack-inputs.d.ts +18 -0
  103. package/dist/environment/stack-inputs.js +210 -0
  104. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  105. package/dist/environment/stack-inventory-prune.js +129 -0
  106. package/dist/environment/stack-inventory-report.d.ts +44 -0
  107. package/dist/environment/stack-inventory-report.js +78 -0
  108. package/dist/environment/stack-inventory.d.ts +94 -0
  109. package/dist/environment/stack-inventory.js +418 -0
  110. package/dist/environment/state.d.ts +48 -9
  111. package/dist/environment/state.js +142 -2
  112. package/dist/environment/tool-registration.js +3 -7
  113. package/dist/environment-exec/execute.d.ts +6 -3
  114. package/dist/environment-exec/execute.js +4 -0
  115. package/dist/fix-catalogue.d.ts +1 -1
  116. package/dist/generated-environment/adopt.js +4 -8
  117. package/dist/guards/answer-confirmation.d.ts +41 -0
  118. package/dist/guards/answer-confirmation.js +109 -0
  119. package/dist/guards/evaluator.d.ts +20 -3
  120. package/dist/guards/evaluator.js +39 -7
  121. package/dist/guards/local-identity.d.ts +6 -2
  122. package/dist/guards/local-identity.js +10 -4
  123. package/dist/guards/policy-change.d.ts +43 -0
  124. package/dist/guards/policy-change.js +159 -0
  125. package/dist/guards/policy-pin.d.ts +1 -1
  126. package/dist/guards/policy-pin.js +14 -12
  127. package/dist/init/command.d.ts +4 -3
  128. package/dist/init/command.js +36 -16
  129. package/dist/init/derive.d.ts +8 -6
  130. package/dist/init/derive.js +140 -36
  131. package/dist/init/draft.d.ts +13 -11
  132. package/dist/init/draft.js +54 -31
  133. package/dist/init/render.d.ts +18 -6
  134. package/dist/init/render.js +31 -9
  135. package/dist/repository-path.d.ts +9 -0
  136. package/dist/repository-path.js +16 -0
  137. package/dist/schema.d.ts +42 -75
  138. package/dist/schema.js +62 -11
  139. package/dist/surfaces/harness-registry.d.ts +80 -0
  140. package/dist/surfaces/harness-registry.js +29 -0
  141. package/dist/surfaces/mcp.d.ts +67 -17
  142. package/dist/surfaces/mcp.js +194 -31
  143. package/dist/surfaces/projection.d.ts +7 -7
  144. package/dist/surfaces/projection.js +41 -6
  145. package/dist/surfaces/render.d.ts +5 -44
  146. package/dist/surfaces/render.js +5 -2
  147. package/dist/team-metadata.js +2 -1
  148. package/dist/testing/test-protection.d.ts +34 -0
  149. package/dist/testing/test-protection.js +137 -0
  150. package/dist/testing/weakening.d.ts +15 -0
  151. package/dist/testing/weakening.js +30 -0
  152. package/dist/work-orders/application-closure.d.ts +7 -0
  153. package/dist/work-orders/application-closure.js +76 -0
  154. package/dist/work-orders/application-context.d.ts +162 -0
  155. package/dist/work-orders/application-context.js +137 -0
  156. package/dist/work-orders/application-decisions.d.ts +27 -0
  157. package/dist/work-orders/application-decisions.js +121 -0
  158. package/dist/work-orders/application-grading.d.ts +11 -0
  159. package/dist/work-orders/application-grading.js +212 -0
  160. package/dist/work-orders/application.d.ts +4 -97
  161. package/dist/work-orders/application.js +94 -254
  162. package/dist/work-orders/composition.d.ts +35 -58
  163. package/dist/work-orders/composition.js +216 -24
  164. package/dist/work-orders/contract.d.ts +22 -6
  165. package/dist/work-orders/contract.js +34 -16
  166. package/dist/work-orders/decisions.d.ts +23 -0
  167. package/dist/work-orders/decisions.js +55 -0
  168. package/dist/work-orders/store.d.ts +54 -5
  169. package/dist/work-orders/store.js +118 -24
  170. package/docs/README.md +6 -3
  171. package/docs/capability-matrix.md +17 -9
  172. package/docs/cli-reference.md +209 -1
  173. package/docs/command-list.md +12 -1
  174. package/docs/concepts.md +6 -4
  175. package/docs/configuration-essentials.md +7 -2
  176. package/docs/configuration-reference.md +18 -3
  177. package/docs/docs-check.md +76 -0
  178. package/docs/environment.md +194 -6
  179. package/docs/faq.md +5 -3
  180. package/docs/getting-started.md +109 -25
  181. package/docs/harness-surfaces.md +69 -2
  182. package/docs/safety-and-autonomy.md +56 -12
  183. package/docs/troubleshooting.md +29 -8
  184. package/docs/tutorial.md +3 -2
  185. package/docs/work-and-browser.md +180 -14
  186. package/libexec/browser-operation.cjs +86 -2
  187. package/package.json +2 -2
  188. package/surfaces/skills/cockpit-browser.skill.md +7 -2
  189. package/surfaces/skills/cockpit-explain.skill.md +1 -1
  190. package/surfaces/skills/cockpit-fix.skill.md +1 -1
  191. package/surfaces/skills/cockpit-setup.skill.md +3 -3
  192. package/surfaces/skills/cockpit-work.skill.md +1 -1
  193. package/surfaces/skills/procedures/explain.md +9 -4
  194. package/surfaces/skills/procedures/fix.md +25 -11
  195. package/surfaces/skills/procedures/setup.md +113 -40
  196. package/surfaces/skills/procedures/work.md +45 -16
@@ -162,6 +162,12 @@ function projectServices(stdout) {
162
162
  const image = service.image;
163
163
  if (image !== undefined && typeof image !== 'string')
164
164
  return undefined;
165
+ const containerName = service.container_name;
166
+ if (containerName !== undefined && typeof containerName !== 'string')
167
+ return undefined;
168
+ const profiles = service.profiles ?? [];
169
+ if (!Array.isArray(profiles) || profiles.some(profile => typeof profile !== 'string'))
170
+ return undefined;
165
171
  const rawVolumes = service.volumes ?? [];
166
172
  if (!Array.isArray(rawVolumes))
167
173
  return undefined;
@@ -176,7 +182,8 @@ function projectServices(stdout) {
176
182
  }
177
183
  projected.push({
178
184
  id, ports, healthcheck,
179
- image: image ?? null, build: service.build !== undefined, mountSources,
185
+ image: image ?? null, build: service.build !== undefined, containerName: containerName ?? null, mountSources,
186
+ ...profiles.length > 0 ? { profiles: profiles } : {},
180
187
  });
181
188
  }
182
189
  return projected;
@@ -300,7 +307,7 @@ async function canonicalPath(path) {
300
307
  * Docker follows a symbolic link in the bind source, so `./api/cache -> /` would otherwise hand
301
308
  * the host root to a container under a mount = "always" checkout.
302
309
  */
303
- export async function redirectedBindSources(binds, declared) {
310
+ export async function redirectedBindSources(binds, declared, resolve = canonicalPath) {
304
311
  const redirected = [];
305
312
  for (const bind of new Set(binds)) {
306
313
  if (declared.includes(bind))
@@ -310,7 +317,7 @@ export async function redirectedBindSources(binds, declared) {
310
317
  continue;
311
318
  let inside;
312
319
  try {
313
- inside = contains(await canonicalPath(owner), await canonicalPath(bind));
320
+ inside = contains(await resolve(owner), await resolve(bind));
314
321
  }
315
322
  catch {
316
323
  inside = false;
@@ -320,34 +327,35 @@ export async function redirectedBindSources(binds, declared) {
320
327
  }
321
328
  return redirected.sort((left, right) => left.bind.localeCompare(right.bind));
322
329
  }
323
- /**
324
- * Judges a cockpit's own Compose topology. A host port that is not drawn from a leased
325
- * `ports.bases` variable is the same number in every worktree and parallel stack, so the second
326
- * stack to start fails to bind it; the remaining findings are warnings about what cockpit can
327
- * then promise, not about whether the stack runs.
328
- */
329
- export async function assessComposeTopology(services, declarations) {
330
- const leased = new Set(declarations.portVariables);
330
+ function profileOf(sibling) {
331
+ return sibling.mount.startsWith('profile:') ? sibling.mount.slice('profile:'.length) : undefined;
332
+ }
333
+ // Image references that differ per stack: Compose's own project name, which cockpit derives per
334
+ // checkout, and the leased ports.bases variables.
335
+ function imageVariesPerStack(image, leased) {
336
+ return [...image.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)/g)].some(([, name]) => name === 'COMPOSE_PROJECT_NAME' || leased.has(name));
337
+ }
338
+ // The bind findings of the services one profile set runs, judged as `up` judges them under that set.
339
+ async function assessBinds(services, declarations, variables, active, resolve) {
331
340
  const diagnostics = [];
332
- // `up` admits the topology only when every bind lands inside a mount = "always" checkout or a
333
- // generated host path, and every such checkout is mounted; check judges the same binds the same
334
- // way, or a configuration check accepts refuses every runtime command.
335
- const always = declarations.siblings.filter(sibling => sibling.mount === 'always');
336
- const declared = [...always.map(sibling => sibling.path), ...declarations.hostPaths.map(({ path }) => path)];
337
- const variables = Object.fromEntries(declarations.siblings.map(sibling => [sibling.var, sibling.path]));
338
- const binds = services.flatMap(service => service.mountSources.map(source => ({
341
+ const running = services.filter(service => !service.profiles?.length || service.profiles.some(profile => active.includes(profile)));
342
+ const mounted = declarations.siblings.filter(sibling => sibling.mount === 'always'
343
+ || profileOf(sibling) !== undefined && active.includes(profileOf(sibling)));
344
+ const declared = [...mounted.map(sibling => sibling.path), ...declarations.hostPaths.map(({ path }) => path)];
345
+ const binds = running.flatMap(service => service.mountSources.map(source => ({
339
346
  service: service.id, source, path: composeBindPath(source, declarations.projectDirectory, variables)
340
347
  })))
341
348
  .filter(bind => bind.path !== 'volume');
342
349
  const resolved = binds.flatMap(bind => bind.path === undefined ? [] : [bind.path]);
343
350
  const { missing, unexpected } = compareBindSources(resolved, declared);
344
- const redirected = (await redirectedBindSources(resolved, declared)).map(({ bind }) => bind);
345
- for (const sibling of always) {
351
+ const redirected = (await redirectedBindSources(resolved, declared, resolve)).map(({ bind }) => bind);
352
+ const under = active.length === 0 ? '' : ` with profile ${active.join(', ')} active`;
353
+ for (const sibling of mounted) {
346
354
  if (!missing.includes(sibling.path))
347
355
  continue;
348
356
  diagnostics.push({
349
357
  severity: 'error', code: 'COMPOSE_SIBLING_NOT_MOUNTED', target: `sibling:${sibling.slug}`,
350
- message: `sibling ${sibling.slug} is declared mount = "always", but no Compose service bind-mounts ${sibling.path} or a directory inside it; up admits a stack only when every such sibling is mounted, so it would refuse to start`,
358
+ message: `sibling ${sibling.slug} is declared mount = "${sibling.mount}", but no Compose service${under} bind-mounts ${sibling.path} or a directory inside it; up admits a stack only when every such sibling is mounted, so it would refuse to start`,
351
359
  fix: `bind-mount it in a service of the Compose file, whole or a directory inside it (for example "\${${sibling.var}}:/workspace/${sibling.slug}"), or set mount = "none" on sibling ${sibling.slug} in cockpit.toml, then run ${cockpitCommand('sync')}`,
352
360
  });
353
361
  }
@@ -364,16 +372,73 @@ export async function assessComposeTopology(services, declarations) {
364
372
  for (const bind of binds) {
365
373
  const why = bind.path === undefined
366
374
  ? 'whose host side names a variable cockpit does not supply and gives it no default'
367
- : unexpected.includes(bind.path) ? 'which lies inside no mount = "always" sibling and is no environment.generated_host_paths entry'
375
+ : unexpected.includes(bind.path) ? `which lies inside no mounted sibling${under} and is no environment.generated_host_paths entry`
368
376
  : redirected.includes(bind.path) ? 'which resolves through a symbolic link to a path outside the sibling that holds it' : undefined;
369
377
  if (why === undefined)
370
378
  continue;
371
379
  diagnostics.push({
372
380
  severity: 'error', code: 'COMPOSE_BIND_UNDECLARED', target: `compose:${bind.service}:${bind.source}`,
373
381
  message: `service ${bind.service} bind-mounts ${bind.path ?? bind.source}, ${why}; up hands a container only the checkouts and host paths cockpit.toml declares, so it would refuse to start`,
374
- fix: `bind a directory inside a mount = "always" sibling (its variable, as in "\${VAR}/path", or a path relative to the first Compose file's directory), list the path under environment.generated_host_paths, or remove the bind mount from service ${bind.service}; then run ${cockpitCommand('check')}`,
382
+ fix: `bind a directory inside a mounted sibling (its variable, as in "\${VAR}/path", or a path relative to the first Compose file's directory), list the path under environment.generated_host_paths, or remove the bind mount from service ${bind.service}; then run ${cockpitCommand('check')}`,
375
383
  });
376
384
  }
385
+ return diagnostics;
386
+ }
387
+ /**
388
+ * Judges a cockpit's own Compose topology. A host port that is not drawn from a leased
389
+ * `ports.bases` variable is the same number in every worktree and parallel stack, so the second
390
+ * stack to start fails to bind it; a fixed `container_name` fails the same way, and a fixed tag on a
391
+ * built image is shared the same way. The remaining findings are warnings about what cockpit can
392
+ * then promise, not about whether the stack runs.
393
+ */
394
+ export async function assessComposeTopology(services, declarations) {
395
+ const leased = new Set(declarations.portVariables);
396
+ const diagnostics = [];
397
+ const defined = new Set(services.flatMap(service => service.profiles ?? []));
398
+ const defaults = declarations.profiles ?? [];
399
+ for (const [profile, source] of [
400
+ ...defaults.map(profile => [profile, 'environment.profiles names it']),
401
+ ...declarations.siblings.flatMap(sibling => profileOf(sibling) === undefined ? []
402
+ : [[profileOf(sibling), `sibling ${sibling.slug} is mounted under it`]]),
403
+ ]) {
404
+ if (defined.has(profile))
405
+ continue;
406
+ diagnostics.push({
407
+ severity: 'error', code: 'COMPOSE_PROFILE_UNDEFINED',
408
+ target: `profile:${profile}`,
409
+ message: `profile ${profile} is declared by no service in environment.compose_files, and ${source}; Compose accepts an unknown profile silently, so activating it would start nothing`,
410
+ fix: `add profiles: [${profile}] to the services it should start, or remove it from cockpit.toml; then run ${cockpitCommand('check')}`,
411
+ });
412
+ }
413
+ // `up` admits the topology only when every bind of an active service lands inside a mounted
414
+ // checkout or a generated host path, and every such checkout is mounted; check judges the same
415
+ // binds the same way, or a configuration check accepts refuses every runtime command. A stack
416
+ // runs the default profile set plus any profiles `up --profile` adds, so the binds are judged
417
+ // under the default set and under the default set plus each other defined profile: a finding
418
+ // under several added profiles already shows under one of them.
419
+ const usable = defaults.filter(profile => defined.has(profile));
420
+ const sets = [usable, ...[...defined].filter(profile => !usable.includes(profile)).sort().map(profile => [...usable, profile])];
421
+ const variables = Object.fromEntries(declarations.siblings.map(sibling => [sibling.var, sibling.path]));
422
+ // The same binds recur in most profile sets, so each real path is resolved once.
423
+ const resolutions = new Map();
424
+ const resolve = (path) => {
425
+ let resolution = resolutions.get(path);
426
+ if (resolution === undefined) {
427
+ resolution = canonicalPath(path);
428
+ resolutions.set(path, resolution);
429
+ }
430
+ return resolution;
431
+ };
432
+ const seen = new Set();
433
+ for (const active of sets) {
434
+ for (const diagnostic of await assessBinds(services, declarations, variables, active, resolve)) {
435
+ const key = `${diagnostic.code}\0${diagnostic.target}`;
436
+ if (!seen.has(key)) {
437
+ seen.add(key);
438
+ diagnostics.push(diagnostic);
439
+ }
440
+ }
441
+ }
377
442
  for (const service of services) {
378
443
  for (const port of service.ports) {
379
444
  // No host interface means Docker binds every one (0.0.0.0 and ::), exposing the service
@@ -396,6 +461,12 @@ export async function assessComposeTopology(services, declarations) {
396
461
  fix: `publish it as "\${${name}}:${port.target}" in the Compose file and declare ${name} under [ports.bases]`,
397
462
  });
398
463
  }
464
+ if (service.containerName !== null)
465
+ diagnostics.push({
466
+ severity: 'error', code: 'COMPOSE_CONTAINER_NAME_FIXED', target: `compose:${service.id}`,
467
+ message: `service ${service.id} sets container_name ${JSON.stringify(service.containerName)}, and Docker allows one container of a name, so a second checkout's stack fails to start`,
468
+ fix: `remove container_name from service ${service.id} and address it by its Compose service name, or with docker compose exec ${service.id}`,
469
+ });
399
470
  if (!service.healthcheck)
400
471
  diagnostics.push({
401
472
  severity: 'warning', code: 'COMPOSE_HEALTHCHECK_MISSING', target: `compose:${service.id}`,
@@ -408,6 +479,12 @@ export async function assessComposeTopology(services, declarations) {
408
479
  message: `service ${service.id} runs image ${JSON.stringify(service.image)} by tag, so a later pull can change what runs`,
409
480
  fix: `pin it by digest, for example ${service.image.replace(/:[^/:]*$/, '')}@sha256:<digest> from docker image inspect`,
410
481
  });
482
+ if (service.build && service.image !== null && !imageVariesPerStack(service.image, leased))
483
+ diagnostics.push({
484
+ severity: 'error', code: 'COMPOSE_IMAGE_TAG_SHARED', target: `compose:${service.id}`,
485
+ message: `service ${service.id} builds its image and tags it ${JSON.stringify(service.image)}, which is the same in every worktree and parallel stack, so one checkout's build replaces the image another checkout's stack runs`,
486
+ fix: `remove image from service ${service.id}, so Compose names the build after this stack's project`,
487
+ });
411
488
  }
412
489
  return diagnostics;
413
490
  }
@@ -7,7 +7,7 @@ export interface Diagnostic<Code extends string = string> {
7
7
  fix?: string;
8
8
  }
9
9
  export type JsonSnapshot = null | boolean | number | string | JsonSnapshotArray | JsonSnapshotObject;
10
- export interface JsonSnapshotArray extends ReadonlyArray<JsonSnapshot> {
10
+ interface JsonSnapshotArray extends ReadonlyArray<JsonSnapshot> {
11
11
  }
12
12
  export interface JsonSnapshotObject {
13
13
  readonly [key: string]: JsonSnapshot;
@@ -113,6 +113,9 @@ export declare const ENVIRONMENT_CODES: Readonly<{
113
113
  readonly RECLAIM_LABEL_ESCAPE: 'RECLAIM_LABEL_ESCAPE';
114
114
  readonly RECLAIM_ACCOUNTING_MISMATCH: 'RECLAIM_ACCOUNTING_MISMATCH';
115
115
  readonly DOWN_RESOURCES_KEPT: 'DOWN_RESOURCES_KEPT';
116
+ readonly STACKS_ORPHANED: 'STACKS_ORPHANED';
117
+ readonly STACKS_PRUNE_UNCONFIRMED: 'STACKS_PRUNE_UNCONFIRMED';
118
+ readonly STACKS_PRUNE_INCOMPLETE: 'STACKS_PRUNE_INCOMPLETE';
116
119
  readonly CONFIG_STALE: 'CONFIG_STALE';
117
120
  readonly SIBLING_MISSING: 'SIBLING_MISSING';
118
121
  readonly GUIDE_CAP_EXCEEDED: 'GUIDE_CAP_EXCEEDED';
@@ -131,6 +134,7 @@ export declare const ENVIRONMENT_CODES: Readonly<{
131
134
  readonly GENERATED_ENVIRONMENT_UNAVAILABLE: 'GENERATED_ENVIRONMENT_UNAVAILABLE';
132
135
  readonly CLAIM_STATE_UNHEALTHY: 'CLAIM_STATE_UNHEALTHY';
133
136
  readonly MOUNT_POSTURE_UNAVAILABLE: 'MOUNT_POSTURE_UNAVAILABLE';
137
+ readonly COMPOSE_PROFILE_UNDEFINED: 'COMPOSE_PROFILE_UNDEFINED';
134
138
  readonly STATE_UNSEEDED: 'STATE_UNSEEDED';
135
139
  readonly STACK_NOT_RUNNING: 'STACK_NOT_RUNNING';
136
140
  readonly SKILL_BUDGET_UNAVAILABLE: 'SKILL_BUDGET_UNAVAILABLE';
@@ -143,6 +147,8 @@ export declare const ENVIRONMENT_CODES: Readonly<{
143
147
  readonly SERVICE_NOT_DECLARED: 'SERVICE_NOT_DECLARED';
144
148
  readonly GUARD_HOSTS_UNDECLARED: 'GUARD_HOSTS_UNDECLARED';
145
149
  readonly TESTING_GATE_UNDECLARED: 'TESTING_GATE_UNDECLARED';
150
+ readonly MCP_COMMAND_UNRESOLVED: 'MCP_COMMAND_UNRESOLVED';
151
+ readonly MCP_ENVIRONMENT_UNSET: 'MCP_ENVIRONMENT_UNSET';
146
152
  readonly EVALUATOR_UNAVAILABLE: 'EVALUATOR_UNAVAILABLE';
147
153
  readonly EVALUATOR_RUN_FAILED: 'EVALUATOR_RUN_FAILED';
148
154
  readonly EVALUATOR_FELL_BACK_TO_RUNNING_TOOL: 'EVALUATOR_FELL_BACK_TO_RUNNING_TOOL';
@@ -150,15 +156,23 @@ export declare const ENVIRONMENT_CODES: Readonly<{
150
156
  readonly TEST_GATE_UNSATISFIED: 'TEST_GATE_UNSATISFIED';
151
157
  readonly WORK_GRADE_UNSATISFIED: 'WORK_GRADE_UNSATISFIED';
152
158
  readonly WORK_CHECK_FAILED: 'WORK_CHECK_FAILED';
159
+ readonly BROWSER_ROUTE_FINDINGS: 'BROWSER_ROUTE_FINDINGS';
153
160
  readonly WORK_VERIFY_FAILED: 'WORK_VERIFY_FAILED';
154
161
  readonly WORK_PERSON_REQUIRED: 'WORK_PERSON_REQUIRED';
162
+ readonly WORK_TEST_WEAKENED: 'WORK_TEST_WEAKENED';
155
163
  readonly AUTONOMY_PERSON_REQUIRED: 'AUTONOMY_PERSON_REQUIRED';
156
164
  readonly AUTONOMY_CI_ONLY: 'AUTONOMY_CI_ONLY';
157
165
  readonly POLICY_WEAKENING_UNCONFIRMED: 'POLICY_WEAKENING_UNCONFIRMED';
166
+ readonly GUARD_CONFIG_UNSYNCED: 'GUARD_CONFIG_UNSYNCED';
167
+ readonly GUARD_CONFIG_WIDENED: 'GUARD_CONFIG_WIDENED';
158
168
  readonly WORK_ORDER_CLOSED: 'WORK_ORDER_CLOSED';
159
169
  readonly CODEX_HOOK_UNTRUSTED: 'CODEX_HOOK_UNTRUSTED';
160
170
  readonly SELFTEST_CUSTOM_PROBE_UNSUPPORTED: 'SELFTEST_CUSTOM_PROBE_UNSUPPORTED';
161
171
  readonly DECLARATION_UNCONFIRMED: 'DECLARATION_UNCONFIRMED';
172
+ readonly CONFIRM_PERSON_REQUIRED: 'CONFIRM_PERSON_REQUIRED';
173
+ readonly CONFIRM_ANSWER_NOT_LISTED: 'CONFIRM_ANSWER_NOT_LISTED';
174
+ readonly CONFIRM_OBSERVATION_FAILED: 'CONFIRM_OBSERVATION_FAILED';
175
+ readonly CONFIRM_RECORD_UNWRITABLE: 'CONFIRM_RECORD_UNWRITABLE';
162
176
  readonly COCKPIT_NOT_INSTALLED: 'COCKPIT_NOT_INSTALLED';
163
177
  readonly COCKPIT_LAUNCHER_UNPROVEN: 'COCKPIT_LAUNCHER_UNPROVEN';
164
178
  readonly SIBLING_REMOTE_UNRECORDED: 'SIBLING_REMOTE_UNRECORDED';
@@ -178,6 +192,25 @@ export declare const ENVIRONMENT_CODES: Readonly<{
178
192
  readonly UPGRADE_SYNC_FAILED: 'UPGRADE_SYNC_FAILED';
179
193
  readonly DATA_SEED_FAILED: 'DATA_SEED_FAILED';
180
194
  readonly DATA_SEED_UNAVAILABLE: 'DATA_SEED_UNAVAILABLE';
195
+ readonly DOCS_NOT_DECLARED: 'DOCS_NOT_DECLARED';
196
+ readonly DOCS_DECLARATION_UNMATCHED: 'DOCS_DECLARATION_UNMATCHED';
197
+ readonly DOCS_REFERENCE_REMOVED: 'DOCS_REFERENCE_REMOVED';
198
+ readonly DOCS_TARGET_REMOVED: 'DOCS_TARGET_REMOVED';
199
+ readonly DECISION_NUMBER_REUSED: 'DECISION_NUMBER_REUSED';
200
+ readonly DECISION_UNINDEXED: 'DECISION_UNINDEXED';
201
+ readonly DECISION_INDEX_ENTRY_MISSING: 'DECISION_INDEX_ENTRY_MISSING';
202
+ readonly DECISION_STATUS_MISSING: 'DECISION_STATUS_MISSING';
203
+ readonly DECISION_LINK_UNRESOLVED: 'DECISION_LINK_UNRESOLVED';
204
+ readonly DECISION_SUPERSEDED_STALE: 'DECISION_SUPERSEDED_STALE';
205
+ readonly SIBLING_CHECKOUT_RECORD_INVALID: 'SIBLING_CHECKOUT_RECORD_INVALID';
206
+ readonly SIBLING_CHECKOUT_UNKNOWN: 'SIBLING_CHECKOUT_UNKNOWN';
207
+ readonly SIBLING_CHECKOUT_UNAVAILABLE: 'SIBLING_CHECKOUT_UNAVAILABLE';
208
+ readonly SIBLING_CHECKOUT_FOREIGN: 'SIBLING_CHECKOUT_FOREIGN';
209
+ readonly SIBLING_CHECKOUT_OVERRIDDEN: 'SIBLING_CHECKOUT_OVERRIDDEN';
210
+ readonly SIBLING_ENVIRONMENT_FILE_MISSING: 'SIBLING_ENVIRONMENT_FILE_MISSING';
211
+ readonly PULL_SKIPPED: 'PULL_SKIPPED';
212
+ readonly STACK_INPUTS_CHANGED: 'STACK_INPUTS_CHANGED';
213
+ readonly STACK_INPUTS_UNRECORDED: 'STACK_INPUTS_UNRECORDED';
181
214
  }>;
182
215
  export declare const EXECUTION_FAILURE_CODES: readonly ["EXECUTION_SPAWN_FAILED", "EXECUTION_PRECONDITION_CHANGED", "EXECUTION_STAGING_FAILED", "EXECUTION_STAGING_CLEANUP_FAILED", "EXECUTION_TIMEOUT", "EXECUTION_STDOUT_LIMIT", "EXECUTION_STDERR_LIMIT", "EXECUTION_STEP_FAILED", "EXECUTION_ABORTED", "EXECUTION_TERMINATION_UNCONFIRMED"];
183
216
  export declare const REFUSAL_CODES: readonly ["CONFIG_NOT_FOUND", "CONFIG_UNREADABLE", "CONFIG_ENCODING_INVALID", "CONFIG_PARSE_INVALID", "CONFIG_SCHEMA_INVALID", "STATE_ROOT_UNAVAILABLE", "STATE_FORMAT_UNSUPPORTED", "STATE_RECORD_MALFORMED", "STATE_LOCKED", "STATE_DURABILITY_INDETERMINATE", "SETUP_CONFIG_REQUIRED", "SETUP_OPTION_DUPLICATE", "SETUP_POSITIONAL_UNSUPPORTED", "SETUP_PATH_OUTSIDE_ROOT", "SETUP_PATH_SYMLINK", "SETUP_PATH_TYPE_INVALID", "SETUP_PARENT_MISSING", "SETUP_REMOTE_MISMATCH", "SETUP_REMOTE_UNRESOLVABLE", "SETUP_MANUAL_MISSING", "SETUP_CLONE_FAILED", "SETUP_POSTCHECK_FAILED", "SETUP_INSTRUCTION_POINTER_INVALID", "SETUP_INSTRUCTION_FILES_INVALID", "CONFIG_CHANGED_DURING_EXECUTION", "EXECUTION_SPAWN_FAILED", "EXECUTION_PRECONDITION_CHANGED", "EXECUTION_STAGING_FAILED", "EXECUTION_STAGING_CLEANUP_FAILED", "EXECUTION_TIMEOUT", "EXECUTION_STDOUT_LIMIT", "EXECUTION_STDERR_LIMIT", "EXECUTION_STEP_FAILED", "EXECUTION_ABORTED", "EXECUTION_TERMINATION_UNCONFIRMED", "EXECUTION_COMPENSATION_TIMEOUT", "EXECUTION_COMPENSATION_STEP_FAILED", "LIFECYCLE_EVIDENCE_INVALID", "LIFECYCLE_PROGRESS_INVALID", "STACK_RECORDED_UNDER_EARLIER_NAME", "PORT_FAMILY_COLLISION", "PORT_LISTENER_EVIDENCE_INVALID", "PORT_FAMILY_OUT_OF_RANGE", "PORT_LEASE_EXHAUSTED", "CLAIM_CO_TENANT", "CLAIM_LIVENESS_UNKNOWN", "DRAIN_ATTEMPT_REPAIR_REQUIRED", "INSTALL_POLICY_OBSERVATION_UNAVAILABLE", "INSTALL_POLICY_OBSERVATION_INVALID", "INSTALL_POLICY_MISSING", "INSTALL_POLICY_AMBIGUOUS", "TEMPLATE_NOT_FOUND", "TEMPLATE_NOT_TRACKED", "TEMPLATE_IGNORED", "TEMPLATE_PATH_OUTSIDE_ROOT", "ENVIRONMENT_FILE_NOT_FOUND", "ENVIRONMENT_FILE_UNREADABLE", "ENVIRONMENT_FILE_TOO_LARGE", "ENVIRONMENT_FILE_MALFORMED", "INSTANCE_NOT_IGNORED", "ENVIRONMENT_SEED_PRECONDITION_CHANGED", "ENVIRONMENT_SEED_FAILED", "RUNTIME_UNAVAILABLE", "RUNTIME_IMAGE_UNAVAILABLE", "RUNTIME_UNMEASURED", "RUNTIME_COMPOSE_REMOTE", "RUNTIME_PROBE_TIMEOUT", "RESET_CONFIRMATION_REQUIRED", "RESET_CONFIRMATION_MISMATCH", "RESET_CO_TENANT", "EVIDENCE_UNAVAILABLE", "RECLAIM_LABEL_ESCAPE", "RECLAIM_ACCOUNTING_MISMATCH", "DOWN_RESOURCES_KEPT", "GIT_COMPARISON_REMOTE_MISMATCH"];
@@ -199,7 +232,7 @@ export declare function tryParseJson(text: string): unknown;
199
232
  export declare function hasErrorCode(error: unknown, ...codes: readonly string[]): boolean;
200
233
  export declare function isMissing(error: unknown): boolean;
201
234
  export declare function isCanonicalTimestamp(value: unknown): value is string;
202
- export type ExpectedObservation = {
235
+ type ExpectedObservation = {
203
236
  kind: 'path';
204
237
  target: string;
205
238
  state: 'absent' | 'directory' | 'repository';
@@ -314,20 +347,20 @@ export interface ExecutionFence {
314
347
  export interface ExecutionPlan {
315
348
  steps: readonly ExecutionStep[];
316
349
  }
317
- export type LifecycleResumeBoundary = Readonly<{
350
+ type LifecycleResumeBoundary = Readonly<{
318
351
  completedStepIds: readonly string[];
319
352
  nextStepId: string | null;
320
353
  }>;
321
354
  type LifecycleStepBase = Readonly<{
322
355
  id: string;
323
356
  }>;
324
- export interface LifecycleEntrypoint {
357
+ interface LifecycleEntrypoint {
325
358
  readonly name: string;
326
359
  readonly kind: EntrypointKind;
327
360
  readonly port: string;
328
361
  readonly value: string;
329
362
  }
330
- export interface LifecycleCompletedService {
363
+ interface LifecycleCompletedService {
331
364
  readonly service: string;
332
365
  readonly containerId: string;
333
366
  }
@@ -336,7 +369,7 @@ export interface LifecycleProgress {
336
369
  readonly operationGeneration: string;
337
370
  readonly completedServices: readonly LifecycleCompletedService[];
338
371
  }
339
- export type LifecycleStep = (LifecycleStepBase & Readonly<{
372
+ type LifecycleStep = (LifecycleStepBase & Readonly<{
340
373
  kind: 'preflight';
341
374
  }>) | (LifecycleStepBase & Readonly<{
342
375
  kind: 'claim';
@@ -411,7 +444,7 @@ export type ObservationExpectation = {
411
444
  } | {
412
445
  readonly kind: 'present';
413
446
  };
414
- export type ObservationApplicability = {
447
+ type ObservationApplicability = {
415
448
  readonly state: 'required';
416
449
  readonly posture: string;
417
450
  } | {
@@ -419,13 +452,13 @@ export type ObservationApplicability = {
419
452
  readonly posture: string;
420
453
  readonly reason: string;
421
454
  };
422
- export type ObservationFreshness = {
455
+ type ObservationFreshness = {
423
456
  readonly state: 'current';
424
457
  } | {
425
458
  readonly state: 'stale' | 'missing';
426
459
  readonly reason: string;
427
460
  };
428
- export type EnvironmentObservedValue = {
461
+ type EnvironmentObservedValue = {
429
462
  readonly state: 'available';
430
463
  readonly value: JsonSnapshot;
431
464
  readonly observedAt: string;
@@ -194,6 +194,9 @@ export const ENVIRONMENT_CODES = Object.freeze({
194
194
  RECLAIM_LABEL_ESCAPE: 'RECLAIM_LABEL_ESCAPE',
195
195
  RECLAIM_ACCOUNTING_MISMATCH: 'RECLAIM_ACCOUNTING_MISMATCH',
196
196
  DOWN_RESOURCES_KEPT: 'DOWN_RESOURCES_KEPT',
197
+ STACKS_ORPHANED: 'STACKS_ORPHANED',
198
+ STACKS_PRUNE_UNCONFIRMED: 'STACKS_PRUNE_UNCONFIRMED',
199
+ STACKS_PRUNE_INCOMPLETE: 'STACKS_PRUNE_INCOMPLETE',
197
200
  CONFIG_STALE: 'CONFIG_STALE',
198
201
  SIBLING_MISSING: 'SIBLING_MISSING',
199
202
  GUIDE_CAP_EXCEEDED: 'GUIDE_CAP_EXCEEDED',
@@ -212,6 +215,7 @@ export const ENVIRONMENT_CODES = Object.freeze({
212
215
  GENERATED_ENVIRONMENT_UNAVAILABLE: 'GENERATED_ENVIRONMENT_UNAVAILABLE',
213
216
  CLAIM_STATE_UNHEALTHY: 'CLAIM_STATE_UNHEALTHY',
214
217
  MOUNT_POSTURE_UNAVAILABLE: 'MOUNT_POSTURE_UNAVAILABLE',
218
+ COMPOSE_PROFILE_UNDEFINED: 'COMPOSE_PROFILE_UNDEFINED',
215
219
  STATE_UNSEEDED: 'STATE_UNSEEDED',
216
220
  STACK_NOT_RUNNING: 'STACK_NOT_RUNNING',
217
221
  SKILL_BUDGET_UNAVAILABLE: 'SKILL_BUDGET_UNAVAILABLE',
@@ -224,6 +228,8 @@ export const ENVIRONMENT_CODES = Object.freeze({
224
228
  SERVICE_NOT_DECLARED: 'SERVICE_NOT_DECLARED',
225
229
  GUARD_HOSTS_UNDECLARED: 'GUARD_HOSTS_UNDECLARED',
226
230
  TESTING_GATE_UNDECLARED: 'TESTING_GATE_UNDECLARED',
231
+ MCP_COMMAND_UNRESOLVED: 'MCP_COMMAND_UNRESOLVED',
232
+ MCP_ENVIRONMENT_UNSET: 'MCP_ENVIRONMENT_UNSET',
227
233
  EVALUATOR_UNAVAILABLE: 'EVALUATOR_UNAVAILABLE',
228
234
  EVALUATOR_RUN_FAILED: 'EVALUATOR_RUN_FAILED',
229
235
  EVALUATOR_FELL_BACK_TO_RUNNING_TOOL: 'EVALUATOR_FELL_BACK_TO_RUNNING_TOOL',
@@ -231,15 +237,23 @@ export const ENVIRONMENT_CODES = Object.freeze({
231
237
  TEST_GATE_UNSATISFIED: 'TEST_GATE_UNSATISFIED',
232
238
  WORK_GRADE_UNSATISFIED: 'WORK_GRADE_UNSATISFIED',
233
239
  WORK_CHECK_FAILED: 'WORK_CHECK_FAILED',
240
+ BROWSER_ROUTE_FINDINGS: 'BROWSER_ROUTE_FINDINGS',
234
241
  WORK_VERIFY_FAILED: 'WORK_VERIFY_FAILED',
235
242
  WORK_PERSON_REQUIRED: 'WORK_PERSON_REQUIRED',
243
+ WORK_TEST_WEAKENED: 'WORK_TEST_WEAKENED',
236
244
  AUTONOMY_PERSON_REQUIRED: 'AUTONOMY_PERSON_REQUIRED',
237
245
  AUTONOMY_CI_ONLY: 'AUTONOMY_CI_ONLY',
238
246
  POLICY_WEAKENING_UNCONFIRMED: 'POLICY_WEAKENING_UNCONFIRMED',
247
+ GUARD_CONFIG_UNSYNCED: 'GUARD_CONFIG_UNSYNCED',
248
+ GUARD_CONFIG_WIDENED: 'GUARD_CONFIG_WIDENED',
239
249
  WORK_ORDER_CLOSED: 'WORK_ORDER_CLOSED',
240
250
  CODEX_HOOK_UNTRUSTED: 'CODEX_HOOK_UNTRUSTED',
241
251
  SELFTEST_CUSTOM_PROBE_UNSUPPORTED: 'SELFTEST_CUSTOM_PROBE_UNSUPPORTED',
242
252
  DECLARATION_UNCONFIRMED: 'DECLARATION_UNCONFIRMED',
253
+ CONFIRM_PERSON_REQUIRED: 'CONFIRM_PERSON_REQUIRED',
254
+ CONFIRM_ANSWER_NOT_LISTED: 'CONFIRM_ANSWER_NOT_LISTED',
255
+ CONFIRM_OBSERVATION_FAILED: 'CONFIRM_OBSERVATION_FAILED',
256
+ CONFIRM_RECORD_UNWRITABLE: 'CONFIRM_RECORD_UNWRITABLE',
243
257
  COCKPIT_NOT_INSTALLED: 'COCKPIT_NOT_INSTALLED',
244
258
  COCKPIT_LAUNCHER_UNPROVEN: 'COCKPIT_LAUNCHER_UNPROVEN',
245
259
  SIBLING_REMOTE_UNRECORDED: 'SIBLING_REMOTE_UNRECORDED',
@@ -259,6 +273,25 @@ export const ENVIRONMENT_CODES = Object.freeze({
259
273
  UPGRADE_SYNC_FAILED: 'UPGRADE_SYNC_FAILED',
260
274
  DATA_SEED_FAILED: 'DATA_SEED_FAILED',
261
275
  DATA_SEED_UNAVAILABLE: 'DATA_SEED_UNAVAILABLE',
276
+ DOCS_NOT_DECLARED: 'DOCS_NOT_DECLARED',
277
+ DOCS_DECLARATION_UNMATCHED: 'DOCS_DECLARATION_UNMATCHED',
278
+ DOCS_REFERENCE_REMOVED: 'DOCS_REFERENCE_REMOVED',
279
+ DOCS_TARGET_REMOVED: 'DOCS_TARGET_REMOVED',
280
+ DECISION_NUMBER_REUSED: 'DECISION_NUMBER_REUSED',
281
+ DECISION_UNINDEXED: 'DECISION_UNINDEXED',
282
+ DECISION_INDEX_ENTRY_MISSING: 'DECISION_INDEX_ENTRY_MISSING',
283
+ DECISION_STATUS_MISSING: 'DECISION_STATUS_MISSING',
284
+ DECISION_LINK_UNRESOLVED: 'DECISION_LINK_UNRESOLVED',
285
+ DECISION_SUPERSEDED_STALE: 'DECISION_SUPERSEDED_STALE',
286
+ SIBLING_CHECKOUT_RECORD_INVALID: 'SIBLING_CHECKOUT_RECORD_INVALID',
287
+ SIBLING_CHECKOUT_UNKNOWN: 'SIBLING_CHECKOUT_UNKNOWN',
288
+ SIBLING_CHECKOUT_UNAVAILABLE: 'SIBLING_CHECKOUT_UNAVAILABLE',
289
+ SIBLING_CHECKOUT_FOREIGN: 'SIBLING_CHECKOUT_FOREIGN',
290
+ SIBLING_CHECKOUT_OVERRIDDEN: 'SIBLING_CHECKOUT_OVERRIDDEN',
291
+ SIBLING_ENVIRONMENT_FILE_MISSING: 'SIBLING_ENVIRONMENT_FILE_MISSING',
292
+ PULL_SKIPPED: 'PULL_SKIPPED',
293
+ STACK_INPUTS_CHANGED: 'STACK_INPUTS_CHANGED',
294
+ STACK_INPUTS_UNRECORDED: 'STACK_INPUTS_UNRECORDED',
262
295
  });
263
296
  export const EXECUTION_FAILURE_CODES = Object.freeze([
264
297
  ENVIRONMENT_CODES.EXECUTION_SPAWN_FAILED,
@@ -20,6 +20,18 @@ export type ExecutableResolution = Readonly<{
20
20
  }>;
21
21
  export declare function resolveExecutablePath(path: string): Promise<ExecutableResolution>;
22
22
  export declare function resolveExecutable(name: string, env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
23
+ /**
24
+ * Whether a command can be started from `cwd`, found the way a wrapper that changes into `cwd` would
25
+ * find it, without hashing the file: a command with a slash is relative to `cwd`, a bare name is
26
+ * looked up on PATH. For callers that report presence rather than run the file.
27
+ */
28
+ export declare function locateExecutable(command: string, env: Readonly<Record<string, string | undefined>>, cwd: string): Promise<Readonly<{
29
+ state: 'present';
30
+ path: string;
31
+ }> | Readonly<{
32
+ state: 'unavailable';
33
+ reason: string;
34
+ }>>;
23
35
  export declare function resolveGitExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
24
36
  export declare function resolveDiskCapacityExecutable(): Promise<ExecutableResolution>;
25
37
  export declare function resolveDockerExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
3
  import { lstat, open, realpath } from 'node:fs/promises';
4
- import { basename, delimiter, isAbsolute, join, normalize } from 'node:path';
4
+ import { basename, delimiter, isAbsolute, join, normalize, resolve } from 'node:path';
5
5
  import { errorCause } from './contract.js';
6
6
  import { fileFingerprint, recordVerifiedDigest, verifiedDigest } from '../environment-exec/verified-digest.js';
7
7
  import { exactKeys } from '../exact-keys.js';
@@ -101,57 +101,80 @@ async function digestRegularFile(path) {
101
101
  catch { /* The caller receives unavailable; no descriptor authority survives. */ }
102
102
  }
103
103
  }
104
- export async function resolveExecutablePath(path) {
104
+ // The checks that need no file contents: an exact absolute path naming an executable file, or a
105
+ // link that resolves to one.
106
+ async function checkExecutablePath(path) {
105
107
  if (typeof path !== 'string' || !isAbsolute(path) || normalize(path) !== path || path.includes('\0')) {
106
- return unavailable('Executable path must be an exact absolute path');
108
+ return { state: 'unavailable', reason: 'Executable path must be an exact absolute path' };
107
109
  }
108
110
  let invocation;
109
111
  try {
110
112
  invocation = await lstat(path);
111
113
  }
112
114
  catch (error) {
113
- return unavailable(`Executable is unavailable: ${path}: ${errorCause(error)}`);
115
+ return { state: 'unavailable', reason: `Executable is unavailable: ${path}: ${errorCause(error)}` };
114
116
  }
115
117
  if (!(invocation.isFile() || invocation.isSymbolicLink()) || invocation.isFile() && (invocation.mode & 0o111) === 0) {
116
- return unavailable(`Executable is unavailable: ${path}`);
118
+ return { state: 'unavailable', reason: `Executable is unavailable: ${path}` };
117
119
  }
118
- let canonicalPath;
119
120
  try {
120
- canonicalPath = await realpath(path);
121
+ return { state: 'present', path, canonicalPath: await realpath(path) };
121
122
  }
122
123
  catch (error) {
123
- return unavailable(`Executable is unavailable: ${path}: ${errorCause(error)}`);
124
+ return { state: 'unavailable', reason: `Executable is unavailable: ${path}: ${errorCause(error)}` };
124
125
  }
125
- const sha256 = await digestRegularFile(canonicalPath);
126
+ }
127
+ export async function resolveExecutablePath(path) {
128
+ const checked = await checkExecutablePath(path);
129
+ if (checked.state === 'unavailable')
130
+ return unavailable(checked.reason);
131
+ const sha256 = await digestRegularFile(checked.canonicalPath);
126
132
  if (sha256 === undefined)
127
133
  return unavailable(`Executable identity is unavailable: ${path}`);
128
- const value = resolvedExecutable({ canonicalPath, invocationPath: path, sha256, executionMode: 'verified-path' });
134
+ const value = resolvedExecutable({ canonicalPath: checked.canonicalPath, invocationPath: path, sha256, executionMode: 'verified-path' });
129
135
  return value ? { state: 'available', value } : unavailable(`Executable identity is invalid: ${path}`);
130
136
  }
131
- export async function resolveExecutable(name, env) {
132
- if (typeof name !== 'string' || name.length === 0 || name.includes('\0'))
133
- return unavailable('Executable name is invalid');
134
- if (isAbsolute(name))
135
- return resolveExecutablePath(name);
136
- if (basename(name) !== name)
137
- return unavailable('Executable name must be absolute or a PATH entry name');
137
+ const miss = (reason) => ({ state: 'unavailable', reason });
138
+ // Validates the search path once and tries each directory in order; `probe` decides what a hit is.
139
+ async function searchExecutable(name, env, probe, found) {
138
140
  const selected = searchPath(env);
139
141
  if (selected.kind === 'unreadable')
140
- return unavailable('Executable search path is not readable');
142
+ return miss('Executable search path is not readable');
141
143
  const pathValue = selected.path;
142
144
  if (typeof pathValue !== 'string' || pathValue.length === 0 || pathValue.includes('\0') || Buffer.byteLength(pathValue) > 65_536) {
143
- return unavailable('Executable search path is missing or invalid');
145
+ return miss('Executable search path is missing or invalid');
144
146
  }
145
147
  const directories = pathValue.split(delimiter);
146
148
  if (directories.length > PATH_ENTRY_LIMIT || directories.some(path => !isAbsolute(path) || normalize(path) !== path)) {
147
- return unavailable('Executable search path must contain bounded absolute directories');
149
+ return miss('Executable search path must contain bounded absolute directories');
148
150
  }
149
151
  for (const directory of directories) {
150
- const result = await resolveExecutablePath(join(directory, name));
151
- if (result.state === 'available')
152
+ const result = await probe(join(directory, name));
153
+ if (found(result))
152
154
  return result;
153
155
  }
154
- return unavailable(`Executable was not found in the supplied search path: ${name}`);
156
+ return miss(`Executable was not found in the supplied search path: ${name}`);
157
+ }
158
+ export async function resolveExecutable(name, env) {
159
+ if (typeof name !== 'string' || name.length === 0 || name.includes('\0'))
160
+ return unavailable('Executable name is invalid');
161
+ if (isAbsolute(name))
162
+ return resolveExecutablePath(name);
163
+ if (basename(name) !== name)
164
+ return unavailable('Executable name must be absolute or a PATH entry name');
165
+ return searchExecutable(name, env, resolveExecutablePath, result => result.state === 'available');
166
+ }
167
+ /**
168
+ * Whether a command can be started from `cwd`, found the way a wrapper that changes into `cwd` would
169
+ * find it, without hashing the file: a command with a slash is relative to `cwd`, a bare name is
170
+ * looked up on PATH. For callers that report presence rather than run the file.
171
+ */
172
+ export async function locateExecutable(command, env, cwd) {
173
+ if (typeof command !== 'string' || command.length === 0 || command.includes('\0'))
174
+ return miss('Executable name is invalid');
175
+ if (command.includes('/'))
176
+ return checkExecutablePath(resolve(cwd, command));
177
+ return searchExecutable(command, env, checkExecutablePath, result => result.state === 'present');
155
178
  }
156
179
  export async function resolveGitExecutable(env) {
157
180
  return resolveExecutable('git', env);
@@ -37,6 +37,7 @@ export async function prepareGeneratedEnvironmentCurrentness(input, adapterOverr
37
37
  sourceRevision: installedPackageMetadata.cockpitSourceRevision },
38
38
  fixContext: { resolvedConfigPath: prepared?.configPath ?? null,
39
39
  generatedTargets: prepared ? [...prepared.owned, ...prepared.surfaces].map(row => row.path) : [] },
40
+ mcpServers: prepared?.mcpServers ?? [],
40
41
  ...(authority ? { repairAuthority: authority } : {}),
41
42
  ...('status' in publication ? { repairRefusal: publication }
42
43
  : 'refusal' in publication ? { repairRefusal: publication.refusal } : {}),
@@ -171,6 +172,7 @@ async function composeTopologyDiagnostics(configPath, sourceSha256, adapters) {
171
172
  slug: sibling.slug, var: sibling.var, mount: sibling.mount, path: resolve(siblingsRoot, sibling.dir)
172
173
  })),
173
174
  hostPaths: config.environment.generated_host_paths.map(declared => ({ declared, path: resolve(root, declared) })),
175
+ profiles: config.environment.profiles,
174
176
  });
175
177
  }
176
178
  export async function checkGeneratedEnvironment(input, adapterOverrides = {}) {
@@ -266,6 +266,8 @@ export async function prepare(input, adapters) {
266
266
  surfaceAssets: [],
267
267
  warnings: [],
268
268
  unapprovedMcpServers: [],
269
+ mcpTransportGaps: [],
270
+ mcpServers: [],
269
271
  registry: derived.result,
270
272
  siblingRoots,
271
273
  workEnvironment,
@@ -9,7 +9,7 @@ import type { SurfaceFeature, SurfaceSourceFile, SurfaceTarget } from '../../sur
9
9
  import type { SurfaceGenerationWarning } from '../../surfaces/generator-adapter.js';
10
10
  import type { ActiveSurfaceOverride, SurfaceUpgradeFinding } from '../../surfaces/merge.js';
11
11
  import { CORE_EVALUATOR_ASSET as EVALUATOR_ASSET, CORE_SKILL_ASSETS as SKILL_ASSETS } from '../../surfaces/core-declarations.js';
12
- import type { McpDeclaration } from '../../surfaces/mcp.js';
12
+ import type { McpServer, McpStdioDeclaration } from '../../surfaces/mcp.js';
13
13
  import { type GeneratedPathFact, type GeneratedProducer, type SurfaceDesiredEntry } from '../../surfaces/ownership.js';
14
14
  import type { DirectoryGrant, GuideObservation, GuideObservationRequest, StandingCost } from '../../surfaces/projection.js';
15
15
  import type { NativeFragmentSet, SurfaceInputEvidence } from '../../surfaces/render.js';
@@ -213,6 +213,12 @@ export interface PreparedEnvironment {
213
213
  sources: readonly string[];
214
214
  sha256: string;
215
215
  }>[];
216
+ readonly mcpTransportGaps: readonly Readonly<{
217
+ name: string;
218
+ targets: readonly string[];
219
+ reason: string;
220
+ }>[];
221
+ readonly mcpServers: readonly McpServer[];
216
222
  readonly registry: DerivedRegistry;
217
223
  readonly siblingRoots: AuthenticatedSiblingRoots;
218
224
  readonly workEnvironment: ConsumedWorkEnvironmentConfig;
@@ -317,7 +323,7 @@ export interface ConsumedWorkApplicationAuthorities {
317
323
  readonly browser?: Readonly<{
318
324
  readonly runtime: BrowserRuntimeProjection;
319
325
  readonly namespace: StateNamespace;
320
- readonly mcp: McpDeclaration;
326
+ readonly mcp: McpStdioDeclaration;
321
327
  readonly skillTemplate: Readonly<{
322
328
  readonly path: string;
323
329
  readonly bytes: string;
@@ -481,4 +487,6 @@ export interface GeneratedEnvironmentCurrentness {
481
487
  readonly fixContext: FixContext;
482
488
  readonly repairAuthority?: GeneratedEnvironmentRepairAuthority;
483
489
  readonly repairRefusal?: RefusalOutcome;
490
+ /** Every declared MCP server as rendered; empty when the environment could not be prepared. */
491
+ readonly mcpServers: readonly McpServer[];
484
492
  }