@b00dea/cockpit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +375 -0
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/bin/cockpit.mjs +33 -0
- package/cockpit.schema.json +2574 -0
- package/dist/autonomy.d.ts +44 -0
- package/dist/autonomy.js +156 -0
- package/dist/bounded-read.d.ts +14 -0
- package/dist/bounded-read.js +42 -0
- package/dist/browser/commands.d.ts +62 -0
- package/dist/browser/commands.js +349 -0
- package/dist/browser/package-paths.d.ts +1 -0
- package/dist/browser/package-paths.js +6 -0
- package/dist/browser/proxy.d.ts +22 -0
- package/dist/browser/proxy.js +37 -0
- package/dist/browser/runtime.d.ts +117 -0
- package/dist/browser/runtime.js +183 -0
- package/dist/browser/surfaces.d.ts +141 -0
- package/dist/browser/surfaces.js +556 -0
- package/dist/browser/ui-gate.d.ts +15 -0
- package/dist/browser/ui-gate.js +18 -0
- package/dist/cli-guard.d.ts +5 -0
- package/dist/cli-guard.js +71 -0
- package/dist/cli-main.d.ts +139 -0
- package/dist/cli-main.js +1405 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +16 -0
- package/dist/command-table.d.ts +113 -0
- package/dist/command-table.js +512 -0
- package/dist/config/load.d.ts +171 -0
- package/dist/config/load.js +345 -0
- package/dist/config/sibling-toml.d.ts +48 -0
- package/dist/config/sibling-toml.js +160 -0
- package/dist/config/surface-tools-toml.d.ts +9 -0
- package/dist/config/surface-tools-toml.js +37 -0
- package/dist/config/verified-write.d.ts +43 -0
- package/dist/config/verified-write.js +89 -0
- package/dist/diagnostics.d.ts +26 -0
- package/dist/diagnostics.js +335 -0
- package/dist/emit.d.ts +22 -0
- package/dist/emit.js +43 -0
- package/dist/entrypoint-kinds.d.ts +3 -0
- package/dist/entrypoint-kinds.js +11 -0
- package/dist/environment/application/destructive.d.ts +6 -0
- package/dist/environment/application/destructive.js +425 -0
- package/dist/environment/application/doctor.d.ts +89 -0
- package/dist/environment/application/doctor.js +235 -0
- package/dist/environment/application/guard.d.ts +43 -0
- package/dist/environment/application/guard.js +622 -0
- package/dist/environment/application/health.d.ts +134 -0
- package/dist/environment/application/health.js +379 -0
- package/dist/environment/application/lifecycle-context.d.ts +228 -0
- package/dist/environment/application/lifecycle-context.js +154 -0
- package/dist/environment/application/lifecycle-state.d.ts +25 -0
- package/dist/environment/application/lifecycle-state.js +201 -0
- package/dist/environment/application/lifecycle.d.ts +17 -0
- package/dist/environment/application/lifecycle.js +729 -0
- package/dist/environment/application/logs.d.ts +45 -0
- package/dist/environment/application/logs.js +127 -0
- package/dist/environment/application/setup.d.ts +129 -0
- package/dist/environment/application/setup.js +741 -0
- package/dist/environment/application/shared.d.ts +11 -0
- package/dist/environment/application/shared.js +61 -0
- package/dist/environment/claims.d.ts +389 -0
- package/dist/environment/claims.js +1709 -0
- package/dist/environment/codex-hook-trust.d.ts +11 -0
- package/dist/environment/codex-hook-trust.js +185 -0
- package/dist/environment/compose-environment.d.ts +1 -0
- package/dist/environment/compose-environment.js +6 -0
- package/dist/environment/compose-ownership.d.ts +21 -0
- package/dist/environment/compose-ownership.js +117 -0
- package/dist/environment/compose-topology.d.ts +117 -0
- package/dist/environment/compose-topology.js +413 -0
- package/dist/environment/contract.d.ts +489 -0
- package/dist/environment/contract.js +761 -0
- package/dist/environment/declaration.d.ts +14 -0
- package/dist/environment/declaration.js +39 -0
- package/dist/environment/destructive-composition.d.ts +29 -0
- package/dist/environment/destructive-composition.js +333 -0
- package/dist/environment/docker-daemon.d.ts +30 -0
- package/dist/environment/docker-daemon.js +78 -0
- package/dist/environment/docker-endpoint.d.ts +10 -0
- package/dist/environment/docker-endpoint.js +19 -0
- package/dist/environment/env-schema-observation.d.ts +46 -0
- package/dist/environment/env-schema-observation.js +241 -0
- package/dist/environment/evidence.d.ts +136 -0
- package/dist/environment/evidence.js +437 -0
- package/dist/environment/executable.d.ts +26 -0
- package/dist/environment/executable.js +167 -0
- package/dist/environment/generated-application/authorities.d.ts +5 -0
- package/dist/environment/generated-application/authorities.js +61 -0
- package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
- package/dist/environment/generated-application/bootstrap-skill.js +328 -0
- package/dist/environment/generated-application/check.d.ts +13 -0
- package/dist/environment/generated-application/check.js +189 -0
- package/dist/environment/generated-application/custom-surface.d.ts +26 -0
- package/dist/environment/generated-application/custom-surface.js +289 -0
- package/dist/environment/generated-application/prepare.d.ts +29 -0
- package/dist/environment/generated-application/prepare.js +276 -0
- package/dist/environment/generated-application/publication.d.ts +33 -0
- package/dist/environment/generated-application/publication.js +547 -0
- package/dist/environment/generated-application/shared.d.ts +484 -0
- package/dist/environment/generated-application/shared.js +349 -0
- package/dist/environment/generated-application/surface-observation.d.ts +4 -0
- package/dist/environment/generated-application/surface-observation.js +69 -0
- package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
- package/dist/environment/generated-application/surface-outputs.js +389 -0
- package/dist/environment/generated-application/surfaces.d.ts +3 -0
- package/dist/environment/generated-application/surfaces.js +699 -0
- package/dist/environment/generated-application/sync.d.ts +6 -0
- package/dist/environment/generated-application/sync.js +109 -0
- package/dist/environment/git-observation.d.ts +183 -0
- package/dist/environment/git-observation.js +544 -0
- package/dist/environment/health-composition.d.ts +11 -0
- package/dist/environment/health-composition.js +628 -0
- package/dist/environment/host-observation.d.ts +56 -0
- package/dist/environment/host-observation.js +52 -0
- package/dist/environment/identity.d.ts +48 -0
- package/dist/environment/identity.js +85 -0
- package/dist/environment/init-application.d.ts +308 -0
- package/dist/environment/init-application.js +697 -0
- package/dist/environment/install-policy-observation.d.ts +71 -0
- package/dist/environment/install-policy-observation.js +284 -0
- package/dist/environment/install-policy.d.ts +48 -0
- package/dist/environment/install-policy.js +96 -0
- package/dist/environment/instance.d.ts +33 -0
- package/dist/environment/instance.js +122 -0
- package/dist/environment/instruction-pointer.d.ts +19 -0
- package/dist/environment/instruction-pointer.js +144 -0
- package/dist/environment/lifecycle-composition.d.ts +29 -0
- package/dist/environment/lifecycle-composition.js +675 -0
- package/dist/environment/lifecycle-stage.d.ts +115 -0
- package/dist/environment/lifecycle-stage.js +398 -0
- package/dist/environment/lifecycle.d.ts +75 -0
- package/dist/environment/lifecycle.js +354 -0
- package/dist/environment/logs.d.ts +132 -0
- package/dist/environment/logs.js +519 -0
- package/dist/environment/observations.d.ts +46 -0
- package/dist/environment/observations.js +344 -0
- package/dist/environment/operator-application.d.ts +50 -0
- package/dist/environment/operator-application.js +155 -0
- package/dist/environment/ports.d.ts +182 -0
- package/dist/environment/ports.js +1205 -0
- package/dist/environment/posture-observation.d.ts +68 -0
- package/dist/environment/posture-observation.js +253 -0
- package/dist/environment/read-composition.d.ts +27 -0
- package/dist/environment/read-composition.js +352 -0
- package/dist/environment/readiness-composition.d.ts +38 -0
- package/dist/environment/readiness-composition.js +130 -0
- package/dist/environment/reclaim.d.ts +333 -0
- package/dist/environment/reclaim.js +1835 -0
- package/dist/environment/repository-identity.d.ts +13 -0
- package/dist/environment/repository-identity.js +108 -0
- package/dist/environment/runtime-measurement-record.d.ts +38 -0
- package/dist/environment/runtime-measurement-record.js +111 -0
- package/dist/environment/runtime-measurement.d.ts +27 -0
- package/dist/environment/runtime-measurement.js +224 -0
- package/dist/environment/runtime-probe-residue.d.ts +43 -0
- package/dist/environment/runtime-probe-residue.js +148 -0
- package/dist/environment/runtime-probe.d.ts +72 -0
- package/dist/environment/runtime-probe.js +725 -0
- package/dist/environment/runtime.d.ts +505 -0
- package/dist/environment/runtime.js +2335 -0
- package/dist/environment/seed.d.ts +16 -0
- package/dist/environment/seed.js +43 -0
- package/dist/environment/selftest-application.d.ts +86 -0
- package/dist/environment/selftest-application.js +713 -0
- package/dist/environment/sensitive.d.ts +3 -0
- package/dist/environment/sensitive.js +48 -0
- package/dist/environment/setup-observation.d.ts +62 -0
- package/dist/environment/setup-observation.js +1473 -0
- package/dist/environment/setup.d.ts +43 -0
- package/dist/environment/setup.js +176 -0
- package/dist/environment/sibling-instructions.d.ts +33 -0
- package/dist/environment/sibling-instructions.js +224 -0
- package/dist/environment/sibling-registration.d.ts +45 -0
- package/dist/environment/sibling-registration.js +538 -0
- package/dist/environment/state.d.ts +371 -0
- package/dist/environment/state.js +2013 -0
- package/dist/environment/templates.d.ts +140 -0
- package/dist/environment/templates.js +685 -0
- package/dist/environment/tool-registration.d.ts +9 -0
- package/dist/environment/tool-registration.js +96 -0
- package/dist/environment/upgrade-application.d.ts +19 -0
- package/dist/environment/upgrade-application.js +175 -0
- package/dist/environment-exec/execute.d.ts +102 -0
- package/dist/environment-exec/execute.js +1175 -0
- package/dist/environment-exec/libexec-program.d.ts +6 -0
- package/dist/environment-exec/libexec-program.js +22 -0
- package/dist/environment-exec/verified-digest.d.ts +4 -0
- package/dist/environment-exec/verified-digest.js +16 -0
- package/dist/exact-keys.d.ts +7 -0
- package/dist/exact-keys.js +15 -0
- package/dist/fix-catalogue.d.ts +32 -0
- package/dist/fix-catalogue.js +60 -0
- package/dist/freshness.d.ts +29 -0
- package/dist/freshness.js +75 -0
- package/dist/generated-environment/adopt.d.ts +16 -0
- package/dist/generated-environment/adopt.js +275 -0
- package/dist/generated-environment/command.d.ts +47 -0
- package/dist/generated-environment/command.js +75 -0
- package/dist/generated-environment/transaction.d.ts +186 -0
- package/dist/generated-environment/transaction.js +1770 -0
- package/dist/grading/evaluate.d.ts +71 -0
- package/dist/grading/evaluate.js +403 -0
- package/dist/grading/grade.d.ts +33 -0
- package/dist/grading/grade.js +57 -0
- package/dist/guards/evaluator.d.ts +113 -0
- package/dist/guards/evaluator.js +1226 -0
- package/dist/guards/harness-adapter.d.ts +17 -0
- package/dist/guards/harness-adapter.js +57 -0
- package/dist/guards/host.d.ts +8 -0
- package/dist/guards/host.js +10 -0
- package/dist/guards/invocation-parser.d.ts +66 -0
- package/dist/guards/invocation-parser.js +595 -0
- package/dist/guards/local-identity.d.ts +40 -0
- package/dist/guards/local-identity.js +123 -0
- package/dist/guards/policy-pin.d.ts +13 -0
- package/dist/guards/policy-pin.js +175 -0
- package/dist/guards/protected-locations.d.ts +62 -0
- package/dist/guards/protected-locations.js +461 -0
- package/dist/guards/read-only.d.ts +37 -0
- package/dist/guards/read-only.js +315 -0
- package/dist/init/command.d.ts +40 -0
- package/dist/init/command.js +310 -0
- package/dist/init/derive.d.ts +51 -0
- package/dist/init/derive.js +715 -0
- package/dist/init/draft.d.ts +138 -0
- package/dist/init/draft.js +1205 -0
- package/dist/init/render.d.ts +35 -0
- package/dist/init/render.js +82 -0
- package/dist/init/scaffold.d.ts +53 -0
- package/dist/init/scaffold.js +121 -0
- package/dist/invocation.d.ts +13 -0
- package/dist/invocation.js +20 -0
- package/dist/package-metadata.d.ts +9 -0
- package/dist/package-metadata.js +19 -0
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.js +88 -0
- package/dist/references/named.d.ts +24 -0
- package/dist/references/named.js +27 -0
- package/dist/refusal.d.ts +15 -0
- package/dist/refusal.js +7 -0
- package/dist/registry-derivation.d.ts +31 -0
- package/dist/registry-derivation.js +32 -0
- package/dist/registry-resolve/command.d.ts +47 -0
- package/dist/registry-resolve/command.js +226 -0
- package/dist/registry-resolve/registry.d.ts +68 -0
- package/dist/registry-resolve/registry.js +292 -0
- package/dist/registry-resolve/render-shell.d.ts +18 -0
- package/dist/registry-resolve/render-shell.js +36 -0
- package/dist/repair.d.ts +29 -0
- package/dist/repair.js +8 -0
- package/dist/schema-json.d.ts +1 -0
- package/dist/schema-json.js +177 -0
- package/dist/schema.d.ts +1322 -0
- package/dist/schema.js +934 -0
- package/dist/setup/command.d.ts +4 -0
- package/dist/setup/command.js +4 -0
- package/dist/sha256.d.ts +1 -0
- package/dist/sha256.js +4 -0
- package/dist/surfaces/adoption.d.ts +41 -0
- package/dist/surfaces/adoption.js +217 -0
- package/dist/surfaces/consumer-instructions.d.ts +20 -0
- package/dist/surfaces/consumer-instructions.js +43 -0
- package/dist/surfaces/contract.d.ts +42 -0
- package/dist/surfaces/contract.js +169 -0
- package/dist/surfaces/core-declarations.d.ts +24 -0
- package/dist/surfaces/core-declarations.js +79 -0
- package/dist/surfaces/development-markers.d.ts +1 -0
- package/dist/surfaces/development-markers.js +18 -0
- package/dist/surfaces/generator-adapter.d.ts +71 -0
- package/dist/surfaces/generator-adapter.js +809 -0
- package/dist/surfaces/harness-registry.d.ts +180 -0
- package/dist/surfaces/harness-registry.js +117 -0
- package/dist/surfaces/mcp.d.ts +95 -0
- package/dist/surfaces/mcp.js +186 -0
- package/dist/surfaces/merge.d.ts +30 -0
- package/dist/surfaces/merge.js +105 -0
- package/dist/surfaces/ownership.d.ts +190 -0
- package/dist/surfaces/ownership.js +795 -0
- package/dist/surfaces/projection.d.ts +136 -0
- package/dist/surfaces/projection.js +681 -0
- package/dist/surfaces/render.d.ts +118 -0
- package/dist/surfaces/render.js +633 -0
- package/dist/surfaces/restrictions.d.ts +130 -0
- package/dist/surfaces/restrictions.js +447 -0
- package/dist/surfaces/selftest.d.ts +162 -0
- package/dist/surfaces/selftest.js +393 -0
- package/dist/surfaces/skill-contract.d.ts +85 -0
- package/dist/surfaces/skill-contract.js +449 -0
- package/dist/surfaces/sync.d.ts +57 -0
- package/dist/surfaces/sync.js +164 -0
- package/dist/team-metadata.d.ts +41 -0
- package/dist/team-metadata.js +512 -0
- package/dist/testing/gates.d.ts +91 -0
- package/dist/testing/gates.js +182 -0
- package/dist/testing/hooks.d.ts +59 -0
- package/dist/testing/hooks.js +139 -0
- package/dist/testing/verdict.d.ts +29 -0
- package/dist/testing/verdict.js +62 -0
- package/dist/work-orders/application.d.ts +99 -0
- package/dist/work-orders/application.js +383 -0
- package/dist/work-orders/composition.d.ts +124 -0
- package/dist/work-orders/composition.js +1010 -0
- package/dist/work-orders/contract.d.ts +61 -0
- package/dist/work-orders/contract.js +122 -0
- package/dist/work-orders/store.d.ts +158 -0
- package/dist/work-orders/store.js +197 -0
- package/docs/README.md +65 -0
- package/docs/architecture.md +136 -0
- package/docs/capability-matrix.md +50 -0
- package/docs/cli-reference.md +599 -0
- package/docs/command-list.md +51 -0
- package/docs/compose-patterns.md +99 -0
- package/docs/concepts.md +171 -0
- package/docs/configuration-essentials.md +172 -0
- package/docs/configuration-reference.md +621 -0
- package/docs/environment.md +760 -0
- package/docs/extending.md +43 -0
- package/docs/faq.md +111 -0
- package/docs/getting-started.md +608 -0
- package/docs/harness-surfaces.md +433 -0
- package/docs/joining-a-team.md +76 -0
- package/docs/release-policy.md +49 -0
- package/docs/safety-and-autonomy.md +419 -0
- package/docs/troubleshooting.md +361 -0
- package/docs/tutorial.md +170 -0
- package/docs/work-and-browser.md +254 -0
- package/libexec/browser-operation.cjs +186 -0
- package/libexec/fence-marker.cjs +101 -0
- package/libexec/fence-marker.d.cts +24 -0
- package/libexec/fenced-group-runner.cjs +77 -0
- package/libexec/fenced-supervisor.cjs +137 -0
- package/libexec/generated-filesystem.cjs +731 -0
- package/libexec/install-filesystem.cjs +225 -0
- package/libexec/rulesync-generator.NOTICE.md +1112 -0
- package/libexec/rulesync-generator.cjs +743 -0
- package/libexec/surface-generator-worker.cjs +266 -0
- package/libexec/surface-output-reader.cjs +99 -0
- package/libexec/surface-scratch-cleanup.cjs +14 -0
- package/libexec/validate-harness-evidence.d.mts +183 -0
- package/libexec/validate-harness-evidence.mjs +1312 -0
- package/package.json +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 +54 -0
- package/surfaces/skills/cockpit-explain.skill.md +32 -0
- package/surfaces/skills/cockpit-fix.skill.md +37 -0
- package/surfaces/skills/cockpit-setup.skill.md +39 -0
- package/surfaces/skills/cockpit-work.skill.md +34 -0
- package/surfaces/skills/collection.json +5 -0
- package/surfaces/skills/procedures/explain.md +61 -0
- package/surfaces/skills/procedures/fix.md +81 -0
- package/surfaces/skills/procedures/setup.md +379 -0
- package/surfaces/skills/procedures/work.md +105 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export declare const HARNESS_REGISTRY: readonly [Readonly<{
|
|
2
|
+
target: "claudecode";
|
|
3
|
+
type: 'agent';
|
|
4
|
+
displayName: "Claude Code";
|
|
5
|
+
rulesyncTarget: "claudecode";
|
|
6
|
+
rulesyncFeatures: readonly ["rules", "mcp", "subagents", "skills", "hooks", "permissions"];
|
|
7
|
+
outputs: Readonly<{
|
|
8
|
+
instruction: "CLAUDE.md";
|
|
9
|
+
skills: ".claude/skills";
|
|
10
|
+
mcp: ".mcp.json";
|
|
11
|
+
permissions: readonly string[];
|
|
12
|
+
}>;
|
|
13
|
+
guard: Readonly<{
|
|
14
|
+
capability: 'instructed';
|
|
15
|
+
firing: Readonly<{
|
|
16
|
+
state: 'enforced';
|
|
17
|
+
evidence: 'not_applicable';
|
|
18
|
+
measuredOn: "2026-11-01";
|
|
19
|
+
measuredVersion: "2.1.316";
|
|
20
|
+
provenance: "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";
|
|
21
|
+
}>;
|
|
22
|
+
}>;
|
|
23
|
+
native: Readonly<{
|
|
24
|
+
state: 'instructed';
|
|
25
|
+
reason: "a person has not yet reviewed whether the tool loads and triggers them as written";
|
|
26
|
+
}>;
|
|
27
|
+
sessionStart: Readonly<{
|
|
28
|
+
state: 'instructed';
|
|
29
|
+
evidence: 'not_applicable';
|
|
30
|
+
measuredOn: "2026-11-03";
|
|
31
|
+
measuredVersion: "2.1.317";
|
|
32
|
+
provenance: "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";
|
|
33
|
+
}>;
|
|
34
|
+
}>, Readonly<{
|
|
35
|
+
target: "codexcli";
|
|
36
|
+
type: 'agent';
|
|
37
|
+
displayName: "Codex";
|
|
38
|
+
rulesyncTarget: "codexcli";
|
|
39
|
+
rulesyncFeatures: readonly ["rules", "mcp", "subagents", "skills", "hooks", "permissions"];
|
|
40
|
+
outputs: Readonly<{
|
|
41
|
+
instruction: "AGENTS.md";
|
|
42
|
+
skills: ".agents/skills";
|
|
43
|
+
mcp: ".codex/config.toml";
|
|
44
|
+
permissions: readonly string[];
|
|
45
|
+
}>;
|
|
46
|
+
guard: Readonly<{
|
|
47
|
+
capability: 'instructed';
|
|
48
|
+
firing: Readonly<{
|
|
49
|
+
state: 'enforced';
|
|
50
|
+
evidence: 'human_needed';
|
|
51
|
+
measuredOn: "2026-11-01";
|
|
52
|
+
measuredVersion: "0.165.0";
|
|
53
|
+
provenance: "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";
|
|
54
|
+
}>;
|
|
55
|
+
}>;
|
|
56
|
+
native: Readonly<{
|
|
57
|
+
state: 'instructed';
|
|
58
|
+
reason: "a person has not yet reviewed whether the tool loads and triggers them as written";
|
|
59
|
+
}>;
|
|
60
|
+
sessionStart: Readonly<{
|
|
61
|
+
state: 'instructed';
|
|
62
|
+
evidence: 'human_needed';
|
|
63
|
+
measuredOn: "2026-11-03";
|
|
64
|
+
measuredVersion: "0.166.0";
|
|
65
|
+
provenance: "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";
|
|
66
|
+
}>;
|
|
67
|
+
}>, Readonly<{
|
|
68
|
+
target: "opencode";
|
|
69
|
+
type: 'agent';
|
|
70
|
+
displayName: "OpenCode";
|
|
71
|
+
rulesyncTarget: "opencodev1";
|
|
72
|
+
rulesyncFeatures: readonly ["rules", "mcp", "subagents", "skills", "hooks"];
|
|
73
|
+
outputs: Readonly<{
|
|
74
|
+
instruction: "AGENTS.md";
|
|
75
|
+
skills: ".opencode/skills";
|
|
76
|
+
mcp: "opencode.jsonc";
|
|
77
|
+
permissions: readonly string[];
|
|
78
|
+
}>;
|
|
79
|
+
guard: Readonly<{
|
|
80
|
+
capability: 'instructed';
|
|
81
|
+
firing: Readonly<{
|
|
82
|
+
state: 'enforced';
|
|
83
|
+
evidence: 'not_applicable';
|
|
84
|
+
measuredOn: "2026-11-01";
|
|
85
|
+
measuredVersion: "1.19.4";
|
|
86
|
+
provenance: "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";
|
|
87
|
+
}>;
|
|
88
|
+
}>;
|
|
89
|
+
native: Readonly<{
|
|
90
|
+
state: 'instructed';
|
|
91
|
+
reason: "a person has not yet reviewed whether the tool loads and triggers them as written";
|
|
92
|
+
}>;
|
|
93
|
+
sessionStart: {
|
|
94
|
+
state: 'unavailable';
|
|
95
|
+
evidence: 'not_applicable';
|
|
96
|
+
measuredOn: string;
|
|
97
|
+
measuredVersion: null;
|
|
98
|
+
provenance: string;
|
|
99
|
+
};
|
|
100
|
+
}>, Readonly<{
|
|
101
|
+
target: "cursor";
|
|
102
|
+
type: 'agent';
|
|
103
|
+
displayName: "Cursor";
|
|
104
|
+
rulesyncTarget: "cursor";
|
|
105
|
+
rulesyncFeatures: readonly ["rules", "mcp", "subagents", "skills", "hooks", "permissions"];
|
|
106
|
+
outputs: Readonly<{
|
|
107
|
+
instruction: ".cursor/rules";
|
|
108
|
+
skills: ".cursor/skills";
|
|
109
|
+
mcp: ".cursor/mcp.json";
|
|
110
|
+
permissions: readonly string[];
|
|
111
|
+
}>;
|
|
112
|
+
guard: Readonly<{
|
|
113
|
+
capability: 'unavailable';
|
|
114
|
+
measuredOn: "2026-10-28";
|
|
115
|
+
reason: "no guard adapter is checked in, so cockpit generates no guard hook";
|
|
116
|
+
}>;
|
|
117
|
+
native: Readonly<{
|
|
118
|
+
state: 'generated';
|
|
119
|
+
measuredOn: "2026-10-28";
|
|
120
|
+
reason: "cockpit writes the files, but no run of the tool beside a broken control has shown that it loads them";
|
|
121
|
+
}>;
|
|
122
|
+
sessionStart: {
|
|
123
|
+
state: 'unavailable';
|
|
124
|
+
evidence: 'not_applicable';
|
|
125
|
+
measuredOn: string;
|
|
126
|
+
measuredVersion: null;
|
|
127
|
+
provenance: string;
|
|
128
|
+
};
|
|
129
|
+
}>, Readonly<{
|
|
130
|
+
target: "grokcli";
|
|
131
|
+
type: 'agent';
|
|
132
|
+
displayName: "Grok Build";
|
|
133
|
+
rulesyncTarget: "grokcli";
|
|
134
|
+
rulesyncFeatures: readonly ["rules", "mcp", "subagents", "skills", "hooks", "permissions"];
|
|
135
|
+
outputs: Readonly<{
|
|
136
|
+
instruction: "AGENTS.md";
|
|
137
|
+
skills: ".grok/skills";
|
|
138
|
+
mcp: ".grok/config.toml";
|
|
139
|
+
permissions: readonly string[];
|
|
140
|
+
}>;
|
|
141
|
+
guard: Readonly<{
|
|
142
|
+
capability: 'unavailable';
|
|
143
|
+
measuredOn: "2026-10-28";
|
|
144
|
+
reason: "no guard adapter is checked in, so cockpit generates no guard hook";
|
|
145
|
+
}>;
|
|
146
|
+
native: Readonly<{
|
|
147
|
+
state: 'generated';
|
|
148
|
+
measuredOn: "2026-10-28";
|
|
149
|
+
reason: "cockpit writes the files, but no run of the tool beside a broken control has shown that it loads them";
|
|
150
|
+
}>;
|
|
151
|
+
sessionStart: {
|
|
152
|
+
state: 'unavailable';
|
|
153
|
+
evidence: 'not_applicable';
|
|
154
|
+
measuredOn: string;
|
|
155
|
+
measuredVersion: null;
|
|
156
|
+
provenance: string;
|
|
157
|
+
};
|
|
158
|
+
}>];
|
|
159
|
+
export type HarnessRegistryRow = typeof HARNESS_REGISTRY[number];
|
|
160
|
+
export type SurfaceTarget = HarnessRegistryRow['target'];
|
|
161
|
+
export type GuardSurfaceTarget = Extract<HarnessRegistryRow, {
|
|
162
|
+
readonly guard: {
|
|
163
|
+
readonly capability: 'instructed';
|
|
164
|
+
};
|
|
165
|
+
}>['target'];
|
|
166
|
+
export declare const SURFACE_TARGETS: readonly SurfaceTarget[];
|
|
167
|
+
export declare const GUARD_SURFACE_TARGETS: readonly GuardSurfaceTarget[];
|
|
168
|
+
export declare const DEFAULT_SURFACE_TOOLS: readonly ("claudecode" | "codexcli" | "opencode")[];
|
|
169
|
+
/** The listed tools in registry order, so rendering and drift do not depend on how the list is spelled. */
|
|
170
|
+
export declare function listedSurfaceTargets(tools: readonly string[]): readonly SurfaceTarget[];
|
|
171
|
+
export declare const SURFACE_EXECUTION_CONTROL_PATHS: readonly string[];
|
|
172
|
+
export declare const SURFACE_PUBLICATION_ANCHORS: readonly string[];
|
|
173
|
+
export declare function isGuardSurfaceTarget(value: unknown): value is GuardSurfaceTarget;
|
|
174
|
+
/**
|
|
175
|
+
* A date a capability was measured on: a real calendar day, written YYYY-MM-DD, that is not still
|
|
176
|
+
* to come. Re-measuring a tool changes its date here and in the rows that quote it; the shape is
|
|
177
|
+
* checked rather than pinned to one literal, so a new run does not mean editing a type and a guard.
|
|
178
|
+
*/
|
|
179
|
+
export declare function isMeasurementDate(value: unknown, today?: Date): value is string;
|
|
180
|
+
export declare function harnessFact(target: SurfaceTarget): HarnessRegistryRow;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Guard firing and instruction loading for these harnesses are dated runs (the firing records below
|
|
2
|
+
// and the projection's instruction capabilities), so this record claims no pending measurement for
|
|
3
|
+
// them; what stays open is a person's review of the rendered skills.
|
|
4
|
+
const reviewedNative = Object.freeze({
|
|
5
|
+
state: 'instructed',
|
|
6
|
+
reason: 'a person has not yet reviewed whether the tool loads and triggers them as written',
|
|
7
|
+
});
|
|
8
|
+
// Cockpit writes rules, skills, MCP and permission files for these harnesses through the
|
|
9
|
+
// generator, so `unavailable` (nothing emitted) would be false; nothing yet shows the tool reads them.
|
|
10
|
+
const generatedNative = Object.freeze({
|
|
11
|
+
state: 'generated',
|
|
12
|
+
measuredOn: '2026-10-28',
|
|
13
|
+
reason: 'cockpit writes the files, but no run of the tool beside a broken control has shown that it loads them',
|
|
14
|
+
});
|
|
15
|
+
const noGuardAdapter = Object.freeze({
|
|
16
|
+
capability: 'unavailable',
|
|
17
|
+
measuredOn: '2026-10-28',
|
|
18
|
+
reason: 'no guard adapter is checked in, so cockpit generates no guard hook',
|
|
19
|
+
});
|
|
20
|
+
// A dated run of the real tool against the generated guard. It replaces the adapter's own record in
|
|
21
|
+
// the capability matrix only while that adapter stays native; the adapter keeps recording the
|
|
22
|
+
// transport shape it was written against.
|
|
23
|
+
const claudeFiring = Object.freeze({
|
|
24
|
+
state: 'enforced', evidence: 'not_applicable', measuredOn: '2026-11-01', measuredVersion: '2.1.316',
|
|
25
|
+
provenance: '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',
|
|
26
|
+
});
|
|
27
|
+
const codexFiring = Object.freeze({
|
|
28
|
+
state: 'enforced', evidence: 'human_needed', measuredOn: '2026-11-01', measuredVersion: '0.165.0',
|
|
29
|
+
provenance: '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',
|
|
30
|
+
});
|
|
31
|
+
const openCodeFiring = Object.freeze({
|
|
32
|
+
state: 'enforced', evidence: 'not_applicable', measuredOn: '2026-11-01', measuredVersion: '1.19.4',
|
|
33
|
+
provenance: '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',
|
|
34
|
+
});
|
|
35
|
+
// The session-start drift check is guidance: the tool runs `cockpit check` as a session starts and
|
|
36
|
+
// shows its report to the agent, which nothing makes act on it. Each run below put a marker line from
|
|
37
|
+
// the hook into the agent's context, beside a misspelled event that never ran.
|
|
38
|
+
const claudeSessionStart = Object.freeze({
|
|
39
|
+
state: 'instructed', evidence: 'not_applicable', measuredOn: '2026-11-03', measuredVersion: '2.1.317',
|
|
40
|
+
provenance: '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',
|
|
41
|
+
});
|
|
42
|
+
const codexSessionStart = Object.freeze({
|
|
43
|
+
state: 'instructed', evidence: 'human_needed', measuredOn: '2026-11-03', measuredVersion: '0.166.0',
|
|
44
|
+
provenance: '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',
|
|
45
|
+
});
|
|
46
|
+
const noSessionStart = (reason) => ({ state: 'unavailable', evidence: 'not_applicable', measuredOn: '2026-11-03', measuredVersion: null, provenance: reason });
|
|
47
|
+
const completeRulesyncFeatures = Object.freeze(['rules', 'mcp', 'subagents', 'skills', 'hooks', 'permissions']);
|
|
48
|
+
const nonPermissionRulesyncFeatures = Object.freeze(['rules', 'mcp', 'subagents', 'skills', 'hooks']);
|
|
49
|
+
export const HARNESS_REGISTRY = Object.freeze([
|
|
50
|
+
Object.freeze({
|
|
51
|
+
target: 'claudecode', type: 'agent', displayName: 'Claude Code', rulesyncTarget: 'claudecode',
|
|
52
|
+
rulesyncFeatures: completeRulesyncFeatures,
|
|
53
|
+
outputs: Object.freeze({ instruction: 'CLAUDE.md', skills: '.claude/skills', mcp: '.mcp.json', permissions: Object.freeze(['.claude/settings.json']) }),
|
|
54
|
+
guard: Object.freeze({ capability: 'instructed', firing: claudeFiring }), native: reviewedNative, sessionStart: claudeSessionStart,
|
|
55
|
+
}),
|
|
56
|
+
Object.freeze({
|
|
57
|
+
target: 'codexcli', type: 'agent', displayName: 'Codex', rulesyncTarget: 'codexcli',
|
|
58
|
+
rulesyncFeatures: completeRulesyncFeatures,
|
|
59
|
+
outputs: Object.freeze({ instruction: 'AGENTS.md', skills: '.agents/skills', mcp: '.codex/config.toml', permissions: Object.freeze(['.codex/config.toml']) }),
|
|
60
|
+
guard: Object.freeze({ capability: 'instructed', firing: codexFiring }), native: reviewedNative, sessionStart: codexSessionStart,
|
|
61
|
+
}),
|
|
62
|
+
Object.freeze({
|
|
63
|
+
target: 'opencode', type: 'agent', displayName: 'OpenCode', rulesyncTarget: 'opencodev1',
|
|
64
|
+
rulesyncFeatures: nonPermissionRulesyncFeatures,
|
|
65
|
+
outputs: Object.freeze({ instruction: 'AGENTS.md', skills: '.opencode/skills', mcp: 'opencode.jsonc', permissions: Object.freeze(['opencode.jsonc']) }),
|
|
66
|
+
guard: Object.freeze({ capability: 'instructed', firing: openCodeFiring }), native: reviewedNative,
|
|
67
|
+
// OpenCode 1.19.5 delivered session.created to a project plugin beside a misspelled hook that
|
|
68
|
+
// never ran, but its model call failed, so no plugin hook was shown to put text in the agent's context.
|
|
69
|
+
sessionStart: noSessionStart('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'),
|
|
70
|
+
}),
|
|
71
|
+
Object.freeze({
|
|
72
|
+
target: 'cursor', type: 'agent', displayName: 'Cursor', rulesyncTarget: 'cursor',
|
|
73
|
+
rulesyncFeatures: completeRulesyncFeatures,
|
|
74
|
+
// Rules overlays only: cockpit's own instructions reach Cursor through the root AGENTS.md.
|
|
75
|
+
outputs: Object.freeze({ instruction: '.cursor/rules', skills: '.cursor/skills', mcp: '.cursor/mcp.json', permissions: Object.freeze(['.cursor/cli.json']) }),
|
|
76
|
+
guard: noGuardAdapter, native: generatedNative, sessionStart: noSessionStart('cockpit generates no hook for this tool'),
|
|
77
|
+
}),
|
|
78
|
+
Object.freeze({
|
|
79
|
+
target: 'grokcli', type: 'agent', displayName: 'Grok Build', rulesyncTarget: 'grokcli',
|
|
80
|
+
rulesyncFeatures: completeRulesyncFeatures,
|
|
81
|
+
outputs: Object.freeze({ instruction: 'AGENTS.md', skills: '.grok/skills', mcp: '.grok/config.toml', permissions: Object.freeze(['.grok/config.toml']) }),
|
|
82
|
+
guard: noGuardAdapter, native: generatedNative, sessionStart: noSessionStart('cockpit generates no hook for this tool'),
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
export const SURFACE_TARGETS = Object.freeze(HARNESS_REGISTRY.map(row => row.target));
|
|
86
|
+
export const GUARD_SURFACE_TARGETS = Object.freeze(HARNESS_REGISTRY
|
|
87
|
+
.filter((row) => row.guard.capability === 'instructed')
|
|
88
|
+
.map(row => row.target));
|
|
89
|
+
// The tools a cockpit renders when cockpit.toml lists none: the ones whose guard firing is a dated run
|
|
90
|
+
// of the real tool. A tool the matrix has not measured is rendered only when a team lists it.
|
|
91
|
+
export const DEFAULT_SURFACE_TOOLS = GUARD_SURFACE_TARGETS;
|
|
92
|
+
/** The listed tools in registry order, so rendering and drift do not depend on how the list is spelled. */
|
|
93
|
+
export function listedSurfaceTargets(tools) {
|
|
94
|
+
return SURFACE_TARGETS.filter(target => tools.includes(target));
|
|
95
|
+
}
|
|
96
|
+
// Native MCP and permission outputs can select executable programs or relax harness authority.
|
|
97
|
+
// The guard derives its protected files from this owner so a new harness cannot omit the boundary.
|
|
98
|
+
export const SURFACE_EXECUTION_CONTROL_PATHS = Object.freeze([...new Set(HARNESS_REGISTRY.flatMap(row => [row.outputs.mcp, ...row.outputs.permissions]))].sort());
|
|
99
|
+
export const SURFACE_PUBLICATION_ANCHORS = Object.freeze([...new Set(HARNESS_REGISTRY.flatMap(row => [row.outputs.instruction, row.outputs.skills, row.outputs.mcp, ...row.outputs.permissions]
|
|
100
|
+
.filter(path => path.includes('/')).map(path => path.split('/')[0]).filter(path => path.startsWith('.'))))].sort());
|
|
101
|
+
export function isGuardSurfaceTarget(value) {
|
|
102
|
+
return typeof value === 'string' && GUARD_SURFACE_TARGETS.some(target => target === value);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A date a capability was measured on: a real calendar day, written YYYY-MM-DD, that is not still
|
|
106
|
+
* to come. Re-measuring a tool changes its date here and in the rows that quote it; the shape is
|
|
107
|
+
* checked rather than pinned to one literal, so a new run does not mean editing a type and a guard.
|
|
108
|
+
*/
|
|
109
|
+
export function isMeasurementDate(value, today = new Date()) {
|
|
110
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
111
|
+
return false;
|
|
112
|
+
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
113
|
+
return !Number.isNaN(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value && value <= today.toISOString().slice(0, 10);
|
|
114
|
+
}
|
|
115
|
+
export function harnessFact(target) {
|
|
116
|
+
return HARNESS_REGISTRY.find(row => row.target === target);
|
|
117
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type SurfaceSourceFile, type SurfaceTarget } from './contract.js';
|
|
3
|
+
declare const Source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4
|
+
kind: z.ZodLiteral<"core">;
|
|
5
|
+
path: z.ZodString;
|
|
6
|
+
sha256: z.ZodString;
|
|
7
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
8
|
+
kind: z.ZodLiteral<"overlay">;
|
|
9
|
+
path: z.ZodString;
|
|
10
|
+
sha256: z.ZodString;
|
|
11
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
12
|
+
kind: z.ZodLiteral<"sibling">;
|
|
13
|
+
path: z.ZodString;
|
|
14
|
+
sha256: z.ZodString;
|
|
15
|
+
slug: z.ZodString;
|
|
16
|
+
}, z.core.$strict>], "kind">;
|
|
17
|
+
declare const Declaration: z.ZodObject<{
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
command: z.ZodString;
|
|
20
|
+
args: z.ZodArray<z.ZodString>;
|
|
21
|
+
cwd: z.ZodString;
|
|
22
|
+
envNames: z.ZodArray<z.ZodString>;
|
|
23
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
24
|
+
kind: z.ZodLiteral<"core">;
|
|
25
|
+
path: z.ZodString;
|
|
26
|
+
sha256: z.ZodString;
|
|
27
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
28
|
+
kind: z.ZodLiteral<"overlay">;
|
|
29
|
+
path: z.ZodString;
|
|
30
|
+
sha256: z.ZodString;
|
|
31
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
32
|
+
kind: z.ZodLiteral<"sibling">;
|
|
33
|
+
path: z.ZodString;
|
|
34
|
+
sha256: z.ZodString;
|
|
35
|
+
slug: z.ZodString;
|
|
36
|
+
}, z.core.$strict>], "kind">;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
export type McpSource = z.infer<typeof Source>;
|
|
39
|
+
export type McpDeclaration = z.infer<typeof Declaration>;
|
|
40
|
+
export interface McpDiagnostic {
|
|
41
|
+
readonly code: 'MCP_INPUT_INVALID' | 'MCP_DECLARATION_INVALID' | 'MCP_COMMAND_CONFLICT' | 'MCP_NOT_APPROVED' | 'MCP_APPROVAL_UNKNOWN';
|
|
42
|
+
readonly message: string;
|
|
43
|
+
readonly server?: string;
|
|
44
|
+
readonly sources?: readonly McpSource[];
|
|
45
|
+
/** The approval digest of the server's current declaration, on MCP_NOT_APPROVED. */
|
|
46
|
+
readonly sha256?: string;
|
|
47
|
+
}
|
|
48
|
+
interface McpServer {
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly command: string;
|
|
51
|
+
readonly args: readonly string[];
|
|
52
|
+
readonly cwd: string;
|
|
53
|
+
readonly envNames: readonly string[];
|
|
54
|
+
readonly enabled: boolean;
|
|
55
|
+
readonly sources: readonly McpSource[];
|
|
56
|
+
}
|
|
57
|
+
export interface McpCollection {
|
|
58
|
+
readonly servers: readonly McpServer[];
|
|
59
|
+
readonly wrappers: readonly {
|
|
60
|
+
readonly path: string;
|
|
61
|
+
readonly bytes: string;
|
|
62
|
+
readonly sha256: string;
|
|
63
|
+
}[];
|
|
64
|
+
readonly harnesses: readonly {
|
|
65
|
+
readonly target: SurfaceTarget;
|
|
66
|
+
readonly servers: readonly {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly command: string;
|
|
69
|
+
readonly args: readonly string[];
|
|
70
|
+
readonly enabled: boolean;
|
|
71
|
+
}[];
|
|
72
|
+
}[];
|
|
73
|
+
}
|
|
74
|
+
export type McpCollectionOutcome = Readonly<{
|
|
75
|
+
kind: 'complete';
|
|
76
|
+
result: McpCollection;
|
|
77
|
+
diagnostics: readonly McpDiagnostic[];
|
|
78
|
+
}> | Readonly<{
|
|
79
|
+
kind: 'refusal';
|
|
80
|
+
diagnostic: McpDiagnostic;
|
|
81
|
+
}>;
|
|
82
|
+
export type McpParseOutcome = Readonly<{
|
|
83
|
+
kind: 'complete';
|
|
84
|
+
declarations: readonly McpDeclaration[];
|
|
85
|
+
}> | Readonly<{
|
|
86
|
+
kind: 'refusal';
|
|
87
|
+
diagnostic: McpDiagnostic;
|
|
88
|
+
}>;
|
|
89
|
+
type McpIdentity = Pick<McpDeclaration, 'command' | 'args' | 'cwd' | 'envNames'>;
|
|
90
|
+
/** The digest `[mcp.approved]` records for a server, so a changed declaration is no longer approved. */
|
|
91
|
+
export declare function mcpApprovalDigest(declaration: McpIdentity, cockpitDirectory: string): string;
|
|
92
|
+
export declare function collectMcpDeclarations(input: unknown): McpCollectionOutcome;
|
|
93
|
+
export declare function hasTargetMcpServers(sources: readonly SurfaceSourceFile[], target: SurfaceTarget): boolean;
|
|
94
|
+
export declare function parseMcpDeclarationFile(input: unknown): McpParseOutcome;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { parseDocument } from 'yaml';
|
|
5
|
+
import { INHERITED_TOOL_ENVIRONMENT, snapshotJson, tryParseJson } from '../environment/contract.js';
|
|
6
|
+
import { reservedPathSegment, SURFACE_TARGETS } from './contract.js';
|
|
7
|
+
import { harnessFact } from './harness-registry.js';
|
|
8
|
+
const EnvironmentName = z.string().regex(/^[A-Z][A-Z0-9_]*$/).max(128);
|
|
9
|
+
const ServerName = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/).max(128);
|
|
10
|
+
const RelativePath = z.string().min(1).max(4096).refine(path => !isAbsolute(path)
|
|
11
|
+
&& !/[\\\x00-\x1f\x7f]/.test(path) && !/^[a-z]:/i.test(path)
|
|
12
|
+
&& path.split('/').every(part => part !== '' && part !== '.' && part !== '..' && !reservedPathSegment(part)));
|
|
13
|
+
const ShellToken = z.string().min(1).max(4096).refine(value => !/[\x00-\x1f\x7f]|\$\(|\$\{|`/.test(value));
|
|
14
|
+
const WorkingDirectory = ShellToken.refine(isAbsolute);
|
|
15
|
+
const Sha256 = z.string().regex(/^[a-f0-9]{64}$/);
|
|
16
|
+
const Source = z.discriminatedUnion('kind', [
|
|
17
|
+
z.object({ kind: z.literal('core'), path: RelativePath, sha256: Sha256 }).strict(),
|
|
18
|
+
z.object({ kind: z.literal('overlay'), path: RelativePath, sha256: Sha256 }).strict(),
|
|
19
|
+
z.object({ kind: z.literal('sibling'), path: RelativePath, sha256: Sha256, slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/) }).strict(),
|
|
20
|
+
]);
|
|
21
|
+
const Argument = ShellToken.refine(value => !/^--?(?:api[-_]?key|token|password|secret|authorization|credential)(?:=|$)/i.test(value)
|
|
22
|
+
&& !/^[a-z][a-z0-9+.-]*:\/\/[^/]*@/i.test(value));
|
|
23
|
+
const Declaration = z.object({
|
|
24
|
+
name: ServerName,
|
|
25
|
+
command: ShellToken.refine(value => !value.startsWith('-')),
|
|
26
|
+
args: z.array(Argument).max(256),
|
|
27
|
+
cwd: WorkingDirectory,
|
|
28
|
+
envNames: z.array(EnvironmentName).max(128).refine(names => new Set(names).size === names.length),
|
|
29
|
+
source: Source,
|
|
30
|
+
}).strict();
|
|
31
|
+
const Input = z.object({
|
|
32
|
+
cockpitDirectory: WorkingDirectory,
|
|
33
|
+
declarations: z.array(Declaration).max(256),
|
|
34
|
+
approved: z.record(ServerName, Sha256).refine(approved => Object.keys(approved).length <= 256),
|
|
35
|
+
targets: z.array(z.enum(SURFACE_TARGETS)).min(1).max(SURFACE_TARGETS.length).refine(targets => new Set(targets).size === targets.length),
|
|
36
|
+
}).strict();
|
|
37
|
+
function refuse(code, message) {
|
|
38
|
+
return { kind: 'refusal', diagnostic: { code, message } };
|
|
39
|
+
}
|
|
40
|
+
function quote(value) {
|
|
41
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
42
|
+
}
|
|
43
|
+
// Each harness starts the wrapper as `.cockpit/mcp/<id>.sh` from the directory the agent was
|
|
44
|
+
// launched in, so it runs only from the cockpit root (the guard hooks require the same) and the
|
|
45
|
+
// server's directory is relative to it: the committed bytes are the same in every clone. The `./`
|
|
46
|
+
// keeps CDPATH from choosing another directory and printing it into the JSON-RPC stream, and `-P`
|
|
47
|
+
// resolves `..` from the real root, against which the relative path was computed.
|
|
48
|
+
function wrapper(server, cockpitDirectory) {
|
|
49
|
+
const inherited = ['PATH', ...INHERITED_TOOL_ENVIRONMENT].filter(name => !server.envNames.includes(name));
|
|
50
|
+
return [
|
|
51
|
+
'#!/bin/sh',
|
|
52
|
+
'set -eu',
|
|
53
|
+
`cd -P -- ${quote(`./${relative(cockpitDirectory, server.cwd) || '.'}`)}`,
|
|
54
|
+
`executable=$(command -v ${quote(server.command)}) || { printf '%s\\n' 'MCP_EXECUTABLE_UNAVAILABLE' >&2; exit 2; }`,
|
|
55
|
+
'[ -f "$executable" ] && [ -x "$executable" ] || { printf \'%s\\n\' \'MCP_EXECUTABLE_UNAVAILABLE\' >&2; exit 2; }',
|
|
56
|
+
// `npx`, `uvx` and most npm servers are `#!/usr/bin/env node` scripts: without PATH they
|
|
57
|
+
// cannot start. The rest of the inherited set carries caches and locale, never a credential.
|
|
58
|
+
`exec /usr/bin/env -i ${inherited.map(name => `\${${name}+"${name}=$${name}"}`).join(' ')}`
|
|
59
|
+
+ `${server.envNames.map(name => ` ${name}="\${${name}?required environment name ${name} is unset}"`).join('')} "$executable"${server.args.map(arg => ` ${quote(arg)}`).join('')}`,
|
|
60
|
+
'',
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
// What a human reviews before approving a server: everything the wrapper runs. The working
|
|
64
|
+
// directory is taken relative to the cockpit so the digest is the same in every clone.
|
|
65
|
+
function identity(declaration, cockpitDirectory) {
|
|
66
|
+
return JSON.stringify({ command: declaration.command, args: declaration.args, cwd: relative(cockpitDirectory, declaration.cwd) || '.', envNames: [...declaration.envNames].sort() });
|
|
67
|
+
}
|
|
68
|
+
/** The digest `[mcp.approved]` records for a server, so a changed declaration is no longer approved. */
|
|
69
|
+
export function mcpApprovalDigest(declaration, cockpitDirectory) {
|
|
70
|
+
return createHash('sha256').update(identity(declaration, cockpitDirectory)).digest('hex');
|
|
71
|
+
}
|
|
72
|
+
export function collectMcpDeclarations(input) {
|
|
73
|
+
const captured = snapshotJson(input);
|
|
74
|
+
const parsed = Input.safeParse(captured);
|
|
75
|
+
if (!parsed.success)
|
|
76
|
+
return refuse('MCP_INPUT_INVALID', 'MCP declarations, approvals and targets must be bounded canonical data');
|
|
77
|
+
const { approved, cockpitDirectory } = parsed.data;
|
|
78
|
+
const collected = new Map();
|
|
79
|
+
for (const declaration of parsed.data.declarations) {
|
|
80
|
+
const prior = collected.get(declaration.name);
|
|
81
|
+
if (prior && identity(prior.declaration, cockpitDirectory) !== identity(declaration, cockpitDirectory)) {
|
|
82
|
+
const conflict = snapshotJson({ kind: 'refusal', diagnostic: {
|
|
83
|
+
code: 'MCP_COMMAND_CONFLICT', server: declaration.name,
|
|
84
|
+
message: 'MCP server identity has divergent command, arguments, working directory or environment names',
|
|
85
|
+
sources: [...prior.sources, declaration.source],
|
|
86
|
+
} });
|
|
87
|
+
return conflict ? conflict
|
|
88
|
+
: refuse('MCP_INPUT_INVALID', 'MCP conflict exceeds the bounded data budget');
|
|
89
|
+
}
|
|
90
|
+
if (prior) {
|
|
91
|
+
if (!prior.sources.some(source => JSON.stringify(source) === JSON.stringify(declaration.source)))
|
|
92
|
+
prior.sources.push(declaration.source);
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
collected.set(declaration.name, { declaration, sources: [declaration.source] });
|
|
96
|
+
}
|
|
97
|
+
const digests = new Map([...collected].map(([name, { declaration }]) => [name, mcpApprovalDigest(declaration, cockpitDirectory)]));
|
|
98
|
+
const servers = [...collected.values()].map(({ declaration, sources }) => ({
|
|
99
|
+
name: declaration.name, command: declaration.command, args: declaration.args, cwd: declaration.cwd,
|
|
100
|
+
envNames: [...declaration.envNames].sort(), enabled: Object.hasOwn(approved, declaration.name) && approved[declaration.name] === digests.get(declaration.name),
|
|
101
|
+
sources: sources.sort((left, right) => JSON.stringify(left) < JSON.stringify(right) ? -1 : JSON.stringify(left) > JSON.stringify(right) ? 1 : 0),
|
|
102
|
+
})).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
103
|
+
const diagnostics = servers.filter(server => !server.enabled).map(server => ({
|
|
104
|
+
code: 'MCP_NOT_APPROVED', server: server.name, sources: server.sources, sha256: digests.get(server.name),
|
|
105
|
+
message: 'MCP server has no shared approval for its current declaration and remains disabled',
|
|
106
|
+
}));
|
|
107
|
+
for (const name of Object.keys(approved).sort())
|
|
108
|
+
if (!collected.has(name))
|
|
109
|
+
diagnostics.push({
|
|
110
|
+
code: 'MCP_APPROVAL_UNKNOWN', server: name, message: 'The shared approval list names an undeclared MCP server',
|
|
111
|
+
});
|
|
112
|
+
const wrappers = servers.map(server => {
|
|
113
|
+
const bytes = wrapper(server, cockpitDirectory);
|
|
114
|
+
return { path: `.cockpit/mcp/${createHash('sha256').update(server.name).digest('hex')}.sh`, bytes, sha256: createHash('sha256').update(bytes).digest('hex') };
|
|
115
|
+
});
|
|
116
|
+
const result = {
|
|
117
|
+
servers, wrappers,
|
|
118
|
+
harnesses: parsed.data.targets.map(target => ({ target, servers: servers.map((server, index) => ({
|
|
119
|
+
name: server.name, command: '/bin/sh', args: [wrappers[index].path], enabled: server.enabled,
|
|
120
|
+
})) })),
|
|
121
|
+
};
|
|
122
|
+
const outcome = snapshotJson({ kind: 'complete', result, diagnostics });
|
|
123
|
+
return outcome ? outcome : refuse('MCP_INPUT_INVALID', 'Canonical MCP output exceeds the bounded data budget');
|
|
124
|
+
}
|
|
125
|
+
const NativeServer = z.object({
|
|
126
|
+
type: z.literal('stdio').optional(), command: Declaration.shape.command,
|
|
127
|
+
args: z.array(Argument).max(256).default([]), cwd: RelativePath.or(z.literal('.')).optional(),
|
|
128
|
+
env: z.record(EnvironmentName, z.string().max(128)).default({}),
|
|
129
|
+
}).strict();
|
|
130
|
+
// Absence is inferred only from cockpit's neutral shape; other source forms retain generator validation.
|
|
131
|
+
const GeneratedServer = NativeServer.extend({ cwd: WorkingDirectory.optional(), disabled: z.boolean().optional() });
|
|
132
|
+
const GeneratedServers = z.record(ServerName, GeneratedServer);
|
|
133
|
+
const TargetServers = z.object({ mcpServers: z.record(ServerName, GeneratedServer.nullable()).optional() }).strict();
|
|
134
|
+
export function hasTargetMcpServers(sources, target) {
|
|
135
|
+
const source = sources.find(candidate => candidate.path === 'mcp.jsonc');
|
|
136
|
+
if (!source)
|
|
137
|
+
return false;
|
|
138
|
+
try {
|
|
139
|
+
const document = parseDocument(source.bytes, { uniqueKeys: true });
|
|
140
|
+
const root = z.record(z.string(), z.unknown()).safeParse(JSON.parse(source.bytes));
|
|
141
|
+
if (document.errors.length > 0 || document.warnings.length > 0 || !root.success)
|
|
142
|
+
return true;
|
|
143
|
+
const shared = GeneratedServers.safeParse(root.data.mcpServers);
|
|
144
|
+
const targetNames = SURFACE_TARGETS.map(candidate => harnessFact(candidate).rulesyncTarget);
|
|
145
|
+
if (!shared.success || Object.entries(root.data).some(([name, value]) => name !== 'mcpServers'
|
|
146
|
+
&& (!targetNames.some(candidate => candidate === name) || !TargetServers.safeParse(value).success)))
|
|
147
|
+
return true;
|
|
148
|
+
const override = TargetServers.safeParse(root.data[harnessFact(target).rulesyncTarget] ?? {});
|
|
149
|
+
if (!override.success)
|
|
150
|
+
return true;
|
|
151
|
+
return Object.values({ ...shared.data, ...override.data.mcpServers }).some(server => server !== null);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Unknown syntax and malformed declarations still reach Rulesync's authenticated parser.
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const NativeFile = z.object({ mcpServers: z.record(ServerName, NativeServer).refine(servers => Object.keys(servers).length <= 256) }).strict();
|
|
159
|
+
const ParseInput = z.object({ bytes: z.string(), source: Source, workingDirectory: WorkingDirectory }).strict();
|
|
160
|
+
export function parseMcpDeclarationFile(input) {
|
|
161
|
+
const parsed = ParseInput.safeParse(snapshotJson(input));
|
|
162
|
+
if (!parsed.success)
|
|
163
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP source must be bounded authenticated declaration bytes');
|
|
164
|
+
const { bytes, source, workingDirectory } = parsed.data;
|
|
165
|
+
if (createHash('sha256').update(bytes).digest('hex') !== source.sha256)
|
|
166
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP source digest does not match declaration bytes');
|
|
167
|
+
const value = tryParseJson(bytes);
|
|
168
|
+
if (value === undefined)
|
|
169
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP declaration is not valid JSON');
|
|
170
|
+
const file = NativeFile.safeParse(value);
|
|
171
|
+
if (!file.success)
|
|
172
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP declaration contains unsupported command, environment or transport fields');
|
|
173
|
+
const declarations = [];
|
|
174
|
+
for (const [name, server] of Object.entries(file.data.mcpServers)) {
|
|
175
|
+
if (Object.entries(server.env).some(([key, reference]) => reference !== `\${${key}}`)) {
|
|
176
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP environment entries must reference their same declared environment name');
|
|
177
|
+
}
|
|
178
|
+
const cwd = resolve(workingDirectory, server.cwd ?? '.');
|
|
179
|
+
const offset = relative(workingDirectory, cwd);
|
|
180
|
+
if (offset === '..' || offset.startsWith('../') || isAbsolute(offset))
|
|
181
|
+
return refuse('MCP_DECLARATION_INVALID', 'MCP working directory escapes its declared sibling');
|
|
182
|
+
declarations.push({ name, command: server.command, args: server.args, cwd, envNames: Object.keys(server.env).sort(), source });
|
|
183
|
+
}
|
|
184
|
+
const captured = snapshotJson({ kind: 'complete', declarations });
|
|
185
|
+
return captured ? captured : refuse('MCP_DECLARATION_INVALID', 'MCP declarations exceed the bounded data budget');
|
|
186
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type SurfaceDeclaration as SurfaceDeclarationValue } from '../schema.js';
|
|
2
|
+
import type { SurfaceFeature } from './contract.js';
|
|
3
|
+
export interface ActiveSurfaceOverride {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly feature: SurfaceFeature;
|
|
6
|
+
readonly originalSha256: string;
|
|
7
|
+
readonly recordedSha256: string;
|
|
8
|
+
readonly overlaySource: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SurfaceUpgradeFinding {
|
|
11
|
+
readonly code: 'OVERRIDDEN_CORE_CHANGED';
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly recordedSha256: string;
|
|
14
|
+
readonly currentSha256: string;
|
|
15
|
+
readonly overlaySource: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SurfaceMergeComplete {
|
|
18
|
+
readonly kind: 'complete';
|
|
19
|
+
readonly surfaces: readonly SurfaceDeclarationValue[];
|
|
20
|
+
readonly overrides: readonly ActiveSurfaceOverride[];
|
|
21
|
+
readonly findings: readonly SurfaceUpgradeFinding[];
|
|
22
|
+
}
|
|
23
|
+
export interface SurfaceMergeRefusal {
|
|
24
|
+
readonly kind: 'refusal';
|
|
25
|
+
readonly code: 'SURFACE_INPUT_INVALID' | 'SURFACE_NAME_DUPLICATE' | 'SURFACE_KIND_AMBIGUOUS' | 'SURFACE_OVERRIDE_INVALID' | 'SURFACE_PATH_CONFLICT';
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}
|
|
28
|
+
export type SurfaceMergeResult = SurfaceMergeComplete | SurfaceMergeRefusal;
|
|
29
|
+
export declare function surfaceDeclarationSha256(declaration: SurfaceDeclarationValue): string;
|
|
30
|
+
export declare function mergeSurfaceDeclarations(coreInput: unknown, overlayInput: unknown): SurfaceMergeResult;
|