@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.
- package/CHANGELOG.md +99 -0
- package/README.md +3 -0
- package/cockpit.schema.json +73 -3
- package/dist/autonomy.d.ts +3 -2
- package/dist/autonomy.js +30 -13
- package/dist/browser/commands.d.ts +57 -2
- package/dist/browser/commands.js +114 -10
- package/dist/browser/runtime.d.ts +1 -0
- package/dist/browser/runtime.js +4 -1
- package/dist/browser/surfaces.d.ts +7 -7
- package/dist/browser/surfaces.js +5 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +5 -0
- package/dist/cli-main.d.ts +40 -3
- package/dist/cli-main.js +241 -62
- package/dist/command-table.d.ts +19 -11
- package/dist/command-table.js +190 -38
- package/dist/config/load.d.ts +16 -4
- package/dist/config/load.js +25 -3
- package/dist/config/unconfirmed-toml.d.ts +9 -0
- package/dist/config/unconfirmed-toml.js +29 -0
- package/dist/diagnostics.js +10 -1
- package/dist/docs/check.d.ts +16 -0
- package/dist/docs/check.js +197 -0
- package/dist/docs/decision-record.d.ts +34 -0
- package/dist/docs/decision-record.js +135 -0
- package/dist/docs/decisions.d.ts +16 -0
- package/dist/docs/decisions.js +103 -0
- package/dist/docs/markdown.d.ts +36 -0
- package/dist/docs/markdown.js +88 -0
- package/dist/docs/references.d.ts +32 -0
- package/dist/docs/references.js +148 -0
- package/dist/environment/application/confirm.d.ts +35 -0
- package/dist/environment/application/confirm.js +122 -0
- package/dist/environment/application/destructive.js +6 -2
- package/dist/environment/application/doctor.d.ts +12 -7
- package/dist/environment/application/doctor.js +20 -8
- package/dist/environment/application/guard.d.ts +12 -6
- package/dist/environment/application/guard.js +87 -32
- package/dist/environment/application/health.d.ts +14 -5
- package/dist/environment/application/health.js +8 -2
- package/dist/environment/application/lifecycle-context.d.ts +10 -3
- package/dist/environment/application/lifecycle-state.js +4 -3
- package/dist/environment/application/lifecycle.d.ts +2 -1
- package/dist/environment/application/lifecycle.js +11 -3
- package/dist/environment/application/services.d.ts +60 -0
- package/dist/environment/application/services.js +89 -0
- package/dist/environment/application/siblings.d.ts +43 -0
- package/dist/environment/application/siblings.js +269 -0
- package/dist/environment/claims.d.ts +10 -10
- package/dist/environment/claims.js +3 -11
- package/dist/environment/compose-topology.d.ts +15 -7
- package/dist/environment/compose-topology.js +100 -23
- package/dist/environment/contract.d.ts +42 -9
- package/dist/environment/contract.js +33 -0
- package/dist/environment/executable.d.ts +12 -0
- package/dist/environment/executable.js +46 -23
- package/dist/environment/generated-application/check.js +2 -0
- package/dist/environment/generated-application/prepare.js +2 -0
- package/dist/environment/generated-application/shared.d.ts +10 -2
- package/dist/environment/generated-application/shared.js +6 -1
- package/dist/environment/generated-application/surface-outputs.js +23 -12
- package/dist/environment/generated-application/surfaces.js +16 -11
- package/dist/environment/generated-application/sync.js +19 -4
- package/dist/environment/git-observation.d.ts +25 -6
- package/dist/environment/git-observation.js +37 -28
- package/dist/environment/health-composition.d.ts +3 -2
- package/dist/environment/health-composition.js +19 -2
- package/dist/environment/init-application.d.ts +39 -14
- package/dist/environment/init-application.js +39 -10
- package/dist/environment/lifecycle-composition.d.ts +4 -2
- package/dist/environment/lifecycle-composition.js +84 -17
- package/dist/environment/lifecycle-stage.d.ts +8 -3
- package/dist/environment/lifecycle-stage.js +11 -1
- package/dist/environment/logs.d.ts +1 -1
- package/dist/environment/map-limit.d.ts +2 -0
- package/dist/environment/map-limit.js +13 -0
- package/dist/environment/mcp-observation.d.ts +9 -0
- package/dist/environment/mcp-observation.js +46 -0
- package/dist/environment/observations.d.ts +1 -1
- package/dist/environment/observations.js +2 -0
- package/dist/environment/operator-application.d.ts +35 -6
- package/dist/environment/operator-application.js +42 -0
- package/dist/environment/presence.d.ts +4 -0
- package/dist/environment/presence.js +26 -0
- package/dist/environment/read-composition.d.ts +8 -3
- package/dist/environment/read-composition.js +53 -8
- package/dist/environment/readiness-composition.d.ts +20 -1
- package/dist/environment/readiness-composition.js +23 -2
- package/dist/environment/reclaim.d.ts +56 -19
- package/dist/environment/reclaim.js +147 -42
- package/dist/environment/runtime.d.ts +64 -18
- package/dist/environment/runtime.js +73 -3
- package/dist/environment/served-checkout-grants.d.ts +53 -0
- package/dist/environment/served-checkout-grants.js +182 -0
- package/dist/environment/sibling-checkouts.d.ts +119 -0
- package/dist/environment/sibling-checkouts.js +254 -0
- package/dist/environment/sibling-observation.d.ts +27 -0
- package/dist/environment/sibling-observation.js +45 -0
- package/dist/environment/sibling-pull.d.ts +23 -0
- package/dist/environment/sibling-pull.js +95 -0
- package/dist/environment/stack-inputs.d.ts +18 -0
- package/dist/environment/stack-inputs.js +210 -0
- package/dist/environment/stack-inventory-prune.d.ts +31 -0
- package/dist/environment/stack-inventory-prune.js +129 -0
- package/dist/environment/stack-inventory-report.d.ts +44 -0
- package/dist/environment/stack-inventory-report.js +78 -0
- package/dist/environment/stack-inventory.d.ts +94 -0
- package/dist/environment/stack-inventory.js +418 -0
- package/dist/environment/state.d.ts +48 -9
- package/dist/environment/state.js +142 -2
- package/dist/environment/tool-registration.js +3 -7
- package/dist/environment-exec/execute.d.ts +6 -3
- package/dist/environment-exec/execute.js +4 -0
- package/dist/fix-catalogue.d.ts +1 -1
- package/dist/generated-environment/adopt.js +4 -8
- package/dist/guards/answer-confirmation.d.ts +41 -0
- package/dist/guards/answer-confirmation.js +109 -0
- package/dist/guards/evaluator.d.ts +20 -3
- package/dist/guards/evaluator.js +39 -7
- package/dist/guards/local-identity.d.ts +6 -2
- package/dist/guards/local-identity.js +10 -4
- package/dist/guards/policy-change.d.ts +43 -0
- package/dist/guards/policy-change.js +159 -0
- package/dist/guards/policy-pin.d.ts +1 -1
- package/dist/guards/policy-pin.js +14 -12
- package/dist/init/command.d.ts +4 -3
- package/dist/init/command.js +36 -16
- package/dist/init/derive.d.ts +8 -6
- package/dist/init/derive.js +140 -36
- package/dist/init/draft.d.ts +13 -11
- package/dist/init/draft.js +54 -31
- package/dist/init/render.d.ts +18 -6
- package/dist/init/render.js +31 -9
- package/dist/repository-path.d.ts +9 -0
- package/dist/repository-path.js +16 -0
- package/dist/schema.d.ts +42 -75
- package/dist/schema.js +62 -11
- package/dist/surfaces/harness-registry.d.ts +80 -0
- package/dist/surfaces/harness-registry.js +29 -0
- package/dist/surfaces/mcp.d.ts +67 -17
- package/dist/surfaces/mcp.js +194 -31
- package/dist/surfaces/projection.d.ts +7 -7
- package/dist/surfaces/projection.js +41 -6
- package/dist/surfaces/render.d.ts +5 -44
- package/dist/surfaces/render.js +5 -2
- package/dist/team-metadata.js +2 -1
- package/dist/testing/test-protection.d.ts +34 -0
- package/dist/testing/test-protection.js +137 -0
- package/dist/testing/weakening.d.ts +15 -0
- package/dist/testing/weakening.js +30 -0
- package/dist/work-orders/application-closure.d.ts +7 -0
- package/dist/work-orders/application-closure.js +76 -0
- package/dist/work-orders/application-context.d.ts +162 -0
- package/dist/work-orders/application-context.js +137 -0
- package/dist/work-orders/application-decisions.d.ts +27 -0
- package/dist/work-orders/application-decisions.js +121 -0
- package/dist/work-orders/application-grading.d.ts +11 -0
- package/dist/work-orders/application-grading.js +212 -0
- package/dist/work-orders/application.d.ts +4 -97
- package/dist/work-orders/application.js +94 -254
- package/dist/work-orders/composition.d.ts +35 -58
- package/dist/work-orders/composition.js +216 -24
- package/dist/work-orders/contract.d.ts +22 -6
- package/dist/work-orders/contract.js +34 -16
- package/dist/work-orders/decisions.d.ts +23 -0
- package/dist/work-orders/decisions.js +55 -0
- package/dist/work-orders/store.d.ts +54 -5
- package/dist/work-orders/store.js +118 -24
- package/docs/README.md +6 -3
- package/docs/capability-matrix.md +17 -9
- package/docs/cli-reference.md +209 -1
- package/docs/command-list.md +12 -1
- package/docs/concepts.md +6 -4
- package/docs/configuration-essentials.md +7 -2
- package/docs/configuration-reference.md +18 -3
- package/docs/docs-check.md +76 -0
- package/docs/environment.md +194 -6
- package/docs/faq.md +5 -3
- package/docs/getting-started.md +109 -25
- package/docs/harness-surfaces.md +69 -2
- package/docs/safety-and-autonomy.md +56 -12
- package/docs/troubleshooting.md +29 -8
- package/docs/tutorial.md +3 -2
- package/docs/work-and-browser.md +180 -14
- package/libexec/browser-operation.cjs +86 -2
- package/package.json +2 -2
- package/surfaces/skills/cockpit-browser.skill.md +7 -2
- package/surfaces/skills/cockpit-explain.skill.md +1 -1
- package/surfaces/skills/cockpit-fix.skill.md +1 -1
- package/surfaces/skills/cockpit-setup.skill.md +3 -3
- package/surfaces/skills/cockpit-work.skill.md +1 -1
- package/surfaces/skills/procedures/explain.md +9 -4
- package/surfaces/skills/procedures/fix.md +25 -11
- package/surfaces/skills/procedures/setup.md +113 -40
- package/surfaces/skills/procedures/work.md +45 -16
|
@@ -64,6 +64,48 @@ export async function runResetOperatorApplication(input, admission, adapters) {
|
|
|
64
64
|
await opened.decide('decline');
|
|
65
65
|
return { status: EXIT_CODES.cancelled, diagnostics: [], result: null };
|
|
66
66
|
}
|
|
67
|
+
function pruneUnconfirmed(message) {
|
|
68
|
+
return { status: 2, result: null, diagnostics: [{ severity: 'error', target: 'stacks',
|
|
69
|
+
code: 'STACKS_PRUNE_UNCONFIRMED',
|
|
70
|
+
message, fix: 'a person runs npx --no cockpit stacks prune --config <your cockpit.toml> --interactive in a terminal outside CI, reads the preview and types its digest' }] };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `stacks prune`: a preview by default. Removing an orphan whose checkout is gone is housekeeping
|
|
74
|
+
* anyone can undo by bringing the stack up again, so `--apply` passes for an agent; a prune that
|
|
75
|
+
* deletes volumes deletes data, and passes only where `[autonomy] reset` lets this run reset. That
|
|
76
|
+
* policy is this cockpit's, so an agent's `--apply` covers only this cockpit's stacks: another
|
|
77
|
+
* cockpit may require a person for the same removal. `--interactive` is a person's approval of the
|
|
78
|
+
* exact preview by its digest, and covers every stack on the machine.
|
|
79
|
+
*/
|
|
80
|
+
export async function runStacksPruneOperatorApplication(input, admission, adapters) {
|
|
81
|
+
if (admission.interactive && (admission.json || admission.ci && admission.ci.length > 0)) {
|
|
82
|
+
return pruneUnconfirmed('interactive prune requires a human terminal outside CI and JSON modes');
|
|
83
|
+
}
|
|
84
|
+
if (admission.interactive && !admission.terminal)
|
|
85
|
+
return pruneUnconfirmed('interactive prune requires terminal input');
|
|
86
|
+
const opened = await adapters.begin({ configPath: input.configPath, scope: !admission.interactive && admission.apply ? 'this-cockpit' : 'machine' });
|
|
87
|
+
if (!('apply' in opened))
|
|
88
|
+
return opened;
|
|
89
|
+
if (!admission.interactive && !admission.apply || opened.empty)
|
|
90
|
+
return opened.preview;
|
|
91
|
+
if (admission.interactive) {
|
|
92
|
+
if (await adapters.confirm(opened.preview) !== opened.digest)
|
|
93
|
+
return pruneUnconfirmed('the typed digest did not match the prune preview; nothing was removed');
|
|
94
|
+
return opened.apply();
|
|
95
|
+
}
|
|
96
|
+
if (opened.deletesData) {
|
|
97
|
+
const policy = await adapters.policy(input.configPath);
|
|
98
|
+
if (!isPolicy(policy))
|
|
99
|
+
return policy;
|
|
100
|
+
const weakened = policy.recorded ? policyWeakening(policy.recorded, policy.current).filter(row => row.field === 'autonomy.reset') : [];
|
|
101
|
+
if (weakened.length)
|
|
102
|
+
return weakeningRefusal(weakened, 'nothing was pruned');
|
|
103
|
+
const refused = autonomyRefusal('reset', policy.current.autonomy.reset, admission);
|
|
104
|
+
if (refused)
|
|
105
|
+
return refused;
|
|
106
|
+
}
|
|
107
|
+
return opened.apply();
|
|
108
|
+
}
|
|
67
109
|
function staleRemovalRefusal(message) {
|
|
68
110
|
return refusal('GENERATED_STALE_APPROVAL_REQUIRED', message, 'generated-environment');
|
|
69
111
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Stats } from 'node:fs';
|
|
2
|
+
/** The path's own status, not following a final symbolic link, or undefined when nothing is there. */
|
|
3
|
+
export declare function lstatIfPresent(path: string): Promise<Stats | undefined>;
|
|
4
|
+
export declare function lstatIfPresentSync(path: string): Stats | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// What is on disk at a path, where "nothing there" (a missing entry or a missing parent) is an answer
|
|
2
|
+
// and any other failure, such as a permission error, is raised for its caller to name.
|
|
3
|
+
import { lstatSync } from 'node:fs';
|
|
4
|
+
import { lstat } from 'node:fs/promises';
|
|
5
|
+
import { isMissing } from './contract.js';
|
|
6
|
+
/** The path's own status, not following a final symbolic link, or undefined when nothing is there. */
|
|
7
|
+
export async function lstatIfPresent(path) {
|
|
8
|
+
try {
|
|
9
|
+
return await lstat(path);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (isMissing(error))
|
|
13
|
+
return undefined;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function lstatIfPresentSync(path) {
|
|
18
|
+
try {
|
|
19
|
+
return lstatSync(path);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (isMissing(error))
|
|
23
|
+
return undefined;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ExecuteProbeOutcome } from '../environment-exec/execute.js';
|
|
2
2
|
import { type RuntimeEvidenceObservation } from './application/health.js';
|
|
3
3
|
import { type LogsCommandAdapters } from './application/logs.js';
|
|
4
|
+
import { type ServiceCommandAdapters } from './application/services.js';
|
|
5
|
+
import type { ForegroundProcessInput, ForegroundProcessOutcome } from '../environment-exec/execute.js';
|
|
4
6
|
import { type ExecutionStep } from './contract.js';
|
|
5
7
|
import { type RuntimeMeasurementProbeAuthority } from './runtime.js';
|
|
6
|
-
|
|
8
|
+
interface ReadCommandCompositionInput {
|
|
7
9
|
readonly configPath: string;
|
|
8
10
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
9
11
|
readonly sessionId: string;
|
|
@@ -12,8 +14,9 @@ export interface ReadCommandCompositionInput {
|
|
|
12
14
|
readonly signal?: AbortSignal;
|
|
13
15
|
readonly runtimeMeasurementProbeAuthority?: RuntimeMeasurementProbeAuthority;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
interface ReadCommandAdapterComposition {
|
|
16
18
|
readonly logs: LogsCommandAdapters;
|
|
19
|
+
readonly services: ServiceCommandAdapters;
|
|
17
20
|
readonly environment: Readonly<{
|
|
18
21
|
observe: (input: {
|
|
19
22
|
readonly configPath: string;
|
|
@@ -23,5 +26,7 @@ export interface ReadCommandAdapterComposition {
|
|
|
23
26
|
type Executor = (step: ExecutionStep, options?: {
|
|
24
27
|
readonly signal?: AbortSignal;
|
|
25
28
|
}) => Promise<ExecuteProbeOutcome>;
|
|
26
|
-
export declare function createReadCommandAdapters(rawInput: ReadCommandCompositionInput, execute: Executor
|
|
29
|
+
export declare function createReadCommandAdapters(rawInput: ReadCommandCompositionInput, execute: Executor, executeForeground?: (input: ForegroundProcessInput, options: {
|
|
30
|
+
readonly signal?: AbortSignal;
|
|
31
|
+
}) => Promise<ForegroundProcessOutcome>): ReadCommandAdapterComposition;
|
|
27
32
|
export {};
|
|
@@ -6,9 +6,12 @@ import {} from './application/health.js';
|
|
|
6
6
|
import { decodeLifecycleRecord } from './application/lifecycle.js';
|
|
7
7
|
import {} from './application/lifecycle-context.js';
|
|
8
8
|
import {} from './application/logs.js';
|
|
9
|
+
import { publishLifecycleRecord } from './application/lifecycle-state.js';
|
|
10
|
+
import {} from './application/services.js';
|
|
9
11
|
import { createLifecycleCommandAdapters } from './lifecycle-composition.js';
|
|
10
12
|
import { deriveEntrypoints, observeRetainedPortFamily, projectRetainedPortFamilyResult } from './ports.js';
|
|
11
13
|
import { DOCKER_CLIENT_ENVIRONMENT, ENVIRONMENT_CODES, RefusalError } from './contract.js';
|
|
14
|
+
import { loadServedSiblings, servedSiblingVariables } from './sibling-checkouts.js';
|
|
12
15
|
import { DeclarationRefusalError, runtimeNotDeclared, serviceNotDeclared } from './declaration.js';
|
|
13
16
|
import { admitRuntimeServiceHealth, revalidateRuntimeService, } from './runtime.js';
|
|
14
17
|
import { resolveDockerExecutable } from './executable.js';
|
|
@@ -133,11 +136,13 @@ function exactCompletedTopology(context) {
|
|
|
133
136
|
return topologyServices.length === completedServices.length
|
|
134
137
|
&& topologyServices.every((service, index) => service === completedServices[index]);
|
|
135
138
|
}
|
|
136
|
-
export function createReadCommandAdapters(rawInput, execute) {
|
|
139
|
+
export function createReadCommandAdapters(rawInput, execute, executeForeground) {
|
|
137
140
|
const input = captureInput(rawInput);
|
|
138
141
|
// `refuse` receives the lifecycle's own refusal, such as an unmeasured runtime, so a caller can
|
|
139
142
|
// report it instead of a generic unavailable context.
|
|
140
|
-
|
|
143
|
+
// A service verb may act on a stack whose start stopped part-way: rebuilding the service that
|
|
144
|
+
// failed is how its fixed code gets running. Every other reader needs a started service.
|
|
145
|
+
const prepare = async (signal, refuse, partial = false) => {
|
|
141
146
|
if (!input || typeof execute !== 'function')
|
|
142
147
|
throw new RefusalError('read command composition is unavailable');
|
|
143
148
|
const lifecycle = createLifecycleCommandAdapters({
|
|
@@ -191,7 +196,11 @@ export function createReadCommandAdapters(rawInput, execute) {
|
|
|
191
196
|
if (lifecycleRead.kind !== 'complete')
|
|
192
197
|
throw new RefusalError('lifecycle state is unavailable');
|
|
193
198
|
const lifecycleValue = decodeLifecycleRecord(lifecycleRead.value, context)?.value;
|
|
194
|
-
if (!lifecycleValue
|
|
199
|
+
if (!lifecycleValue)
|
|
200
|
+
throw new RefusalError('lifecycle state is malformed');
|
|
201
|
+
if (lifecycleValue.teardownComplete)
|
|
202
|
+
throw new RefusalError('down removed the stack');
|
|
203
|
+
if (lifecycleValue.completedServices.length === 0 && !partial)
|
|
195
204
|
throw new RefusalError('lifecycle state is malformed');
|
|
196
205
|
if (lifecycleValue.holderId !== context.holderId)
|
|
197
206
|
throw new RefusalError('lifecycle holder authority changed');
|
|
@@ -207,17 +216,19 @@ export function createReadCommandAdapters(rawInput, execute) {
|
|
|
207
216
|
if (!confirmedComposeFiles || JSON.stringify(confirmedComposeFiles) !== JSON.stringify(composeFiles)) {
|
|
208
217
|
throw new RefusalError('compose artifacts changed during runtime observation');
|
|
209
218
|
}
|
|
219
|
+
// The stack interpolates the checkout `use` recorded, so reading it must name the same one.
|
|
220
|
+
const served = await loadServedSiblings(checkoutRoot, configuration.config);
|
|
221
|
+
if (served.kind === 'refusal')
|
|
222
|
+
throw new RefusalError(served.diagnostic.message);
|
|
210
223
|
const ownComposeAuthority = {
|
|
211
224
|
ownComposeFiles: true,
|
|
212
225
|
cockpitId: context.cockpitId,
|
|
213
226
|
stackId: context.stackId,
|
|
214
227
|
portLeaseProjection: ports,
|
|
215
|
-
siblingDirectories:
|
|
216
|
-
sibling.var,
|
|
217
|
-
resolve(checkoutRoot, configuration.config.cockpit.siblings_root, sibling.dir),
|
|
218
|
-
])),
|
|
228
|
+
siblingDirectories: servedSiblingVariables(configuration.config, served.siblings),
|
|
219
229
|
};
|
|
220
230
|
return {
|
|
231
|
+
run: context,
|
|
221
232
|
ownComposeAuthority,
|
|
222
233
|
configuration,
|
|
223
234
|
namespace: context.namespace,
|
|
@@ -343,10 +354,44 @@ export function createReadCommandAdapters(rawInput, execute) {
|
|
|
343
354
|
};
|
|
344
355
|
return { snapshot: ownedSnapshot, evidence: ownedEvidence };
|
|
345
356
|
};
|
|
357
|
+
const observeServices = async (request) => {
|
|
358
|
+
if (!input || resolve(request.configPath) !== input.configPath)
|
|
359
|
+
throw new RefusalError('service configuration path does not match');
|
|
360
|
+
const context = await prepare(input.signal, (diagnostics) => { throw new DeclarationRefusalError(diagnostics[0]); }, true);
|
|
361
|
+
return {
|
|
362
|
+
topology: context.topology,
|
|
363
|
+
completedServices: context.lifecycle.completedServices,
|
|
364
|
+
// The recorded container is what `up`, `preflight` and `logs` revalidate, so a rebuild that
|
|
365
|
+
// replaced it updates the record rather than leaving the stack looking broken.
|
|
366
|
+
recordContainer: async (service, containerId) => {
|
|
367
|
+
const value = { ...context.lifecycle, completedServices: context.lifecycle.completedServices
|
|
368
|
+
.map((row) => row.service === service ? { service, containerId } : row) };
|
|
369
|
+
const published = await publishLifecycleRecord(context.run, context.lifecycleRecord, value);
|
|
370
|
+
if ('status' in published)
|
|
371
|
+
return published.diagnostics;
|
|
372
|
+
if (published.kind === 'complete')
|
|
373
|
+
return [];
|
|
374
|
+
return [{ severity: 'error',
|
|
375
|
+
code: published.kind === 'missing' ? ENVIRONMENT_CODES.STATE_RECORD_MALFORMED : published.code,
|
|
376
|
+
target: context.namespace.paths.lifecycle(context.stackId).path,
|
|
377
|
+
message: `the rebuilt container of service ${service} runs, and the stack record could not be updated to it${published.kind === 'missing' ? '' : `: ${published.message}`}`,
|
|
378
|
+
fix: 'run npx --no cockpit up --config <your cockpit.toml> to record the running containers again' }];
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
const services = {
|
|
383
|
+
observe: observeServices,
|
|
384
|
+
execute: (step) => execute(step, input?.signal ? { signal: input.signal } : undefined),
|
|
385
|
+
executeForeground: async (planned) => {
|
|
386
|
+
if (!executeForeground)
|
|
387
|
+
throw new RefusalError('foreground execution is not bound');
|
|
388
|
+
return executeForeground(planned, input?.signal ? { signal: input.signal } : {});
|
|
389
|
+
},
|
|
390
|
+
};
|
|
346
391
|
const logs = {
|
|
347
392
|
observe: observeLogs,
|
|
348
393
|
execute: (step, options) => execute(step, options),
|
|
349
394
|
...(input?.signal ? { signal: input.signal } : {}),
|
|
350
395
|
};
|
|
351
|
-
return { logs, environment: { observe: observeEnvironment } };
|
|
396
|
+
return { logs, services, environment: { observe: observeEnvironment } };
|
|
352
397
|
}
|
|
@@ -2,7 +2,7 @@ import { loadCockpitConfig } from '../config/load.js';
|
|
|
2
2
|
import type { ExecuteProbeOutcome } from '../environment-exec/execute.js';
|
|
3
3
|
import { type EnvironmentDiagnostic, type ExecutionStep } from './contract.js';
|
|
4
4
|
import type { ReadinessReport } from '../readiness.js';
|
|
5
|
-
|
|
5
|
+
interface ProjectReadinessInput {
|
|
6
6
|
/** The cockpit root, where cockpit.toml is or will be written. */
|
|
7
7
|
readonly root: string;
|
|
8
8
|
/** Present once cockpit.toml exists; its siblings are the repositories asked. */
|
|
@@ -35,4 +35,23 @@ export declare function inspectInitReadiness(input: Omit<ProjectReadinessInput,
|
|
|
35
35
|
state: ReadinessReport['state'];
|
|
36
36
|
findings: readonly EnvironmentDiagnostic[];
|
|
37
37
|
}>>;
|
|
38
|
+
/**
|
|
39
|
+
* What the container runtime on this machine offers, measured by the same collector preflight
|
|
40
|
+
* compares the machine floor with, so init can propose a floor where no Compose limit records one.
|
|
41
|
+
*/
|
|
42
|
+
export declare function measureInitMachine(input: Readonly<{
|
|
43
|
+
root: string;
|
|
44
|
+
}>, binding: Readonly<{
|
|
45
|
+
env: ProjectReadinessInput['env'];
|
|
46
|
+
timeoutMs: number;
|
|
47
|
+
execute: (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
|
|
48
|
+
}>): Promise<Readonly<{
|
|
49
|
+
kind: 'complete';
|
|
50
|
+
memoryMb: number;
|
|
51
|
+
cpus: number;
|
|
52
|
+
observedAt: string;
|
|
53
|
+
}> | Readonly<{
|
|
54
|
+
kind: 'unavailable';
|
|
55
|
+
reason: string;
|
|
56
|
+
}>>;
|
|
38
57
|
export {};
|
|
@@ -9,8 +9,9 @@ import { loadCockpitConfig } from '../config/load.js';
|
|
|
9
9
|
import { authenticatedExecutionOutcome } from '../environment-exec/execute.js';
|
|
10
10
|
import { inspectTeamReadiness } from '../team-metadata.js';
|
|
11
11
|
import { guardSearchPathRelativeEntry } from '../guards/protected-locations.js';
|
|
12
|
-
import { ENVIRONMENT_CODES, snapshotExecutionStep, RefusalError, isMissing } from './contract.js';
|
|
13
|
-
import { resolveGitExecutable } from './executable.js';
|
|
12
|
+
import { DOCKER_CLIENT_ENVIRONMENT, ENVIRONMENT_CODES, snapshotExecutionStep, RefusalError, isMissing } from './contract.js';
|
|
13
|
+
import { resolveDiskCapacityExecutable, resolveDockerExecutable, resolveGitExecutable } from './executable.js';
|
|
14
|
+
import { measureMachineFloor } from './runtime.js';
|
|
14
15
|
import { gitRepositoryRefusal, inspectTrackedSecretFiles } from './git-observation.js';
|
|
15
16
|
import { declaresNothingToRun } from './declaration.js';
|
|
16
17
|
import { inspectSiblingInstructions } from './sibling-instructions.js';
|
|
@@ -128,3 +129,23 @@ export async function inspectInitReadiness(input, binding) {
|
|
|
128
129
|
: classification === undefined ? 'undetermined' : classification === 'no-runnable-product' ? 'surfaces-only' : 'runtime';
|
|
129
130
|
return { state, findings };
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* What the container runtime on this machine offers, measured by the same collector preflight
|
|
134
|
+
* compares the machine floor with, so init can propose a floor where no Compose limit records one.
|
|
135
|
+
*/
|
|
136
|
+
export async function measureInitMachine(input, binding) {
|
|
137
|
+
const [docker, disk] = await Promise.all([resolveDockerExecutable(binding.env), resolveDiskCapacityExecutable()]);
|
|
138
|
+
if (docker.state !== 'available')
|
|
139
|
+
return { kind: 'unavailable', reason: docker.reason };
|
|
140
|
+
if (disk.state !== 'available')
|
|
141
|
+
return { kind: 'unavailable', reason: disk.reason };
|
|
142
|
+
const env = Object.fromEntries(Object.entries(binding.env)
|
|
143
|
+
.filter((entry) => DOCKER_CLIENT_ENVIRONMENT.has(entry[0]) && typeof entry[1] === 'string'));
|
|
144
|
+
const measured = await measureMachineFloor({ runtimeExecutable: docker.value, diskExecutable: disk.value, cwd: input.root,
|
|
145
|
+
env, timeoutMs: binding.timeoutMs }, binding.execute);
|
|
146
|
+
if (measured.kind !== 'complete')
|
|
147
|
+
return { kind: 'unavailable', reason: measured.kind === 'refusal'
|
|
148
|
+
? measured.diagnostics.map(row => row.message).join('; ') : 'docker info did not report the runtime\'s memory and CPUs' };
|
|
149
|
+
return { kind: 'complete', memoryMb: Math.floor(measured.evidence.memoryBytes / 1024 ** 2),
|
|
150
|
+
cpus: measured.evidence.cpuMillis / 1_000, observedAt: measured.evidence.observedAt };
|
|
151
|
+
}
|
|
@@ -4,7 +4,7 @@ import { type CockpitId, type EnvironmentDiagnostic, type ExecutionFence, type E
|
|
|
4
4
|
import type { ResolvedExecutable } from './executable.js';
|
|
5
5
|
import { type StateAddress, type StateRecord } from './state.js';
|
|
6
6
|
type Executor = (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
|
|
7
|
-
|
|
7
|
+
interface DockerSelection {
|
|
8
8
|
readonly context: string;
|
|
9
9
|
readonly endpoint: string;
|
|
10
10
|
}
|
|
@@ -15,7 +15,7 @@ export interface ReclaimDiscovery extends ReclaimDiscoverySnapshot {
|
|
|
15
15
|
readonly selection: DockerSelection;
|
|
16
16
|
}
|
|
17
17
|
/** What a person or the planner itself approved: the digest, and the discovery it was read from. */
|
|
18
|
-
|
|
18
|
+
interface ReclaimConsent {
|
|
19
19
|
readonly digest: string;
|
|
20
20
|
readonly observedAt: string;
|
|
21
21
|
}
|
|
@@ -24,7 +24,7 @@ export interface ReclaimConsent {
|
|
|
24
24
|
* to the next. `drain` is the attempt the action ran under, which the next plan matches against
|
|
25
25
|
* the record its outcome published; null until the action runs.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
interface ReclaimContinuation {
|
|
28
28
|
readonly purpose: 'down' | 'reset';
|
|
29
29
|
readonly digest: string;
|
|
30
30
|
readonly completedTargetId: string;
|
|
@@ -37,7 +37,7 @@ export interface ReclaimContinuation {
|
|
|
37
37
|
readonly drain: ReclaimDrainStep | null;
|
|
38
38
|
}
|
|
39
39
|
/** The one removal a plan permits, executed only by `executeReclaimAction` under a reserved attempt. */
|
|
40
|
-
|
|
40
|
+
interface PlannedReclaimAction {
|
|
41
41
|
readonly stackId: StackId;
|
|
42
42
|
readonly configSha256: string;
|
|
43
43
|
/** Null when the interrupted target is already absent and only its removal is recorded. */
|
|
@@ -48,7 +48,7 @@ export interface PlannedReclaimAction {
|
|
|
48
48
|
/** For an absent target: the actionable target ids a later discovery still shows. */
|
|
49
49
|
readonly remainingTargets?: (discovery: ReclaimDiscovery) => Promise<readonly string[] | undefined>;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
type ReclaimTargetIdentity = ResetVolumeTargetIdentity | TeardownRuntimeTargetIdentity | PathTargetIdentity;
|
|
52
52
|
interface ResetVolumeTargetIdentity {
|
|
53
53
|
readonly medium: 'runtime';
|
|
54
54
|
readonly scope: 'reset-volume';
|
|
@@ -85,10 +85,10 @@ interface PathTargetIdentity {
|
|
|
85
85
|
readonly birthtimeNs: string;
|
|
86
86
|
readonly ancestors: readonly FilesystemAncestorIdentity[];
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
type ReclaimResourceKind = 'container' | 'network' | 'volume' | 'image' | 'build-cache' | 'cache';
|
|
89
|
+
type OwnershipEvidence = 'current' | 'stale-config' | 'foreign' | 'unknown' | 'malformed' | 'partial' | 'unlabelled';
|
|
90
|
+
type Reversibility = 'runtime' | 'regenerable-cache' | 'data-bearing';
|
|
91
|
+
type ReclaimAction = 'teardown' | 'reclaim' | 'reset-required' | 'report-only';
|
|
92
92
|
export interface ReclaimResource {
|
|
93
93
|
readonly id: string;
|
|
94
94
|
readonly name: string;
|
|
@@ -104,7 +104,7 @@ export interface ReclaimDiscoverySnapshot {
|
|
|
104
104
|
readonly producer: 'runtime-resource-discovery';
|
|
105
105
|
readonly resources: readonly ReclaimResource[];
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
interface ClassifiedReclaimResource extends ReclaimResource {
|
|
108
108
|
readonly ownership: OwnershipEvidence;
|
|
109
109
|
readonly reversibility: Reversibility;
|
|
110
110
|
readonly action: ReclaimAction;
|
|
@@ -115,7 +115,7 @@ export interface ClassifiedReclaimResource extends ReclaimResource {
|
|
|
115
115
|
readonly risk: string;
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
interface ClassifyReclaimInput {
|
|
119
119
|
readonly stackId: StackId;
|
|
120
120
|
readonly cockpitId: CockpitId;
|
|
121
121
|
readonly configSha256: string;
|
|
@@ -123,7 +123,7 @@ export interface ClassifyReclaimInput {
|
|
|
123
123
|
readonly declaredCacheIds: readonly string[];
|
|
124
124
|
readonly discovery: ReclaimDiscoverySnapshot;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
type ReclaimResult = {
|
|
127
127
|
readonly kind: 'complete';
|
|
128
128
|
readonly noop: boolean;
|
|
129
129
|
readonly resources: readonly ClassifiedReclaimResource[];
|
|
@@ -132,7 +132,7 @@ export type ReclaimResult = {
|
|
|
132
132
|
readonly diagnostics: readonly EnvironmentDiagnostic[];
|
|
133
133
|
};
|
|
134
134
|
export declare function classifyReclaim(rawInput: ClassifyReclaimInput): ReclaimResult;
|
|
135
|
-
|
|
135
|
+
interface KeptTeardownResource {
|
|
136
136
|
readonly kind: ReclaimResourceKind;
|
|
137
137
|
readonly id: string;
|
|
138
138
|
readonly name: string;
|
|
@@ -149,7 +149,7 @@ export declare function keptTeardownResources(input: ClassifyReclaimInput): read
|
|
|
149
149
|
* resources to look at instead of a generic unobservable precondition.
|
|
150
150
|
*/
|
|
151
151
|
export declare function inspectionOverflow(step: ExecutionStep, outcome: ExecuteProbeOutcome): string | undefined;
|
|
152
|
-
|
|
152
|
+
interface ReclaimDiscoveryInput {
|
|
153
153
|
readonly executable: ResolvedExecutable;
|
|
154
154
|
readonly cwd: string;
|
|
155
155
|
readonly env: Readonly<Record<string, string>>;
|
|
@@ -172,6 +172,43 @@ type DiscoveryRefusal = {
|
|
|
172
172
|
* complete round covers that race without admitting partial evidence.
|
|
173
173
|
*/
|
|
174
174
|
export declare function discoverReclaim(input: ReclaimDiscoveryInput, execute: Executor): Promise<ReclaimDiscovery | DiscoveryRefusal | undefined>;
|
|
175
|
+
/** One container, network or volume that carries every cockpit ownership label, attributed by them alone. */
|
|
176
|
+
export interface MachineStackResource {
|
|
177
|
+
readonly kind: 'container' | 'network' | 'volume';
|
|
178
|
+
readonly id: string;
|
|
179
|
+
readonly name: string;
|
|
180
|
+
readonly cockpitId: CockpitId;
|
|
181
|
+
readonly stackId: StackId;
|
|
182
|
+
/** Containers only: Docker reports it running, restarting or paused. */
|
|
183
|
+
readonly running: boolean;
|
|
184
|
+
readonly composeProject: string | null;
|
|
185
|
+
/** Containers only: the directory Compose ran the project from, as Compose itself labelled it. */
|
|
186
|
+
readonly workingDir: string | null;
|
|
187
|
+
}
|
|
188
|
+
export interface MachineStackDiscovery {
|
|
189
|
+
readonly selection: DockerSelection;
|
|
190
|
+
readonly observedAt: string;
|
|
191
|
+
readonly resources: readonly MachineStackResource[];
|
|
192
|
+
/** Resources carrying the stack label whose other ownership labels are missing or malformed: never attributed. */
|
|
193
|
+
readonly unattributed: number;
|
|
194
|
+
}
|
|
195
|
+
type MachineStackDiscoveryInput = Omit<ReclaimDiscoveryInput, 'stackId' | 'cockpitId'>;
|
|
196
|
+
/**
|
|
197
|
+
* Lists every container, network and volume on the selected daemon that carries cockpit's stack
|
|
198
|
+
* label, whichever cockpit or checkout started it, for the machine-wide stack inventory. Ownership
|
|
199
|
+
* comes from the labels alone; nothing is attributed by its name. The selection is read before and
|
|
200
|
+
* after the listings, as `discoverReclaim` does, so the listings describe one daemon.
|
|
201
|
+
*/
|
|
202
|
+
export declare function discoverMachineStacks(input: MachineStackDiscoveryInput, execute: Executor): Promise<MachineStackDiscovery | undefined>;
|
|
203
|
+
/**
|
|
204
|
+
* The Docker removals prune runs for one kind of one orphaned stack's resources, as the machine
|
|
205
|
+
* discovery attributed them: one client per chunk rather than one per resource. Undefined when the
|
|
206
|
+
* resources are not all of one kind and one stack, or a step cannot be built.
|
|
207
|
+
*/
|
|
208
|
+
export declare function machineStackRemovalSteps(input: Pick<MachineStackDiscoveryInput, 'executable' | 'cwd' | 'env' | 'timeoutMs'>, selection: DockerSelection, resources: readonly MachineStackResource[]): readonly Readonly<{
|
|
209
|
+
step: ExecutionStep;
|
|
210
|
+
resources: readonly MachineStackResource[];
|
|
211
|
+
}>[] | undefined;
|
|
175
212
|
export interface ResetTargetInput {
|
|
176
213
|
readonly kind: 'container' | 'volume' | 'host-path';
|
|
177
214
|
readonly identity: string;
|
|
@@ -183,7 +220,7 @@ interface HostPathDeclaration {
|
|
|
183
220
|
readonly path: string;
|
|
184
221
|
readonly declaration: string;
|
|
185
222
|
}
|
|
186
|
-
|
|
223
|
+
interface DependencyCacheDeclaration {
|
|
187
224
|
readonly cacheId: string;
|
|
188
225
|
readonly root: string;
|
|
189
226
|
readonly path: string;
|
|
@@ -269,7 +306,7 @@ export type TeardownPlanResult = {
|
|
|
269
306
|
export declare function planReset(input: PlanResetInput): Promise<ResetPreview | ResetPlanResult>;
|
|
270
307
|
type ByteClasses = Readonly<Record<'orphans' | 'images' | 'buildCache' | 'staleNetworks', number | null>>;
|
|
271
308
|
export declare function planTeardown(input: PlanTeardownInput): Promise<TeardownPlanResult>;
|
|
272
|
-
|
|
309
|
+
interface ReclaimTargetCommitment extends DrainTargetCommitment {
|
|
273
310
|
readonly targetId: string;
|
|
274
311
|
}
|
|
275
312
|
export interface ReclaimTargetEnvelope {
|
|
@@ -280,7 +317,7 @@ export interface ReclaimTargetEnvelope {
|
|
|
280
317
|
readonly commitments: readonly ReclaimTargetCommitment[];
|
|
281
318
|
}
|
|
282
319
|
/** A removal the attempt admitted, with the attempt it ran under and what it observed. */
|
|
283
|
-
|
|
320
|
+
interface ExecutedReclaimAction {
|
|
284
321
|
readonly action: PlannedReclaimAction;
|
|
285
322
|
readonly reservation: DrainReservation;
|
|
286
323
|
readonly attempt: NonNullable<DrainReservation['attempt']>;
|
|
@@ -289,7 +326,7 @@ export interface ExecutedReclaimAction {
|
|
|
289
326
|
readonly receipt: ExecutionReceipt | null;
|
|
290
327
|
readonly fence: ExecutionFence | null;
|
|
291
328
|
}
|
|
292
|
-
|
|
329
|
+
type ReclaimActionExecution = {
|
|
293
330
|
readonly kind: 'executed';
|
|
294
331
|
readonly executed: ExecutedReclaimAction;
|
|
295
332
|
}
|
|
@@ -314,7 +351,7 @@ export declare function executeReclaimAction(input: {
|
|
|
314
351
|
readonly attempt: ReservedDrainAttempt;
|
|
315
352
|
readonly execute: Executor;
|
|
316
353
|
}): Promise<ReclaimActionExecution>;
|
|
317
|
-
|
|
354
|
+
type ReconcileReclaimActionResult = {
|
|
318
355
|
readonly kind: 'complete';
|
|
319
356
|
readonly outcome: DrainOutcome;
|
|
320
357
|
readonly continuation: ReclaimContinuation | null;
|