@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
@@ -2,24 +2,36 @@ import type { CockpitConfig, configurationDefaults, UNCONFIRMABLE_ANSWERS } from
2
2
  import type { DocumentedSibling, Gap } from '../registry-resolve/registry.js';
3
3
  /**
4
4
  * Who gave an interview answer: a person, the setup agent choosing a default it was allowed to
5
- * choose, or no one said (an answers file that does not attribute a value cockpit never proposes).
6
- * Only a person's answer is written as stated; the others are listed under `unconfirmed`.
5
+ * choose, no one said (an answers file that does not attribute a value cockpit never proposes), or
6
+ * the setup agent accepting the value init read from exact sources, which init checks against what
7
+ * it reads. A person's answer is written as stated and a derived one with its source; the others
8
+ * are listed under `unconfirmed`.
7
9
  */
8
- export declare const ANSWER_PROVENANCES: readonly ['operator-confirmed', 'agent-default', 'unattributed'];
10
+ export declare const ANSWER_PROVENANCES: readonly ['operator-confirmed', 'agent-default', 'unattributed', 'derived'];
9
11
  export type AnswerProvenance = typeof ANSWER_PROVENANCES[number];
10
12
  export type UnconfirmableAnswer = typeof UNCONFIRMABLE_ANSWERS[number];
11
13
  export type UnconfirmedAnswer = Readonly<{
12
14
  field: UnconfirmableAnswer;
13
- provenance: Exclude<AnswerProvenance, 'operator-confirmed'>;
15
+ provenance: 'agent-default' | 'unattributed';
16
+ }>;
17
+ /** An answer accepted as read: the field and the file, as file:line where one line holds it, it was read from. */
18
+ export type DerivedAnswer = Readonly<{
19
+ field: UnconfirmableAnswer;
20
+ source: string;
14
21
  }>;
15
22
  export declare function unconfirmedReason(row: UnconfirmedAnswer): string;
16
- export declare const UNCONFIRMED_FIX = "confirm the value with whoever owns the product and correct it in cockpit.toml if it is wrong, then delete its entry from unconfirmed and run npx --no cockpit sync --config <your cockpit.toml>";
17
23
  export interface Attribution {
18
24
  target: string;
19
25
  value: unknown;
20
26
  from: 'inferred' | 'default';
21
27
  because: string;
22
28
  }
29
+ /**
30
+ * Who confirms a surfaced value: the setup agent for a schema default or the cockpit's own layout,
31
+ * which bring-up proves, and a person for a security boundary or a value a registry dialect left
32
+ * to a guess.
33
+ */
34
+ export declare function confirmedBy(attribution: Attribution): 'agent' | 'person';
23
35
  export interface DocumentModel {
24
36
  name?: string;
25
37
  defaults: ReturnType<typeof configurationDefaults>;
@@ -32,4 +44,4 @@ export interface DocumentModel {
32
44
  }
33
45
  export declare function renderIncompleteDocument(model: DocumentModel, schemaDirective: string, configuration?: CockpitConfig): string;
34
46
  /** Decisions are comments: the schema has no field for them, and the reader of the file needs them. */
35
- export declare function renderCompleteDocument(config: CockpitConfig, schemaDirective: string, decisions?: readonly string[], unconfirmed?: readonly UnconfirmedAnswer[]): string;
47
+ export declare function renderCompleteDocument(config: CockpitConfig, schemaDirective: string, decisions?: readonly string[], unconfirmed?: readonly UnconfirmedAnswer[], derived?: readonly DerivedAnswer[]): string;
@@ -1,21 +1,26 @@
1
1
  import { AUTONOMY_DEFAULTS, GATE_DEFAULTS } from '../autonomy.js';
2
+ import { unconfirmedFix } from '../guards/answer-confirmation.js';
2
3
  import { emitGaps, emitToml, toSiblingRows } from '../emit.js';
3
4
  /**
4
5
  * Who gave an interview answer: a person, the setup agent choosing a default it was allowed to
5
- * choose, or no one said (an answers file that does not attribute a value cockpit never proposes).
6
- * Only a person's answer is written as stated; the others are listed under `unconfirmed`.
6
+ * choose, no one said (an answers file that does not attribute a value cockpit never proposes), or
7
+ * the setup agent accepting the value init read from exact sources, which init checks against what
8
+ * it reads. A person's answer is written as stated and a derived one with its source; the others
9
+ * are listed under `unconfirmed`.
7
10
  */
8
- export const ANSWER_PROVENANCES = ['operator-confirmed', 'agent-default', 'unattributed'];
11
+ export const ANSWER_PROVENANCES = ['operator-confirmed', 'agent-default', 'unattributed', 'derived'];
9
12
  export function unconfirmedReason(row) {
10
13
  return row.provenance === 'agent-default'
11
14
  ? `${row.field} was chosen by the setup agent as a default; no person stated it`
12
15
  : `${row.field} came from an answers file that did not say who chose it; no person is known to have stated it`;
13
16
  }
14
- export const UNCONFIRMED_FIX = 'confirm the value with whoever owns the product and correct it in cockpit.toml if it is wrong, then delete its entry from unconfirmed and run npx --no cockpit sync --config <your cockpit.toml>';
15
17
  function renderUnconfirmed(rows = []) {
16
18
  if (rows.length === 0)
17
19
  return '';
18
- return `${rows.map(row => `# Unconfirmed: ${unconfirmedReason(row)}.\n`).join('')}# To confirm: ${UNCONFIRMED_FIX}; until then doctor warns.\n`;
20
+ return `${rows.map(row => `# Unconfirmed: ${unconfirmedReason(row)}.\n`).join('')}# To confirm: ${unconfirmedFix(rows.map(row => row.field))}; until then doctor warns.\n`;
21
+ }
22
+ function renderDerived(rows) {
23
+ return rows.map(row => `# Derived: ${row.field} was read from ${row.source} and accepted by the setup agent; bring-up proves it.\n`).join('');
19
24
  }
20
25
  /** Every decision with its default, commented out: flip one to "person" to take that decision yourself. */
21
26
  function renderDecisionDefaults() {
@@ -27,12 +32,29 @@ function renderDecisionDefaults() {
27
32
  ...table('work.gates', GATE_DEFAULTS), ...table('autonomy', AUTONOMY_DEFAULTS),
28
33
  ].join('\n')}\n`;
29
34
  }
35
+ // A wrong value in one of these widens what an agent may do or delete, so a person confirms it.
36
+ const PERSON_CONFIRMED_TARGETS = new Set(['guards.protected_branches', 'guards.allow', 'guards.deny',
37
+ 'mcp.approved', 'environment.resettable_volumes', 'environment.generated_host_paths']);
38
+ /**
39
+ * Who confirms a surfaced value: the setup agent for a schema default or the cockpit's own layout,
40
+ * which bring-up proves, and a person for a security boundary or a value a registry dialect left
41
+ * to a guess.
42
+ */
43
+ export function confirmedBy(attribution) {
44
+ if (PERSON_CONFIRMED_TARGETS.has(attribution.target))
45
+ return 'person';
46
+ return attribution.from === 'default' || attribution.target === 'cockpit.siblings_root' ? 'agent' : 'person';
47
+ }
30
48
  function renderAttributions(attributions) {
31
49
  if (attributions.length === 0)
32
50
  return '';
33
51
  const lines = ['# Provenance — every unread value names its source and reason.'];
34
- for (const attribution of attributions) {
35
- lines.push(`# [${attribution.from}] ${attribution.target} = ${JSON.stringify(attribution.value)}: ${attribution.because}`);
52
+ for (const [by, heading] of [['agent', '# The setup agent accepts these: schema defaults and the cockpit\'s own layout, which bring-up proves.'],
53
+ ['person', '# A person confirms these, and the defaults digest covers only them: each bounds what an agent may do.']]) {
54
+ const rows = attributions.filter(row => confirmedBy(row) === by);
55
+ if (rows.length === 0)
56
+ continue;
57
+ lines.push(heading, ...rows.map(row => `# [${row.from}] ${row.target} = ${JSON.stringify(row.value)}: ${row.because}`));
36
58
  }
37
59
  return `${lines.join('\n')}\n`;
38
60
  }
