@b00dea/cockpit 0.0.0-stage → 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 +474 -0
- package/LICENSE +21 -0
- package/README.md +25 -2
- package/bin/cockpit.mjs +33 -0
- package/cockpit.schema.json +2644 -0
- package/dist/autonomy.d.ts +45 -0
- package/dist/autonomy.js +173 -0
- package/dist/bounded-read.d.ts +14 -0
- package/dist/bounded-read.js +42 -0
- package/dist/browser/commands.d.ts +117 -0
- package/dist/browser/commands.js +453 -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 +118 -0
- package/dist/browser/runtime.js +186 -0
- package/dist/browser/surfaces.d.ts +141 -0
- package/dist/browser/surfaces.js +561 -0
- package/dist/browser/ui-gate.d.ts +15 -0
- package/dist/browser/ui-gate.js +18 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +5 -0
- package/dist/cli-guard.d.ts +5 -0
- package/dist/cli-guard.js +71 -0
- package/dist/cli-main.d.ts +176 -0
- package/dist/cli-main.js +1584 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +16 -0
- package/dist/command-table.d.ts +121 -0
- package/dist/command-table.js +664 -0
- package/dist/config/load.d.ts +183 -0
- package/dist/config/load.js +367 -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/unconfirmed-toml.d.ts +9 -0
- package/dist/config/unconfirmed-toml.js +29 -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 +344 -0
- 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/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/confirm.d.ts +35 -0
- package/dist/environment/application/confirm.js +122 -0
- package/dist/environment/application/destructive.d.ts +6 -0
- package/dist/environment/application/destructive.js +429 -0
- package/dist/environment/application/doctor.d.ts +94 -0
- package/dist/environment/application/doctor.js +247 -0
- package/dist/environment/application/guard.d.ts +49 -0
- package/dist/environment/application/guard.js +677 -0
- package/dist/environment/application/health.d.ts +143 -0
- package/dist/environment/application/health.js +385 -0
- package/dist/environment/application/lifecycle-context.d.ts +235 -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 +202 -0
- package/dist/environment/application/lifecycle.d.ts +18 -0
- package/dist/environment/application/lifecycle.js +737 -0
- package/dist/environment/application/logs.d.ts +45 -0
- package/dist/environment/application/logs.js +127 -0
- package/dist/environment/application/services.d.ts +60 -0
- package/dist/environment/application/services.js +89 -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/application/siblings.d.ts +43 -0
- package/dist/environment/application/siblings.js +269 -0
- package/dist/environment/claims.d.ts +389 -0
- package/dist/environment/claims.js +1701 -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 +125 -0
- package/dist/environment/compose-topology.js +490 -0
- package/dist/environment/contract.d.ts +522 -0
- package/dist/environment/contract.js +794 -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 +38 -0
- package/dist/environment/executable.js +190 -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 +191 -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 +278 -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 +492 -0
- package/dist/environment/generated-application/shared.js +354 -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 +400 -0
- package/dist/environment/generated-application/surfaces.d.ts +3 -0
- package/dist/environment/generated-application/surfaces.js +704 -0
- package/dist/environment/generated-application/sync.d.ts +6 -0
- package/dist/environment/generated-application/sync.js +124 -0
- package/dist/environment/git-observation.d.ts +202 -0
- package/dist/environment/git-observation.js +553 -0
- package/dist/environment/health-composition.d.ts +12 -0
- package/dist/environment/health-composition.js +645 -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 +333 -0
- package/dist/environment/init-application.js +726 -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 +31 -0
- package/dist/environment/lifecycle-composition.js +742 -0
- package/dist/environment/lifecycle-stage.d.ts +120 -0
- package/dist/environment/lifecycle-stage.js +408 -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/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 +46 -0
- package/dist/environment/observations.js +346 -0
- package/dist/environment/operator-application.d.ts +79 -0
- package/dist/environment/operator-application.js +197 -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/presence.d.ts +4 -0
- package/dist/environment/presence.js +26 -0
- package/dist/environment/read-composition.d.ts +32 -0
- package/dist/environment/read-composition.js +397 -0
- package/dist/environment/readiness-composition.d.ts +57 -0
- package/dist/environment/readiness-composition.js +151 -0
- package/dist/environment/reclaim.d.ts +370 -0
- package/dist/environment/reclaim.js +1940 -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 +551 -0
- package/dist/environment/runtime.js +2405 -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/served-checkout-grants.d.ts +53 -0
- package/dist/environment/served-checkout-grants.js +182 -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-checkouts.d.ts +119 -0
- package/dist/environment/sibling-checkouts.js +254 -0
- package/dist/environment/sibling-instructions.d.ts +33 -0
- package/dist/environment/sibling-instructions.js +224 -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/sibling-registration.d.ts +45 -0
- package/dist/environment/sibling-registration.js +538 -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 +410 -0
- package/dist/environment/state.js +2153 -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 +92 -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 +105 -0
- package/dist/environment-exec/execute.js +1179 -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 +271 -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/answer-confirmation.d.ts +41 -0
- package/dist/guards/answer-confirmation.js +109 -0
- package/dist/guards/evaluator.d.ts +130 -0
- package/dist/guards/evaluator.js +1258 -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 +44 -0
- package/dist/guards/local-identity.js +129 -0
- package/dist/guards/policy-change.d.ts +43 -0
- package/dist/guards/policy-change.js +159 -0
- package/dist/guards/policy-pin.d.ts +13 -0
- package/dist/guards/policy-pin.js +177 -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 +41 -0
- package/dist/init/command.js +330 -0
- package/dist/init/derive.d.ts +53 -0
- package/dist/init/derive.js +819 -0
- package/dist/init/draft.d.ts +140 -0
- package/dist/init/draft.js +1228 -0
- package/dist/init/render.d.ts +47 -0
- package/dist/init/render.js +104 -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/repository-path.d.ts +9 -0
- package/dist/repository-path.js +16 -0
- package/dist/schema-json.d.ts +1 -0
- package/dist/schema-json.js +177 -0
- package/dist/schema.d.ts +1289 -0
- package/dist/schema.js +985 -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 +260 -0
- package/dist/surfaces/harness-registry.js +146 -0
- package/dist/surfaces/mcp.d.ts +145 -0
- package/dist/surfaces/mcp.js +349 -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 +716 -0
- package/dist/surfaces/render.d.ts +79 -0
- package/dist/surfaces/render.js +636 -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 +513 -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/test-protection.d.ts +34 -0
- package/dist/testing/test-protection.js +137 -0
- package/dist/testing/verdict.d.ts +29 -0
- package/dist/testing/verdict.js +62 -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 +6 -0
- package/dist/work-orders/application.js +223 -0
- package/dist/work-orders/composition.d.ts +101 -0
- package/dist/work-orders/composition.js +1202 -0
- package/dist/work-orders/contract.d.ts +77 -0
- package/dist/work-orders/contract.js +140 -0
- package/dist/work-orders/decisions.d.ts +23 -0
- package/dist/work-orders/decisions.js +55 -0
- package/dist/work-orders/store.d.ts +207 -0
- package/dist/work-orders/store.js +291 -0
- package/docs/README.md +68 -0
- package/docs/architecture.md +136 -0
- package/docs/capability-matrix.md +58 -0
- package/docs/cli-reference.md +807 -0
- package/docs/command-list.md +62 -0
- package/docs/compose-patterns.md +99 -0
- package/docs/concepts.md +173 -0
- package/docs/configuration-essentials.md +177 -0
- package/docs/configuration-reference.md +636 -0
- package/docs/docs-check.md +76 -0
- package/docs/environment.md +948 -0
- package/docs/extending.md +43 -0
- package/docs/faq.md +113 -0
- package/docs/getting-started.md +692 -0
- package/docs/harness-surfaces.md +500 -0
- package/docs/joining-a-team.md +76 -0
- package/docs/release-policy.md +49 -0
- package/docs/safety-and-autonomy.md +463 -0
- package/docs/troubleshooting.md +382 -0
- package/docs/tutorial.md +171 -0
- package/docs/work-and-browser.md +420 -0
- package/libexec/browser-operation.cjs +270 -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 +74 -4
- 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 +59 -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 +66 -0
- package/surfaces/skills/procedures/fix.md +95 -0
- package/surfaces/skills/procedures/setup.md +452 -0
- package/surfaces/skills/procedures/work.md +134 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { sha256Hex } from '../sha256.js';
|
|
2
|
+
import { isAbsolute, normalize } from 'node:path';
|
|
3
|
+
import { authenticatedExecutionOutcome } from '../environment-exec/execute.js';
|
|
4
|
+
import { RefusalError, ENVIRONMENT_CODES, EXECUTION_FAILURE_CODES, snapshotJson, snapshotExecutionPlan, forwardedRefusal, } from './contract.js';
|
|
5
|
+
import { classifyResolvedRuntimeLiterals } from './runtime.js';
|
|
6
|
+
import { isSensitiveField, redactSensitiveText } from './sensitive.js';
|
|
7
|
+
import { isResolvedDockerExecutable } from './executable.js';
|
|
8
|
+
import { exactKeys } from '../exact-keys.js';
|
|
9
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
10
|
+
const STACK_ID = /^stack:[a-f0-9]{64}$/;
|
|
11
|
+
const CONTAINER_ID = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
12
|
+
const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
13
|
+
const MAX_TIMEOUT_MS = 30_000;
|
|
14
|
+
// Exported so a caller passes the capture's own bound: the capture prints one stat line and one
|
|
15
|
+
// digest line, and a general output limit sized for logs exceeds what this planner admits.
|
|
16
|
+
export const STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES = 65_536;
|
|
17
|
+
const UNAVAILABLE_EVIDENCE_CODES = new Set([ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE, ...EXECUTION_FAILURE_CODES]);
|
|
18
|
+
const PROCESS_STEP_PREFIX = 'evidence:process-identity:';
|
|
19
|
+
const CONTENT_STEP_PREFIX = 'evidence:content-hash:';
|
|
20
|
+
const PROCESS_SCRIPT = 'IFS= read -r stat < /proc/1/stat; pid=${stat%% *}; rest=${stat##*) }; set -- $rest; printf "%s %s\\n" "$pid" "${20}"';
|
|
21
|
+
// -H follows a symlink named as a content path: /proc/1/exe is one, and without -H find skips it,
|
|
22
|
+
// hashes nothing, and the capture fails with an empty corpus on every real container.
|
|
23
|
+
const CONTENT_SCRIPT = 'tmp=${TMPDIR:-/tmp}/cockpit-content-$$; cockpit_hash_failure=$tmp.failed; export cockpit_hash_failure; trap \'rm -f "$tmp.raw" "$tmp.sorted" "$tmp.failed"\' EXIT HUP INT TERM; find -H "$@" -type f -exec sh -c \'sha256sum -- "$@" || { : > "$cockpit_hash_failure"; exit 1; }\' sh {} + > "$tmp.raw"; [ ! -e "$tmp.failed" ] && [ -s "$tmp.raw" ]; LC_ALL=C sort "$tmp.raw" > "$tmp.sorted"; sha256sum "$tmp.sorted"';
|
|
24
|
+
function stableValue(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return `[${value.map(stableValue).join(',')}]`;
|
|
27
|
+
if (value !== null && typeof value === 'object') {
|
|
28
|
+
return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
29
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${stableValue(child)}`).join(',')}}`;
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
function ownedObject(value) {
|
|
34
|
+
const snapshot = snapshotJson(value);
|
|
35
|
+
return snapshot !== undefined && snapshot !== null && typeof snapshot === 'object' && !Array.isArray(snapshot)
|
|
36
|
+
? snapshot
|
|
37
|
+
: undefined;
|
|
38
|
+
}
|
|
39
|
+
function sanitize(value, key = '') {
|
|
40
|
+
if (isSensitiveField(key))
|
|
41
|
+
return '[redacted]';
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return value.map((item) => sanitize(item));
|
|
44
|
+
if (value !== null && typeof value === 'object') {
|
|
45
|
+
return Object.fromEntries(Object.entries(value).map(([name, child]) => [name, sanitize(child, name)]));
|
|
46
|
+
}
|
|
47
|
+
return typeof value === 'string' ? redactSensitiveText(value) : value;
|
|
48
|
+
}
|
|
49
|
+
function validInstant(value) {
|
|
50
|
+
if (!ISO_INSTANT.test(value))
|
|
51
|
+
return false;
|
|
52
|
+
const date = new Date(value);
|
|
53
|
+
const canonical = value.includes('.') ? value : value.replace('Z', '.000Z');
|
|
54
|
+
return !Number.isNaN(date.valueOf()) && date.toISOString() === canonical;
|
|
55
|
+
}
|
|
56
|
+
function instantMs(value) { return new Date(value).valueOf(); }
|
|
57
|
+
function diagnostic(message, target) {
|
|
58
|
+
return { severity: 'error', code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE, message, target };
|
|
59
|
+
}
|
|
60
|
+
function refusal(message, target) {
|
|
61
|
+
return forwardedRefusal([diagnostic(message, target)]);
|
|
62
|
+
}
|
|
63
|
+
export function createEnvironmentSnapshot(input) {
|
|
64
|
+
const owned = ownedObject(input);
|
|
65
|
+
if (owned === undefined || !exactKeys(owned, ['configSha256', 'createdAt', 'creationSource', 'intendedState', 'stackId'])
|
|
66
|
+
|| typeof owned.stackId !== 'string' || typeof owned.configSha256 !== 'string' || typeof owned.createdAt !== 'string'
|
|
67
|
+
|| !STACK_ID.test(owned.stackId) || !SHA256.test(owned.configSha256) || !validInstant(owned.createdAt)
|
|
68
|
+
|| owned.creationSource !== 'resolved-config') {
|
|
69
|
+
throw new RefusalError('environment snapshot provenance is invalid');
|
|
70
|
+
}
|
|
71
|
+
const intendedState = sanitize(owned.intendedState);
|
|
72
|
+
const snapshotSha256 = sha256Hex(stableValue({
|
|
73
|
+
stackId: owned.stackId,
|
|
74
|
+
configSha256: owned.configSha256,
|
|
75
|
+
createdAt: owned.createdAt,
|
|
76
|
+
creationSource: owned.creationSource,
|
|
77
|
+
intendedState,
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
artifact: 'generated-environment-snapshot', schemaVersion: 1, stackId: owned.stackId,
|
|
81
|
+
configSha256: owned.configSha256, createdAt: owned.createdAt,
|
|
82
|
+
creationSource: owned.creationSource, intendedState, snapshotSha256,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function stabilityCapturePlanInput(value) {
|
|
86
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
87
|
+
|| !exactKeys(value, ['stackId', 'configSha256', 'runtimeExecutable', 'cwd', 'env', 'containerId', 'batchId', 'runtime', 'contentPaths', 'timeoutMs', 'outputLimitBytes']))
|
|
88
|
+
return undefined;
|
|
89
|
+
const input = value;
|
|
90
|
+
if (!isResolvedDockerExecutable(input.runtimeExecutable)
|
|
91
|
+
|| input.runtime === null || typeof input.runtime !== 'object' || Array.isArray(input.runtime)
|
|
92
|
+
|| !exactKeys(input.runtime, ['name', 'version']) || Object.values(input.runtime).some((part) => typeof part !== 'string')
|
|
93
|
+
|| input.env === null || typeof input.env !== 'object' || Array.isArray(input.env)
|
|
94
|
+
|| Object.entries(input.env).some(([name, part]) => name.length === 0 || typeof part !== 'string')
|
|
95
|
+
|| !Array.isArray(input.contentPaths) || input.contentPaths.some((path) => typeof path !== 'string')
|
|
96
|
+
|| typeof input.stackId !== 'string' || typeof input.configSha256 !== 'string' || typeof input.cwd !== 'string'
|
|
97
|
+
|| typeof input.containerId !== 'string' || typeof input.batchId !== 'string'
|
|
98
|
+
|| typeof input.timeoutMs !== 'number' || typeof input.outputLimitBytes !== 'number')
|
|
99
|
+
return undefined;
|
|
100
|
+
return input;
|
|
101
|
+
}
|
|
102
|
+
function captureStep(input, id, argv) {
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
argv: [...argv],
|
|
106
|
+
argv0: input.runtimeExecutable.invocationPath,
|
|
107
|
+
cwd: input.cwd,
|
|
108
|
+
env: { ...input.env },
|
|
109
|
+
timeoutMs: input.timeoutMs,
|
|
110
|
+
stdoutLimitBytes: input.outputLimitBytes,
|
|
111
|
+
stderrLimitBytes: input.outputLimitBytes,
|
|
112
|
+
paths: [...input.contentPaths],
|
|
113
|
+
ports: [],
|
|
114
|
+
labels: { stackId: input.stackId, configSha256: input.configSha256, containerId: input.containerId, batchId: input.batchId, runtimeName: input.runtime.name, runtimeVersion: input.runtime.version, executableSha256: input.runtimeExecutable.sha256, evidenceRole: id.startsWith(PROCESS_STEP_PREFIX) ? 'process-identity' : 'content-hash' },
|
|
115
|
+
expected: [],
|
|
116
|
+
consumedFiles: [{ path: input.runtimeExecutable.canonicalPath, sha256: input.runtimeExecutable.sha256, maxBytes: 536_870_912, argvIndexes: [0], executable: true, executionMode: input.runtimeExecutable.executionMode }],
|
|
117
|
+
filesystemChecks: [],
|
|
118
|
+
compensation: [],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function createStabilityCapturePlan(rawInput) {
|
|
122
|
+
const input = stabilityCapturePlanInput(snapshotJson(rawInput));
|
|
123
|
+
if (input === undefined)
|
|
124
|
+
return refusal('stability capture input must be readable JSON data', 'stability-capture');
|
|
125
|
+
const pathsValid = input.contentPaths.length > 0 && new Set(input.contentPaths).size === input.contentPaths.length
|
|
126
|
+
&& input.contentPaths.every((path) => isAbsolute(path) && normalize(path) === path && !path.includes('\0'));
|
|
127
|
+
if (!STACK_ID.test(input.stackId) || !SHA256.test(input.configSha256)
|
|
128
|
+
|| !isResolvedDockerExecutable(input.runtimeExecutable)
|
|
129
|
+
|| !isAbsolute(input.cwd) || normalize(input.cwd) !== input.cwd || !CONTAINER_ID.test(input.containerId) || !SHA256.test(input.batchId)
|
|
130
|
+
|| input.runtime.name !== 'docker' || !/^\d+\.\d+\.\d+$/.test(input.runtime.version)
|
|
131
|
+
|| !pathsValid || !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > MAX_TIMEOUT_MS
|
|
132
|
+
|| !Number.isInteger(input.outputLimitBytes) || input.outputLimitBytes < 1 || input.outputLimitBytes > STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES) {
|
|
133
|
+
return refusal('stability capture requires resolved in-container targets and bounded execution', input.containerId);
|
|
134
|
+
}
|
|
135
|
+
if (Object.keys(input.env).some((name) => name.startsWith('DOCKER_') || name.startsWith('COMPOSE_'))
|
|
136
|
+
|| classifyResolvedRuntimeLiterals([...Object.keys(input.env), ...Object.values(input.env)]) !== 'resolved') {
|
|
137
|
+
return refusal('stability capture cannot accept decision-bearing runtime environment', input.containerId);
|
|
138
|
+
}
|
|
139
|
+
const steps = [
|
|
140
|
+
captureStep(input, `${PROCESS_STEP_PREFIX}${input.containerId}`, [
|
|
141
|
+
input.runtimeExecutable.canonicalPath, 'exec', '--', input.containerId,
|
|
142
|
+
'/bin/sh', '-eu', '-c', PROCESS_SCRIPT,
|
|
143
|
+
]),
|
|
144
|
+
captureStep(input, `${CONTENT_STEP_PREFIX}${input.containerId}`, [
|
|
145
|
+
input.runtimeExecutable.canonicalPath, 'exec', '--', input.containerId,
|
|
146
|
+
'/bin/sh', '-eu', '-c', CONTENT_SCRIPT, 'cockpit-content-hash', ...input.contentPaths,
|
|
147
|
+
]),
|
|
148
|
+
];
|
|
149
|
+
const plan = snapshotExecutionPlan({ steps });
|
|
150
|
+
return plan === undefined
|
|
151
|
+
? refusal('stability capture did not produce a valid execution plan', input.containerId)
|
|
152
|
+
: { kind: 'preview', status: 0, diagnostics: [], result: null, plan };
|
|
153
|
+
}
|
|
154
|
+
const authenticatedCaptureBrand = Symbol('authenticated-stability-capture');
|
|
155
|
+
const issuedCaptures = new WeakMap();
|
|
156
|
+
function ownedResults(value) {
|
|
157
|
+
return Array.isArray(value) && value.length === 2 ? [value[0], value[1]] : undefined;
|
|
158
|
+
}
|
|
159
|
+
function captureAuthenticationInput(value) {
|
|
160
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value) || !exactKeys(value, ['plan', 'results']))
|
|
161
|
+
return undefined;
|
|
162
|
+
const { plan, results } = value;
|
|
163
|
+
return { plan, results };
|
|
164
|
+
}
|
|
165
|
+
export function authenticateStabilityCapture(input) {
|
|
166
|
+
const ownedInput = captureAuthenticationInput(input);
|
|
167
|
+
if (ownedInput === undefined)
|
|
168
|
+
return unavailable('stability capture input is not readable');
|
|
169
|
+
const plan = ownedObject(ownedInput.plan);
|
|
170
|
+
const capturedResults = ownedResults(ownedInput.results);
|
|
171
|
+
if (plan === undefined || capturedResults === undefined || !exactKeys(plan, ['kind', 'status', 'diagnostics', 'result', 'plan'])
|
|
172
|
+
|| plan.kind !== 'preview' || plan.status !== 0 || plan.result !== null || !Array.isArray(plan.diagnostics) || plan.diagnostics.length !== 0) {
|
|
173
|
+
return unavailable('stability capture input is not readable');
|
|
174
|
+
}
|
|
175
|
+
const executionPlan = snapshotExecutionPlan(plan.plan);
|
|
176
|
+
if (executionPlan === undefined)
|
|
177
|
+
return unavailable('stability capture input is not readable');
|
|
178
|
+
const steps = executionPlan.steps;
|
|
179
|
+
if (steps.length !== 2) {
|
|
180
|
+
return unavailable('stability capture requires two complete planned execution results');
|
|
181
|
+
}
|
|
182
|
+
const authenticatedResults = capturedResults.map((result, index) => authenticatedExecutionOutcome(result, steps[index]));
|
|
183
|
+
if (authenticatedResults.some((result) => result === undefined)) {
|
|
184
|
+
return unavailable('stability results do not authenticate against one planned execution cohort');
|
|
185
|
+
}
|
|
186
|
+
const refused = authenticatedResults.find((result) => result?.kind === 'refusal');
|
|
187
|
+
if (refused?.kind === 'refusal')
|
|
188
|
+
return unavailable('stability capture execution was refused', refused.code);
|
|
189
|
+
const results = authenticatedResults;
|
|
190
|
+
const [first, second] = results;
|
|
191
|
+
const labels = steps[0].labels;
|
|
192
|
+
const sameCohort = steps.every((step) => step.labels.stackId === labels.stackId
|
|
193
|
+
&& step.labels.configSha256 === labels.configSha256 && step.labels.batchId === labels.batchId
|
|
194
|
+
&& step.labels.containerId === labels.containerId && step.labels.runtimeName === labels.runtimeName
|
|
195
|
+
&& step.labels.runtimeVersion === labels.runtimeVersion && step.labels.executableSha256 === labels.executableSha256
|
|
196
|
+
&& step.argv0 === steps[0].argv0);
|
|
197
|
+
const executable = steps[0].argv[0];
|
|
198
|
+
const exactCommands = steps[0].argv.length === 8 && steps[0].argv.slice(1).join('\0') === ['exec', '--', labels.containerId, '/bin/sh', '-eu', '-c', PROCESS_SCRIPT].join('\0')
|
|
199
|
+
&& steps[1].argv.length > 9 && steps[1].argv.slice(1, 8).join('\0') === ['exec', '--', labels.containerId, '/bin/sh', '-eu', '-c', CONTENT_SCRIPT].join('\0')
|
|
200
|
+
&& steps[1].argv[8] === 'cockpit-content-hash'
|
|
201
|
+
&& isAbsolute(steps[0].argv0) && normalize(steps[0].argv0) === steps[0].argv0
|
|
202
|
+
&& steps.every((step) => step.argv[0] === executable && step.filesystemChecks.length === 0
|
|
203
|
+
&& step.consumedFiles.length === 1 && step.consumedFiles[0].path === executable
|
|
204
|
+
&& step.consumedFiles[0].sha256 === labels.executableSha256
|
|
205
|
+
&& step.consumedFiles[0].executable && step.consumedFiles[0].argvIndexes.length === 1
|
|
206
|
+
&& step.consumedFiles[0].argvIndexes[0] === 0);
|
|
207
|
+
if (!sameCohort || !exactCommands || !STACK_ID.test(labels.stackId ?? '') || !SHA256.test(labels.configSha256 ?? '')
|
|
208
|
+
|| !SHA256.test(labels.batchId ?? '') || !CONTAINER_ID.test(labels.containerId ?? '') || labels.runtimeName !== 'docker'
|
|
209
|
+
|| !/^\d+\.\d+\.\d+$/.test(labels.runtimeVersion ?? '') || !SHA256.test(labels.executableSha256 ?? '')
|
|
210
|
+
|| steps[0].labels.evidenceRole !== 'process-identity' || steps[1].labels.evidenceRole !== 'content-hash'
|
|
211
|
+
|| !validInstant(first.receipt.startedAt) || !validInstant(first.receipt.finishedAt)
|
|
212
|
+
|| !validInstant(second.receipt.startedAt) || !validInstant(second.receipt.finishedAt)
|
|
213
|
+
|| instantMs(first.receipt.startedAt) > instantMs(first.receipt.finishedAt) || instantMs(first.receipt.finishedAt) > instantMs(second.receipt.startedAt)
|
|
214
|
+
|| instantMs(second.receipt.startedAt) > instantMs(second.receipt.finishedAt)
|
|
215
|
+
|| steps[0].id !== `${PROCESS_STEP_PREFIX}${labels.containerId}` || steps[1].id !== `${CONTENT_STEP_PREFIX}${labels.containerId}`
|
|
216
|
+
|| first.receipt.stepId !== steps[0].id || second.receipt.stepId !== steps[1].id) {
|
|
217
|
+
return unavailable('stability results do not authenticate against one planned execution cohort');
|
|
218
|
+
}
|
|
219
|
+
const stdout = first.stdout.byteLength + second.stdout.byteLength;
|
|
220
|
+
const stderr = first.stderr.byteLength + second.stderr.byteLength;
|
|
221
|
+
if (results.some((result, index) => result.stdout.byteLength > steps[index].stdoutLimitBytes || result.stderr.byteLength > steps[index].stderrLimitBytes
|
|
222
|
+
|| result.execution.stdoutRetainedBytes !== result.stdout.byteLength || result.execution.stderrRetainedBytes !== result.stderr.byteLength
|
|
223
|
+
|| result.execution.stdoutObservedBytes !== result.stdout.byteLength || result.execution.stderrObservedBytes !== result.stderr.byteLength))
|
|
224
|
+
return unavailable('stability result exceeded its planned limit');
|
|
225
|
+
let processIdentity;
|
|
226
|
+
let contentHash;
|
|
227
|
+
try {
|
|
228
|
+
processIdentity = new TextDecoder('utf-8', { fatal: true }).decode(first.stdout).trim();
|
|
229
|
+
contentHash = new TextDecoder('utf-8', { fatal: true }).decode(second.stdout).trim().split(/\s+/)[0];
|
|
230
|
+
}
|
|
231
|
+
catch { /* Invalid UTF-8 is the only throw here and is what the message names. */
|
|
232
|
+
return unavailable('stability result encoding is invalid');
|
|
233
|
+
}
|
|
234
|
+
if (!/^\d+ \d+$/.test(processIdentity) || !SHA256.test(contentHash))
|
|
235
|
+
return unavailable('stability result shape is invalid');
|
|
236
|
+
const payload = {
|
|
237
|
+
stackId: labels.stackId, configSha256: labels.configSha256,
|
|
238
|
+
batchId: labels.batchId, containerId: labels.containerId,
|
|
239
|
+
runtime: { name: labels.runtimeName, version: labels.runtimeVersion, executableSha256: labels.executableSha256 }, startedAt: first.receipt.startedAt, finishedAt: second.receipt.finishedAt,
|
|
240
|
+
command: { stepIds: [steps[0].id, steps[1].id], timeoutMs: Math.max(...steps.map(({ timeoutMs }) => timeoutMs)), stdoutLimitBytes: steps.reduce((sum, step) => sum + step.stdoutLimitBytes, 0), stderrLimitBytes: steps.reduce((sum, step) => sum + step.stderrLimitBytes, 0) },
|
|
241
|
+
observedBytes: { stdout, stderr }, processIdentity, contentHash,
|
|
242
|
+
};
|
|
243
|
+
const capture = { [authenticatedCaptureBrand]: true, ...payload };
|
|
244
|
+
issuedCaptures.set(capture, payload);
|
|
245
|
+
return capture;
|
|
246
|
+
}
|
|
247
|
+
function unavailable(reason, code = ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE) {
|
|
248
|
+
return { state: 'unavailable', code, reason };
|
|
249
|
+
}
|
|
250
|
+
export function captureRuntimeEvidence(input) {
|
|
251
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input) || !exactKeys(input, ['capture'])
|
|
252
|
+
|| input.capture === null || typeof input.capture !== 'object') {
|
|
253
|
+
return unavailable('runtime evidence capture is not readable');
|
|
254
|
+
}
|
|
255
|
+
const capture = issuedCaptures.get(input.capture);
|
|
256
|
+
if (capture === undefined)
|
|
257
|
+
return unavailable('runtime evidence was not produced by an authenticated capture');
|
|
258
|
+
if (!validAuthenticatedCapture(capture))
|
|
259
|
+
return unavailable('runtime evidence provenance is invalid');
|
|
260
|
+
const [processStepId, contentStepId] = capture.command.stepIds;
|
|
261
|
+
if (!STACK_ID.test(capture.stackId) || !SHA256.test(capture.configSha256)
|
|
262
|
+
|| !SHA256.test(capture.runtime.executableSha256) || !/^\d+\.\d+\.\d+$/.test(capture.runtime.version)
|
|
263
|
+
|| capture.runtime.name.length === 0 || !SHA256.test(capture.batchId) || !CONTAINER_ID.test(capture.containerId)
|
|
264
|
+
|| processStepId !== `${PROCESS_STEP_PREFIX}${capture.containerId}` || contentStepId !== `${CONTENT_STEP_PREFIX}${capture.containerId}`)
|
|
265
|
+
return unavailable('runtime evidence provenance is invalid');
|
|
266
|
+
if (!Number.isSafeInteger(capture.observedBytes.stdout) || !Number.isSafeInteger(capture.observedBytes.stderr)) {
|
|
267
|
+
return unavailable('runtime evidence exceeded or omitted its declared execution limits');
|
|
268
|
+
}
|
|
269
|
+
const observedAt = capture.finishedAt;
|
|
270
|
+
const command = { ...capture.command, batchId: capture.batchId, containerId: capture.containerId, stepIds: [...capture.command.stepIds] };
|
|
271
|
+
const observedBytes = { ...capture.observedBytes };
|
|
272
|
+
const runtime = { ...capture.runtime };
|
|
273
|
+
const stability = {
|
|
274
|
+
processIdentitySha256: sha256Hex(capture.processIdentity),
|
|
275
|
+
contentHash: capture.contentHash,
|
|
276
|
+
};
|
|
277
|
+
const captureStartedAt = capture.startedAt;
|
|
278
|
+
const core = { stackId: capture.stackId, configSha256: capture.configSha256, runtime, observedAt, captureStartedAt, command, observedBytes, stability };
|
|
279
|
+
return { state: 'available', artifact: 'captured-runtime-evidence', schemaVersion: 1, ...core, evidenceSha256: sha256Hex(stableValue(core)) };
|
|
280
|
+
}
|
|
281
|
+
function sameRuntime(left, right) {
|
|
282
|
+
return left.runtime.name === right.runtime.name && left.runtime.version === right.runtime.version
|
|
283
|
+
&& left.runtime.executableSha256 === right.runtime.executableSha256;
|
|
284
|
+
}
|
|
285
|
+
function validRuntimeEvidence(value) {
|
|
286
|
+
if (validEvidence(value))
|
|
287
|
+
return true;
|
|
288
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
289
|
+
|| !exactKeys(value, ['state', 'code', 'reason']))
|
|
290
|
+
return false;
|
|
291
|
+
const candidate = value;
|
|
292
|
+
return candidate.state === 'unavailable' && typeof candidate.code === 'string'
|
|
293
|
+
&& UNAVAILABLE_EVIDENCE_CODES.has(candidate.code) && typeof candidate.reason === 'string';
|
|
294
|
+
}
|
|
295
|
+
export function compareEnvironmentSnapshot(rawSnapshot, rawBefore, rawAfter) {
|
|
296
|
+
const snapshot = ownedObject(rawSnapshot);
|
|
297
|
+
const before = ownedObject(rawBefore);
|
|
298
|
+
const after = ownedObject(rawAfter);
|
|
299
|
+
if (snapshot === undefined || before === undefined || after === undefined) {
|
|
300
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('environment evidence is not readable', 'environment-evidence')] };
|
|
301
|
+
}
|
|
302
|
+
if (!validSnapshot(snapshot)) {
|
|
303
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('generated environment snapshot is invalid', 'environment-snapshot')] };
|
|
304
|
+
}
|
|
305
|
+
if (!validRuntimeEvidence(before) || !validRuntimeEvidence(after)) {
|
|
306
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('captured runtime evidence is invalid', snapshot.stackId)] };
|
|
307
|
+
}
|
|
308
|
+
if (before.state !== 'available' || after.state !== 'available') {
|
|
309
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('required runtime evidence is unavailable', snapshot.stackId)] };
|
|
310
|
+
}
|
|
311
|
+
if (!isRuntimeEvidenceAuthentic(before) || !isRuntimeEvidenceAuthentic(after)) {
|
|
312
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('captured runtime evidence is invalid', snapshot.stackId)] };
|
|
313
|
+
}
|
|
314
|
+
if (snapshot.stackId !== before.stackId || snapshot.stackId !== after.stackId
|
|
315
|
+
|| snapshot.configSha256 !== before.configSha256 || snapshot.configSha256 !== after.configSha256
|
|
316
|
+
|| !sameRuntime(before, after)) {
|
|
317
|
+
return { verdict: 'stale', diagnostics: [diagnostic('evidence authorities changed during comparison', snapshot.stackId)] };
|
|
318
|
+
}
|
|
319
|
+
if (before.command.batchId === after.command.batchId || instantMs(before.observedAt) > instantMs(after.captureStartedAt)) {
|
|
320
|
+
return { verdict: 'unavailable', diagnostics: [diagnostic('evidence batches are replayed or out of order', snapshot.stackId)] };
|
|
321
|
+
}
|
|
322
|
+
const processIdentity = before.stability.processIdentitySha256 === after.stability.processIdentitySha256 ? 'unchanged' : 'moved';
|
|
323
|
+
const contentHash = before.stability.contentHash === after.stability.contentHash ? 'unchanged' : 'moved';
|
|
324
|
+
if (processIdentity === 'moved' || contentHash === 'moved') {
|
|
325
|
+
return {
|
|
326
|
+
verdict: 'void', processIdentity, contentHash,
|
|
327
|
+
diagnostics: [diagnostic('test subject changed while evidence was captured', snapshot.stackId)],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const diagnostics = [];
|
|
331
|
+
return { verdict: 'stable', processIdentity, contentHash, diagnostics };
|
|
332
|
+
}
|
|
333
|
+
function validSnapshot(value) {
|
|
334
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
335
|
+
|| !exactKeys(value, ['artifact', 'schemaVersion', 'stackId', 'configSha256', 'createdAt', 'creationSource', 'intendedState', 'snapshotSha256']))
|
|
336
|
+
return false;
|
|
337
|
+
const snapshot = value;
|
|
338
|
+
if (typeof snapshot.artifact !== 'string' || typeof snapshot.schemaVersion !== 'number'
|
|
339
|
+
|| typeof snapshot.stackId !== 'string' || typeof snapshot.configSha256 !== 'string'
|
|
340
|
+
|| typeof snapshot.createdAt !== 'string' || typeof snapshot.creationSource !== 'string'
|
|
341
|
+
|| typeof snapshot.snapshotSha256 !== 'string' || snapshot.intendedState === undefined)
|
|
342
|
+
return false;
|
|
343
|
+
const core = {
|
|
344
|
+
stackId: snapshot.stackId, configSha256: snapshot.configSha256, createdAt: snapshot.createdAt,
|
|
345
|
+
creationSource: snapshot.creationSource, intendedState: snapshot.intendedState,
|
|
346
|
+
};
|
|
347
|
+
return snapshot.artifact === 'generated-environment-snapshot' && snapshot.schemaVersion === 1
|
|
348
|
+
&& STACK_ID.test(snapshot.stackId) && SHA256.test(snapshot.configSha256) && validInstant(snapshot.createdAt)
|
|
349
|
+
&& snapshot.creationSource === 'resolved-config' && stableValue(sanitize(snapshot.intendedState)) === stableValue(snapshot.intendedState)
|
|
350
|
+
&& SHA256.test(snapshot.snapshotSha256)
|
|
351
|
+
&& snapshot.snapshotSha256 === sha256Hex(stableValue(core));
|
|
352
|
+
}
|
|
353
|
+
function validAuthenticatedCapture(value) {
|
|
354
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
355
|
+
|| !exactKeys(value, ['stackId', 'configSha256', 'batchId', 'containerId', 'runtime', 'startedAt', 'finishedAt', 'command', 'observedBytes', 'processIdentity', 'contentHash']))
|
|
356
|
+
return false;
|
|
357
|
+
const capture = value;
|
|
358
|
+
return typeof capture.stackId === 'string' && STACK_ID.test(capture.stackId)
|
|
359
|
+
&& typeof capture.configSha256 === 'string' && SHA256.test(capture.configSha256)
|
|
360
|
+
&& typeof capture.batchId === 'string' && SHA256.test(capture.batchId)
|
|
361
|
+
&& typeof capture.containerId === 'string' && CONTAINER_ID.test(capture.containerId)
|
|
362
|
+
&& capture.runtime !== null && typeof capture.runtime === 'object' && !Array.isArray(capture.runtime)
|
|
363
|
+
&& exactKeys(capture.runtime, ['name', 'version', 'executableSha256'])
|
|
364
|
+
&& typeof capture.runtime.name === 'string' && capture.runtime.name.length > 0
|
|
365
|
+
&& typeof capture.runtime.version === 'string' && /^\d+\.\d+\.\d+$/.test(capture.runtime.version)
|
|
366
|
+
&& typeof capture.runtime.executableSha256 === 'string' && SHA256.test(capture.runtime.executableSha256)
|
|
367
|
+
&& typeof capture.startedAt === 'string' && validInstant(capture.startedAt)
|
|
368
|
+
&& typeof capture.finishedAt === 'string' && validInstant(capture.finishedAt)
|
|
369
|
+
&& capture.command !== null && typeof capture.command === 'object' && !Array.isArray(capture.command)
|
|
370
|
+
&& exactKeys(capture.command, ['stepIds', 'timeoutMs', 'stdoutLimitBytes', 'stderrLimitBytes'])
|
|
371
|
+
&& Array.isArray(capture.command.stepIds) && capture.command.stepIds.length === 2
|
|
372
|
+
&& capture.command.stepIds.every((id) => typeof id === 'string')
|
|
373
|
+
&& Number.isSafeInteger(capture.command.timeoutMs) && capture.command.timeoutMs > 0 && capture.command.timeoutMs <= MAX_TIMEOUT_MS
|
|
374
|
+
&& Number.isSafeInteger(capture.command.stdoutLimitBytes) && capture.command.stdoutLimitBytes > 0 && capture.command.stdoutLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
|
|
375
|
+
&& Number.isSafeInteger(capture.command.stderrLimitBytes) && capture.command.stderrLimitBytes > 0 && capture.command.stderrLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
|
|
376
|
+
&& capture.observedBytes !== null && typeof capture.observedBytes === 'object' && !Array.isArray(capture.observedBytes)
|
|
377
|
+
&& exactKeys(capture.observedBytes, ['stdout', 'stderr'])
|
|
378
|
+
&& Number.isSafeInteger(capture.observedBytes.stdout) && capture.observedBytes.stdout >= 0
|
|
379
|
+
&& Number.isSafeInteger(capture.observedBytes.stderr) && capture.observedBytes.stderr >= 0
|
|
380
|
+
&& capture.observedBytes.stdout <= capture.command.stdoutLimitBytes
|
|
381
|
+
&& capture.observedBytes.stderr <= capture.command.stderrLimitBytes
|
|
382
|
+
&& typeof capture.processIdentity === 'string' && /^\d+ \d+$/.test(capture.processIdentity)
|
|
383
|
+
&& typeof capture.contentHash === 'string' && SHA256.test(capture.contentHash);
|
|
384
|
+
}
|
|
385
|
+
function validEvidence(value) {
|
|
386
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
387
|
+
|| !exactKeys(value, ['state', 'artifact', 'schemaVersion', 'stackId', 'configSha256', 'runtime', 'observedAt', 'captureStartedAt', 'command', 'observedBytes', 'stability', 'evidenceSha256']))
|
|
388
|
+
return false;
|
|
389
|
+
const evidence = value;
|
|
390
|
+
if (evidence.runtime === null || typeof evidence.runtime !== 'object' || Array.isArray(evidence.runtime)
|
|
391
|
+
|| !exactKeys(evidence.runtime, ['name', 'version', 'executableSha256'])
|
|
392
|
+
|| typeof evidence.runtime.name !== 'string' || typeof evidence.runtime.version !== 'string'
|
|
393
|
+
|| typeof evidence.runtime.executableSha256 !== 'string'
|
|
394
|
+
|| evidence.command === null || typeof evidence.command !== 'object' || Array.isArray(evidence.command)
|
|
395
|
+
|| !exactKeys(evidence.command, ['batchId', 'containerId', 'stepIds', 'timeoutMs', 'stdoutLimitBytes', 'stderrLimitBytes'])
|
|
396
|
+
|| typeof evidence.command.batchId !== 'string' || typeof evidence.command.containerId !== 'string'
|
|
397
|
+
|| !Array.isArray(evidence.command.stepIds) || evidence.command.stepIds.length !== 2
|
|
398
|
+
|| evidence.command.stepIds.some((id) => typeof id !== 'string')
|
|
399
|
+
|| evidence.observedBytes === null || typeof evidence.observedBytes !== 'object' || Array.isArray(evidence.observedBytes)
|
|
400
|
+
|| !exactKeys(evidence.observedBytes, ['stdout', 'stderr'])
|
|
401
|
+
|| evidence.stability === null || typeof evidence.stability !== 'object' || Array.isArray(evidence.stability)
|
|
402
|
+
|| !exactKeys(evidence.stability, ['processIdentitySha256', 'contentHash'])
|
|
403
|
+
|| typeof evidence.stability.processIdentitySha256 !== 'string' || typeof evidence.stability.contentHash !== 'string'
|
|
404
|
+
|| typeof evidence.state !== 'string' || typeof evidence.artifact !== 'string' || typeof evidence.schemaVersion !== 'number'
|
|
405
|
+
|| typeof evidence.stackId !== 'string' || typeof evidence.configSha256 !== 'string'
|
|
406
|
+
|| typeof evidence.observedAt !== 'string' || typeof evidence.captureStartedAt !== 'string'
|
|
407
|
+
|| typeof evidence.evidenceSha256 !== 'string')
|
|
408
|
+
return false;
|
|
409
|
+
const core = {
|
|
410
|
+
stackId: evidence.stackId, configSha256: evidence.configSha256, runtime: evidence.runtime,
|
|
411
|
+
observedAt: evidence.observedAt, captureStartedAt: evidence.captureStartedAt,
|
|
412
|
+
command: evidence.command, observedBytes: evidence.observedBytes,
|
|
413
|
+
stability: evidence.stability,
|
|
414
|
+
};
|
|
415
|
+
return evidence.state === 'available' && evidence.artifact === 'captured-runtime-evidence' && evidence.schemaVersion === 1
|
|
416
|
+
&& STACK_ID.test(evidence.stackId) && SHA256.test(evidence.configSha256) && validInstant(evidence.observedAt)
|
|
417
|
+
&& validInstant(evidence.captureStartedAt) && instantMs(evidence.captureStartedAt) <= instantMs(evidence.observedAt)
|
|
418
|
+
&& evidence.runtime.name === 'docker' && /^\d+\.\d+\.\d+$/.test(evidence.runtime.version)
|
|
419
|
+
&& SHA256.test(evidence.runtime.executableSha256) && SHA256.test(evidence.command.batchId)
|
|
420
|
+
&& CONTAINER_ID.test(evidence.command.containerId)
|
|
421
|
+
&& evidence.command.stepIds.length === 2
|
|
422
|
+
&& evidence.command.stepIds[0] === `${PROCESS_STEP_PREFIX}${evidence.command.containerId}`
|
|
423
|
+
&& evidence.command.stepIds[1] === `${CONTENT_STEP_PREFIX}${evidence.command.containerId}`
|
|
424
|
+
&& Number.isSafeInteger(evidence.command.timeoutMs) && evidence.command.timeoutMs > 0 && evidence.command.timeoutMs <= MAX_TIMEOUT_MS
|
|
425
|
+
&& Number.isSafeInteger(evidence.command.stdoutLimitBytes) && evidence.command.stdoutLimitBytes > 0 && evidence.command.stdoutLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
|
|
426
|
+
&& Number.isSafeInteger(evidence.command.stderrLimitBytes) && evidence.command.stderrLimitBytes > 0 && evidence.command.stderrLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
|
|
427
|
+
&& Number.isSafeInteger(evidence.observedBytes.stdout) && evidence.observedBytes.stdout >= 0
|
|
428
|
+
&& Number.isSafeInteger(evidence.observedBytes.stderr) && evidence.observedBytes.stderr >= 0
|
|
429
|
+
&& evidence.observedBytes.stdout <= evidence.command.stdoutLimitBytes
|
|
430
|
+
&& evidence.observedBytes.stderr <= evidence.command.stderrLimitBytes
|
|
431
|
+
&& SHA256.test(evidence.stability.processIdentitySha256) && SHA256.test(evidence.stability.contentHash)
|
|
432
|
+
&& SHA256.test(evidence.evidenceSha256) && evidence.evidenceSha256 === sha256Hex(stableValue(core));
|
|
433
|
+
}
|
|
434
|
+
export function isRuntimeEvidenceAuthentic(rawEvidence) {
|
|
435
|
+
const evidence = ownedObject(rawEvidence);
|
|
436
|
+
return evidence !== undefined && validEvidence(evidence);
|
|
437
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const GUARD_EXECUTABLE_SEARCH_PATH = "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin";
|
|
2
|
+
export interface ResolvedExecutable {
|
|
3
|
+
readonly canonicalPath: string;
|
|
4
|
+
readonly invocationPath: string;
|
|
5
|
+
readonly sha256: string;
|
|
6
|
+
readonly executionMode: 'verified-path';
|
|
7
|
+
}
|
|
8
|
+
export declare function resolvedExecutable(value: unknown): ResolvedExecutable | undefined;
|
|
9
|
+
export declare function isResolvedExecutable(value: unknown): boolean;
|
|
10
|
+
export declare function resolvedDockerExecutable(value: unknown): ResolvedExecutable | undefined;
|
|
11
|
+
export declare function isResolvedDockerExecutable(value: unknown): boolean;
|
|
12
|
+
export declare function resolvedGitExecutable(value: unknown): ResolvedExecutable | undefined;
|
|
13
|
+
export declare function isResolvedGitExecutable(value: unknown): boolean;
|
|
14
|
+
export type ExecutableResolution = Readonly<{
|
|
15
|
+
state: 'available';
|
|
16
|
+
value: ResolvedExecutable;
|
|
17
|
+
}> | Readonly<{
|
|
18
|
+
state: 'unavailable';
|
|
19
|
+
reason: string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function resolveExecutablePath(path: string): Promise<ExecutableResolution>;
|
|
22
|
+
export declare function resolveExecutable(name: string, env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a command can be started from `cwd`, found the way a wrapper that changes into `cwd` would
|
|
25
|
+
* find it, without hashing the file: a command with a slash is relative to `cwd`, a bare name is
|
|
26
|
+
* looked up on PATH. For callers that report presence rather than run the file.
|
|
27
|
+
*/
|
|
28
|
+
export declare function locateExecutable(command: string, env: Readonly<Record<string, string | undefined>>, cwd: string): Promise<Readonly<{
|
|
29
|
+
state: 'present';
|
|
30
|
+
path: string;
|
|
31
|
+
}> | Readonly<{
|
|
32
|
+
state: 'unavailable';
|
|
33
|
+
reason: string;
|
|
34
|
+
}>>;
|
|
35
|
+
export declare function resolveGitExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
|
|
36
|
+
export declare function resolveDiskCapacityExecutable(): Promise<ExecutableResolution>;
|
|
37
|
+
export declare function resolveDockerExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
|
|
38
|
+
export declare function resolvePathRemovalExecutable(): Promise<ExecutableResolution>;
|