@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,291 @@
|
|
|
1
|
+
// Owns the record cockpit keeps of each work order, `.cockpit/work-orders/<id>.json`: what was
|
|
2
|
+
// accepted and by whom, the checks cockpit ran, the grade, notes and questions, and how the order
|
|
3
|
+
// closed. Records are committed so the `work verify` CI job can read them, and only cockpit writes
|
|
4
|
+
// them: the guard denies an agent's write below `.cockpit/work-orders/`, because an unkeyed record
|
|
5
|
+
// an agent wrote there would read as cockpit's own. A write holds the order's lock file, which a
|
|
6
|
+
// crashed writer leaves behind until `work recover --apply` removes it.
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { link, lstat, mkdir, open, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { hostname } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { isProcessGone, observeProcessStart, processHost } from '../environment/claims.js';
|
|
14
|
+
import { hasErrorCode, RefusalError } from '../environment/contract.js';
|
|
15
|
+
import { executeProbe } from '../environment-exec/execute.js';
|
|
16
|
+
import { DECIDERS, WORK_GRADING } from '../schema.js';
|
|
17
|
+
import { WORK_ORDER_ID, WorkOrderFrontMatter } from './contract.js';
|
|
18
|
+
export const WORK_RECORD_DIRECTORY = '.cockpit/work-orders';
|
|
19
|
+
const RECORD_LIMIT_BYTES = 1_048_576;
|
|
20
|
+
const OUTPUT_TAIL_BYTES = 4_096;
|
|
21
|
+
const at = z.string().datetime();
|
|
22
|
+
const by = z.enum(DECIDERS);
|
|
23
|
+
const note = z.string().min(1).max(4_096);
|
|
24
|
+
const CheckResult = z.object({
|
|
25
|
+
criterion: z.string().max(64),
|
|
26
|
+
kind: z.enum(['argv', 'gate']),
|
|
27
|
+
passed: z.boolean(),
|
|
28
|
+
// How the check ended: an exit status, a gate verdict, or why it could not run.
|
|
29
|
+
outcome: z.string().max(256),
|
|
30
|
+
output: z.string().max(OUTPUT_TAIL_BYTES * 2),
|
|
31
|
+
}).strict();
|
|
32
|
+
const Verdict = z.object({ id: z.string().max(64), verdict: z.enum(['met', 'unmet', 'unclear']), note: z.string().max(4_096) }).strict();
|
|
33
|
+
const Grade = z.object({
|
|
34
|
+
at, digest: z.string().length(64), mode: z.enum(WORK_GRADING), passed: z.boolean(),
|
|
35
|
+
criteria: z.array(Verdict).max(64), outOfScope: z.array(z.string().max(1_100)).max(512),
|
|
36
|
+
// Which evaluator answered and those passed over on the way; absent when no evaluator ran.
|
|
37
|
+
evaluator: z.object({ tool: z.string().max(64), fallbacks: z.array(z.object({ tool: z.string().max(64), code: z.string().max(64), message: z.string().max(8_192) }).strict()).max(8) }).strict().optional(),
|
|
38
|
+
note: note.optional(),
|
|
39
|
+
}).strict();
|
|
40
|
+
export const WorkRecord = z.object({
|
|
41
|
+
format: z.literal('cockpit-work-order/v1'),
|
|
42
|
+
id: z.string().regex(WORK_ORDER_ID),
|
|
43
|
+
state: z.enum(['accepted', 'checked', 'graded', 'done', 'partial', 'abandoned']),
|
|
44
|
+
accepted: z.object({
|
|
45
|
+
at, by, digest: z.string().length(64),
|
|
46
|
+
// The accepted contract, so a later amendment can be compared criterion by criterion.
|
|
47
|
+
order: WorkOrderFrontMatter,
|
|
48
|
+
sources: z.record(z.string(), z.string().length(64)),
|
|
49
|
+
base: z.record(z.string(), z.string().regex(/^[a-f0-9]{40,64}$/)),
|
|
50
|
+
}).strict(),
|
|
51
|
+
// `weakened` names what the amendment removed; `review` each change a model, or a person where none grades, judges
|
|
52
|
+
// against the bar, with its text before and after. An agent's amendment is judged at the next grade.
|
|
53
|
+
amendments: z.array(z.object({ at, by, from: z.string().length(64), to: z.string().length(64), weakened: z.array(z.string()).max(64),
|
|
54
|
+
review: z.array(z.object({ field: z.string().max(64), before: z.string().max(131_072), after: z.string().max(131_072) }).strict()).max(128) }).strict()).max(256),
|
|
55
|
+
// A person's (or, where [work.gates] allows, an agent's or CI's) acknowledgement that the change weakened tests:
|
|
56
|
+
// it covers exactly the findings its digest names, so a later weakening needs its own.
|
|
57
|
+
acknowledgements: z.array(z.object({ at, by, digest: z.string().length(64), findings: z.array(z.string().max(1_100)).max(256), note }).strict()).max(256),
|
|
58
|
+
attempts: z.number().int().min(0),
|
|
59
|
+
checks: z.object({ at, digest: z.string().length(64), results: z.array(CheckResult).max(64) }).strict().optional(),
|
|
60
|
+
grade: Grade.optional(),
|
|
61
|
+
// A grade found the agent's amendments lowered the bar. It outlives the grade a later check or grade
|
|
62
|
+
// replaces, and only an amendment a person or CI accepts clears it, so the next amendment stays theirs.
|
|
63
|
+
amendmentsFailedReview: z.object({ at, note: z.string().max(4_096) }).strict().optional(),
|
|
64
|
+
// A decision is resolved by a later `resolution` note naming it in `resolves`: with `record`, the decision
|
|
65
|
+
// record it is written in; without, `text` says why it made no lasting decision. The latest resolution counts.
|
|
66
|
+
notes: z.array(z.object({ at, by: z.enum(['agent', 'person', 'ci']), kind: z.enum(['note', 'question', 'answer', 'decision', 'resolution']), text: note,
|
|
67
|
+
answers: z.number().int().min(1).optional(), resolves: z.number().int().min(1).optional(), record: z.string().max(1_100).optional() }).strict()).max(1_024),
|
|
68
|
+
closure: z.object({ at, by, how: z.enum(['graded', 'checks-passed', 'self-reported', 'partial', 'abandoned']), note: note.optional() }).strict().optional(),
|
|
69
|
+
// Each lock `work recover --apply` removed: the holder it named and what was checked to call it gone.
|
|
70
|
+
recoveries: z.array(z.object({ at, holder: z.string().max(1_024), checked: z.array(z.string().max(1_024)).max(16) }).strict()).max(256).optional(),
|
|
71
|
+
}).strict();
|
|
72
|
+
/** Keeps the end of a command's output, where a runner prints its summary, within the record's bound. */
|
|
73
|
+
export function outputTail(value) {
|
|
74
|
+
const bytes = Buffer.from(value);
|
|
75
|
+
return bytes.byteLength <= OUTPUT_TAIL_BYTES ? value : `[...]${bytes.subarray(bytes.byteLength - OUTPUT_TAIL_BYTES).toString('utf8')}`;
|
|
76
|
+
}
|
|
77
|
+
// A lock names its writer: pid and start, so a reused pid never keeps a dead writer's lock alive, and
|
|
78
|
+
// the host and whether it ran in a container, because a pid means nothing outside the process table
|
|
79
|
+
// it came from. Docker names a container's host after the container, unless it shares the machine's network.
|
|
80
|
+
const LockHolder = z.object({ pid: z.number().int().min(1), start: z.string().max(256).optional(), host: z.string().min(1).max(255), container: z.boolean() }).strict();
|
|
81
|
+
const inContainer = () => existsSync('/.dockerenv') || existsSync('/run/.containerenv');
|
|
82
|
+
const holder = async () => {
|
|
83
|
+
const observed = await observeProcessStart(process.pid, processHost(executeProbe));
|
|
84
|
+
return { pid: process.pid, ...observed.state === 'present' ? { start: observed.start } : {}, host: hostname(), container: inContainer() };
|
|
85
|
+
};
|
|
86
|
+
// Whether the holder is shown running or gone, looking first where a pid probe cannot; anything unproven is unknown.
|
|
87
|
+
async function judgeHolder(found, containers) {
|
|
88
|
+
const here = hostname();
|
|
89
|
+
const listed = containers.state === 'listed' ? containers.containers : [];
|
|
90
|
+
const checked = [`the lock was written by process ${found.pid} on host ${found.host}${found.container ? ' inside a container' : ''}`, `this machine is host ${here}`,
|
|
91
|
+
(containers.state === 'listed' ? `Docker context ${containers.context} lists ${listed.length} cockpit-labelled container(s)${listed.length
|
|
92
|
+
? `: ${listed.map(row => `${row.name} (${row.running ? 'running' : 'stopped'})`).join(', ')}` : ''}`
|
|
93
|
+
: containers.state === 'no-docker' ? `no Docker client is installed (${containers.reason}), so no container on this machine runs cockpit`
|
|
94
|
+
: `Docker could not be read: ${containers.reason}`).slice(0, 1_024)];
|
|
95
|
+
if (containers.state === 'unavailable')
|
|
96
|
+
return { lock: 'unknown', checked };
|
|
97
|
+
if (found.host !== here || found.container) {
|
|
98
|
+
const writer = /^[a-f0-9]{12}$/.test(found.host) ? listed.find(row => row.id.startsWith(found.host)) : undefined;
|
|
99
|
+
if (!writer)
|
|
100
|
+
return { lock: 'unknown', checked: [...checked, 'the writer ran on another host or in a container this machine does not list, whose processes this machine cannot see'] };
|
|
101
|
+
return { lock: writer.running ? 'live' : 'dead', checked: [...checked, `container ${writer.name}, which wrote it, is ${writer.running ? 'running' : 'stopped, so none of its processes runs'}`] };
|
|
102
|
+
}
|
|
103
|
+
const observed = await observeProcessStart(found.pid, processHost(executeProbe));
|
|
104
|
+
const lock = isProcessGone(found.start, observed) ? 'dead' : observed.state === 'unknown' ? 'unknown' : 'live';
|
|
105
|
+
return { lock, checked: [...checked, `process ${found.pid} on this machine is ${lock === 'dead' ? 'gone' : observed.state === 'unknown' ? `not observable (${observed.reason})` : 'running'}`] };
|
|
106
|
+
}
|
|
107
|
+
export function createWorkStore(root) {
|
|
108
|
+
const directory = join(root, WORK_RECORD_DIRECTORY);
|
|
109
|
+
const pathOf = (id, suffix) => {
|
|
110
|
+
if (!WORK_ORDER_ID.test(id))
|
|
111
|
+
throw new RefusalError(`work order id ${JSON.stringify(id)} is not an id`);
|
|
112
|
+
return join(directory, `${id}${suffix}`);
|
|
113
|
+
};
|
|
114
|
+
const read = async (id) => {
|
|
115
|
+
const path = pathOf(id, '.json');
|
|
116
|
+
let bytes;
|
|
117
|
+
try {
|
|
118
|
+
const status = await lstat(path);
|
|
119
|
+
if (!status.isFile() || status.size > RECORD_LIMIT_BYTES)
|
|
120
|
+
throw new RefusalError(`${WORK_RECORD_DIRECTORY}/${id}.json is not a regular file of at most 1 MiB`);
|
|
121
|
+
bytes = await readFile(path);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (hasErrorCode(error, 'ENOENT'))
|
|
125
|
+
return undefined;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
let value;
|
|
129
|
+
try {
|
|
130
|
+
value = JSON.parse(bytes.toString('utf8'));
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (!(error instanceof SyntaxError))
|
|
134
|
+
throw error;
|
|
135
|
+
value = undefined;
|
|
136
|
+
}
|
|
137
|
+
const parsed = WorkRecord.safeParse(value);
|
|
138
|
+
if (!parsed.success || parsed.data.id !== id) {
|
|
139
|
+
throw new RefusalError(`${WORK_RECORD_DIRECTORY}/${id}.json is not a cockpit work-order record; restore it with git restore -- ${WORK_RECORD_DIRECTORY}/${id}.json`);
|
|
140
|
+
}
|
|
141
|
+
return parsed.data;
|
|
142
|
+
};
|
|
143
|
+
const lock = async (id) => {
|
|
144
|
+
await mkdir(directory, { recursive: true });
|
|
145
|
+
const path = pathOf(id, '.lock');
|
|
146
|
+
// The holder is written before the lock appears, by linking a finished file into place, so a
|
|
147
|
+
// crash never leaves a lock that names nobody and so could never be shown gone.
|
|
148
|
+
const staged = `${path}.${randomUUID()}.tmp`;
|
|
149
|
+
await writeFile(staged, `${JSON.stringify(await holder())}\n`, { flag: 'wx', mode: 0o644 });
|
|
150
|
+
try {
|
|
151
|
+
await link(staged, path);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (!hasErrorCode(error, 'EEXIST'))
|
|
155
|
+
throw error;
|
|
156
|
+
throw new RefusalError(`work order ${id} is locked by another cockpit command (${WORK_RECORD_DIRECTORY}/${id}.lock); wait for it, or if it crashed run npx --no cockpit work recover --config <your cockpit.toml> --work-order ${id} --apply`);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
await rm(staged, { force: true });
|
|
160
|
+
}
|
|
161
|
+
return async () => { await rm(path, { force: true }); };
|
|
162
|
+
};
|
|
163
|
+
const write = async (record) => {
|
|
164
|
+
const checked = WorkRecord.parse(record);
|
|
165
|
+
const bytes = `${JSON.stringify(checked, null, 2)}\n`;
|
|
166
|
+
if (Buffer.byteLength(bytes) > RECORD_LIMIT_BYTES)
|
|
167
|
+
throw new RefusalError(`the ${record.id} record would exceed 1 MiB`);
|
|
168
|
+
const path = pathOf(record.id, '.json');
|
|
169
|
+
const staged = `${path}.${randomUUID()}.tmp`;
|
|
170
|
+
const handle = await open(staged, 'wx', 0o644);
|
|
171
|
+
try {
|
|
172
|
+
await handle.writeFile(bytes);
|
|
173
|
+
await handle.sync();
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
await handle.close();
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
await rename(staged, path);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
await rm(staged, { force: true });
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const store = {
|
|
187
|
+
read,
|
|
188
|
+
list: async () => {
|
|
189
|
+
let names;
|
|
190
|
+
try {
|
|
191
|
+
names = await readdir(directory);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (hasErrorCode(error, 'ENOENT'))
|
|
195
|
+
return [];
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
const ids = names.filter(name => name.endsWith('.json')).map(name => name.slice(0, -'.json'.length)).filter(id => WORK_ORDER_ID.test(id)).sort();
|
|
199
|
+
return Promise.all(ids.map(async (id) => (await read(id))));
|
|
200
|
+
},
|
|
201
|
+
update: async (id, change) => {
|
|
202
|
+
const release = await lock(id);
|
|
203
|
+
try {
|
|
204
|
+
const result = await change(await read(id));
|
|
205
|
+
if (result.record)
|
|
206
|
+
await write(result.record);
|
|
207
|
+
return result.value;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await release();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
recover: async (id, apply, containers) => {
|
|
214
|
+
const path = pathOf(id, '.lock');
|
|
215
|
+
let text;
|
|
216
|
+
try {
|
|
217
|
+
text = await readFile(path, 'utf8');
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (hasErrorCode(error, 'ENOENT'))
|
|
221
|
+
return { id, lock: 'none' };
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(text);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (!(error instanceof SyntaxError))
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
const found = LockHolder.safeParse(parsed);
|
|
233
|
+
const shown = text.trim().slice(0, 1_024);
|
|
234
|
+
if (!found.success)
|
|
235
|
+
return { id, lock: 'unknown', holder: shown, checked: ['the lock does not name its holder the way cockpit writes one'] };
|
|
236
|
+
// A lock whose writer is still running, or cannot be shown gone, is never removed: the write it guards may be in progress.
|
|
237
|
+
const judged = await judgeHolder(found.data, await containers());
|
|
238
|
+
if (judged.lock !== 'dead' || !apply)
|
|
239
|
+
return { id, lock: judged.lock, holder: shown, checked: judged.checked };
|
|
240
|
+
// The lock is taken over rather than deleted, so no other writer slips in before the recovery is recorded.
|
|
241
|
+
// Two recoveries that both judged it dead must not both replace it and then each delete the other's: each
|
|
242
|
+
// links its holder at the takeover name, which only one can create, and only that one replaces the lock.
|
|
243
|
+
const ours = `${path}.${randomUUID()}.tmp`;
|
|
244
|
+
const takeover = `${path}.takeover`;
|
|
245
|
+
await writeFile(ours, `${JSON.stringify(await holder())}\n`, { flag: 'wx', mode: 0o644 });
|
|
246
|
+
try {
|
|
247
|
+
try {
|
|
248
|
+
await link(ours, takeover);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (!hasErrorCode(error, 'EEXIST'))
|
|
252
|
+
throw error;
|
|
253
|
+
throw new RefusalError(`another work recover is taking over the lock on ${id}; rerun work recover once it finishes, `
|
|
254
|
+
+ `or if none is running, a person removes ${WORK_RECORD_DIRECTORY}/${id}.lock.takeover, which a crashed recovery left`);
|
|
255
|
+
}
|
|
256
|
+
let replaced = false;
|
|
257
|
+
try {
|
|
258
|
+
let current;
|
|
259
|
+
try {
|
|
260
|
+
current = await readFile(path, 'utf8');
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (!hasErrorCode(error, 'ENOENT'))
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
if (current !== text)
|
|
267
|
+
throw new RefusalError(`the lock on ${id} changed while it was being recovered; rerun work recover`);
|
|
268
|
+
await rename(takeover, path);
|
|
269
|
+
replaced = true;
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
if (!replaced)
|
|
273
|
+
await rm(takeover, { force: true });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
await rm(ours, { force: true });
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const record = await read(id);
|
|
281
|
+
if (record)
|
|
282
|
+
await write({ ...record, recoveries: [...record.recoveries ?? [], { at: new Date().toISOString(), holder: shown, checked: [...judged.checked] }] });
|
|
283
|
+
return { id, lock: 'removed', holder: shown, checked: judged.checked, recorded: record !== undefined };
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
await rm(path, { force: true });
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
return store;
|
|
291
|
+
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Cockpit documentation
|
|
2
|
+
|
|
3
|
+
cockpit is the local environment coding agents work from: every repository of the product in reach,
|
|
4
|
+
the product running on the checkouts they edit, the same instructions, skills and guard in every agent
|
|
5
|
+
tool from one `cockpit.toml`, and work orders checked against criteria you set and graded the way you choose. The instructions and
|
|
6
|
+
guard need only Git and Node.js; the stack and grading switch on when configured. New here: read [Getting started](getting-started.md)
|
|
7
|
+
chapter 1, which needs one repository and no Docker.
|
|
8
|
+
|
|
9
|
+
This index lists every page for people using cockpit on their own products, grouped by what you
|
|
10
|
+
are trying to do. To work on cockpit itself, start with [CONTRIBUTING.md](https://github.com/b0dea/cockpit/blob/main/CONTRIBUTING.md); the
|
|
11
|
+
contributor pages live separately under docs/contributing/.
|
|
12
|
+
|
|
13
|
+
## Learn
|
|
14
|
+
|
|
15
|
+
- [Tutorial](tutorial.md): run the Compose-first example end to end.
|
|
16
|
+
- [Getting started](getting-started.md): one repository, one tool and no Docker first, then more
|
|
17
|
+
repositories and a Compose stack; includes the guide and safety contract for an AI agent setting
|
|
18
|
+
cockpit up.
|
|
19
|
+
- [Concepts](concepts.md): the glossary and the terms diagnostics use.
|
|
20
|
+
- [FAQ](faq.md): short answers, including when not to use cockpit.
|
|
21
|
+
|
|
22
|
+
## Guides
|
|
23
|
+
|
|
24
|
+
- [Joining a team's cockpit](joining-a-team.md): install and verify a cockpit from a fresh clone.
|
|
25
|
+
- [Environment lifecycle](environment.md): setup, bring-up, health, logs, reset and seeding;
|
|
26
|
+
worktrees, Compose profiles, one-service commands and pruning stacks across the machine.
|
|
27
|
+
- [Compose patterns](compose-patterns.md): vendor stubs, TLS and data restore in plain Compose.
|
|
28
|
+
- [Harness surfaces](harness-surfaces.md): what `sync` writes for each agent tool, and overlays.
|
|
29
|
+
- [Extending cockpit](extending.md): supported extension points, what an upgrade replaces and the build-beside pattern.
|
|
30
|
+
- [Safety and autonomy](safety-and-autonomy.md): what guards allow, ask about and deny.
|
|
31
|
+
- [Work and browser workflows](work-and-browser.md): work orders, grading, test protection,
|
|
32
|
+
decisions, browser checks and test tiers.
|
|
33
|
+
- [Living documents and decision records](docs-check.md): find documents that cite removed paths
|
|
34
|
+
or make targets, and decision records whose numbers, index or statuses disagree.
|
|
35
|
+
|
|
36
|
+
## Reference
|
|
37
|
+
|
|
38
|
+
The command list, CLI reference, configuration reference and capability matrix are generated from
|
|
39
|
+
the source and change with it. Troubleshooting combines hand-written recovery guidance with a
|
|
40
|
+
generated diagnostic index.
|
|
41
|
+
|
|
42
|
+
- [Configuration essentials](configuration-essentials.md): the tables a typical `cockpit.toml`
|
|
43
|
+
sets, each linked to its full reference entry.
|
|
44
|
+
- [Command list](command-list.md)
|
|
45
|
+
- [CLI reference](cli-reference.md)
|
|
46
|
+
- [Configuration reference](configuration-reference.md)
|
|
47
|
+
- [Capability matrix](capability-matrix.md)
|
|
48
|
+
- [Troubleshooting](troubleshooting.md): hand-written remedies followed by the generated table
|
|
49
|
+
of codes with a fixed remedy.
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
Each example is a cockpit you can copy and run; its README gives the exact commands.
|
|
54
|
+
|
|
55
|
+
- [examples/minimal](https://github.com/b0dea/cockpit/blob/main/examples/minimal/README.md): one service, one port and one health check
|
|
56
|
+
in a plain `compose.yml`, for a running stack.
|
|
57
|
+
- [examples/compose-first](https://github.com/b0dea/cockpit/blob/main/examples/compose-first/README.md): a cockpit that keeps its own
|
|
58
|
+
`compose.yml`.
|
|
59
|
+
- [examples/docs-only](https://github.com/b0dea/cockpit/blob/main/examples/docs-only/README.md): no environment, for a repository of
|
|
60
|
+
documents or a product not yet built.
|
|
61
|
+
- [examples/extended](https://github.com/b0dea/cockpit/blob/main/examples/extended/README.md): a custom Compose service and
|
|
62
|
+
product-specific checks kept beside cockpit.
|
|
63
|
+
|
|
64
|
+
## Background
|
|
65
|
+
|
|
66
|
+
- [Architecture and design](architecture.md): why cockpit is shaped this way, non-goals, and
|
|
67
|
+
extension seams.
|
|
68
|
+
- [Release policy](release-policy.md): supported runtimes, deprecation and migration.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Architecture and design
|
|
2
|
+
|
|
3
|
+
This page is for integrators and contributors who want the reasoning behind cockpit: the problem it
|
|
4
|
+
solves, the design decisions that follow, and where in the source to extend it. The fields and
|
|
5
|
+
commands themselves are in the generated [configuration reference](configuration-reference.md) and
|
|
6
|
+
[CLI reference](cli-reference.md).
|
|
7
|
+
|
|
8
|
+
## The problem cockpit solves
|
|
9
|
+
|
|
10
|
+
Agent work across several repositories of one product fails in predictable, mechanical ways:
|
|
11
|
+
|
|
12
|
+
1. **No shared environment.** Each agent sees one checkout. Nothing boots the product it is
|
|
13
|
+
changing, so "it works" means "it compiles".
|
|
14
|
+
2. **File access is not instruction.** Granting an agent a sibling directory does not tell it
|
|
15
|
+
what that repository is, how it is tested, or which commands are safe there.
|
|
16
|
+
3. **Guards do not carry across tools.** A hook written for one harness does nothing in another,
|
|
17
|
+
and each harness spells permissions differently.
|
|
18
|
+
4. **Environments collide.** Two agents on one machine fight over ports, volumes and the right to
|
|
19
|
+
stop the stack.
|
|
20
|
+
5. **Completion is self-reported.** The session that did the work also declares it done.
|
|
21
|
+
|
|
22
|
+
There are three ways to respond. A planning tool describes the work and leaves the environment
|
|
23
|
+
to you. A sandbox isolates the agent from your machine, and from the product. cockpit boots the
|
|
24
|
+
product the agent is editing, from the checkouts it is editing, and gives every harness the same
|
|
25
|
+
rules about it.
|
|
26
|
+
|
|
27
|
+
## Design decisions
|
|
28
|
+
|
|
29
|
+
### Two axes: mount and acquire
|
|
30
|
+
|
|
31
|
+
A sibling declares how the stack uses it (`mount`) separately from how it reaches disk
|
|
32
|
+
(`acquire`). Neither is derived from the other. Collapsing them produces a preflight that reports
|
|
33
|
+
healthy while the stack mounts the wrong checkout, or none.
|
|
34
|
+
|
|
35
|
+
### Configuration that parses does something
|
|
36
|
+
|
|
37
|
+
Every schema field is consumed by code. A field that is accepted and then ignored reads as a
|
|
38
|
+
control the next person relies on, so it is a defect. Where a harness offers no
|
|
39
|
+
mechanism for a restriction, cockpit emits nothing and records the feature as `unavailable`
|
|
40
|
+
rather than writing a setting that binds nothing.
|
|
41
|
+
|
|
42
|
+
### Generated files are committed and drift-checked
|
|
43
|
+
|
|
44
|
+
Generated surfaces and Compose files are written into your repository, committed, and compared
|
|
45
|
+
byte for byte by `cockpit check`. This sits between vendoring (copies that silently go stale) and
|
|
46
|
+
a hidden runtime dependency (nothing to review). You can read every line an agent will load, and
|
|
47
|
+
CI fails when a line changes without the configuration changing.
|
|
48
|
+
|
|
49
|
+
### TypeScript decides, shell runs the hooks
|
|
50
|
+
|
|
51
|
+
Configuration, validation, codegen, drift and state live in the TypeScript CLI, and every child
|
|
52
|
+
process goes through one executor. Guards and hooks stay in shell, because every harness invokes
|
|
53
|
+
them directly on each tool call and they must behave identically under all of them with no
|
|
54
|
+
interpreter start-up cost.
|
|
55
|
+
|
|
56
|
+
### Leases, locks and destruction
|
|
57
|
+
|
|
58
|
+
These rules came from failures on shared machines:
|
|
59
|
+
|
|
60
|
+
- **Ports are leased, not derived.** Two checkouts of one product would compute the same port from
|
|
61
|
+
the same configuration. A lease records who holds each port family.
|
|
62
|
+
- **Locks are directory creations.** Creating a directory either succeeds or fails atomically, and
|
|
63
|
+
a process that dies leaves the directory behind for inspection instead of a half-written file.
|
|
64
|
+
- **Unknown is not dead.** A lock or holder whose liveness cannot be established is reported, not
|
|
65
|
+
stolen. Age alone never justifies removing it.
|
|
66
|
+
- **Classification overrides intent.** Destructive commands act only on resources whose cockpit
|
|
67
|
+
labels and records match, so "just delete it" cannot be expressed. The question cockpit asks
|
|
68
|
+
is "can I account for everything I am about to destroy?", not "does this look dangerous?".
|
|
69
|
+
|
|
70
|
+
### Why the evaluator is independent
|
|
71
|
+
|
|
72
|
+
Self-assessment is overconfident: an agent that wrote the code also wrote the account of it. The
|
|
73
|
+
evaluator is a separate subagent that reruns the declared verification commands, grades each
|
|
74
|
+
acceptance criterion from that output, and may abstain when the output is not enough to decide. Abstention
|
|
75
|
+
is a valid result; it is what turns a likely false pass into a visible gap.
|
|
76
|
+
|
|
77
|
+
### Prior art
|
|
78
|
+
|
|
79
|
+
Spec and workflow tools also generate files into repositories. cockpit adds three things they
|
|
80
|
+
typically lack: a drift check against installed generated files, a `--dry-run` preview of every
|
|
81
|
+
write, and CLI and configuration references generated from the source so they cannot go stale.
|
|
82
|
+
|
|
83
|
+
## Non-goals
|
|
84
|
+
|
|
85
|
+
- **Not a planner.** cockpit does not decide what work to do. Pair it with a spec or workflow tool.
|
|
86
|
+
- **Not a sandbox.** It does not isolate an agent from your machine; see
|
|
87
|
+
[Safety and autonomy](safety-and-autonomy.md).
|
|
88
|
+
- **No telemetry.** The only network request cockpit makes itself is `doctor` reading the latest
|
|
89
|
+
published version from the npm registry, skipped when `CI` or `NO_UPDATE_NOTIFIER` is set.
|
|
90
|
+
- **Not a monorepo or build-system replacement.** It can manage one repository or several, but
|
|
91
|
+
delegates each repository's build and dependency graph to that repository's own tools.
|
|
92
|
+
- **No per-test containers.** One stack per checkout, shared across the agent's work.
|
|
93
|
+
|
|
94
|
+
## Processing path
|
|
95
|
+
|
|
96
|
+
Configuration is declared and loaded, the environment is observed, policy code decides, the
|
|
97
|
+
environment application orders the work, and the shared executor runs child processes.
|
|
98
|
+
Postchecks verify results before command handlers print them. The
|
|
99
|
+
[Cockpit evidence boundary](concepts.md#cockpit-evidence-boundary) applies at every step: a
|
|
100
|
+
parsed declaration is not an observation, a dispatched command is not a successful postcheck,
|
|
101
|
+
and a generated file does not show that its harness loaded it.
|
|
102
|
+
|
|
103
|
+
## Surface overlays
|
|
104
|
+
|
|
105
|
+
Use a [Cockpit surface overlay](concepts.md#cockpit-surface-overlay) for product-specific rules,
|
|
106
|
+
subagents, skills and hooks. Overlay merging belongs to `packages/cockpit/src/surfaces/merge.ts`;
|
|
107
|
+
rendering continues through the existing surface code, and publication stays within
|
|
108
|
+
[Cockpit generated ownership](concepts.md#cockpit-generated-ownership).
|
|
109
|
+
[Ownership and extension](harness-surfaces.md#ownership-and-extension) covers the `replaces` rule
|
|
110
|
+
and the review procedure.
|
|
111
|
+
|
|
112
|
+
## Guards
|
|
113
|
+
|
|
114
|
+
Extend policy in `packages/cockpit/src/guards/evaluator.ts`, then adapt its decision through the
|
|
115
|
+
per-harness adapters. Do not interpret guard policy in shell or rebuild it in a CLI
|
|
116
|
+
handler. A harness mechanism that has not been probed beside a deliberately broken control stays
|
|
117
|
+
`instructed` or `unavailable`; see [Capability states](harness-surfaces.md#capability-states).
|
|
118
|
+
|
|
119
|
+
## Runtime behavior
|
|
120
|
+
|
|
121
|
+
Add a public command to `packages/cockpit/src/command-table.ts`, bind it in
|
|
122
|
+
`packages/cockpit/src/cli.ts`, and order environment work through the environment application
|
|
123
|
+
(`packages/cockpit/src/environment/application/`). Every child process crosses
|
|
124
|
+
`packages/cockpit/src/environment-exec/execute.ts`; health and lifecycle verdicts stay in their
|
|
125
|
+
existing modules. Add a schema field only together with the code that consumes it.
|
|
126
|
+
|
|
127
|
+
The runtime sequence and recovery rules are in [Environment](environment.md). A new diagnostic
|
|
128
|
+
needs one actionable remedy in [Troubleshooting](troubleshooting.md).
|
|
129
|
+
|
|
130
|
+
## Harnesses
|
|
131
|
+
|
|
132
|
+
A custom harness declares one row for every feature, with the evidence described in
|
|
133
|
+
[Custom harnesses](harness-surfaces.md#custom-harnesses). Probe the exact harness version beside
|
|
134
|
+
a deliberately broken control before changing a capability claim. Repository tests verify
|
|
135
|
+
cockpit's side of the contract; native loading and enforcement need their own check through
|
|
136
|
+
`cockpit selftest`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Capability matrix
|
|
2
|
+
|
|
3
|
+
<!-- Generated by `npm run references`; edit its sources, not this file. -->
|
|
4
|
+
|
|
5
|
+
<!-- Source manifest: 7db847121f6f4cf7fc6608625e755acc97326e8320ea425b807d7de8a283f70c -->
|
|
6
|
+
|
|
7
|
+
This generated page is for users checking what cockpit actually enforces in each agent tool. Each cell holds one of four states:
|
|
8
|
+
|
|
9
|
+
- `enforced`: a dated run of the real tool, beside a deliberately broken control, showed it refusing the forbidden operation.
|
|
10
|
+
- `instructed`: cockpit writes the guidance or setting, but nothing makes the agent follow it.
|
|
11
|
+
- `generated`: cockpit writes the files, but nobody has checked that the tool even loads them.
|
|
12
|
+
- `unavailable`: cockpit writes nothing for this cell.
|
|
13
|
+
|
|
14
|
+
Only a dated run of the real tool changes a state; writing a file or passing cockpit's own tests never does.
|
|
15
|
+
|
|
16
|
+
Written says whether `sync` writes the tool's files for a cockpit.toml that lists no tools: the tools whose guard firing is measured are the default `[surfaces] tools`, and any other tool is written only once listed (`npx --no cockpit tool add <tool> --config <your cockpit.toml>`).
|
|
17
|
+
|
|
18
|
+
Guard firing is the generated hook refusing a protected operation. Instruction loading is the tool reading the guidance cockpit writes for it, including the guides of sibling repositories. Skills are the tool loading the skills cockpit renders: `cockpit-setup`, `cockpit-explain`, `cockpit-fix` and `cockpit-work`. The session-start check is the tool running `cockpit check` as a session starts and showing its drift report to the agent. Remote MCP servers are the tool connecting to an approved streamable HTTP server with the credential cockpit spells for it: an environment reference or a headers helper. Worktree grants are the tool letting the agent write a checkout `cockpit use` serves from outside the declared one, through a machine-local file cockpit writes.
|
|
19
|
+
|
|
20
|
+
| Tool | Written | Guard firing | Instruction loading | Skills | Session-start check | Remote MCP servers | Worktree grants |
|
|
21
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
22
|
+
| Claude Code (`claudecode`) | by default | enforced | instructed | instructed | instructed | instructed (needs a person) | enforced (needs a person) |
|
|
23
|
+
| Codex (`codexcli`) | by default | enforced (needs a person) | instructed | instructed | instructed (needs a person) | instructed | unavailable |
|
|
24
|
+
| OpenCode (`opencode`) | by default | enforced | instructed | instructed | unavailable | instructed | enforced |
|
|
25
|
+
| Cursor (`cursor`) | when listed | unavailable | generated | generated | unavailable | unavailable | unavailable |
|
|
26
|
+
| Grok Build (`grokcli`) | when listed | unavailable | generated | generated | unavailable | unavailable | unavailable |
|
|
27
|
+
|
|
28
|
+
## Measurements
|
|
29
|
+
|
|
30
|
+
Each line gives the date and tool version of the run and what it showed.
|
|
31
|
+
|
|
32
|
+
- Claude Code, guard firing (2026-11-01, version 2.1.316): with the exact push pre-allowed by permissions, the hook refused git push to a protected branch and the remote stayed empty while git status ran; the same checkout without the hook pushed.
|
|
33
|
+
- Codex, guard firing (2026-11-01, version 0.165.0): only after a person trusts the hook: untrusted, Codex skipped it and git push ran; trusted (forced with --dangerously-bypass-hook-trust), it refused the push. Trust is per machine and cockpit doctor warns until it is given.
|
|
34
|
+
- OpenCode, guard firing (2026-11-01, version 1.19.4): the plugin refused git push and a native read of a secrets file while an unprotected read stayed allowed; before read was routed through the guard, the same read returned the file.
|
|
35
|
+
- Claude Code, instruction loading (2026-11-03, version 2.1.317): in a workspace Claude Code had not trusted, a sibling guide copied into the root AGENTS.md that CLAUDE.md imports loaded, with or without Claude Code's user option for reading AGENTS.md, while the undeclared sibling's guide and the decoy did not; nothing loaded through a committed CLAUDE.md @-import of a link to the guide or of its relative path, through permissions.additionalDirectories with CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD in the settings env (ignored until a person trusts the workspace), or through --add-dir with that variable set.
|
|
36
|
+
- Codex, instruction loading (2026-11-03, version 0.166.0): a sibling guide copied into the root AGENTS.md loaded while the undeclared sibling's guide and the decoy did not; nothing loaded through a line in AGENTS.md telling the agent to read a link to the guide, through --add-dir naming the sibling, or through project_doc_fallback_filenames naming the link, and Codex dropped the end of a project AGENTS.md past 32 KiB.
|
|
37
|
+
- OpenCode, instruction loading (2026-11-03, version 1.19.5): a sibling guide copied into the root AGENTS.md loaded, as did a guide named through a link in the opencode.jsonc instructions list, while the undeclared sibling's guide and the decoy did not.
|
|
38
|
+
- Claude Code, session-start check (2026-11-03, version 2.1.317): a SessionStart hook in .claude/settings.json ran under claude -p and the agent quoted its stdout back, while a hook under a misspelled event name never ran and Claude Code named it as ignored.
|
|
39
|
+
- Codex, session-start check (2026-11-03, version 0.166.0): only after a person trusts the hook, as for the guard: a trusted SessionStart hook in .codex/hooks.json ran under codex exec and the agent quoted its stdout back, while a hook under a misspelled event name never ran.
|
|
40
|
+
- Claude Code, remote MCP servers (2026-11-04, version 2.1.318): from a project .mcp.json, Claude Code sent the Authorization header a ${NAME} reference named while a $NAME control went out literally; it ran a headersHelper given with --mcp-config, but refused to run one from the project file until a person trusts the workspace, and a misspelled headerHelper sent nothing.
|
|
41
|
+
- Codex, remote MCP servers (2026-11-04, version 0.166.0): from a project .codex/config.toml in a directory Codex had not been told to trust, codex exec sent the bearer_token_env_var and env_http_headers values and the http_headers_helper output, named a misspelled http_header_helper as ignored, and sent a ${NAME} written in http_headers literally.
|
|
42
|
+
- OpenCode, remote MCP servers (2026-11-04, version 1.19.5): opencode mcp list sent headers expanded from {env:NAME} in a project opencode.jsonc while a ${NAME} control went out literally; its remote server schema has no headers helper, so a server that declares one is not written for it.
|
|
43
|
+
- Claude Code, worktree grants (2026-11-04, version 2.1.318): a directory in .claude/settings.local.json permissions.additionalDirectories was written and the control refused, and a malformed twin granted nothing; measured in an untrusted workspace, so whether it combines with the committed list once a person trusts the workspace is unmeasured.
|
|
44
|
+
- Codex, worktree grants (2026-11-04, version 0.166.0): Codex reads no machine-local project file; --add-dir at start made the worktree writable and without it the write was refused, so cockpit use prints that flag and writes nothing.
|
|
45
|
+
- OpenCode, worktree grants (2026-11-04, version 1.19.5): the machine-local grant file holding the worktree path allowed the write and the control was denied, and a pattern naming a missing grant file stopped OpenCode at start, naming the file.
|
|
46
|
+
|
|
47
|
+
## Without a measurement
|
|
48
|
+
|
|
49
|
+
- Guard firing in Cursor and Grok Build: no guard adapter is checked in, so cockpit generates no guard hook.
|
|
50
|
+
- Instruction loading in Cursor: cockpit's instructions and the sibling guides it copies reach Cursor only through the root AGENTS.md, and .cursor/rules holds only rules overlays; no run of the tool beside a broken control has shown that it loads either.
|
|
51
|
+
- Instruction loading in Grok Build: cockpit writes the files, but no run of the tool beside a broken control has shown that it loads them.
|
|
52
|
+
- Skills in Claude Code (`.claude/skills`), Codex (`.agents/skills`) and OpenCode (`.opencode/skills`): a person has not yet reviewed whether the tool loads and triggers them as written.
|
|
53
|
+
- Skills in Cursor (`.cursor/skills`) and Grok Build (`.grok/skills`): cockpit writes the files, but no run of the tool beside a broken control has shown that it loads them.
|
|
54
|
+
- Session-start check in OpenCode: OpenCode 1.19.5 delivers session.created to a plugin, but no run has shown a plugin putting text in the agent's context, so cockpit generates no session-start check.
|
|
55
|
+
- Session-start check in Cursor and Grok Build: cockpit generates no hook for this tool.
|
|
56
|
+
- Remote MCP servers in Cursor and Grok Build: cockpit writes no remote MCP server for this tool: its remote transport and header spelling have not been checked.
|
|
57
|
+
- Worktree grants in Cursor and Grok Build: the tool was not installed where this was measured, so cockpit use writes nothing for it.
|
|
58
|
+
- Browser native transports (the playwright MCP tools and the pinned Playwright CLI) in Claude Code, Codex and OpenCode: cockpit declares them and the browser skill says each still needs a person's paired call beside a broken control; no cockpit command runs or records that call, so the browser commands drive the browser through cockpit's own project client instead.
|