@b00dea/cockpit 0.0.0-bootstrap.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +375 -0
- package/LICENSE +21 -0
- package/README.md +21 -1
- package/bin/cockpit.mjs +33 -0
- package/cockpit.schema.json +2574 -0
- package/dist/autonomy.d.ts +44 -0
- package/dist/autonomy.js +156 -0
- package/dist/bounded-read.d.ts +14 -0
- package/dist/bounded-read.js +42 -0
- package/dist/browser/commands.d.ts +62 -0
- package/dist/browser/commands.js +349 -0
- package/dist/browser/package-paths.d.ts +1 -0
- package/dist/browser/package-paths.js +6 -0
- package/dist/browser/proxy.d.ts +22 -0
- package/dist/browser/proxy.js +37 -0
- package/dist/browser/runtime.d.ts +117 -0
- package/dist/browser/runtime.js +183 -0
- package/dist/browser/surfaces.d.ts +141 -0
- package/dist/browser/surfaces.js +556 -0
- package/dist/browser/ui-gate.d.ts +15 -0
- package/dist/browser/ui-gate.js +18 -0
- package/dist/cli-guard.d.ts +5 -0
- package/dist/cli-guard.js +71 -0
- package/dist/cli-main.d.ts +139 -0
- package/dist/cli-main.js +1405 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +16 -0
- package/dist/command-table.d.ts +113 -0
- package/dist/command-table.js +512 -0
- package/dist/config/load.d.ts +171 -0
- package/dist/config/load.js +345 -0
- package/dist/config/sibling-toml.d.ts +48 -0
- package/dist/config/sibling-toml.js +160 -0
- package/dist/config/surface-tools-toml.d.ts +9 -0
- package/dist/config/surface-tools-toml.js +37 -0
- package/dist/config/verified-write.d.ts +43 -0
- package/dist/config/verified-write.js +89 -0
- package/dist/diagnostics.d.ts +26 -0
- package/dist/diagnostics.js +335 -0
- package/dist/emit.d.ts +22 -0
- package/dist/emit.js +43 -0
- package/dist/entrypoint-kinds.d.ts +3 -0
- package/dist/entrypoint-kinds.js +11 -0
- package/dist/environment/application/destructive.d.ts +6 -0
- package/dist/environment/application/destructive.js +425 -0
- package/dist/environment/application/doctor.d.ts +89 -0
- package/dist/environment/application/doctor.js +235 -0
- package/dist/environment/application/guard.d.ts +43 -0
- package/dist/environment/application/guard.js +622 -0
- package/dist/environment/application/health.d.ts +134 -0
- package/dist/environment/application/health.js +379 -0
- package/dist/environment/application/lifecycle-context.d.ts +228 -0
- package/dist/environment/application/lifecycle-context.js +154 -0
- package/dist/environment/application/lifecycle-state.d.ts +25 -0
- package/dist/environment/application/lifecycle-state.js +201 -0
- package/dist/environment/application/lifecycle.d.ts +17 -0
- package/dist/environment/application/lifecycle.js +729 -0
- package/dist/environment/application/logs.d.ts +45 -0
- package/dist/environment/application/logs.js +127 -0
- package/dist/environment/application/setup.d.ts +129 -0
- package/dist/environment/application/setup.js +741 -0
- package/dist/environment/application/shared.d.ts +11 -0
- package/dist/environment/application/shared.js +61 -0
- package/dist/environment/claims.d.ts +389 -0
- package/dist/environment/claims.js +1709 -0
- package/dist/environment/codex-hook-trust.d.ts +11 -0
- package/dist/environment/codex-hook-trust.js +185 -0
- package/dist/environment/compose-environment.d.ts +1 -0
- package/dist/environment/compose-environment.js +6 -0
- package/dist/environment/compose-ownership.d.ts +21 -0
- package/dist/environment/compose-ownership.js +117 -0
- package/dist/environment/compose-topology.d.ts +117 -0
- package/dist/environment/compose-topology.js +413 -0
- package/dist/environment/contract.d.ts +489 -0
- package/dist/environment/contract.js +761 -0
- package/dist/environment/declaration.d.ts +14 -0
- package/dist/environment/declaration.js +39 -0
- package/dist/environment/destructive-composition.d.ts +29 -0
- package/dist/environment/destructive-composition.js +333 -0
- package/dist/environment/docker-daemon.d.ts +30 -0
- package/dist/environment/docker-daemon.js +78 -0
- package/dist/environment/docker-endpoint.d.ts +10 -0
- package/dist/environment/docker-endpoint.js +19 -0
- package/dist/environment/env-schema-observation.d.ts +46 -0
- package/dist/environment/env-schema-observation.js +241 -0
- package/dist/environment/evidence.d.ts +136 -0
- package/dist/environment/evidence.js +437 -0
- package/dist/environment/executable.d.ts +26 -0
- package/dist/environment/executable.js +167 -0
- package/dist/environment/generated-application/authorities.d.ts +5 -0
- package/dist/environment/generated-application/authorities.js +61 -0
- package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
- package/dist/environment/generated-application/bootstrap-skill.js +328 -0
- package/dist/environment/generated-application/check.d.ts +13 -0
- package/dist/environment/generated-application/check.js +189 -0
- package/dist/environment/generated-application/custom-surface.d.ts +26 -0
- package/dist/environment/generated-application/custom-surface.js +289 -0
- package/dist/environment/generated-application/prepare.d.ts +29 -0
- package/dist/environment/generated-application/prepare.js +276 -0
- package/dist/environment/generated-application/publication.d.ts +33 -0
- package/dist/environment/generated-application/publication.js +547 -0
- package/dist/environment/generated-application/shared.d.ts +484 -0
- package/dist/environment/generated-application/shared.js +349 -0
- package/dist/environment/generated-application/surface-observation.d.ts +4 -0
- package/dist/environment/generated-application/surface-observation.js +69 -0
- package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
- package/dist/environment/generated-application/surface-outputs.js +389 -0
- package/dist/environment/generated-application/surfaces.d.ts +3 -0
- package/dist/environment/generated-application/surfaces.js +699 -0
- package/dist/environment/generated-application/sync.d.ts +6 -0
- package/dist/environment/generated-application/sync.js +109 -0
- package/dist/environment/git-observation.d.ts +183 -0
- package/dist/environment/git-observation.js +544 -0
- package/dist/environment/health-composition.d.ts +11 -0
- package/dist/environment/health-composition.js +628 -0
- package/dist/environment/host-observation.d.ts +56 -0
- package/dist/environment/host-observation.js +52 -0
- package/dist/environment/identity.d.ts +48 -0
- package/dist/environment/identity.js +85 -0
- package/dist/environment/init-application.d.ts +308 -0
- package/dist/environment/init-application.js +697 -0
- package/dist/environment/install-policy-observation.d.ts +71 -0
- package/dist/environment/install-policy-observation.js +284 -0
- package/dist/environment/install-policy.d.ts +48 -0
- package/dist/environment/install-policy.js +96 -0
- package/dist/environment/instance.d.ts +33 -0
- package/dist/environment/instance.js +122 -0
- package/dist/environment/instruction-pointer.d.ts +19 -0
- package/dist/environment/instruction-pointer.js +144 -0
- package/dist/environment/lifecycle-composition.d.ts +29 -0
- package/dist/environment/lifecycle-composition.js +675 -0
- package/dist/environment/lifecycle-stage.d.ts +115 -0
- package/dist/environment/lifecycle-stage.js +398 -0
- package/dist/environment/lifecycle.d.ts +75 -0
- package/dist/environment/lifecycle.js +354 -0
- package/dist/environment/logs.d.ts +132 -0
- package/dist/environment/logs.js +519 -0
- package/dist/environment/observations.d.ts +46 -0
- package/dist/environment/observations.js +344 -0
- package/dist/environment/operator-application.d.ts +50 -0
- package/dist/environment/operator-application.js +155 -0
- package/dist/environment/ports.d.ts +182 -0
- package/dist/environment/ports.js +1205 -0
- package/dist/environment/posture-observation.d.ts +68 -0
- package/dist/environment/posture-observation.js +253 -0
- package/dist/environment/read-composition.d.ts +27 -0
- package/dist/environment/read-composition.js +352 -0
- package/dist/environment/readiness-composition.d.ts +38 -0
- package/dist/environment/readiness-composition.js +130 -0
- package/dist/environment/reclaim.d.ts +333 -0
- package/dist/environment/reclaim.js +1835 -0
- package/dist/environment/repository-identity.d.ts +13 -0
- package/dist/environment/repository-identity.js +108 -0
- package/dist/environment/runtime-measurement-record.d.ts +38 -0
- package/dist/environment/runtime-measurement-record.js +111 -0
- package/dist/environment/runtime-measurement.d.ts +27 -0
- package/dist/environment/runtime-measurement.js +224 -0
- package/dist/environment/runtime-probe-residue.d.ts +43 -0
- package/dist/environment/runtime-probe-residue.js +148 -0
- package/dist/environment/runtime-probe.d.ts +72 -0
- package/dist/environment/runtime-probe.js +725 -0
- package/dist/environment/runtime.d.ts +505 -0
- package/dist/environment/runtime.js +2335 -0
- package/dist/environment/seed.d.ts +16 -0
- package/dist/environment/seed.js +43 -0
- package/dist/environment/selftest-application.d.ts +86 -0
- package/dist/environment/selftest-application.js +713 -0
- package/dist/environment/sensitive.d.ts +3 -0
- package/dist/environment/sensitive.js +48 -0
- package/dist/environment/setup-observation.d.ts +62 -0
- package/dist/environment/setup-observation.js +1473 -0
- package/dist/environment/setup.d.ts +43 -0
- package/dist/environment/setup.js +176 -0
- package/dist/environment/sibling-instructions.d.ts +33 -0
- package/dist/environment/sibling-instructions.js +224 -0
- package/dist/environment/sibling-registration.d.ts +45 -0
- package/dist/environment/sibling-registration.js +538 -0
- package/dist/environment/state.d.ts +371 -0
- package/dist/environment/state.js +2013 -0
- package/dist/environment/templates.d.ts +140 -0
- package/dist/environment/templates.js +685 -0
- package/dist/environment/tool-registration.d.ts +9 -0
- package/dist/environment/tool-registration.js +96 -0
- package/dist/environment/upgrade-application.d.ts +19 -0
- package/dist/environment/upgrade-application.js +175 -0
- package/dist/environment-exec/execute.d.ts +102 -0
- package/dist/environment-exec/execute.js +1175 -0
- package/dist/environment-exec/libexec-program.d.ts +6 -0
- package/dist/environment-exec/libexec-program.js +22 -0
- package/dist/environment-exec/verified-digest.d.ts +4 -0
- package/dist/environment-exec/verified-digest.js +16 -0
- package/dist/exact-keys.d.ts +7 -0
- package/dist/exact-keys.js +15 -0
- package/dist/fix-catalogue.d.ts +32 -0
- package/dist/fix-catalogue.js +60 -0
- package/dist/freshness.d.ts +29 -0
- package/dist/freshness.js +75 -0
- package/dist/generated-environment/adopt.d.ts +16 -0
- package/dist/generated-environment/adopt.js +275 -0
- package/dist/generated-environment/command.d.ts +47 -0
- package/dist/generated-environment/command.js +75 -0
- package/dist/generated-environment/transaction.d.ts +186 -0
- package/dist/generated-environment/transaction.js +1770 -0
- package/dist/grading/evaluate.d.ts +71 -0
- package/dist/grading/evaluate.js +403 -0
- package/dist/grading/grade.d.ts +33 -0
- package/dist/grading/grade.js +57 -0
- package/dist/guards/evaluator.d.ts +113 -0
- package/dist/guards/evaluator.js +1226 -0
- package/dist/guards/harness-adapter.d.ts +17 -0
- package/dist/guards/harness-adapter.js +57 -0
- package/dist/guards/host.d.ts +8 -0
- package/dist/guards/host.js +10 -0
- package/dist/guards/invocation-parser.d.ts +66 -0
- package/dist/guards/invocation-parser.js +595 -0
- package/dist/guards/local-identity.d.ts +40 -0
- package/dist/guards/local-identity.js +123 -0
- package/dist/guards/policy-pin.d.ts +13 -0
- package/dist/guards/policy-pin.js +175 -0
- package/dist/guards/protected-locations.d.ts +62 -0
- package/dist/guards/protected-locations.js +461 -0
- package/dist/guards/read-only.d.ts +37 -0
- package/dist/guards/read-only.js +315 -0
- package/dist/init/command.d.ts +40 -0
- package/dist/init/command.js +310 -0
- package/dist/init/derive.d.ts +51 -0
- package/dist/init/derive.js +715 -0
- package/dist/init/draft.d.ts +138 -0
- package/dist/init/draft.js +1205 -0
- package/dist/init/render.d.ts +35 -0
- package/dist/init/render.js +82 -0
- package/dist/init/scaffold.d.ts +53 -0
- package/dist/init/scaffold.js +121 -0
- package/dist/invocation.d.ts +13 -0
- package/dist/invocation.js +20 -0
- package/dist/package-metadata.d.ts +9 -0
- package/dist/package-metadata.js +19 -0
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.js +88 -0
- package/dist/references/named.d.ts +24 -0
- package/dist/references/named.js +27 -0
- package/dist/refusal.d.ts +15 -0
- package/dist/refusal.js +7 -0
- package/dist/registry-derivation.d.ts +31 -0
- package/dist/registry-derivation.js +32 -0
- package/dist/registry-resolve/command.d.ts +47 -0
- package/dist/registry-resolve/command.js +226 -0
- package/dist/registry-resolve/registry.d.ts +68 -0
- package/dist/registry-resolve/registry.js +292 -0
- package/dist/registry-resolve/render-shell.d.ts +18 -0
- package/dist/registry-resolve/render-shell.js +36 -0
- package/dist/repair.d.ts +29 -0
- package/dist/repair.js +8 -0
- package/dist/schema-json.d.ts +1 -0
- package/dist/schema-json.js +177 -0
- package/dist/schema.d.ts +1322 -0
- package/dist/schema.js +934 -0
- package/dist/setup/command.d.ts +4 -0
- package/dist/setup/command.js +4 -0
- package/dist/sha256.d.ts +1 -0
- package/dist/sha256.js +4 -0
- package/dist/surfaces/adoption.d.ts +41 -0
- package/dist/surfaces/adoption.js +217 -0
- package/dist/surfaces/consumer-instructions.d.ts +20 -0
- package/dist/surfaces/consumer-instructions.js +43 -0
- package/dist/surfaces/contract.d.ts +42 -0
- package/dist/surfaces/contract.js +169 -0
- package/dist/surfaces/core-declarations.d.ts +24 -0
- package/dist/surfaces/core-declarations.js +79 -0
- package/dist/surfaces/development-markers.d.ts +1 -0
- package/dist/surfaces/development-markers.js +18 -0
- package/dist/surfaces/generator-adapter.d.ts +71 -0
- package/dist/surfaces/generator-adapter.js +809 -0
- package/dist/surfaces/harness-registry.d.ts +180 -0
- package/dist/surfaces/harness-registry.js +117 -0
- package/dist/surfaces/mcp.d.ts +95 -0
- package/dist/surfaces/mcp.js +186 -0
- package/dist/surfaces/merge.d.ts +30 -0
- package/dist/surfaces/merge.js +105 -0
- package/dist/surfaces/ownership.d.ts +190 -0
- package/dist/surfaces/ownership.js +795 -0
- package/dist/surfaces/projection.d.ts +136 -0
- package/dist/surfaces/projection.js +681 -0
- package/dist/surfaces/render.d.ts +118 -0
- package/dist/surfaces/render.js +633 -0
- package/dist/surfaces/restrictions.d.ts +130 -0
- package/dist/surfaces/restrictions.js +447 -0
- package/dist/surfaces/selftest.d.ts +162 -0
- package/dist/surfaces/selftest.js +393 -0
- package/dist/surfaces/skill-contract.d.ts +85 -0
- package/dist/surfaces/skill-contract.js +449 -0
- package/dist/surfaces/sync.d.ts +57 -0
- package/dist/surfaces/sync.js +164 -0
- package/dist/team-metadata.d.ts +41 -0
- package/dist/team-metadata.js +512 -0
- package/dist/testing/gates.d.ts +91 -0
- package/dist/testing/gates.js +182 -0
- package/dist/testing/hooks.d.ts +59 -0
- package/dist/testing/hooks.js +139 -0
- package/dist/testing/verdict.d.ts +29 -0
- package/dist/testing/verdict.js +62 -0
- package/dist/work-orders/application.d.ts +99 -0
- package/dist/work-orders/application.js +383 -0
- package/dist/work-orders/composition.d.ts +124 -0
- package/dist/work-orders/composition.js +1010 -0
- package/dist/work-orders/contract.d.ts +61 -0
- package/dist/work-orders/contract.js +122 -0
- package/dist/work-orders/store.d.ts +158 -0
- package/dist/work-orders/store.js +197 -0
- package/docs/README.md +65 -0
- package/docs/architecture.md +136 -0
- package/docs/capability-matrix.md +50 -0
- package/docs/cli-reference.md +599 -0
- package/docs/command-list.md +51 -0
- package/docs/compose-patterns.md +99 -0
- package/docs/concepts.md +171 -0
- package/docs/configuration-essentials.md +172 -0
- package/docs/configuration-reference.md +621 -0
- package/docs/environment.md +760 -0
- package/docs/extending.md +43 -0
- package/docs/faq.md +111 -0
- package/docs/getting-started.md +608 -0
- package/docs/harness-surfaces.md +433 -0
- package/docs/joining-a-team.md +76 -0
- package/docs/release-policy.md +49 -0
- package/docs/safety-and-autonomy.md +419 -0
- package/docs/troubleshooting.md +361 -0
- package/docs/tutorial.md +170 -0
- package/docs/work-and-browser.md +254 -0
- package/libexec/browser-operation.cjs +186 -0
- package/libexec/fence-marker.cjs +101 -0
- package/libexec/fence-marker.d.cts +24 -0
- package/libexec/fenced-group-runner.cjs +77 -0
- package/libexec/fenced-supervisor.cjs +137 -0
- package/libexec/generated-filesystem.cjs +731 -0
- package/libexec/install-filesystem.cjs +225 -0
- package/libexec/rulesync-generator.NOTICE.md +1112 -0
- package/libexec/rulesync-generator.cjs +743 -0
- package/libexec/surface-generator-worker.cjs +266 -0
- package/libexec/surface-output-reader.cjs +99 -0
- package/libexec/surface-scratch-cleanup.cjs +14 -0
- package/libexec/validate-harness-evidence.d.mts +183 -0
- package/libexec/validate-harness-evidence.mjs +1312 -0
- package/package.json +76 -1
- package/surfaces/README.md +33 -0
- package/surfaces/agents/evaluator.agent.md +76 -0
- package/surfaces/guards/bash-guard.sh +48 -0
- package/surfaces/guards/claude.adapter.json +25 -0
- package/surfaces/guards/codex.adapter.json +24 -0
- package/surfaces/guards/opencode.adapter.json +25 -0
- package/surfaces/references/browser.md +63 -0
- package/surfaces/references/test-data-and-personas.md +84 -0
- package/surfaces/references/testing-tiers.md +11 -0
- package/surfaces/skills/cockpit-browser.skill.md +54 -0
- package/surfaces/skills/cockpit-explain.skill.md +32 -0
- package/surfaces/skills/cockpit-fix.skill.md +37 -0
- package/surfaces/skills/cockpit-setup.skill.md +39 -0
- package/surfaces/skills/cockpit-work.skill.md +34 -0
- package/surfaces/skills/collection.json +5 -0
- package/surfaces/skills/procedures/explain.md +61 -0
- package/surfaces/skills/procedures/fix.md +81 -0
- package/surfaces/skills/procedures/setup.md +379 -0
- package/surfaces/skills/procedures/work.md +105 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { cleanupFencedExecution, repairFencedExecution } from '../../environment-exec/execute.js';
|
|
5
|
+
import { ENVIRONMENT_CODES, forwardedRefusal } from '../contract.js';
|
|
6
|
+
import { readState } from '../state.js';
|
|
7
|
+
import { compensateAcquiredPortHolder } from '../ports.js';
|
|
8
|
+
import { consumeLifecycleIssuance, planDown, planResetLifecycle } from '../lifecycle.js';
|
|
9
|
+
import { stopRuntime } from '../runtime.js';
|
|
10
|
+
import { inspectDestructiveDrainRecovery, settleInterruptedDrainRepair, recoverDrainReservation, reserveDrain, reserveDrainAttempt, } from '../claims.js';
|
|
11
|
+
import { executeReclaimAction, keptTeardownResources, planReset, planTeardown, reconcileReclaimAction, } from '../reclaim.js';
|
|
12
|
+
import { selectDestructiveInstance } from '../instance.js';
|
|
13
|
+
import { issuedDestructiveRuns, lifecycleContextRefusal, provisionalClaims, withLifecycleRunNotices, } from './lifecycle-context.js';
|
|
14
|
+
import { exactCommandConfigPath, invalidateCompletedLifecycle, lifecycleSuggestion, publishDrainOutcome, publishDrainOutcomeRecord, } from './lifecycle-state.js';
|
|
15
|
+
import { lifecycleRefusal, uniqueDiagnostics } from './shared.js';
|
|
16
|
+
function destructiveRepairRequired(context) {
|
|
17
|
+
return lifecycleRefusal(ENVIRONMENT_CODES.DRAIN_ATTEMPT_REPAIR_REQUIRED, 'cockpit cannot prove the interrupted destructive executor stopped, so it will not overlap it with another destructive action; preserve the coordination state for explicit repair', context.stackId);
|
|
18
|
+
}
|
|
19
|
+
// A stack no session holds is claimed only so down or reset can read it, and the claim belongs to
|
|
20
|
+
// the drain the run reserves, which releases it. A run that reserves no drain (a preview, a decline,
|
|
21
|
+
// a refusal before the drain) gives it back, or the next up meets a holder it did not take and
|
|
22
|
+
// refuses on every retry.
|
|
23
|
+
async function settleProvisionalClaim(context, outcome) {
|
|
24
|
+
const claim = provisionalClaims.get(context);
|
|
25
|
+
if (!claim)
|
|
26
|
+
return outcome;
|
|
27
|
+
provisionalClaims.delete(context);
|
|
28
|
+
const released = await compensateAcquiredPortHolder({ namespace: context.namespace, leaseResult: claim, now: new Date().toISOString() });
|
|
29
|
+
if (released.kind === 'complete')
|
|
30
|
+
return outcome;
|
|
31
|
+
return forwardedRefusal(uniqueDiagnostics([...outcome.kind === 'refusal' ? outcome.diagnostics : [], ...released.diagnostics]));
|
|
32
|
+
}
|
|
33
|
+
async function loadDestructiveContext(input, adapters) {
|
|
34
|
+
const configPath = exactCommandConfigPath(input);
|
|
35
|
+
if (!configPath)
|
|
36
|
+
return lifecycleContextRefusal();
|
|
37
|
+
const loaded = await adapters.load({ configPath });
|
|
38
|
+
if (loaded.kind !== 'complete')
|
|
39
|
+
return loaded.kind === 'refusal' ? loaded : lifecycleContextRefusal();
|
|
40
|
+
if (loaded.result.configPath !== configPath || !issuedDestructiveRuns.delete(loaded.result)) {
|
|
41
|
+
return lifecycleRefusal(ENVIRONMENT_CODES.LIFECYCLE_EVIDENCE_INVALID, 'destructive run authority is unavailable', configPath);
|
|
42
|
+
}
|
|
43
|
+
return loaded;
|
|
44
|
+
}
|
|
45
|
+
function selectedObservation(context, observation) {
|
|
46
|
+
if (!observation)
|
|
47
|
+
return undefined;
|
|
48
|
+
try {
|
|
49
|
+
const selected = selectDestructiveInstance(observation.instance);
|
|
50
|
+
return selected.kind === 'selected' && selected.stackId === context.stackId
|
|
51
|
+
&& selected.checkoutId === context.checkoutId ? observation : undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// An observation whose instance cannot be read is not this run's, which the caller refuses.
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function observeDestructive(context, adapters) {
|
|
59
|
+
const observed = await adapters.observe(context);
|
|
60
|
+
return observed && 'diagnostics' in observed ? observed : selectedObservation(context, observed);
|
|
61
|
+
}
|
|
62
|
+
// Each removal is proven by a fresh discovery, but re-measuring the runtime and re-listing the
|
|
63
|
+
// daemon twice per removal made `down` take minutes on a busy shared daemon. A reserved drain's
|
|
64
|
+
// steps re-read only reclaim evidence, and one discovery both reconciles a removal and plans the next.
|
|
65
|
+
async function observeDestructiveReclaim(context, adapters) {
|
|
66
|
+
const observed = adapters.observeReclaim ? await adapters.observeReclaim(context) : await adapters.observe(context);
|
|
67
|
+
return observed && 'diagnostics' in observed ? observed : selectedObservation(context, observed);
|
|
68
|
+
}
|
|
69
|
+
// Before this run reserves a drain there is no attempt of its own to repair, so a missing
|
|
70
|
+
// observation is an evidence failure. Reporting DRAIN_ATTEMPT_REPAIR_REQUIRED here sent the
|
|
71
|
+
// operator of a stopped runtime looking for an interrupted attempt that did not exist.
|
|
72
|
+
function destructivePreconditionRefusal(context, observed) {
|
|
73
|
+
return observed ?? lifecycleRefusal(ENVIRONMENT_CODES.LIFECYCLE_EVIDENCE_INVALID, 'destructive preconditions (configuration, retained ports, runtime readiness or reclaim discovery) could not be observed; this run reserved no destructive attempt', context.stackId);
|
|
74
|
+
}
|
|
75
|
+
// After a reservation the attempt is real and must be repaired; the owner's own diagnostics
|
|
76
|
+
// still say why the observation failed.
|
|
77
|
+
function destructiveRepairAfterObservation(context, observed) {
|
|
78
|
+
const repair = destructiveRepairRequired(context);
|
|
79
|
+
return observed ? forwardedRefusal(uniqueDiagnostics([...repair.diagnostics, ...observed.diagnostics])) : repair;
|
|
80
|
+
}
|
|
81
|
+
async function publishDestructiveOutcome(context, outcome, fence) {
|
|
82
|
+
const result = await publishDrainOutcome(context, outcome, fence);
|
|
83
|
+
return result?.kind === 'refusal' ? result : undefined;
|
|
84
|
+
}
|
|
85
|
+
async function executeRuntimeDrain(context, adapters, topology, attempt) {
|
|
86
|
+
if (attempt.kind !== 'complete')
|
|
87
|
+
return forwardedRefusal(attempt.diagnostics);
|
|
88
|
+
const stopped = await stopRuntime({ topology, attempt, execute: (step) => adapters.execute(step) });
|
|
89
|
+
if (stopped.kind === 'unavailable')
|
|
90
|
+
return destructiveRepairRequired(context);
|
|
91
|
+
const { outcome, fence } = stopped;
|
|
92
|
+
if (stopped.kind === 'not-started')
|
|
93
|
+
return { outcome, fence };
|
|
94
|
+
const reservation = attempt.reservation;
|
|
95
|
+
if (reservation.purpose === 'down' && reservation.actions.length === 1) {
|
|
96
|
+
const invalidated = await invalidateCompletedLifecycle(context, reservation.operationBasis, reservation.generation, 'down');
|
|
97
|
+
if (invalidated)
|
|
98
|
+
return invalidated;
|
|
99
|
+
}
|
|
100
|
+
return { outcome, fence };
|
|
101
|
+
}
|
|
102
|
+
// Teardown removes only what it can prove is this stack's and unused, so a finished drain can
|
|
103
|
+
// still leave the stack's own resources in place. Reporting `removed` then told the operator the
|
|
104
|
+
// stack was gone while its containers and network stayed, so down refuses and names each one.
|
|
105
|
+
function downKeptRefusal(context, observed) {
|
|
106
|
+
const kept = keptTeardownResources(observed.down);
|
|
107
|
+
if (kept === undefined)
|
|
108
|
+
return lifecycleRefusal(ENVIRONMENT_CODES.RECLAIM_ACCOUNTING_MISMATCH, 'down finished its removals, but its last resource discovery could not be classified, so it cannot confirm the stack is gone; inspect the stack with docker compose ls and docker ps --all', context.stackId);
|
|
109
|
+
if (kept.length === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
const listed = kept.slice(0, 8).map(({ kind, id, name, reason }) => `${kind} ${name} (${id}): ${reason}`).join('; ');
|
|
112
|
+
return forwardedRefusal([{
|
|
113
|
+
severity: 'error', code: ENVIRONMENT_CODES.DOWN_RESOURCES_KEPT, target: context.stackId,
|
|
114
|
+
message: `down removed what it could, but kept ${kept.length} of this stack's own resources: ${listed}${kept.length > 8 ? `; and ${kept.length - 8} more` : ''}`,
|
|
115
|
+
fix: `release each kept resource as its reason says (remove the image's other tag, stop the container still attached to the network), then run \`${lifecycleSuggestion(context, 'down')}\``,
|
|
116
|
+
}]);
|
|
117
|
+
}
|
|
118
|
+
async function executeDestructivePlan(context, adapters, purpose, lifecyclePlan, firstPlan, plannedFrom, recoveredReservation, recoveredRequest) {
|
|
119
|
+
if (lifecyclePlan.kind !== 'drain')
|
|
120
|
+
return forwardedRefusal(lifecyclePlan.diagnostics);
|
|
121
|
+
const issuance = consumeLifecycleIssuance(lifecyclePlan.plan);
|
|
122
|
+
if (!issuance?.drainRequest || !issuance.reclaim || issuance.reclaim.purpose !== purpose) {
|
|
123
|
+
return destructiveRepairRequired(context);
|
|
124
|
+
}
|
|
125
|
+
const request = recoveredRequest ?? lifecyclePlan.request;
|
|
126
|
+
// From here the drain owns the stack's claim: it releases it on completion, and keeps it for repair.
|
|
127
|
+
provisionalClaims.delete(context);
|
|
128
|
+
const reserved = recoveredReservation ?? await reserveDrain({ namespace: context.namespace, request });
|
|
129
|
+
if (reserved.kind !== 'complete')
|
|
130
|
+
return forwardedRefusal(reserved.diagnostics);
|
|
131
|
+
let availableReservation = reserved.reservation.completedActionCount === 0 ? undefined : reserved;
|
|
132
|
+
if (reserved.reservation.completedActionCount === 0) {
|
|
133
|
+
const runtimeAttempt = await reserveDrainAttempt({ namespace: context.namespace, reservation: reserved.reservation });
|
|
134
|
+
const runtimeOutcome = await executeRuntimeDrain(context, adapters, issuance.topology, runtimeAttempt);
|
|
135
|
+
if ('kind' in runtimeOutcome)
|
|
136
|
+
return runtimeOutcome;
|
|
137
|
+
const runtimePublished = await publishDestructiveOutcome(context, runtimeOutcome.outcome, runtimeOutcome.fence);
|
|
138
|
+
if (runtimePublished)
|
|
139
|
+
return runtimePublished;
|
|
140
|
+
}
|
|
141
|
+
let plan = firstPlan;
|
|
142
|
+
// The latest discovery: what the stack still holds once the last removal is reconciled.
|
|
143
|
+
let observed = plannedFrom;
|
|
144
|
+
while (plan.action !== null) {
|
|
145
|
+
const recovered = availableReservation ?? await recoverDrainReservation({ namespace: context.namespace, request });
|
|
146
|
+
availableReservation = undefined;
|
|
147
|
+
if (recovered.kind !== 'complete')
|
|
148
|
+
return forwardedRefusal(recovered.diagnostics);
|
|
149
|
+
const attempt = await reserveDrainAttempt({ namespace: context.namespace, reservation: recovered.reservation });
|
|
150
|
+
if (attempt.kind !== 'complete')
|
|
151
|
+
return forwardedRefusal(attempt.diagnostics);
|
|
152
|
+
const ran = await executeReclaimAction({ action: plan.action, attempt, execute: (step) => adapters.execute(step) });
|
|
153
|
+
if (ran.kind === 'unavailable')
|
|
154
|
+
return destructiveRepairRequired(context);
|
|
155
|
+
if (ran.kind === 'not-started') {
|
|
156
|
+
const published = await publishDestructiveOutcome(context, ran.outcome, ran.fence);
|
|
157
|
+
return published ?? forwardedRefusal([{
|
|
158
|
+
severity: 'error', code: ran.code,
|
|
159
|
+
message: 'destructive action did not start', target: context.stackId,
|
|
160
|
+
}]);
|
|
161
|
+
}
|
|
162
|
+
const postAction = await observeDestructiveReclaim(context, adapters);
|
|
163
|
+
if (!postAction || 'diagnostics' in postAction)
|
|
164
|
+
return destructiveRepairAfterObservation(context, postAction);
|
|
165
|
+
observed = postAction;
|
|
166
|
+
const reconciled = await reconcileReclaimAction({
|
|
167
|
+
executed: ran.executed,
|
|
168
|
+
discovery: purpose === 'down' ? postAction.down.discovery : postAction.reset.discovery,
|
|
169
|
+
});
|
|
170
|
+
if (reconciled.kind === 'refusal') {
|
|
171
|
+
// An already-absent target ran nothing, so the owner's reason is the whole story.
|
|
172
|
+
return ran.executed.receipt === null ? forwardedRefusal(reconciled.diagnostics) : destructiveRepairRequired(context);
|
|
173
|
+
}
|
|
174
|
+
if (purpose === 'down' && reconciled.continuation === null) {
|
|
175
|
+
const invalidated = await invalidateCompletedLifecycle(context, request.operationBasis, request.generation, 'down');
|
|
176
|
+
if (invalidated)
|
|
177
|
+
return invalidated;
|
|
178
|
+
}
|
|
179
|
+
const fence = ran.executed.fence;
|
|
180
|
+
const published = fence === null
|
|
181
|
+
? await publishDrainOutcomeRecord(context, reconciled.outcome)
|
|
182
|
+
: await publishDestructiveOutcome(context, reconciled.outcome, fence);
|
|
183
|
+
if (published)
|
|
184
|
+
return published;
|
|
185
|
+
if (reconciled.continuation === null)
|
|
186
|
+
break;
|
|
187
|
+
// The discovery that reconciled this removal plans the next; only the coordination record
|
|
188
|
+
// moved since, by the outcome just published, so it is read again.
|
|
189
|
+
const coordination = await readState(context.namespace.paths.coordination(context.stackId));
|
|
190
|
+
if (coordination.kind !== 'complete')
|
|
191
|
+
return destructiveRepairRequired(context);
|
|
192
|
+
const next = purpose === 'down'
|
|
193
|
+
? await planTeardown({ ...postAction.down, coordinationRecord: coordination.value,
|
|
194
|
+
expectedDiscoveryDigest: request.planDigest, continuation: reconciled.continuation })
|
|
195
|
+
: await planReset({ ...postAction.reset, coordinationRecord: coordination.value,
|
|
196
|
+
expectedCoordinationRevision: coordination.value.revision,
|
|
197
|
+
confirmationDigest: request.planDigest, continuation: reconciled.continuation });
|
|
198
|
+
if (next.kind !== 'complete')
|
|
199
|
+
return next.kind === 'refusal'
|
|
200
|
+
? forwardedRefusal(next.diagnostics) : destructiveRepairRequired(context);
|
|
201
|
+
plan = next;
|
|
202
|
+
}
|
|
203
|
+
return purpose === 'down'
|
|
204
|
+
? downKeptRefusal(context, observed) ?? {
|
|
205
|
+
kind: 'complete', status: 0, diagnostics: lifecyclePlan.plan.diagnostics,
|
|
206
|
+
result: { stackId: context.stackId, change: 'removed' },
|
|
207
|
+
}
|
|
208
|
+
: {
|
|
209
|
+
kind: 'complete', status: 0, diagnostics: lifecyclePlan.plan.diagnostics,
|
|
210
|
+
result: { stackId: context.stackId, change: 'reset' },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function destructivePlanningBasis(request) {
|
|
214
|
+
const runtimeStop = request?.actions.find(({ stage }) => stage === 'runtime-stop');
|
|
215
|
+
const reclaim = request?.actions.find(({ stage }) => stage === 'reclaim');
|
|
216
|
+
return {
|
|
217
|
+
generation: request?.generation ?? randomBytes(32).toString('hex'),
|
|
218
|
+
runtimeStopValidForMs: runtimeStop?.validForMs ?? 60_000,
|
|
219
|
+
reclaimValidForMs: reclaim?.validForMs ?? 60_000,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function matchesPersistedDrainRequest(planned, persisted) {
|
|
223
|
+
if (persisted === undefined)
|
|
224
|
+
return true;
|
|
225
|
+
const { expectedCoordinationRevision: plannedRevision, ...plannedWork } = planned;
|
|
226
|
+
const { expectedCoordinationRevision: persistedRevision, ...persistedWork } = persisted;
|
|
227
|
+
return plannedRevision >= persistedRevision && isDeepStrictEqual(plannedWork, persistedWork);
|
|
228
|
+
}
|
|
229
|
+
async function prepareDown(context, adapters, recovered) {
|
|
230
|
+
const previewObservation = await observeDestructive(context, adapters);
|
|
231
|
+
if (!previewObservation || 'diagnostics' in previewObservation)
|
|
232
|
+
return destructivePreconditionRefusal(context, previewObservation);
|
|
233
|
+
const preview = await planTeardown({ ...previewObservation.down,
|
|
234
|
+
...(recovered ? { recoveredReservation: recovered.reservation } : {}) });
|
|
235
|
+
if (preview.kind !== 'complete')
|
|
236
|
+
return forwardedRefusal(preview.diagnostics);
|
|
237
|
+
// The plan needs a discovery after the preview's consent; the runtime the preview measured
|
|
238
|
+
// seconds ago still stands for planning the drain.
|
|
239
|
+
const observation = await observeDestructiveReclaim(context, adapters);
|
|
240
|
+
if (!observation || 'diagnostics' in observation)
|
|
241
|
+
return destructivePreconditionRefusal(context, observation);
|
|
242
|
+
return { runtime: previewObservation.runtime, ports: previewObservation.ports, observation, preview };
|
|
243
|
+
}
|
|
244
|
+
async function inspectDestructiveRecoveryWithRepair(context, purpose) {
|
|
245
|
+
let inspection = await inspectDestructiveDrainRecovery({
|
|
246
|
+
namespace: context.namespace, stackId: context.stackId, holderId: context.holderId, purpose,
|
|
247
|
+
});
|
|
248
|
+
if (inspection.kind !== 'repairable')
|
|
249
|
+
return inspection;
|
|
250
|
+
const terminated = await repairFencedExecution(inspection.fence);
|
|
251
|
+
if (terminated.kind !== 'complete')
|
|
252
|
+
return inspection;
|
|
253
|
+
const repaired = await settleInterruptedDrainRepair({ namespace: context.namespace, record: inspection.record, evidence: terminated.evidence });
|
|
254
|
+
if (repaired.kind !== 'complete')
|
|
255
|
+
return inspection;
|
|
256
|
+
if (!await cleanupFencedExecution(inspection.fence))
|
|
257
|
+
return inspection;
|
|
258
|
+
inspection = await inspectDestructiveDrainRecovery({
|
|
259
|
+
namespace: context.namespace, stackId: context.stackId, holderId: context.holderId, purpose,
|
|
260
|
+
});
|
|
261
|
+
return inspection;
|
|
262
|
+
}
|
|
263
|
+
export async function runDown(input, adapters) {
|
|
264
|
+
const loaded = await loadDestructiveContext(input, adapters);
|
|
265
|
+
if (loaded.kind !== 'complete')
|
|
266
|
+
return loaded;
|
|
267
|
+
return withLifecycleRunNotices(loaded.result, await settleProvisionalClaim(loaded.result, await downWithContext(loaded.result, adapters)));
|
|
268
|
+
}
|
|
269
|
+
async function downWithContext(context, adapters) {
|
|
270
|
+
const inspection = await inspectDestructiveRecoveryWithRepair(context, 'down');
|
|
271
|
+
if (inspection.kind === 'refusal')
|
|
272
|
+
return forwardedRefusal(inspection.diagnostics);
|
|
273
|
+
if (inspection.kind === 'repairable')
|
|
274
|
+
return destructiveRepairRequired(context);
|
|
275
|
+
const persistedRequest = inspection.kind === 'recoverable' ? inspection.request : undefined;
|
|
276
|
+
const recovered = persistedRequest === undefined ? undefined
|
|
277
|
+
: await recoverDrainReservation({ namespace: context.namespace, request: persistedRequest });
|
|
278
|
+
if (recovered?.kind === 'refusal')
|
|
279
|
+
return forwardedRefusal(recovered.diagnostics);
|
|
280
|
+
const prepared = await prepareDown(context, adapters, recovered);
|
|
281
|
+
if ('diagnostics' in prepared)
|
|
282
|
+
return prepared;
|
|
283
|
+
const basis = destructivePlanningBasis(persistedRequest);
|
|
284
|
+
const lifecycle = planDown({
|
|
285
|
+
runtime: prepared.runtime,
|
|
286
|
+
ports: prepared.ports,
|
|
287
|
+
reclaim: prepared.preview.envelope,
|
|
288
|
+
operationGeneration: basis.generation,
|
|
289
|
+
runtimeStopValidForMs: basis.runtimeStopValidForMs,
|
|
290
|
+
reclaimValidForMs: basis.reclaimValidForMs,
|
|
291
|
+
});
|
|
292
|
+
if (lifecycle.kind !== 'drain')
|
|
293
|
+
return forwardedRefusal(lifecycle.diagnostics);
|
|
294
|
+
if (!matchesPersistedDrainRequest(lifecycle.request, persistedRequest))
|
|
295
|
+
return destructiveRepairRequired(context);
|
|
296
|
+
const plan = await planTeardown({
|
|
297
|
+
...prepared.observation.down,
|
|
298
|
+
expectedDiscoveryDigest: prepared.preview.discoveryDigest,
|
|
299
|
+
...(recovered?.kind === 'complete'
|
|
300
|
+
? { recoveredReservation: recovered.reservation }
|
|
301
|
+
: prepared.preview.consent === null ? {} : { consent: prepared.preview.consent }),
|
|
302
|
+
});
|
|
303
|
+
if (plan.kind !== 'complete')
|
|
304
|
+
return forwardedRefusal(plan.diagnostics);
|
|
305
|
+
return await executeDestructivePlan(context, adapters, 'down', lifecycle, plan, prepared.observation, recovered?.kind === 'complete' ? recovered : undefined, persistedRequest);
|
|
306
|
+
}
|
|
307
|
+
const privateResetSessions = new WeakMap();
|
|
308
|
+
async function resetPreview(context, adapters) {
|
|
309
|
+
const observation = await observeDestructive(context, adapters);
|
|
310
|
+
if (!observation || 'diagnostics' in observation)
|
|
311
|
+
return destructivePreconditionRefusal(context, observation);
|
|
312
|
+
const preview = await planReset(observation.reset);
|
|
313
|
+
return preview.kind === 'preview' ? preview : preview.kind === 'refusal'
|
|
314
|
+
? forwardedRefusal(preview.diagnostics) : destructivePreconditionRefusal(context, undefined);
|
|
315
|
+
}
|
|
316
|
+
function publicResetPreview(context, preview) {
|
|
317
|
+
return {
|
|
318
|
+
kind: 'preview', status: 0, diagnostics: [],
|
|
319
|
+
result: {
|
|
320
|
+
stackId: context.stackId, planDigest: preview.digest,
|
|
321
|
+
targets: preview.targets.map((target) => ({ ...target })),
|
|
322
|
+
noop: preview.noop,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
export async function runReset(input, adapters) {
|
|
327
|
+
const loaded = await loadDestructiveContext(input, adapters);
|
|
328
|
+
if (loaded.kind !== 'complete')
|
|
329
|
+
return loaded;
|
|
330
|
+
const preview = await resetPreview(loaded.result, adapters);
|
|
331
|
+
return withLifecycleRunNotices(loaded.result, await settleProvisionalClaim(loaded.result, 'status' in preview ? preview : publicResetPreview(loaded.result, preview)));
|
|
332
|
+
}
|
|
333
|
+
export async function beginOperatorReset(input, adapters) {
|
|
334
|
+
const loaded = await loadDestructiveContext(input, adapters);
|
|
335
|
+
if (loaded.kind !== 'complete')
|
|
336
|
+
return loaded;
|
|
337
|
+
const context = loaded.result;
|
|
338
|
+
const preview = await resetPreview(context, adapters);
|
|
339
|
+
if ('status' in preview)
|
|
340
|
+
return withLifecycleRunNotices(context, await settleProvisionalClaim(context, preview));
|
|
341
|
+
const token = {};
|
|
342
|
+
privateResetSessions.set(token, Object.freeze({ context, adapters, preview }));
|
|
343
|
+
const session = {
|
|
344
|
+
preview: withLifecycleRunNotices(context, publicResetPreview(context, preview)),
|
|
345
|
+
decide: (decision) => continueOperatorReset(token, decision),
|
|
346
|
+
};
|
|
347
|
+
return session;
|
|
348
|
+
}
|
|
349
|
+
async function continueOperatorReset(token, decision) {
|
|
350
|
+
const session = privateResetSessions.get(token);
|
|
351
|
+
privateResetSessions.delete(token);
|
|
352
|
+
const unapproved = forwardedRefusal([{
|
|
353
|
+
severity: 'error', code: ENVIRONMENT_CODES.RESET_CONFIRMATION_MISMATCH,
|
|
354
|
+
message: decision === 'decline' ? 'reset was declined' : 'reset operator session is unavailable', target: 'reset',
|
|
355
|
+
}]);
|
|
356
|
+
if (!session)
|
|
357
|
+
return unapproved;
|
|
358
|
+
return settleProvisionalClaim(session.context, decision === 'approve' ? await approveOperatorReset(session) : unapproved);
|
|
359
|
+
}
|
|
360
|
+
async function approveOperatorReset(session) {
|
|
361
|
+
const inspection = await inspectDestructiveRecoveryWithRepair(session.context, 'reset');
|
|
362
|
+
if (inspection.kind === 'refusal')
|
|
363
|
+
return forwardedRefusal(inspection.diagnostics);
|
|
364
|
+
if (inspection.kind === 'repairable')
|
|
365
|
+
return destructiveRepairRequired(session.context);
|
|
366
|
+
const persistedRequest = inspection.kind === 'recoverable' ? inspection.request : undefined;
|
|
367
|
+
const recovered = persistedRequest === undefined ? undefined
|
|
368
|
+
: await recoverDrainReservation({ namespace: session.context.namespace, request: persistedRequest });
|
|
369
|
+
if (recovered?.kind === 'refusal')
|
|
370
|
+
return forwardedRefusal(recovered.diagnostics);
|
|
371
|
+
const observation = await observeDestructive(session.context, session.adapters);
|
|
372
|
+
if (!observation || 'diagnostics' in observation)
|
|
373
|
+
return destructivePreconditionRefusal(session.context, observation);
|
|
374
|
+
if (session.preview.noop && !persistedRequest) {
|
|
375
|
+
const noop = await planReset({
|
|
376
|
+
...observation.reset,
|
|
377
|
+
confirmationDigest: session.preview.digest,
|
|
378
|
+
consent: session.preview.consent,
|
|
379
|
+
});
|
|
380
|
+
if (noop.kind !== 'complete' || !noop.noop || noop.action !== null) {
|
|
381
|
+
return noop.kind === 'refusal' ? forwardedRefusal(noop.diagnostics) : destructivePreconditionRefusal(session.context, undefined);
|
|
382
|
+
}
|
|
383
|
+
const finalInspection = await inspectDestructiveDrainRecovery({ namespace: session.context.namespace,
|
|
384
|
+
stackId: session.context.stackId, holderId: session.context.holderId, purpose: 'reset' });
|
|
385
|
+
if (finalInspection.kind !== 'none')
|
|
386
|
+
return finalInspection.kind === 'refusal'
|
|
387
|
+
? forwardedRefusal(finalInspection.diagnostics) : destructiveRepairRequired(session.context);
|
|
388
|
+
return {
|
|
389
|
+
kind: 'complete', status: 0, diagnostics: [],
|
|
390
|
+
result: { stackId: session.context.stackId, change: 'unchanged' },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const recoveryPlan = recovered ? await planReset({ ...observation.reset,
|
|
394
|
+
confirmationDigest: persistedRequest.planDigest,
|
|
395
|
+
recoveredReservation: recovered.reservation, operatorApproval: session.preview }) : undefined;
|
|
396
|
+
if (recoveryPlan && recoveryPlan.kind !== 'complete')
|
|
397
|
+
return recoveryPlan.kind === 'refusal'
|
|
398
|
+
? forwardedRefusal(recoveryPlan.diagnostics) : destructiveRepairRequired(session.context);
|
|
399
|
+
const basis = destructivePlanningBasis(persistedRequest);
|
|
400
|
+
const lifecycle = planResetLifecycle({
|
|
401
|
+
runtime: observation.runtime,
|
|
402
|
+
ports: observation.ports,
|
|
403
|
+
reclaim: recoveryPlan ? recoveryPlan.envelope : session.preview.envelope,
|
|
404
|
+
operationGeneration: basis.generation,
|
|
405
|
+
runtimeStopValidForMs: basis.runtimeStopValidForMs,
|
|
406
|
+
reclaimValidForMs: basis.reclaimValidForMs,
|
|
407
|
+
});
|
|
408
|
+
if (lifecycle.kind !== 'drain')
|
|
409
|
+
return forwardedRefusal(lifecycle.diagnostics);
|
|
410
|
+
if (!matchesPersistedDrainRequest(lifecycle.request, persistedRequest))
|
|
411
|
+
return destructiveRepairRequired(session.context);
|
|
412
|
+
const actionablePlan = recoveryPlan ?? await planReset({
|
|
413
|
+
...observation.reset,
|
|
414
|
+
confirmationDigest: session.preview.digest,
|
|
415
|
+
...(recovered?.kind === 'complete'
|
|
416
|
+
? { recoveredReservation: recovered.reservation }
|
|
417
|
+
: { consent: session.preview.consent }),
|
|
418
|
+
});
|
|
419
|
+
if (actionablePlan.kind !== 'complete')
|
|
420
|
+
return actionablePlan.kind === 'refusal'
|
|
421
|
+
? forwardedRefusal(actionablePlan.diagnostics) : destructiveRepairRequired(session.context);
|
|
422
|
+
const applied = await executeDestructivePlan(session.context, session.adapters, 'reset', lifecycle, actionablePlan, observation, recovered?.kind === 'complete' ? recovered : undefined, persistedRequest);
|
|
423
|
+
return applied.kind === 'complete' ? { ...applied, result: { ...applied.result,
|
|
424
|
+
targets: publicResetPreview(session.context, session.preview).result.targets } } : applied;
|
|
425
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type EnvironmentDiagnostic } from '../contract.js';
|
|
2
|
+
import type { EvaluatorTool } from '../../schema.js';
|
|
3
|
+
import type { SkippedStateNamespace } from '../state.js';
|
|
4
|
+
import { type GeneratedEnvironmentCurrentness } from '../generated-application/shared.js';
|
|
5
|
+
import { type ReadinessReport } from '../../readiness.js';
|
|
6
|
+
import type { ReleaseFreshness } from '../../freshness.js';
|
|
7
|
+
import { proposeGeneratedEnvironmentRepair } from '../../repair.js';
|
|
8
|
+
import type { ActiveSurfaceOverride, SurfaceUpgradeFinding } from '../../surfaces/merge.js';
|
|
9
|
+
import { type DoctorCheck, type PreflightEvaluation } from '../observations.js';
|
|
10
|
+
import { type ComparisonRemoteEvaluation, type ObservationCommandAdapters } from './health.js';
|
|
11
|
+
export interface DoctorCommandInput {
|
|
12
|
+
readonly configPath: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DoctorSurfaceOverrideInspectionComplete {
|
|
15
|
+
readonly kind: 'complete';
|
|
16
|
+
readonly activeOverrides: readonly ActiveSurfaceOverride[];
|
|
17
|
+
readonly changedOverrideOriginals: readonly SurfaceUpgradeFinding[];
|
|
18
|
+
}
|
|
19
|
+
export interface DoctorSurfaceOverrideInspectionUnavailable {
|
|
20
|
+
readonly kind: 'unavailable';
|
|
21
|
+
readonly code: string;
|
|
22
|
+
readonly reason: string;
|
|
23
|
+
}
|
|
24
|
+
export interface DoctorSurfaceOverrideInspectionRefusal {
|
|
25
|
+
readonly kind: 'refusal';
|
|
26
|
+
readonly code: string;
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
}
|
|
29
|
+
export type DoctorSurfaceOverrideInspection = DoctorSurfaceOverrideInspectionComplete | DoctorSurfaceOverrideInspectionUnavailable | DoctorSurfaceOverrideInspectionRefusal;
|
|
30
|
+
export interface DoctorCommandAdapters extends ObservationCommandAdapters {
|
|
31
|
+
readonly inspectSurfaceOverrides?: (configPath: string) => Promise<DoctorSurfaceOverrideInspection>;
|
|
32
|
+
readonly discoverReleaseFreshness?: () => Promise<ReleaseFreshness>;
|
|
33
|
+
/** Warnings when an agent tool on this machine would skip the generated guard hook. */
|
|
34
|
+
readonly inspectHookTrust?: () => Promise<readonly EnvironmentDiagnostic[]>;
|
|
35
|
+
/** A warning when work orders exist but their independent evaluator cannot run on this machine. */
|
|
36
|
+
readonly inspectEvaluator?: (evaluators: readonly EvaluatorTool[]) => Promise<readonly EnvironmentDiagnostic[]>;
|
|
37
|
+
/** Warnings about prerequisites the stack checks do not cover: the local install, sibling remotes, committed secrets. */
|
|
38
|
+
readonly inspectReadiness?: () => Promise<readonly EnvironmentDiagnostic[]>;
|
|
39
|
+
/** Other cockpits' state namespaces this version skipped, or why the inventory could not be read; read after load has selected this cockpit's state root. */
|
|
40
|
+
readonly readUnreadableState?: () => Promise<Readonly<{
|
|
41
|
+
kind: 'complete';
|
|
42
|
+
skipped: readonly SkippedStateNamespace[];
|
|
43
|
+
}> | Readonly<{
|
|
44
|
+
kind: 'refusal';
|
|
45
|
+
diagnostics: readonly EnvironmentDiagnostic[];
|
|
46
|
+
}>>;
|
|
47
|
+
}
|
|
48
|
+
interface DoctorFreshness {
|
|
49
|
+
readonly local: Readonly<{
|
|
50
|
+
kind: GeneratedEnvironmentCurrentness['outcome']['kind'];
|
|
51
|
+
status: number;
|
|
52
|
+
diagnostics: GeneratedEnvironmentCurrentness['outcome']['diagnostics'];
|
|
53
|
+
}>;
|
|
54
|
+
readonly changes: GeneratedEnvironmentCurrentness['changes'];
|
|
55
|
+
readonly storedProducer: GeneratedEnvironmentCurrentness['storedProducer'];
|
|
56
|
+
readonly installedProducer: GeneratedEnvironmentCurrentness['currentProducer'];
|
|
57
|
+
readonly release: ReleaseFreshness;
|
|
58
|
+
readonly repair?: ReturnType<typeof proposeGeneratedEnvironmentRepair>;
|
|
59
|
+
}
|
|
60
|
+
export interface DoctorDeclaration {
|
|
61
|
+
readonly subject: 'runtime' | 'reset-volumes' | 'seed';
|
|
62
|
+
readonly state: 'declared' | 'not-declared';
|
|
63
|
+
readonly source: string;
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
}
|
|
66
|
+
export interface ApplicationDoctorEvaluation {
|
|
67
|
+
readonly verdict: PreflightEvaluation['verdict'];
|
|
68
|
+
readonly checks: readonly DoctorCheck[];
|
|
69
|
+
readonly comparisons: readonly ComparisonRemoteEvaluation[];
|
|
70
|
+
readonly surfaceOverrides: DoctorSurfaceOverrideInspection;
|
|
71
|
+
readonly freshness: DoctorFreshness;
|
|
72
|
+
/** What the configuration declares, read from cockpit.toml alone; empty when it does not load. */
|
|
73
|
+
readonly declarations: readonly DoctorDeclaration[];
|
|
74
|
+
/** The reported diagnostics grouped under the prerequisites cockpit depends on; absent when cockpit.toml does not load. */
|
|
75
|
+
readonly readiness?: ReadinessReport;
|
|
76
|
+
}
|
|
77
|
+
export type DoctorCommandResult = Readonly<{
|
|
78
|
+
kind: 'complete';
|
|
79
|
+
status: 0;
|
|
80
|
+
diagnostics: readonly EnvironmentDiagnostic[];
|
|
81
|
+
result: ApplicationDoctorEvaluation;
|
|
82
|
+
}> | Readonly<{
|
|
83
|
+
kind: 'refusal';
|
|
84
|
+
status: 2;
|
|
85
|
+
diagnostics: readonly EnvironmentDiagnostic[];
|
|
86
|
+
result: ApplicationDoctorEvaluation;
|
|
87
|
+
}>;
|
|
88
|
+
export declare function runDoctor(input: DoctorCommandInput, adapters: DoctorCommandAdapters): Promise<DoctorCommandResult>;
|
|
89
|
+
export {};
|