@@ -77,6 +99,6 @@ export function renderIncompleteDocument(model, schemaDirective, configuration)
77
99
  ].join('');
78
100
  }
79
101
  /** Decisions are comments: the schema has no field for them, and the reader of the file needs them. */
80
- export function renderCompleteDocument(config, schemaDirective, decisions = [], unconfirmed = []) {
81
- return `#:schema ${schemaDirective}\n${decisions.map(decision => `# Decision: ${decision}\n`).join('')}${renderUnconfirmed(unconfirmed)}${emitToml(config)}${renderDecisionDefaults()}`;
102
+ export function renderCompleteDocument(config, schemaDirective, decisions = [], unconfirmed = [], derived = []) {
103
+ return `#:schema ${schemaDirective}\n${decisions.map(decision => `# Decision: ${decision}\n`).join('')}${renderDerived(derived)}${renderUnconfirmed(unconfirmed)}${emitToml(config)}${renderDecisionDefaults()}`;
82
104
  }
@@ -0,0 +1,9 @@
1
+ /** A path or glob (`*`, `?`, `**`), optionally `<slug>:`-prefixed, without `..` segments or a leading `/`. */
2
+ export declare const REPOSITORY_PATH: RegExp;
3
+ /** One file or directory, optionally `<slug>:`-prefixed: no wildcards, no `.` or `..` segments, no leading or trailing `/`. */
4
+ export declare const REPOSITORY_FILE: RegExp;
5
+ /** The sibling a reference names, if any, and the path inside that repository. */
6
+ export declare function splitRepositoryPath(reference: string): Readonly<{
7
+ slug?: string;
8
+ path: string;
9
+ }>;
@@ -0,0 +1,16 @@
1
+ // A path in one of a cockpit's repositories: `path` in the cockpit's own, or `<sibling slug>:path` in a
2
+ // sibling. Work-order scopes and sources, the [docs] declarations and every reader of them spell and
3
+ // split it here, so a reference one admits is the reference the other resolves.
4
+ const SLUG = '[a-z0-9][a-z0-9-]{0,63}';
5
+ // Never absolute or climbing. The climbing check looks past the sibling prefix, which would otherwise
6
+ // consume the `:` before `..`.
7
+ /** A path or glob (`*`, `?`, `**`), optionally `<slug>:`-prefixed, without `..` segments or a leading `/`. */
8
+ export const REPOSITORY_PATH = new RegExp(`^(?!(?:${SLUG}:)?(?:.*/)?\\.\\.(?:/|$))(?:${SLUG}:)?(?![/])[A-Za-z0-9._@+\\-/*?]+$`);
9
+ /** One file or directory, optionally `<slug>:`-prefixed: no wildcards, no `.` or `..` segments, no leading or trailing `/`. */
10
+ export const REPOSITORY_FILE = new RegExp(`^(?!(?:${SLUG}:)?(?:.*/)?\\.\\.?(?:/|$))(?:${SLUG}:)?(?![/])[A-Za-z0-9._@+\\-/]*[A-Za-z0-9._@+-]$`);
11
+ const PREFIXED = new RegExp(`^(${SLUG}):(.*)$`);
12
+ /** The sibling a reference names, if any, and the path inside that repository. */
13
+ export function splitRepositoryPath(reference) {
14
+ const match = PREFIXED.exec(reference);
15
+ return match ? { slug: match[1], path: match[2] } : { path: reference };
16
+ }
package/dist/schema.d.ts CHANGED
@@ -16,15 +16,6 @@ export declare const AuthPosture: z.ZodEnum<{
16
16
  none: "none";
17
17
  real: "real";
18
18
  }>;
19
- declare const Toolchain: z.ZodEnum<{
20
- dotnet: "dotnet";
21
- go: "go";
22
- node: "node";
23
- none: "none";
24
- python: "python";
25
- ruby: "ruby";
26
- rust: "rust";
27
- }>;
28
19
  export declare const EnvironmentFile: z.ZodObject<{
29
20
  template: z.ZodString;
30
21
  instance: z.ZodString;
@@ -37,6 +28,8 @@ export declare const MachineFloor: z.ZodObject<{
37
28
  cold_start_seconds: z.ZodNumber;
38
29
  }, z.core.$strict>;
39
30
  export declare const ResettableVolumeId: z.ZodString;
31
+ /** A Compose profile name, in the lowercase spelling every stack command and state record shares. */
32
+ export declare const ComposeProfileName: z.ZodString;
40
33
  export declare const GeneratedHostPath: z.ZodString;
41
34
  export declare function parseBrowserNetworkExposure(value: string | undefined, reason: string | undefined): Readonly<{
42
35
  value: string;
@@ -116,6 +109,7 @@ export declare const Environment: z.ZodObject<{
116
109
  resettable_volumes: z.ZodDefault<z.ZodArray<z.ZodString>>;
117
110
  generated_host_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
118
111
  compose_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
112
+ profiles: z.ZodDefault<z.ZodArray<z.ZodString>>;
119
113
  entrypoint_derivatives: z.ZodDefault<z.ZodArray<z.ZodObject<{
120
114
  name: z.ZodString;
121
115
  kind: z.ZodEnum<{
@@ -191,6 +185,7 @@ export declare const Sibling: z.ZodObject<{
191
185
  template: z.ZodString;
192
186
  instance: z.ZodString;
193
187
  }, z.core.$strict>>>;
188
+ rebuild_inputs: z.ZodOptional<z.ZodArray<z.ZodString>>;
194
189
  derives: z.ZodOptional<z.ZodObject<{
195
190
  mcp_sources: z.ZodArray<z.ZodObject<{
196
191
  path: z.ZodString;
@@ -275,6 +270,11 @@ export declare const Work: z.ZodObject<{
275
270
  ci: "ci";
276
271
  person: "person";
277
272
  }>>;
273
+ acknowledge: z.ZodOptional<z.ZodEnum<{
274
+ agent: "agent";
275
+ ci: "ci";
276
+ person: "person";
277
+ }>>;
278
278
  }, z.core.$strict>>;
279
279
  }, z.core.$strict>;
280
280
  export declare const Autonomy: z.ZodObject<{
@@ -301,6 +301,12 @@ export declare const Autonomy: z.ZodObject<{
301
301
  export declare const BROWSER_DRIVER_VERSION: RegExp;
302
302
  /** Playwright refuses a client and server whose major.minor differ, so only that much must agree. */
303
303
  export declare function browserDriversCompatible(left: string, right: string): boolean;
304
+ /** The data posture that lets browser tools run without prompts: isolated, not restored, not reaching production. */
305
+ export declare const browserDataQualifies: (data: Readonly<{
306
+ mode: string;
307
+ isolated: boolean;
308
+ reaches_production: boolean;
309
+ }>) => boolean;
304
310
  export declare const Browser: z.ZodObject<{
305
311
  engine: z.ZodDefault<z.ZodEnum<{
306
312
  chromium: "chromium";
@@ -366,7 +372,7 @@ export declare const Browser: z.ZodObject<{
366
372
  isolated: z.ZodBoolean;
367
373
  reaches_production: z.ZodBoolean;
368
374
  }, z.core.$strict>;
369
- request_preapproval: z.ZodDefault<z.ZodBoolean>;
375
+ request_preapproval: z.ZodOptional<z.ZodBoolean>;
370
376
  artifacts: z.ZodObject<{
371
377
  mcp: z.ZodString;
372
378
  cli: z.ZodString;
@@ -375,6 +381,7 @@ export declare const Browser: z.ZodObject<{
375
381
  }, z.core.$strict>;
376
382
  export declare const TestingName: z.ZodString;
377
383
  export declare const Testing: z.ZodObject<{
384
+ test_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
378
385
  stages: z.ZodArray<z.ZodObject<{
379
386
  id: z.ZodString;
380
387
  command: z.ZodArray<z.ZodString>;
@@ -407,14 +414,6 @@ export declare const Testing: z.ZodObject<{
407
414
  sha256: z.ZodString;
408
415
  }, z.core.$strict>>;
409
416
  }, z.core.$strict>;
410
- declare const SurfaceSource: z.ZodObject<{
411
- path: z.ZodIntersection<z.ZodString, z.ZodString>;
412
- bytes: z.ZodString;
413
- }, z.core.$strict>;
414
- declare const SurfaceOverrideReference: z.ZodObject<{
415
- name: z.ZodString;
416
- sha256: z.ZodString;
417
- }, z.core.$strict>;
418
417
  export declare const SurfaceDeclaration: z.ZodObject<{
419
418
  name: z.ZodString;
420
419
  feature: z.ZodEnum<{
@@ -474,57 +473,6 @@ export declare const SurfaceAdoption: z.ZodObject<{
474
473
  confirmed: z.ZodOptional<z.ZodBoolean>;
475
474
  }, z.core.$strict>;
476
475
  }, z.core.$strict>;
477
- declare const CustomHarnessCapability: z.ZodDiscriminatedUnion<[z.ZodObject<{
478
- feature: z.ZodEnum<{
479
- hooks: "hooks";
480
- mcp: "mcp";
481
- permissions: "permissions";
482
- rules: "rules";
483
- skills: "skills";
484
- subagents: "subagents";
485
- }>;
486
- measured_on: z.ZodString;
487
- spelling: z.ZodIntersection<z.ZodString, z.ZodString>;
488
- control: z.ZodIntersection<z.ZodString, z.ZodString>;
489
- mechanism: z.ZodObject<{
490
- module: z.ZodIntersection<z.ZodString, z.ZodString>;
491
- export: z.ZodString;
492
- }, z.core.$strict>;
493
- state: z.ZodLiteral<"enforced">;
494
- evidence: z.ZodObject<{
495
- reference: z.ZodIntersection<z.ZodString, z.ZodString>;
496
- sha256: z.ZodString;
497
- }, z.core.$strict>;
498
- }, z.core.$strict>, z.ZodObject<{
499
- feature: z.ZodEnum<{
500
- hooks: "hooks";
501
- mcp: "mcp";
502
- permissions: "permissions";
503
- rules: "rules";
504
- skills: "skills";
505
- subagents: "subagents";
506
- }>;
507
- measured_on: z.ZodString;
508
- spelling: z.ZodIntersection<z.ZodString, z.ZodString>;
509
- control: z.ZodIntersection<z.ZodString, z.ZodString>;
510
- mechanism: z.ZodObject<{
511
- module: z.ZodIntersection<z.ZodString, z.ZodString>;
512
- export: z.ZodString;
513
- }, z.core.$strict>;
514
- state: z.ZodLiteral<"instructed">;
515
- }, z.core.$strict>, z.ZodObject<{
516
- feature: z.ZodEnum<{
517
- hooks: "hooks";
518
- mcp: "mcp";
519
- permissions: "permissions";
520
- rules: "rules";
521
- skills: "skills";
522
- subagents: "subagents";
523
- }>;
524
- measured_on: z.ZodString;
525
- state: z.ZodLiteral<"unavailable">;
526
- reason: z.ZodString;
527
- }, z.core.$strict>], "state">;
528
476
  export declare const CustomHarness: z.ZodObject<{
529
477
  name: z.ZodString;
530
478
  capabilities: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -732,6 +680,7 @@ export declare function configurationDefaults(): {
732
680
  amend?: "agent" | "ci" | "person" | undefined;
733
681
  answer?: "agent" | "ci" | "person" | undefined;
734
682
  signoff?: "agent" | "ci" | "person" | undefined;
683
+ acknowledge?: "agent" | "ci" | "person" | undefined;
735
684
  };
736
685
  };
737
686
  autonomy: {
@@ -757,6 +706,7 @@ export declare function configurationDefaults(): {
757
706
  resettable_volumes: string[];
758
707
  generated_host_paths: string[];
759
708
  compose_files: string[];
709
+ profiles: string[];
760
710
  entrypoint_derivatives: {
761
711
  name: string;
762
712
  kind: "advertised_listener" | "callback_url" | "cors_origin" | "entrypoint" | "origin" | "public_base_url";
@@ -847,6 +797,11 @@ export declare function configurationDefaults(): {
847
797
  * stated but were guessed is the failure cockpit exists to prevent.
848
798
  */
849
799
  export declare const UNCONFIRMABLE_ANSWERS: readonly ['cockpit.name', 'sibling', 'estate.runnable', 'ports.bases', 'environment.install_policies', 'environment.test_data', 'environment.compose_files', 'environment.machine', 'environment.posture', 'testing', 'guards'];
800
+ export declare const Docs: z.ZodObject<{
801
+ living: z.ZodDefault<z.ZodArray<z.ZodString>>;
802
+ decisions: z.ZodOptional<z.ZodString>;
803
+ decisions_index: z.ZodOptional<z.ZodString>;
804
+ }, z.core.$strict>;
850
805
  export declare const CockpitConfig: z.ZodObject<{
851
806
  unconfirmed: z.ZodOptional<z.ZodArray<z.ZodEnum<{
852
807
  "cockpit.name": "cockpit.name";
@@ -898,6 +853,7 @@ export declare const CockpitConfig: z.ZodObject<{
898
853
  template: z.ZodString;
899
854
  instance: z.ZodString;
900
855
  }, z.core.$strict>>>;
856
+ rebuild_inputs: z.ZodOptional<z.ZodArray<z.ZodString>>;
901
857
  derives: z.ZodOptional<z.ZodObject<{
902
858
  mcp_sources: z.ZodArray<z.ZodObject<{
903
859
  path: z.ZodString;
@@ -978,6 +934,11 @@ export declare const CockpitConfig: z.ZodObject<{
978
934
  ci: "ci";
979
935
  person: "person";
980
936
  }>>;
937
+ acknowledge: z.ZodOptional<z.ZodEnum<{
938
+ agent: "agent";
939
+ ci: "ci";
940
+ person: "person";
941
+ }>>;
981
942
  }, z.core.$strict>>;
982
943
  }, z.core.$strict>>;
983
944
  autonomy: z.ZodPrefault<z.ZodObject<{
@@ -1066,7 +1027,7 @@ export declare const CockpitConfig: z.ZodObject<{
1066
1027
  isolated: z.ZodBoolean;
1067
1028
  reaches_production: z.ZodBoolean;
1068
1029
  }, z.core.$strict>;
1069
- request_preapproval: z.ZodDefault<z.ZodBoolean>;
1030
+ request_preapproval: z.ZodOptional<z.ZodBoolean>;
1070
1031
  artifacts: z.ZodObject<{
1071
1032
  mcp: z.ZodString;
1072
1033
  cli: z.ZodString;
@@ -1074,6 +1035,7 @@ export declare const CockpitConfig: z.ZodObject<{
1074
1035
  }, z.core.$strict>;
1075
1036
  }, z.core.$strict>>;
1076
1037
  testing: z.ZodOptional<z.ZodObject<{
1038
+ test_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
1077
1039
  stages: z.ZodArray<z.ZodObject<{
1078
1040
  id: z.ZodString;
1079
1041
  command: z.ZodArray<z.ZodString>;
@@ -1137,6 +1099,7 @@ export declare const CockpitConfig: z.ZodObject<{
1137
1099
  resettable_volumes: z.ZodDefault<z.ZodArray<z.ZodString>>;
1138
1100
  generated_host_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
1139
1101
  compose_files: z.ZodDefault<z.ZodArray<z.ZodString>>;
1102
+ profiles: z.ZodDefault<z.ZodArray<z.ZodString>>;
1140
1103
  entrypoint_derivatives: z.ZodDefault<z.ZodArray<z.ZodObject<{
1141
1104
  name: z.ZodString;
1142
1105
  kind: z.ZodEnum<{
@@ -1278,11 +1241,15 @@ export declare const CockpitConfig: z.ZodObject<{
1278
1241
  }, z.core.$strict>;
1279
1242
  }, z.core.$strict>>>;
1280
1243
  }, z.core.$strict>>;
1244
+ docs: z.ZodOptional<z.ZodObject<{
1245
+ living: z.ZodDefault<z.ZodArray<z.ZodString>>;
1246
+ decisions: z.ZodOptional<z.ZodString>;
1247
+ decisions_index: z.ZodOptional<z.ZodString>;
1248
+ }, z.core.$strict>>;
1281
1249
  }, z.core.$strict>;
1282
1250
  export type CockpitConfig = z.infer<typeof CockpitConfig>;
1283
1251
  export type Sibling = z.infer<typeof Sibling>;
1284
1252
  export type Acquire = z.infer<typeof Acquire>;
1285
- export type Toolchain = z.infer<typeof Toolchain>;
1286
1253
  export type AddressingPosture = z.infer<typeof AddressingPosture>;
1287
1254
  export type AuthPosture = z.infer<typeof AuthPosture>;
1288
1255
  export type EnvironmentFile = z.infer<typeof EnvironmentFile>;
@@ -1293,17 +1260,17 @@ export type EntrypointDerivative = z.infer<typeof EntrypointDerivative>;
1293
1260
  export type InstallPolicy = z.infer<typeof InstallPolicy>;
1294
1261
  export type Browser = z.infer<typeof Browser>;
1295
1262
  export type Testing = z.infer<typeof Testing>;
1263
+ export type Docs = z.infer<typeof Docs>;
1296
1264
  export type Provenance = z.infer<typeof Provenance>;
1297
- export type SurfaceSource = z.infer<typeof SurfaceSource>;
1298
- export type SurfaceOverrideReference = z.infer<typeof SurfaceOverrideReference>;
1299
1265
  export type SurfaceDeclaration = z.infer<typeof SurfaceDeclaration>;
1300
1266
  export type SurfaceOverlay = z.infer<typeof SurfaceOverlay>;
1301
1267
  export type SurfaceAdoption = z.infer<typeof SurfaceAdoption>;
1302
- export type CustomHarnessCapability = z.infer<typeof CustomHarnessCapability>;
1303
1268
  export type CustomHarness = z.infer<typeof CustomHarness>;
1304
1269
  export type Surfaces = z.infer<typeof Surfaces>;
1305
1270
  /** Rows the stack bind-mounts under the given profile set — the projection's import list. */
1306
- export declare function mounted(cfg: CockpitConfig, profiles: readonly string[]): Sibling[];
1271
+ export declare function mounted<Row extends Readonly<Pick<Sibling, 'mount'>>>(cfg: Readonly<{
1272
+ sibling: readonly Row[];
1273
+ }>, profiles: readonly string[]): Row[];
1307
1274
  /**
1308
1275
  * The SHA-256 that identifies a running stack: ownership labels, the generation, saved progress and
1309
1276
  * data history are keyed by it rather than by the bytes of cockpit.toml. Keying them by the file
package/dist/schema.js CHANGED
@@ -8,6 +8,7 @@ import { GUARD_HOST_LIMITS, GUARD_HOST_PATTERN, normalizeGuardHost } from './gua
8
8
  import { ADOPTABLE_SURFACE_PATHS, ADOPTED_SURFACE_ROOT, SURFACE_BUNDLE_FORMATS, SURFACE_FEATURES, SURFACE_TARGETS, surfaceSourcesMatchFeature } from './surfaces/contract.js';
9
9
  import { DEFAULT_SURFACE_TOOLS, GUARD_SURFACE_TARGETS } from './surfaces/harness-registry.js';
10
10
  import { RefusalError, errorCause } from './environment/contract.js';
11
+ import { REPOSITORY_FILE, REPOSITORY_PATH, splitRepositoryPath } from './repository-path.js';
11
12
  export const STRUCTURED_INPUT_ENTRY_LIMIT = 4_096;
12
13
  // Zod cannot reflect a regex in a string type; successful parses satisfy this narrower output.
13
14
  export const Mount = z
@@ -57,6 +58,8 @@ export const MachineFloor = z
57
58
  })
58
59
  .strict();
59
60
  export const ResettableVolumeId = z.string().regex(/^[a-z0-9][a-z0-9_.-]*$/);
61
+ /** A Compose profile name, in the lowercase spelling every stack command and state record shares. */
62
+ export const ComposeProfileName = z.string().max(64).regex(/^[a-z0-9][a-z0-9_.-]*$/, 'expected a lowercase Compose profile name');
60
63
  export const GeneratedHostPath = SafeRelativePath;
61
64
  export function parseBrowserNetworkExposure(value, reason) {
62
65
  const exposure = value ?? '<loopback>';
@@ -151,6 +154,7 @@ export const Environment = z
151
154
  resettable_volumes: z.array(ResettableVolumeId).default([]).describe('Compose volumes that reset may delete and recreate, named by their Compose key.'),
152
155
  generated_host_paths: z.array(GeneratedHostPath).default([]).describe('Host paths cockpit generates and owns, relative to the cockpit.'),
153
156
  compose_files: z.array(LocalComposePath).default([]).describe('Compose files, relative to the cockpit, that make up the stack; cockpit runs them as written.'),
157
+ profiles: z.array(ComposeProfileName).max(64).default([]).describe('Compose profiles every up and session activates; up --profile adds more for one run.'),
154
158
  entrypoint_derivatives: z.array(EntrypointDerivative).default([]).describe('Addresses derived from leased ports, such as application origins and callback URLs.'),
155
159
  install_policies: z.array(InstallPolicy).default([]).describe('The build and test commands the guard admits for each toolchain and lockfile.'),
156
160
  // Product data lives in the resettable volumes, so the seed reruns exactly when one of them is
@@ -166,6 +170,7 @@ export const Environment = z
166
170
  resettable_volumes: environment.resettable_volumes,
167
171
  generated_host_paths: environment.generated_host_paths,
168
172
  compose_files: environment.compose_files,
173
+ profiles: environment.profiles,
169
174
  entrypoint_derivatives: environment.entrypoint_derivatives.map((row) => row.name),
170
175
  posture_requirements: environment.posture_requirements?.map((row) => row.requirement) ?? [],
171
176
  };
@@ -256,6 +261,9 @@ export const Sibling = z
256
261
  // Printed when the checkout is missing, so it says what the absence costs.
257
262
  purpose: z.string().min(1).describe('What the sibling is for; printed when its checkout is missing.'),
258
263
  environment_files: z.array(EnvironmentFile).optional().describe('Template and instance environment file pairs the sibling needs.'),
264
+ // A running stack keeps the dependencies and schema it started with; a pulled lockfile or a new
265
+ // migration behind it fails like a product bug. Lockfiles are watched without this list.
266
+ rebuild_inputs: z.array(SafeRelativePath).max(64).optional().describe('Files or directories in the sibling, beyond its lockfiles, whose change leaves a running stack stale, such as a migrations directory.'),
259
267
  derives: SiblingDerives.optional().describe('Facts about the sibling that cockpit derives rows or MCP servers from.'),
260
268
  })
261
269
  .strict()
@@ -388,9 +396,10 @@ export const WORK_GRADING = ['independent', 'checks', 'self'];
388
396
  const WorkGates = z
389
397
  .object({
390
398
  accept: Decider.optional().describe('Who accepts a draft work order: agent, person or ci. Default agent.'),
391
- amend: Decider.optional().describe('Who accepts a changed contract. Default agent. Removing or rewriting an accepted criterion always needs a person or ci.'),
399
+ amend: Decider.optional().describe('Who accepts a changed contract. Default agent. Removing an accepted criterion or gate always needs a person or ci; an agent\'s rewrite of a criterion, scope, non-goal or objective is judged at the next grade, and needs a person under checks grading.'),
392
400
  answer: Decider.optional().describe('Who answers an open question on an order. Default agent.'),
393
401
  signoff: Decider.optional().describe('Who closes a graded order. Default agent.'),
402
+ acknowledge: Decider.optional().describe('Who acknowledges that an order weakened tests, which checks grading otherwise fails; under independent and self grading the acknowledgement is context for the verdict. Default agent.'),
394
403
  })
395
404
  .strict();
396
405
  export const Work = z
@@ -446,6 +455,8 @@ const BrowserArtifactRoots = z.object({
446
455
  }
447
456
  }
448
457
  });
458
+ /** The data posture that lets browser tools run without prompts: isolated, not restored, not reaching production. */
459
+ export const browserDataQualifies = (data) => data.mode !== 'restored' && data.isolated && !data.reaches_production;
449
460
  export const Browser = z.object({
450
461
  engine: z.enum(['chromium', 'firefox', 'webkit']).default('chromium').describe('Browser engine to drive.'),
451
462
  network_exposure: z.string().min(1).max(512).regex(/^[a-z0-9.*:<>\[\],-]+$/).refine((value) => {
@@ -494,7 +505,7 @@ export const Browser = z.object({
494
505
  isolated: z.boolean().describe('True when the data is separate from anyone else\'s.'),
495
506
  reaches_production: z.boolean().describe('True when browser actions can affect production.'),
496
507
  }).strict().describe('Statement about the data the browser touches; cockpit cannot observe it.'),
497
- request_preapproval: z.boolean().default(false).describe('Run browser tools without prompts; allowed only on isolated, non-production, non-restored data.'),
508
+ request_preapproval: z.boolean().optional().describe('Run browser tools without prompts; allowed only on isolated, non-production, non-restored data. Defaults to true when data qualifies and false otherwise.'),
498
509
  artifacts: BrowserArtifactRoots.describe('Generated host paths for browser output.'),
499
510
  }).strict().superRefine((browser, ctx) => {
500
511
  try {
@@ -538,7 +549,7 @@ export const Browser = z.object({
538
549
  if (paths.some((path) => !path.startsWith(`${browser.artifacts.auth}/`))) {
539
550
  ctx.addIssue({ code: 'custom', path: ['authentication', 'states'], message: 'browser authentication state must stay inside the auth artifact root' });
540
551
  }
541
- if (browser.request_preapproval && (browser.data.mode === 'restored' || !browser.data.isolated || browser.data.reaches_production)) {
552
+ if (browser.request_preapproval && !browserDataQualifies(browser.data)) {
542
553
  ctx.addIssue({ code: 'custom', path: ['request_preapproval'], message: 'browser preapproval requires isolated empty or seeded data without production reach' });
543
554
  }
544
555
  });
@@ -555,6 +566,8 @@ const TestingSelect = z.object({
555
566
  route: TestingSelectArguments.optional().describe('Arguments that select tests by route; {value} becomes the selection.'),
556
567
  }).strict();
557
568
  export const Testing = z.object({
569
+ test_files: z.array(z.string().min(1).max(512).regex(/^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._@+\-/*?]+$/, 'a relative path or glob, without .. or a leading /')).min(1).max(64).optional()
570
+ .describe('Paths and globs, relative to each checkout, that hold tests. Grading fails an order that deletes, skips or loosens what they match. Defaults to common layouts.'),
558
571
  stages: z.array(z.object({
559
572
  id: TestingName.describe('Name of the stage.'),
560
573
  command: TestingCommand.describe('Command to run, as an argument list.'),
@@ -777,6 +790,27 @@ export function configurationDefaults() {
777
790
  export const UNCONFIRMABLE_ANSWERS = ['cockpit.name', 'sibling', 'estate.runnable', 'ports.bases', 'environment.install_policies',
778
791
  'environment.test_data', 'environment.compose_files', 'environment.machine', 'environment.posture',
779
792
  'testing', 'guards'];
793
+ // `path` in the cockpit's own repository, or `<sibling slug>:path` in a sibling, spelt as a work
794
+ // order's scope is. Living documents may be globs; the decision paths name one file or directory.
795
+ const DocsGlob = z.string().max(1_024).regex(REPOSITORY_PATH, 'a relative path, optionally <sibling slug>:path, without .. or a leading /');
796
+ const DocsFile = z.string().max(1_024).regex(REPOSITORY_FILE, 'one relative path without wildcards, optionally <sibling slug>:path, without . or .. segments or a leading or trailing /');
797
+ const repositoryOf = (path) => splitRepositoryPath(path).slug ?? '.';
798
+ export const Docs = z.object({
799
+ living: z.array(DocsGlob).max(64).default([])
800
+ .describe('Globs of the Markdown documents that must stay true; docs check reports the paths and make targets they cite that the repository has since removed.'),
801
+ decisions: DocsFile.optional().describe('Directory of decision records, such as docs/adr. Work orders record their lasting decisions there, and docs check reports duplicate numbers, missing statuses and broken supersedes links.'),
802
+ decisions_index: DocsFile.optional().describe('Index file that links every decision record; a record cockpit writes is appended to it, and docs check reports records it omits and links to records that do not exist.'),
803
+ }).strict().superRefine((docs, ctx) => {
804
+ // A [docs] table that names nothing would parse and do nothing.
805
+ if (docs.living.length === 0 && docs.decisions === undefined)
806
+ ctx.addIssue({ code: 'custom', path: [], message: '[docs] declares neither living documents nor decisions' });
807
+ // An index with no records directory, or of another repository's records, would check nothing.
808
+ if (docs.decisions_index !== undefined && docs.decisions === undefined)
809
+ ctx.addIssue({ code: 'custom', path: ['decisions_index'], message: 'decisions_index needs decisions, the directory it indexes' });
810
+ else if (docs.decisions_index !== undefined && repositoryOf(docs.decisions_index) !== repositoryOf(docs.decisions)) {
811
+ ctx.addIssue({ code: 'custom', path: ['decisions_index'], message: 'decisions_index must be in the same repository as decisions, so its links resolve' });
812
+ }
813
+ });
780
814
  export const CockpitConfig = z
781
815
  .object({
782
816
  unconfirmed: z.array(z.enum(UNCONFIRMABLE_ANSWERS)).min(1).max(UNCONFIRMABLE_ANSWERS.length)
@@ -792,6 +826,7 @@ export const CockpitConfig = z
792
826
  testing: Testing.optional().describe('Test stages and gates.'),
793
827
  environment: Environment.prefault({}).describe('How the stack is reached, signed in to, built and kept.'),
794
828
  surfaces: Surfaces.prefault({}).describe('Instructions, skills and settings generated for agent tools.'),
829
+ docs: Docs.optional().describe('Living documents and decision records: docs check keeps them true, and work orders record their decisions there.'),
795
830
  })
796
831
  .strict()
797
832
  .superRefine((cfg, ctx) => {
@@ -872,20 +907,36 @@ export const CockpitConfig = z
872
907
  message: `resettable volume ${JSON.stringify(name)} names no volume: the environment declares no compose_files`,
873
908
  });
874
909
  });
910
+ cfg.environment.profiles.forEach((name, index) => {
911
+ ctx.addIssue({
912
+ code: 'custom', path: ['environment', 'profiles', index],
913
+ message: `profile ${JSON.stringify(name)} activates nothing: the environment declares no compose_files`,
914
+ });
915
+ });
916
+ }
917
+ if (cfg.docs) {
918
+ const paths = [...cfg.docs.living.map((path, index) => ({ path, at: ['docs', 'living', index] })),
919
+ ...cfg.docs.decisions === undefined ? [] : [{ path: cfg.docs.decisions, at: ['docs', 'decisions'] }],
920
+ ...cfg.docs.decisions_index === undefined ? [] : [{ path: cfg.docs.decisions_index, at: ['docs', 'decisions_index'] }]];
921
+ for (const { path, at } of paths) {
922
+ const slug = repositoryOf(path);
923
+ if (slug !== '.' && !cfg.sibling.some(row => row.slug === slug)) {
924
+ ctx.addIssue({ code: 'custom', path: at, message: `${JSON.stringify(path)} names sibling "${slug}", which no [[sibling]] declares` });
925
+ }
926
+ }
875
927
  }
876
- // Deliberately NOT checked here: that a `profile:<name>` lane names a profile in
877
- // `profiles.default`. A profile lane is opt-in by definition — it exists precisely to
878
- // stay off until EXTRA_PROFILES turns it on — so absence from the default set is the
879
- // normal case, not a defect. The spelling is defined by the compose file,
880
- // which preflight reads and a schema cannot.
928
+ // Not checked here: that `environment.profiles` and each `profile:<name>` mount name a
929
+ // profile the Compose files define. Those names live in the Compose files, which `check`
930
+ // and every stack command read and a schema cannot. A profile mount outside
931
+ // `environment.profiles` is the normal case: it stays off until `up --profile` turns it on.
881
932
  });
882
933
  /** Rows the stack bind-mounts under the given profile set — the projection's import list. */
883
934
  export function mounted(cfg, profiles) {
884
935
  return cfg.sibling.filter((s) => s.mount === 'always' || (s.mount.startsWith('profile:') && profiles.includes(s.mount.slice(8))));
885
936
  }
886
- // How a sibling is described, fetched, gated for agents or projected into guides. None of it reaches
887
- // a container, so editing it must leave a running stack alone.
888
- const SIBLING_NON_RUNTIME_FIELDS = new Set(['purpose', 'guides', 'instruction_pointer', 'remote', 'branch', 'acquire', 'host_reason', 'gate']);
937
+ // How a sibling is described, fetched, gated for agents, projected into guides or watched for a stale
938
+ // stack. None of it reaches a container, so editing it must leave a running stack alone.
939
+ const SIBLING_NON_RUNTIME_FIELDS = new Set(['purpose', 'guides', 'instruction_pointer', 'remote', 'branch', 'acquire', 'host_reason', 'gate', 'rebuild_inputs']);
889
940
  // What the host checks or reports around the containers: the machine floor and cold-start budget
890
941
  // `up` and preflight hold the host to, the posture preflight reads, the volumes `reset` may delete,
891
942
  // the host command that seeds them, and the URLs derived from leased ports. Hashing them made raising cold_start_seconds strand a