@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,191 @@
|
|
|
1
|
+
import { installedPackageMetadata } from '../../package-metadata.js';
|
|
2
|
+
import { proposeGeneratedEnvironmentRepair } from '../../repair.js';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { loadCockpitConfig, observeConfigSource } from '../../config/load.js';
|
|
5
|
+
import { classifyGeneratedPaths, encodeOwnershipManifest } from '../../surfaces/ownership.js';
|
|
6
|
+
import { SIBLING_GUIDE_MARKER } from '../../surfaces/projection.js';
|
|
7
|
+
import { nodeAuthority, readAnchoredFile, readAnchoredBatches } from '../../generated-environment/transaction.js';
|
|
8
|
+
import { errorCause, RefusalError } from '../contract.js';
|
|
9
|
+
import { resolveDockerExecutable } from '../executable.js';
|
|
10
|
+
import { assessComposeTopology, composeProjectDirectory, observeComposeTopology } from '../compose-topology.js';
|
|
11
|
+
import { declaredRuntime } from '../declaration.js';
|
|
12
|
+
import { refusalOutcome as refuse } from '../../refusal.js';
|
|
13
|
+
import { MAX_GENERATED_BYTES, OWNERSHIP_PATH, compareCodeUnits, complete, defaultAdapters, generatedPathFacts, mergedAdapters, overrideChangedRefusal, sha256, sourceIdentity, } from './shared.js';
|
|
14
|
+
import { prepare, readOwnership } from './prepare.js';
|
|
15
|
+
import { classifyOwnedState, inputSourcesCurrent, observeSurfaceState, ownedInputOverlap, planObservedSurfaces, preparePublication, surfacePlanRefusal, } from './publication.js';
|
|
16
|
+
import { prepareSurfaces } from './surfaces.js';
|
|
17
|
+
export const issuedRepairs = new WeakMap();
|
|
18
|
+
export function issueRepairAuthority(record) {
|
|
19
|
+
const authority = { kind: 'generated-environment-repair' };
|
|
20
|
+
issuedRepairs.set(authority, record);
|
|
21
|
+
return authority;
|
|
22
|
+
}
|
|
23
|
+
export async function prepareGeneratedEnvironmentCurrentness(input, adapterOverrides = {}) {
|
|
24
|
+
const adapters = mergedAdapters(adapterOverrides);
|
|
25
|
+
const base = await prepare(input, adapters);
|
|
26
|
+
const ownership = 'status' in base ? undefined : await readOwnership(base, adapters);
|
|
27
|
+
const publication = 'status' in base ? base : await preparePublication(input, adapters, base, ownership);
|
|
28
|
+
const prepared = 'prepared' in publication ? publication.prepared : undefined;
|
|
29
|
+
const outcome = 'status' in publication ? publication : await checkCanonicalGeneratedEnvironment(input, prepared, adapters);
|
|
30
|
+
const authority = prepared && 'plan' in publication
|
|
31
|
+
? issueRepairAuthority({ prepared, plan: publication.plan, adapters }) : undefined;
|
|
32
|
+
return {
|
|
33
|
+
outcome,
|
|
34
|
+
changes: 'plan' in publication ? publication.plan.changes : [],
|
|
35
|
+
storedProducer: ownership?.kind === 'complete' ? ownership.manifest.producer : null,
|
|
36
|
+
currentProducer: { package: installedPackageMetadata.name, version: installedPackageMetadata.version,
|
|
37
|
+
sourceRevision: installedPackageMetadata.cockpitSourceRevision },
|
|
38
|
+
fixContext: { resolvedConfigPath: prepared?.configPath ?? null,
|
|
39
|
+
generatedTargets: prepared ? [...prepared.owned, ...prepared.surfaces].map(row => row.path) : [] },
|
|
40
|
+
mcpServers: prepared?.mcpServers ?? [],
|
|
41
|
+
...(authority ? { repairAuthority: authority } : {}),
|
|
42
|
+
...('status' in publication ? { repairRefusal: publication }
|
|
43
|
+
: 'refusal' in publication ? { repairRefusal: publication.refusal } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Each copied guide carries a marker naming its sibling, path and digest, so the guides whose copy in
|
|
47
|
+
// the AGENTS.md on disk differs from the one sync would write can be named rather than the file alone.
|
|
48
|
+
function staleSiblingGuides(prepared, observed) {
|
|
49
|
+
const desired = prepared.surfaces.find(entry => entry.path === 'AGENTS.md');
|
|
50
|
+
const current = observed.find(entry => entry.path === 'AGENTS.md');
|
|
51
|
+
const markers = (text) => new Map([...text.matchAll(SIBLING_GUIDE_MARKER)].map(match => [`${match[1]}/${match[2]}`, match[3]]));
|
|
52
|
+
const want = markers(desired?.kind === 'shared-region' && desired.region.format === 'markdown-delimited' ? desired.region.bytes : '');
|
|
53
|
+
const have = markers(current?.kind === 'file' ? current.bytes : '');
|
|
54
|
+
return [...new Set([...want.keys(), ...have.keys()])].filter(key => want.get(key) !== have.get(key)).sort(compareCodeUnits);
|
|
55
|
+
}
|
|
56
|
+
export async function checkCanonicalGeneratedEnvironment(input, seed, adapters = defaultAdapters) {
|
|
57
|
+
const base = seed ?? await prepare(input, adapters);
|
|
58
|
+
if ('status' in base)
|
|
59
|
+
return base;
|
|
60
|
+
const ownership = await readOwnership(base, adapters);
|
|
61
|
+
if (ownership.kind === 'unsupported')
|
|
62
|
+
return refuse('GENERATED_OWNERSHIP_FORMAT_UNSUPPORTED', `generated ownership format ${ownership.format} is not supported`, OWNERSHIP_PATH);
|
|
63
|
+
if (ownership.kind !== 'complete')
|
|
64
|
+
return refuse(ownership.kind === 'missing' ? 'GENERATED_OWNERSHIP_MISSING' : 'GENERATED_OWNERSHIP_INVALID', 'durable generated ownership metadata is missing or invalid', OWNERSHIP_PATH, 'restore trusted ownership metadata or run npx --no cockpit sync --config <your cockpit.toml>');
|
|
65
|
+
const priorSurfaces = ownership.manifest.entries.filter((row) => 'harnesses' in row);
|
|
66
|
+
const prepared = seed?.surfaceSelftest ? seed : await prepareSurfaces(base, priorSurfaces, adapters, ownership.manifest);
|
|
67
|
+
if ('status' in prepared)
|
|
68
|
+
return prepared;
|
|
69
|
+
if (prepared.changedOverrideOriginals[0])
|
|
70
|
+
return overrideChangedRefusal(prepared.changedOverrideOriginals[0]);
|
|
71
|
+
const ownedInput = await ownedInputOverlap(prepared, ownership.manifest.entries);
|
|
72
|
+
if (ownedInput.kind === 'invalid')
|
|
73
|
+
return refuse('GENERATED_OWNERSHIP_INVALID', 'authenticated input identity could not be compared with generated ownership metadata', OWNERSHIP_PATH, 'restore trusted ownership metadata or run npx --no cockpit sync --config <your cockpit.toml>');
|
|
74
|
+
if (ownedInput.path)
|
|
75
|
+
return refuse('GENERATED_OWNERSHIP_INVALID', 'generated ownership metadata overlaps an authenticated input', ownedInput.path, 'restore trusted ownership metadata or run npx --no cockpit sync --config <your cockpit.toml>');
|
|
76
|
+
const surfaceState = await observeSurfaceState(prepared, priorSurfaces, adapters);
|
|
77
|
+
if (!surfaceState)
|
|
78
|
+
return refuse('SURFACE_STATE_CONFLICT', 'surface state could not be observed safely', 'surfaces');
|
|
79
|
+
const classified = await classifyOwnedState(prepared, ownership.manifest, surfaceState.raw);
|
|
80
|
+
if (classified.kind !== 'complete')
|
|
81
|
+
return refuse(classified.diagnostic.code, classified.diagnostic.message, classified.diagnostic.target);
|
|
82
|
+
const unknown = classified.paths.find(row => row.state === 'unknown');
|
|
83
|
+
if (unknown)
|
|
84
|
+
return refuse('SURFACE_STATE_CONFLICT', 'owned surface identity or confinement is unknown', unknown.fact.path);
|
|
85
|
+
const effectivePriorSurfaces = priorSurfaces;
|
|
86
|
+
const surfacePaths = new Set(prepared.surfaces.map(row => row.path));
|
|
87
|
+
const staleEnvironment = ownership.manifest.entries.filter(row => !('harnesses' in row) && !surfacePaths.has(row.path)
|
|
88
|
+
&& !prepared.owned.some(file => file.path === row.path));
|
|
89
|
+
if (staleEnvironment.length > 0)
|
|
90
|
+
return refuse('GENERATED_STALE_ARTIFACT', 'a formerly owned generated artifact remains in durable ownership metadata', staleEnvironment[0].path, 'a person runs npx --no cockpit sync --config <your cockpit.toml> --interactive in a terminal and approves the stale-removal digest it shows');
|
|
91
|
+
const required = generatedPathFacts(prepared, 'required-after-sync');
|
|
92
|
+
if (required.kind !== 'complete')
|
|
93
|
+
return refuse(required.diagnostic.code, required.diagnostic.message, required.diagnostic.target);
|
|
94
|
+
const canonicalReads = await readAnchoredBatches(prepared.root, prepared.generated.map(file => ({
|
|
95
|
+
path: file.path, maxBytes: MAX_GENERATED_BYTES, action: 'read-file',
|
|
96
|
+
})), prepared.node, adapters);
|
|
97
|
+
// An indexed refusal omits the prefix receipts. Reobserve only that prefix to retain
|
|
98
|
+
// first-failure ordering; the failed row remains fatal even if a later read could succeed.
|
|
99
|
+
for (const [index, file] of prepared.generated.entries()) {
|
|
100
|
+
try {
|
|
101
|
+
const read = canonicalReads?.kind === 'complete' ? canonicalReads.results[index]
|
|
102
|
+
: canonicalReads?.kind === 'invalid' && index < canonicalReads.index
|
|
103
|
+
? await readAnchoredFile(prepared.root, file.path, MAX_GENERATED_BYTES, prepared.node, adapters)
|
|
104
|
+
: undefined;
|
|
105
|
+
if (!read || read.kind !== 'complete')
|
|
106
|
+
throw new RefusalError('generated artifact unavailable');
|
|
107
|
+
const fact = required.facts.find(fact => fact.path === file.path);
|
|
108
|
+
const classification = classifyGeneratedPaths([fact], [{ path: file.path, kind: 'file', bytes: read.bytes,
|
|
109
|
+
confinement: 'confined', identity: sourceIdentity(read.identity) }]);
|
|
110
|
+
if (classification.kind !== 'complete' || classification.paths[0]?.state === 'unknown')
|
|
111
|
+
return refuse('GENERATED_ARTIFACT_MISSING', 'owned generated artifact could not be classified safely', file.path, 'run npx --no cockpit sync --config <your cockpit.toml>');
|
|
112
|
+
if (classification.paths[0]?.state !== 'unchanged')
|
|
113
|
+
return refuse('GENERATED_ARTIFACT_DRIFT', 'owned generated artifact differs from canonical bytes', file.path, 'run npx --no cockpit sync --config <your cockpit.toml>');
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return refuse('GENERATED_ARTIFACT_MISSING', `owned generated artifact became unreadable: ${errorCause(error)}`, file.path, 'run npx --no cockpit sync --config <your cockpit.toml>');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const comparison = planObservedSurfaces(prepared, effectivePriorSurfaces, surfaceState);
|
|
120
|
+
if (comparison.kind !== 'complete')
|
|
121
|
+
return surfacePlanRefusal(prepared, comparison.diagnostic);
|
|
122
|
+
const drift = comparison.actions.map(action => action.path);
|
|
123
|
+
// Removing a formerly owned path is plain sync's to do only where [autonomy] stale_delete is agent;
|
|
124
|
+
// otherwise it needs a person's approval of its exact digest, so send the reader to the one command that can.
|
|
125
|
+
const stale = comparison.actions.find(action => action.kind === 'remove');
|
|
126
|
+
if (stale)
|
|
127
|
+
return prepared.workEnvironment.policy.autonomy.stale_delete === 'agent'
|
|
128
|
+
? refuse('GENERATED_ARTIFACT_DRIFT', 'a formerly generated path is still on disk and in the ownership record', stale.path, 'run npx --no cockpit sync --config <your cockpit.toml>')
|
|
129
|
+
: refuse('GENERATED_ARTIFACT_DRIFT', 'a formerly generated path is still on disk and in the ownership record', stale.path, 'a person runs npx --no cockpit sync --config <your cockpit.toml> --interactive in a terminal and approves the stale-removal digest it shows');
|
|
130
|
+
const staleGuides = drift.includes('AGENTS.md') ? staleSiblingGuides(prepared, surfaceState.observed) : [];
|
|
131
|
+
if (staleGuides.length > 0)
|
|
132
|
+
return refuse('GENERATED_ARTIFACT_DRIFT', `AGENTS.md holds an out-of-date copy of ${staleGuides.join(', ')}: the guide changed in its sibling, or the declared guides changed, since the last sync`, 'AGENTS.md', 'run npx --no cockpit sync --config <your cockpit.toml>, then commit AGENTS.md together with the guide change');
|
|
133
|
+
if (drift.length > 0)
|
|
134
|
+
return refuse('GENERATED_ARTIFACT_DRIFT', 'owned surface state differs from the canonical model', drift[0], 'run npx --no cockpit sync --config <your cockpit.toml>');
|
|
135
|
+
if (ownership.bytes !== encodeOwnershipManifest(prepared).bytes)
|
|
136
|
+
return refuse('GENERATED_OWNERSHIP_DRIFT', 'generated ownership metadata differs from the current canonical manifest', OWNERSHIP_PATH, 'run npx --no cockpit sync --config <your cockpit.toml>');
|
|
137
|
+
const source = await observeConfigSource(prepared.configPath);
|
|
138
|
+
if (source.state !== 'available' || source.sha256 !== prepared.sourceSha256
|
|
139
|
+
|| !await inputSourcesCurrent(prepared, adapters)) {
|
|
140
|
+
return refuse('GENERATED_SOURCE_CHANGED', 'a generated input changed during generated environment checking', 'generated-environment');
|
|
141
|
+
}
|
|
142
|
+
return complete('check', prepared);
|
|
143
|
+
}
|
|
144
|
+
// A cockpit running its own Compose files owns that topology, so check reads it as Compose resolves
|
|
145
|
+
// it and reports what would break parallel stacks.
|
|
146
|
+
async function composeTopologyDiagnostics(configPath, sourceSha256, adapters) {
|
|
147
|
+
const loaded = await loadCockpitConfig(configPath);
|
|
148
|
+
if (loaded.kind !== 'complete' || loaded.result.source.sha256 !== sourceSha256)
|
|
149
|
+
return [{ severity: 'error',
|
|
150
|
+
code: 'GENERATED_SOURCE_CHANGED', message: 'the configuration changed while check was reading the Compose topology', target: configPath }];
|
|
151
|
+
const config = loaded.result.config;
|
|
152
|
+
if (declaredRuntime(config.environment) !== 'compose')
|
|
153
|
+
return [];
|
|
154
|
+
const env = { PATH: process.env.PATH, DOCKER_HOST: process.env.DOCKER_HOST, DOCKER_CONTEXT: process.env.DOCKER_CONTEXT,
|
|
155
|
+
DOCKER_TLS_VERIFY: process.env.DOCKER_TLS_VERIFY, DOCKER_CERT_PATH: process.env.DOCKER_CERT_PATH };
|
|
156
|
+
const [docker, node] = await Promise.all([resolveDockerExecutable(process.env), nodeAuthority()]);
|
|
157
|
+
const observed = docker.state !== 'available' ? { kind: 'unavailable', reason: docker.reason }
|
|
158
|
+
: !node ? { kind: 'unavailable', reason: 'the current Node executable could not be authenticated' }
|
|
159
|
+
: await observeComposeTopology({ root: dirname(loaded.result.source.path), references: config.environment.compose_files,
|
|
160
|
+
projectName: config.cockpit.name, docker: docker.value, node, env }, adapters.execute);
|
|
161
|
+
if (observed.kind !== 'complete')
|
|
162
|
+
return [{ severity: 'error', code: 'COMPOSE_TOPOLOGY_UNAVAILABLE',
|
|
163
|
+
message: `check could not read the declared Compose topology, so its ports, healthchecks and images are unverified: ${observed.reason}`,
|
|
164
|
+
target: config.environment.compose_files.join(', '),
|
|
165
|
+
fix: 'install Docker with the Compose plugin on PATH and make `docker compose config` succeed for environment.compose_files' }];
|
|
166
|
+
const root = dirname(loaded.result.source.path);
|
|
167
|
+
const siblingsRoot = resolve(root, config.cockpit.siblings_root);
|
|
168
|
+
return assessComposeTopology(observed.services, {
|
|
169
|
+
projectDirectory: composeProjectDirectory(root, config.environment.compose_files),
|
|
170
|
+
portVariables: Object.keys(config.ports.bases),
|
|
171
|
+
siblings: config.sibling.map(sibling => ({
|
|
172
|
+
slug: sibling.slug, var: sibling.var, mount: sibling.mount, path: resolve(siblingsRoot, sibling.dir)
|
|
173
|
+
})),
|
|
174
|
+
hostPaths: config.environment.generated_host_paths.map(declared => ({ declared, path: resolve(root, declared) })),
|
|
175
|
+
profiles: config.environment.profiles,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export async function checkGeneratedEnvironment(input, adapterOverrides = {}) {
|
|
179
|
+
const adapters = mergedAdapters(adapterOverrides);
|
|
180
|
+
const currentness = await prepareGeneratedEnvironmentCurrentness(input, adapters);
|
|
181
|
+
const findings = currentness.outcome.kind === 'complete'
|
|
182
|
+
? await composeTopologyDiagnostics(input.configPath, currentness.outcome.result.sourceSha256, adapters) : [];
|
|
183
|
+
const diagnostics = [...currentness.outcome.diagnostics, ...findings];
|
|
184
|
+
const outcome = findings.some(row => row.severity === 'error')
|
|
185
|
+
? { kind: 'refusal', artifact: null, destination: null, result: null, status: 2, diagnostics }
|
|
186
|
+
: { ...currentness.outcome, diagnostics };
|
|
187
|
+
return { ...outcome, fixContext: currentness.fixContext, report: {
|
|
188
|
+
storedProducer: currentness.storedProducer, currentProducer: currentness.currentProducer,
|
|
189
|
+
repair: proposeGeneratedEnvironmentRepair(currentness),
|
|
190
|
+
} };
|
|
191
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DerivedRegistry } from '../../registry-derivation.js';
|
|
2
|
+
import type { SurfaceSourceFile } from '../../surfaces/contract.js';
|
|
3
|
+
import type { OwnershipManifest } from '../../surfaces/ownership.js';
|
|
4
|
+
import { type CustomSurfaceReceipt } from '../../surfaces/render.js';
|
|
5
|
+
import { type CustomSourceIdentity, type GeneratedEnvironmentCommandAdapters, type PreparedEnvironment } from './shared.js';
|
|
6
|
+
export declare function executeCustomCapability(prepared: PreparedEnvironment, adapters: GeneratedEnvironmentCommandAdapters, harness: string, capability: Extract<DerivedRegistry['config']['surfaces']['harnesses'][number]['capabilities'][number], {
|
|
7
|
+
state: 'enforced' | 'instructed';
|
|
8
|
+
}>, sources: readonly SurfaceSourceFile[]): Promise<{
|
|
9
|
+
receipt: CustomSurfaceReceipt;
|
|
10
|
+
sources: readonly CustomSourceIdentity[];
|
|
11
|
+
evidence?: Readonly<{
|
|
12
|
+
reference: string;
|
|
13
|
+
sha256: string;
|
|
14
|
+
}>;
|
|
15
|
+
} | undefined>;
|
|
16
|
+
export declare function projectExplicitNativeHooks(prepared: PreparedEnvironment, manifest: OwnershipManifest | undefined, adapters: GeneratedEnvironmentCommandAdapters, policyDeclaration: Readonly<{
|
|
17
|
+
path: string;
|
|
18
|
+
sha256: string;
|
|
19
|
+
}>, harnesses: readonly string[]): Promise<{
|
|
20
|
+
projection: import("../../testing/hooks.js").NativeHookProjection;
|
|
21
|
+
source: {
|
|
22
|
+
path: string;
|
|
23
|
+
sha256: string;
|
|
24
|
+
identity: import("../../generated-environment/transaction.js").FileIdentity;
|
|
25
|
+
};
|
|
26
|
+
} | undefined>;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { chmod, lstat, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { authenticatedExecutionOutcome } from '../../environment-exec/execute.js';
|
|
5
|
+
import { surfaceModelSha256 } from '../../surfaces/render.js';
|
|
6
|
+
import { readAnchoredFile, readAnchoredBatches } from '../../generated-environment/transaction.js';
|
|
7
|
+
import { snapshotExecutionStep, snapshotGeneratedJson, RefusalError, hasErrorCode } from '../contract.js';
|
|
8
|
+
import { resolveExecutablePath, resolveGitExecutable } from '../executable.js';
|
|
9
|
+
import { cockpitId } from '../identity.js';
|
|
10
|
+
import { NATIVE_HOOK_COST_SCRIPT, projectNativeHooks } from '../../testing/hooks.js';
|
|
11
|
+
import { exactKeys } from '../../exact-keys.js';
|
|
12
|
+
import { HELPER_TIMEOUT_MS, MAX_EXECUTABLE_BYTES, MAX_GENERATED_BYTES, MAX_HELPER_FRAME_BYTES, MAX_SURFACE_FILE_BYTES, NATIVE_HOOK_COST_TIMEOUT_MS, SHA256, confined, ownRecord, sha256, } from './shared.js';
|
|
13
|
+
// The worker runs in its own process and cannot import modules; `safe` mirrors safeSurfacePath in
|
|
14
|
+
// surfaces/contract.ts, including its case-folded reserved segments.
|
|
15
|
+
const CUSTOM_SURFACE_WORKER = String.raw `
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const request = JSON.parse(process.argv[1]);
|
|
20
|
+
const digest = value => crypto.createHash('sha256').update(value).digest('hex');
|
|
21
|
+
const own = value => value && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
22
|
+
const safe = value => typeof value === 'string' && value.length > 0 && !path.isAbsolute(value) && !value.includes('\\') && !value.includes('\0') && value.split('/').every(part => part && part !== '.' && part !== '..' && !['.GIT','NODE_MODULES'].includes(part.normalize('NFC').toUpperCase()));
|
|
23
|
+
(async () => {
|
|
24
|
+
const source = fs.readFileSync(process.argv[2]);
|
|
25
|
+
if (digest(source) !== request.sourceSha256) throw new RefusalError('module changed');
|
|
26
|
+
const imported = await import('data:text/javascript;base64,' + source.toString('base64'));
|
|
27
|
+
const fn = imported[request.export]; if (typeof fn !== 'function') throw new RefusalError('export');
|
|
28
|
+
const result = await fn(request.input);
|
|
29
|
+
if (!own(result) || Object.keys(result).sort().join(',') !== 'files,kind' || result.kind !== 'complete' || !Array.isArray(result.files) || result.files.length === 0 || result.files.length > 256) throw new RefusalError('result');
|
|
30
|
+
let total = 0; let spelling = false; const files = [];
|
|
31
|
+
for (const [index, file] of result.files.entries()) {
|
|
32
|
+
if (!own(file) || Object.keys(file).sort().join(',') !== 'bytes,path,scope,sha256' || !safe(file.path) || typeof file.bytes !== 'string' || Buffer.byteLength(file.bytes) > 65536 || digest(file.bytes) !== file.sha256 || !['always','scoped'].includes(file.scope)) throw new RefusalError('file');
|
|
33
|
+
spelling = spelling || file.path === request.spelling || file.bytes.includes(request.spelling);
|
|
34
|
+
total += Buffer.byteLength(file.bytes); if (total > 4194304) throw new RefusalError('limit');
|
|
35
|
+
const output = path.join(request.output, String(index)); fs.writeFileSync(output, file.bytes, { flag: 'wx', mode: 0o600 });
|
|
36
|
+
files.push({ path: file.path, sha256: file.sha256, scope: file.scope, output: String(index) });
|
|
37
|
+
}
|
|
38
|
+
if (!spelling) throw new RefusalError('spelling');
|
|
39
|
+
process.stdout.write(JSON.stringify({ version: 1, files }) + '\n');
|
|
40
|
+
})().catch(() => { process.stdout.write(JSON.stringify({ version: 1, refusal: true }) + '\n'); });
|
|
41
|
+
`;
|
|
42
|
+
export async function executeCustomCapability(prepared, adapters, harness, capability, sources) {
|
|
43
|
+
let evidenceSource;
|
|
44
|
+
if (capability.state === 'enforced') {
|
|
45
|
+
const evidenceRead = await readAnchoredFile(prepared.root, capability.evidence.reference, MAX_SURFACE_FILE_BYTES, prepared.node, adapters);
|
|
46
|
+
if (evidenceRead.kind !== 'complete' || evidenceRead.sha256 !== capability.evidence.sha256)
|
|
47
|
+
return undefined;
|
|
48
|
+
evidenceSource = { harness, feature: capability.feature, path: capability.evidence.reference,
|
|
49
|
+
sha256: evidenceRead.sha256, identity: evidenceRead.identity, maxBytes: MAX_SURFACE_FILE_BYTES };
|
|
50
|
+
}
|
|
51
|
+
const moduleRead = await readAnchoredFile(prepared.root, capability.mechanism.module, MAX_GENERATED_BYTES, prepared.node, adapters);
|
|
52
|
+
if (moduleRead.kind !== 'complete')
|
|
53
|
+
return undefined;
|
|
54
|
+
const permissionFlag = process.allowedNodeEnvironmentFlags.has('--permission') ? '--permission'
|
|
55
|
+
: process.allowedNodeEnvironmentFlags.has('--experimental-permission') ? '--experimental-permission' : undefined;
|
|
56
|
+
if (!permissionFlag || !process.allowedNodeEnvironmentFlags.has('--allow-fs-read')
|
|
57
|
+
|| !process.allowedNodeEnvironmentFlags.has('--allow-fs-write'))
|
|
58
|
+
return undefined;
|
|
59
|
+
let scratch;
|
|
60
|
+
let scratchIdentity;
|
|
61
|
+
try {
|
|
62
|
+
scratch = await realpath(await mkdtemp(join(tmpdir(), 'cockpit-custom-surface-')));
|
|
63
|
+
const scratchStatus = await lstat(scratch);
|
|
64
|
+
if (!scratchStatus.isDirectory() || scratchStatus.isSymbolicLink())
|
|
65
|
+
return undefined;
|
|
66
|
+
scratchIdentity = { dev: scratchStatus.dev, ino: scratchStatus.ino };
|
|
67
|
+
const input = snapshotGeneratedJson({ harness, feature: capability.feature, sources });
|
|
68
|
+
if (!input)
|
|
69
|
+
return undefined;
|
|
70
|
+
const absoluteModule = confined(prepared.root, capability.mechanism.module);
|
|
71
|
+
if (!absoluteModule || [scratch, absoluteModule].some(path => path.includes('*')))
|
|
72
|
+
return undefined;
|
|
73
|
+
const request = { export: capability.mechanism.export, input, output: scratch,
|
|
74
|
+
sourceSha256: moduleRead.sha256, spelling: capability.spelling };
|
|
75
|
+
const raw = JSON.stringify(request);
|
|
76
|
+
if (Buffer.byteLength(raw) > MAX_HELPER_FRAME_BYTES)
|
|
77
|
+
return undefined;
|
|
78
|
+
const step = snapshotExecutionStep({
|
|
79
|
+
id: `custom-surface-${harness}-${capability.feature}`, argv: [prepared.node.canonicalPath, permissionFlag,
|
|
80
|
+
'--allow-fs-read', absoluteModule, `--allow-fs-read=${scratch}`, `--allow-fs-write=${scratch}`,
|
|
81
|
+
'-e', CUSTOM_SURFACE_WORKER, raw, absoluteModule],
|
|
82
|
+
argv0: prepared.node.invocationPath, cwd: scratch, env: {}, timeoutMs: HELPER_TIMEOUT_MS,
|
|
83
|
+
spawnNotAfter: new Date(Date.now() + HELPER_TIMEOUT_MS).toISOString(), stdoutLimitBytes: MAX_HELPER_FRAME_BYTES,
|
|
84
|
+
stderrLimitBytes: 16_384, paths: [prepared.root, scratch], ports: [], labels: { harness, feature: capability.feature }, expected: [],
|
|
85
|
+
consumedFiles: [
|
|
86
|
+
{ path: prepared.node.canonicalPath, sha256: prepared.node.sha256, maxBytes: MAX_EXECUTABLE_BYTES, argvIndexes: [0], executable: true, executionMode: prepared.node.executionMode },
|
|
87
|
+
{ path: absoluteModule, sha256: moduleRead.sha256, maxBytes: MAX_GENERATED_BYTES, argvIndexes: [3, 9], executable: false },
|
|
88
|
+
], filesystemChecks: [{ path: scratch, expected: 'directory' }], compensation: [],
|
|
89
|
+
});
|
|
90
|
+
if (!step)
|
|
91
|
+
return undefined;
|
|
92
|
+
const outcome = authenticatedExecutionOutcome(await adapters.execute(step), step);
|
|
93
|
+
if (!outcome || outcome.kind !== 'complete' || outcome.stderr.byteLength !== 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(outcome.stdout);
|
|
96
|
+
const record = ownRecord(JSON.parse(text));
|
|
97
|
+
if (!record || !exactKeys(record, ['version', 'files']) || record.version !== 1 || !Array.isArray(record.files))
|
|
98
|
+
return undefined;
|
|
99
|
+
const descriptors = [];
|
|
100
|
+
for (const [index, value] of record.files.entries()) {
|
|
101
|
+
const row = ownRecord(value);
|
|
102
|
+
if (!row || !exactKeys(row, ['path', 'sha256', 'scope', 'output']) || row.output !== String(index)
|
|
103
|
+
|| typeof row.path !== 'string' || typeof row.sha256 !== 'string' || !SHA256.test(row.sha256)
|
|
104
|
+
|| row.scope !== 'always' && row.scope !== 'scoped')
|
|
105
|
+
return undefined;
|
|
106
|
+
descriptors.push({ path: row.path, sha256: row.sha256, scope: row.scope, output: row.output });
|
|
107
|
+
}
|
|
108
|
+
const reads = await readAnchoredBatches(scratch, descriptors.map(row => ({
|
|
109
|
+
path: row.output, maxBytes: MAX_SURFACE_FILE_BYTES, action: 'read-file',
|
|
110
|
+
})), prepared.node, adapters);
|
|
111
|
+
if (!reads || reads.kind !== 'complete' || reads.results.length !== descriptors.length)
|
|
112
|
+
return undefined;
|
|
113
|
+
const files = [];
|
|
114
|
+
for (const [index, row] of descriptors.entries()) {
|
|
115
|
+
const read = reads.results[index];
|
|
116
|
+
if (read.kind !== 'complete' || read.sha256 !== row.sha256
|
|
117
|
+
|| read.identity.root.dev !== scratchIdentity.dev || read.identity.root.ino !== scratchIdentity.ino)
|
|
118
|
+
return undefined;
|
|
119
|
+
const bytes = new TextDecoder('utf-8', { fatal: true }).decode(read.bytes);
|
|
120
|
+
files.push({ path: row.path, bytes, sha256: row.sha256, scope: row.scope });
|
|
121
|
+
}
|
|
122
|
+
if (files.length === 0 || !files.some(file => file.path === capability.spelling || file.bytes.includes(capability.spelling)))
|
|
123
|
+
return undefined;
|
|
124
|
+
const resultSha256 = surfaceModelSha256(files);
|
|
125
|
+
if (!resultSha256)
|
|
126
|
+
return undefined;
|
|
127
|
+
return {
|
|
128
|
+
receipt: { harness, feature: capability.feature, state: capability.state, measuredOn: capability.measured_on,
|
|
129
|
+
module: capability.mechanism.module, export: capability.mechanism.export, sourceSha256: moduleRead.sha256,
|
|
130
|
+
resultSha256, files },
|
|
131
|
+
sources: [
|
|
132
|
+
{ harness, feature: capability.feature, path: capability.mechanism.module,
|
|
133
|
+
sha256: moduleRead.sha256, identity: moduleRead.identity, maxBytes: MAX_GENERATED_BYTES },
|
|
134
|
+
...(evidenceSource ? [evidenceSource] : []),
|
|
135
|
+
],
|
|
136
|
+
...(capability.state === 'enforced' ? { evidence: { ...capability.evidence } } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch { /* an unavailable authority is refused by the caller. */
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (scratch && scratchIdentity) {
|
|
144
|
+
try {
|
|
145
|
+
const current = await lstat(scratch);
|
|
146
|
+
if (current.isDirectory() && !current.isSymbolicLink()
|
|
147
|
+
&& current.dev === scratchIdentity.dev && current.ino === scratchIdentity.ino) {
|
|
148
|
+
await rm(scratch, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (!hasErrorCode(error, 'ENOENT'))
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export async function projectExplicitNativeHooks(prepared, manifest, adapters, policyDeclaration, harnesses) {
|
|
159
|
+
if (!manifest)
|
|
160
|
+
return undefined;
|
|
161
|
+
const policyRead = await readAnchoredFile(prepared.root, policyDeclaration.path, MAX_SURFACE_FILE_BYTES, prepared.node, adapters);
|
|
162
|
+
if (policyRead.kind !== 'complete' || policyRead.sha256 !== policyDeclaration.sha256)
|
|
163
|
+
return undefined;
|
|
164
|
+
let policyBytes;
|
|
165
|
+
try {
|
|
166
|
+
policyBytes = new TextDecoder('utf-8', { fatal: true }).decode(policyRead.bytes);
|
|
167
|
+
}
|
|
168
|
+
catch { /* undecodable bytes are malformed input the caller refuses. */
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const policy = {
|
|
172
|
+
manifest,
|
|
173
|
+
authority: { root: prepared.root, cockpitId: prepared.cockpitId, configPath: prepared.configPath },
|
|
174
|
+
declarations: [],
|
|
175
|
+
path: policyDeclaration.path,
|
|
176
|
+
exists: true,
|
|
177
|
+
bytes: policyBytes,
|
|
178
|
+
};
|
|
179
|
+
const source = { path: policyDeclaration.path, sha256: policyRead.sha256, identity: policyRead.identity };
|
|
180
|
+
const git = await resolveGitExecutable(process.env);
|
|
181
|
+
if (git.state !== 'available')
|
|
182
|
+
return {
|
|
183
|
+
projection: { kind: 'refusal', reason: git.reason },
|
|
184
|
+
source,
|
|
185
|
+
};
|
|
186
|
+
const revisionStep = snapshotExecutionStep({
|
|
187
|
+
id: 'native-hook-revisions',
|
|
188
|
+
argv: [git.value.invocationPath, 'rev-list', '--max-count=2', 'HEAD'],
|
|
189
|
+
argv0: git.value.invocationPath,
|
|
190
|
+
cwd: prepared.root,
|
|
191
|
+
env: {},
|
|
192
|
+
timeoutMs: HELPER_TIMEOUT_MS,
|
|
193
|
+
spawnNotAfter: new Date(Date.now() + HELPER_TIMEOUT_MS).toISOString(),
|
|
194
|
+
stdoutLimitBytes: 256,
|
|
195
|
+
stderrLimitBytes: 4_096,
|
|
196
|
+
paths: [prepared.root],
|
|
197
|
+
ports: [],
|
|
198
|
+
labels: { action: 'native-hook-revisions' },
|
|
199
|
+
expected: [],
|
|
200
|
+
consumedFiles: [{ path: git.value.canonicalPath, sha256: git.value.sha256, maxBytes: MAX_EXECUTABLE_BYTES,
|
|
201
|
+
argvIndexes: [0], executable: true, executionMode: git.value.executionMode }],
|
|
202
|
+
filesystemChecks: [{ path: prepared.root, expected: 'directory' }],
|
|
203
|
+
compensation: [],
|
|
204
|
+
});
|
|
205
|
+
if (!revisionStep)
|
|
206
|
+
return undefined;
|
|
207
|
+
const revisionOutcome = authenticatedExecutionOutcome(await adapters.execute(revisionStep), revisionStep);
|
|
208
|
+
if (!revisionOutcome || revisionOutcome.kind !== 'complete' || revisionOutcome.stderr.byteLength !== 0)
|
|
209
|
+
return undefined;
|
|
210
|
+
let revisions;
|
|
211
|
+
try {
|
|
212
|
+
revisions = new TextDecoder('utf-8', { fatal: true }).decode(revisionOutcome.stdout).trim().split('\n');
|
|
213
|
+
}
|
|
214
|
+
catch { /* undecodable bytes are malformed input the caller refuses. */
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
if (revisions.length !== 2 || revisions[0] === revisions[1]
|
|
218
|
+
|| revisions.some(value => !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value) || !/[1-9a-f]/.test(value)))
|
|
219
|
+
return undefined;
|
|
220
|
+
let scratch;
|
|
221
|
+
let scratchIdentity;
|
|
222
|
+
try {
|
|
223
|
+
scratch = await realpath(await mkdtemp(join(tmpdir(), 'cockpit-native-hook-cost-')));
|
|
224
|
+
const status = await lstat(scratch);
|
|
225
|
+
if (!status.isDirectory() || status.isSymbolicLink())
|
|
226
|
+
return undefined;
|
|
227
|
+
scratchIdentity = { dev: status.dev, ino: status.ino };
|
|
228
|
+
const shell = await resolveExecutablePath('/bin/sh');
|
|
229
|
+
if (shell.state !== 'available')
|
|
230
|
+
return undefined;
|
|
231
|
+
const projection = await projectNativeHooks({
|
|
232
|
+
cockpitId: prepared.cockpitId,
|
|
233
|
+
harnesses,
|
|
234
|
+
install: 'explicit-native',
|
|
235
|
+
budget: {
|
|
236
|
+
policy,
|
|
237
|
+
prepareMeasurement: async (entry) => {
|
|
238
|
+
const path = join(scratch, 'pre-push');
|
|
239
|
+
await writeFile(path, entry.bytes, { flag: 'wx', mode: 0o700 });
|
|
240
|
+
await chmod(path, 0o700);
|
|
241
|
+
const step = snapshotExecutionStep({
|
|
242
|
+
id: 'native-hook-cost',
|
|
243
|
+
argv: [shell.value.canonicalPath, '-c', NATIVE_HOOK_COST_SCRIPT, 'native-hook-cost', path, revisions[0], revisions[1]],
|
|
244
|
+
argv0: shell.value.canonicalPath,
|
|
245
|
+
cwd: prepared.root,
|
|
246
|
+
env: { PATH: dirname(git.value.canonicalPath) },
|
|
247
|
+
timeoutMs: NATIVE_HOOK_COST_TIMEOUT_MS,
|
|
248
|
+
spawnNotAfter: new Date(Date.now() + HELPER_TIMEOUT_MS).toISOString(),
|
|
249
|
+
stdoutLimitBytes: 4_096,
|
|
250
|
+
stderrLimitBytes: 4_096,
|
|
251
|
+
paths: [prepared.root, scratch],
|
|
252
|
+
ports: [],
|
|
253
|
+
labels: { action: 'native-hook-cost' },
|
|
254
|
+
expected: [],
|
|
255
|
+
consumedFiles: [
|
|
256
|
+
{ path: shell.value.canonicalPath, sha256: shell.value.sha256, maxBytes: MAX_EXECUTABLE_BYTES,
|
|
257
|
+
argvIndexes: [0], executable: true, executionMode: shell.value.executionMode },
|
|
258
|
+
{ path, sha256: entry.sha256, maxBytes: Buffer.byteLength(entry.bytes), argvIndexes: [4], executable: false },
|
|
259
|
+
],
|
|
260
|
+
filesystemChecks: [{ path: prepared.root, expected: 'directory' }, { path: scratch, expected: 'directory' }],
|
|
261
|
+
compensation: [],
|
|
262
|
+
});
|
|
263
|
+
if (!step)
|
|
264
|
+
throw new RefusalError('native hook cost measurement could not be admitted');
|
|
265
|
+
return step;
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
projection,
|
|
271
|
+
source,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
if (scratch && scratchIdentity) {
|
|
276
|
+
try {
|
|
277
|
+
const current = await lstat(scratch);
|
|
278
|
+
if (current.isDirectory() && !current.isSymbolicLink()
|
|
279
|
+
&& current.dev === scratchIdentity.dev && current.ino === scratchIdentity.ino) {
|
|
280
|
+
await rm(scratch, { recursive: true, force: true });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (!hasErrorCode(error, 'ENOENT'))
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type OwnershipManifest } from '../../surfaces/ownership.js';
|
|
2
|
+
import { type FileIdentity } from '../../generated-environment/transaction.js';
|
|
3
|
+
import { type GeneratedEnvironmentCommandAdapters, type GeneratedEnvironmentCommandInput, type GeneratedEnvironmentSyncInput, type PreparedEnvironment, type RefusalOutcome } from './shared.js';
|
|
4
|
+
export declare function commandInput(input: unknown): GeneratedEnvironmentCommandInput | undefined;
|
|
5
|
+
export declare function syncCommandInput(input: unknown): GeneratedEnvironmentSyncInput | undefined;
|
|
6
|
+
export declare function safePath(root: string, path: string, allowMissing: boolean): Promise<string | undefined>;
|
|
7
|
+
export type OwnershipState = {
|
|
8
|
+
readonly kind: 'missing';
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'invalid';
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'unsupported';
|
|
13
|
+
readonly format: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: 'complete';
|
|
16
|
+
readonly manifest: OwnershipManifest;
|
|
17
|
+
readonly bytes: string;
|
|
18
|
+
readonly sha256: string;
|
|
19
|
+
readonly identity: FileIdentity;
|
|
20
|
+
};
|
|
21
|
+
export declare function readOwnership(prepared: PreparedEnvironment, adapters: GeneratedEnvironmentCommandAdapters): Promise<OwnershipState>;
|
|
22
|
+
export declare function expectedOutputExists(prepared: PreparedEnvironment): Promise<boolean>;
|
|
23
|
+
export declare function renamedStackRefusal(prepared: PreparedEnvironment): Promise<RefusalOutcome | undefined>;
|
|
24
|
+
/** Why no single Compose service publishes the endpoint's port on loopback, or undefined when exactly one does. */
|
|
25
|
+
export declare function browserEndpointUnpublished(endpoint: string, sources: readonly Readonly<{
|
|
26
|
+
reference: string;
|
|
27
|
+
source: string;
|
|
28
|
+
}>[], bases: Readonly<Record<string, number>>): string | undefined;
|
|
29
|
+
export declare function prepare(input: unknown, adapters: GeneratedEnvironmentCommandAdapters): Promise<PreparedEnvironment | RefusalOutcome>;
|