@b00dea/cockpit 0.1.0 → 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 +99 -0
- package/README.md +3 -0
- package/cockpit.schema.json +73 -3
- package/dist/autonomy.d.ts +3 -2
- package/dist/autonomy.js +30 -13
- package/dist/browser/commands.d.ts +57 -2
- package/dist/browser/commands.js +114 -10
- package/dist/browser/runtime.d.ts +1 -0
- package/dist/browser/runtime.js +4 -1
- package/dist/browser/surfaces.d.ts +7 -7
- package/dist/browser/surfaces.js +5 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +5 -0
- package/dist/cli-main.d.ts +40 -3
- package/dist/cli-main.js +241 -62
- package/dist/command-table.d.ts +19 -11
- package/dist/command-table.js +190 -38
- package/dist/config/load.d.ts +16 -4
- package/dist/config/load.js +25 -3
- package/dist/config/unconfirmed-toml.d.ts +9 -0
- package/dist/config/unconfirmed-toml.js +29 -0
- package/dist/diagnostics.js +10 -1
- 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/environment/application/confirm.d.ts +35 -0
- package/dist/environment/application/confirm.js +122 -0
- package/dist/environment/application/destructive.js +6 -2
- package/dist/environment/application/doctor.d.ts +12 -7
- package/dist/environment/application/doctor.js +20 -8
- package/dist/environment/application/guard.d.ts +12 -6
- package/dist/environment/application/guard.js +87 -32
- package/dist/environment/application/health.d.ts +14 -5
- package/dist/environment/application/health.js +8 -2
- package/dist/environment/application/lifecycle-context.d.ts +10 -3
- package/dist/environment/application/lifecycle-state.js +4 -3
- package/dist/environment/application/lifecycle.d.ts +2 -1
- package/dist/environment/application/lifecycle.js +11 -3
- package/dist/environment/application/services.d.ts +60 -0
- package/dist/environment/application/services.js +89 -0
- package/dist/environment/application/siblings.d.ts +43 -0
- package/dist/environment/application/siblings.js +269 -0
- package/dist/environment/claims.d.ts +10 -10
- package/dist/environment/claims.js +3 -11
- package/dist/environment/compose-topology.d.ts +15 -7
- package/dist/environment/compose-topology.js +100 -23
- package/dist/environment/contract.d.ts +42 -9
- package/dist/environment/contract.js +33 -0
- package/dist/environment/executable.d.ts +12 -0
- package/dist/environment/executable.js +46 -23
- package/dist/environment/generated-application/check.js +2 -0
- package/dist/environment/generated-application/prepare.js +2 -0
- package/dist/environment/generated-application/shared.d.ts +10 -2
- package/dist/environment/generated-application/shared.js +6 -1
- package/dist/environment/generated-application/surface-outputs.js +23 -12
- package/dist/environment/generated-application/surfaces.js +16 -11
- package/dist/environment/generated-application/sync.js +19 -4
- package/dist/environment/git-observation.d.ts +25 -6
- package/dist/environment/git-observation.js +37 -28
- package/dist/environment/health-composition.d.ts +3 -2
- package/dist/environment/health-composition.js +19 -2
- package/dist/environment/init-application.d.ts +39 -14
- package/dist/environment/init-application.js +39 -10
- package/dist/environment/lifecycle-composition.d.ts +4 -2
- package/dist/environment/lifecycle-composition.js +84 -17
- package/dist/environment/lifecycle-stage.d.ts +8 -3
- package/dist/environment/lifecycle-stage.js +11 -1
- package/dist/environment/logs.d.ts +1 -1
- 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 +1 -1
- package/dist/environment/observations.js +2 -0
- package/dist/environment/operator-application.d.ts +35 -6
- package/dist/environment/operator-application.js +42 -0
- package/dist/environment/presence.d.ts +4 -0
- package/dist/environment/presence.js +26 -0
- package/dist/environment/read-composition.d.ts +8 -3
- package/dist/environment/read-composition.js +53 -8
- package/dist/environment/readiness-composition.d.ts +20 -1
- package/dist/environment/readiness-composition.js +23 -2
- package/dist/environment/reclaim.d.ts +56 -19
- package/dist/environment/reclaim.js +147 -42
- package/dist/environment/runtime.d.ts +64 -18
- package/dist/environment/runtime.js +73 -3
- package/dist/environment/served-checkout-grants.d.ts +53 -0
- package/dist/environment/served-checkout-grants.js +182 -0
- package/dist/environment/sibling-checkouts.d.ts +119 -0
- package/dist/environment/sibling-checkouts.js +254 -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/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 +48 -9
- package/dist/environment/state.js +142 -2
- package/dist/environment/tool-registration.js +3 -7
- package/dist/environment-exec/execute.d.ts +6 -3
- package/dist/environment-exec/execute.js +4 -0
- package/dist/fix-catalogue.d.ts +1 -1
- package/dist/generated-environment/adopt.js +4 -8
- package/dist/guards/answer-confirmation.d.ts +41 -0
- package/dist/guards/answer-confirmation.js +109 -0
- package/dist/guards/evaluator.d.ts +20 -3
- package/dist/guards/evaluator.js +39 -7
- package/dist/guards/local-identity.d.ts +6 -2
- package/dist/guards/local-identity.js +10 -4
- package/dist/guards/policy-change.d.ts +43 -0
- package/dist/guards/policy-change.js +159 -0
- package/dist/guards/policy-pin.d.ts +1 -1
- package/dist/guards/policy-pin.js +14 -12
- package/dist/init/command.d.ts +4 -3
- package/dist/init/command.js +36 -16
- package/dist/init/derive.d.ts +8 -6
- package/dist/init/derive.js +140 -36
- package/dist/init/draft.d.ts +13 -11
- package/dist/init/draft.js +54 -31
- package/dist/init/render.d.ts +18 -6
- package/dist/init/render.js +31 -9
- package/dist/repository-path.d.ts +9 -0
- package/dist/repository-path.js +16 -0
- package/dist/schema.d.ts +42 -75
- package/dist/schema.js +62 -11
- package/dist/surfaces/harness-registry.d.ts +80 -0
- package/dist/surfaces/harness-registry.js +29 -0
- package/dist/surfaces/mcp.d.ts +67 -17
- package/dist/surfaces/mcp.js +194 -31
- package/dist/surfaces/projection.d.ts +7 -7
- package/dist/surfaces/projection.js +41 -6
- package/dist/surfaces/render.d.ts +5 -44
- package/dist/surfaces/render.js +5 -2
- package/dist/team-metadata.js +2 -1
- package/dist/testing/test-protection.d.ts +34 -0
- package/dist/testing/test-protection.js +137 -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 +4 -97
- package/dist/work-orders/application.js +94 -254
- package/dist/work-orders/composition.d.ts +35 -58
- package/dist/work-orders/composition.js +216 -24
- package/dist/work-orders/contract.d.ts +22 -6
- package/dist/work-orders/contract.js +34 -16
- package/dist/work-orders/decisions.d.ts +23 -0
- package/dist/work-orders/decisions.js +55 -0
- package/dist/work-orders/store.d.ts +54 -5
- package/dist/work-orders/store.js +118 -24
- package/docs/README.md +6 -3
- package/docs/capability-matrix.md +17 -9
- package/docs/cli-reference.md +209 -1
- package/docs/command-list.md +12 -1
- package/docs/concepts.md +6 -4
- package/docs/configuration-essentials.md +7 -2
- package/docs/configuration-reference.md +18 -3
- package/docs/docs-check.md +76 -0
- package/docs/environment.md +194 -6
- package/docs/faq.md +5 -3
- package/docs/getting-started.md +109 -25
- package/docs/harness-surfaces.md +69 -2
- package/docs/safety-and-autonomy.md +56 -12
- package/docs/troubleshooting.md +29 -8
- package/docs/tutorial.md +3 -2
- package/docs/work-and-browser.md +180 -14
- package/libexec/browser-operation.cjs +86 -2
- package/package.json +2 -2
- package/surfaces/skills/cockpit-browser.skill.md +7 -2
- package/surfaces/skills/cockpit-explain.skill.md +1 -1
- package/surfaces/skills/cockpit-fix.skill.md +1 -1
- package/surfaces/skills/cockpit-setup.skill.md +3 -3
- package/surfaces/skills/cockpit-work.skill.md +1 -1
- package/surfaces/skills/procedures/explain.md +9 -4
- package/surfaces/skills/procedures/fix.md +25 -11
- package/surfaces/skills/procedures/setup.md +113 -40
- package/surfaces/skills/procedures/work.md +45 -16
package/dist/command-table.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ParseArgsConfig } from 'node:util';
|
|
2
|
-
import type
|
|
2
|
+
import { type GuardInvocationProfile } from './guards/evaluator.js';
|
|
3
3
|
import { GUARD_HANDLER_ARGV } from './guards/harness-adapter.js';
|
|
4
4
|
export declare function isRecordedAnswerField(value: string): boolean;
|
|
5
|
-
export type WorkHandlerIdentifier = 'work-status' | 'work-accept' | 'work-check' | 'work-note' | 'work-answer' | 'work-grade' | 'work-signoff' | 'work-recover' | 'work-verify' | 'grade-criteria' | 'browser-drive' | 'browser-screenshot' | 'test-run';
|
|
6
|
-
export type HandlerIdentifier = WorkHandlerIdentifier | 'check' | 'doctor' | 'down' | 'init' | 'logs' | 'preflight' | 'registry-resolve' | 'reset' | 'runtime' | 'setup' | 'selftest' | 'sibling' | 'tool' | 'session' | 'sleep' | 'sync' | 'up' | 'upgrade';
|
|
7
|
-
|
|
5
|
+
export type WorkHandlerIdentifier = 'work-status' | 'work-accept' | 'work-check' | 'work-note' | 'work-decide' | 'work-answer' | 'work-acknowledge' | 'work-grade' | 'work-signoff' | 'work-recover' | 'work-verify' | 'grade-criteria' | 'browser-drive' | 'browser-screenshot' | 'test-run';
|
|
6
|
+
export type HandlerIdentifier = WorkHandlerIdentifier | 'check' | 'confirm' | 'docs-check' | 'doctor' | 'down' | 'exec' | 'init' | 'logs' | 'preflight' | 'ps' | 'rebuild' | 'registry-resolve' | 'reset' | 'restart' | 'runtime' | 'setup' | 'selftest' | 'sibling' | 'tool' | 'session' | 'sleep' | 'stacks' | 'sync' | 'up' | 'upgrade' | 'use' | 'pull';
|
|
7
|
+
type CommandStage = 'set-up' | 'generate-and-verify' | 'run-the-stack' | 'diagnose' | 'test-and-grade';
|
|
8
8
|
/** The order a newcomer meets the commands in, as the README's "Commands at a glance" table names it. */
|
|
9
9
|
export declare const commandStages: readonly Readonly<{
|
|
10
10
|
id: CommandStage;
|
|
@@ -41,14 +41,14 @@ export interface CommandDefinition {
|
|
|
41
41
|
ordinary: OrdinaryCommandMetadata;
|
|
42
42
|
}
|
|
43
43
|
export declare function describePublicOption(name: string): string;
|
|
44
|
-
type OrdinaryValueRole = 'confined-path' | 'resolved-config' | 'declared-service' | 'declared-gate' | 'gate-mode' | 'gate-selector' | 'identifier' | 'label' | 'field' | 'tail' | 'since' | 'sha256' | 'surface-target' | 'local-image' | 'evaluator-tool';
|
|
45
|
-
|
|
44
|
+
type OrdinaryValueRole = 'confined-path' | 'resolved-config' | 'declared-service' | 'declared-gate' | 'gate-mode' | 'gate-selector' | 'identifier' | 'route' | 'label' | 'field' | 'tail' | 'since' | 'sha256' | 'surface-target' | 'local-image' | 'evaluator-tool' | 'compose-profile';
|
|
45
|
+
interface OrdinaryOption {
|
|
46
46
|
readonly kind: 'flag' | 'value';
|
|
47
47
|
readonly role: 'boolean' | OrdinaryValueRole;
|
|
48
|
-
readonly repetition: 'singleton';
|
|
48
|
+
readonly repetition: 'singleton' | 'repeated';
|
|
49
49
|
readonly required?: true;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
type OrdinaryCommandMetadata = Readonly<{
|
|
52
52
|
availability: 'unavailable';
|
|
53
53
|
reason: string;
|
|
54
54
|
}> | Readonly<{
|
|
@@ -58,7 +58,7 @@ export type OrdinaryCommandMetadata = Readonly<{
|
|
|
58
58
|
positional: Readonly<{
|
|
59
59
|
min: number;
|
|
60
60
|
max: number;
|
|
61
|
-
roles: readonly ['confined-root'] | readonly ['operation'] | readonly ['version'] | readonly [];
|
|
61
|
+
roles: readonly ['confined-root'] | readonly ['operation'] | readonly ['version'] | readonly ['slug', 'confined-root'] | readonly ['operation', 'surface-target'] | readonly [];
|
|
62
62
|
operations?: readonly string[];
|
|
63
63
|
}>;
|
|
64
64
|
options: Readonly<Record<string, OrdinaryOption>>;
|
|
@@ -67,11 +67,12 @@ export type OrdinaryCommandMetadata = Readonly<{
|
|
|
67
67
|
forbidden: Readonly<{
|
|
68
68
|
presence: readonly string[];
|
|
69
69
|
}>;
|
|
70
|
+
admittedWhileUnsynced?: true;
|
|
70
71
|
requires?: OrdinaryGrant;
|
|
71
72
|
}>;
|
|
72
73
|
export declare function isWorkHandler(value: HandlerIdentifier): value is WorkHandlerIdentifier;
|
|
73
74
|
export declare const commands: readonly CommandDefinition[];
|
|
74
|
-
|
|
75
|
+
interface InternalCommandDefinition {
|
|
75
76
|
readonly name: 'guard-evaluate';
|
|
76
77
|
readonly handler: 'guard-evaluate';
|
|
77
78
|
readonly visibility: 'internal';
|
|
@@ -87,7 +88,7 @@ export interface InternalCommandDefinition {
|
|
|
87
88
|
}>;
|
|
88
89
|
}
|
|
89
90
|
export declare const internalCommands: readonly InternalCommandDefinition[];
|
|
90
|
-
|
|
91
|
+
interface OrdinaryInvocationAuthority {
|
|
91
92
|
readonly executable: string;
|
|
92
93
|
readonly canonicalRoot: string;
|
|
93
94
|
readonly allowedRoots: readonly string[];
|
|
@@ -97,8 +98,14 @@ export interface OrdinaryInvocationAuthority {
|
|
|
97
98
|
readonly gates: readonly string[];
|
|
98
99
|
/** What the configuration grants an agent: browser preapproval, and the [autonomy] decisions set to agent. */
|
|
99
100
|
readonly grants: readonly OrdinaryGrant[];
|
|
101
|
+
/** Compose profiles an agent may name with --profile; any other starts services no person declared for it. */
|
|
102
|
+
readonly profiles: readonly string[];
|
|
100
103
|
}
|
|
101
104
|
export type OrdinaryGrant = 'browser-preapproval' | 'autonomy-sync' | 'autonomy-reset';
|
|
105
|
+
/** The commands the guard admits while a non-widening edit of cockpit.toml waits for sync. */
|
|
106
|
+
export declare const UNSYNCED_ADMITTED_COMMANDS: readonly string[];
|
|
107
|
+
/** The same commands as prose, for the hold that names them: "cockpit sync and check". */
|
|
108
|
+
export declare const UNSYNCED_ADMITTED_PROSE: string;
|
|
102
109
|
/**
|
|
103
110
|
* The inner command of an exact `session --config <configured file> -- <command…>` run from the
|
|
104
111
|
* cockpit root, or undefined. The guard admits the session only when it admits the inner command,
|
|
@@ -107,6 +114,7 @@ export type OrdinaryGrant = 'browser-preapproval' | 'autonomy-sync' | 'autonomy-
|
|
|
107
114
|
export declare function ordinarySessionCommand(argv: readonly string[], cwd: string, authority: Readonly<{
|
|
108
115
|
canonicalRoot: string;
|
|
109
116
|
configPath: string;
|
|
117
|
+
profiles: readonly string[];
|
|
110
118
|
}>): readonly string[] | undefined;
|
|
111
119
|
/** Classify one exact candidate; the caller authenticates filesystem and executable authority. */
|
|
112
120
|
export declare function resolveOrdinaryInvocation(input: GuardInvocationProfile, authority: OrdinaryInvocationAuthority, inspectPath: (path: string) => boolean): GuardInvocationProfile | undefined;
|
package/dist/command-table.js
CHANGED
|
@@ -2,6 +2,7 @@ import { SURFACE_TARGETS } from './surfaces/contract.js';
|
|
|
2
2
|
import { isAbsolute, normalize, resolve } from 'node:path';
|
|
3
3
|
import { WORK_ORDER_ID } from './work-orders/contract.js';
|
|
4
4
|
import { snapshotJson } from './environment/contract.js';
|
|
5
|
+
import { innerCommand, WRAPPING_COMMANDS } from './guards/evaluator.js';
|
|
5
6
|
import { GUARD_HANDLER_ARGV } from './guards/harness-adapter.js';
|
|
6
7
|
import { GUARD_INPUT_LIMITS } from './guards/invocation-parser.js';
|
|
7
8
|
export function isRecordedAnswerField(value) {
|
|
@@ -23,6 +24,7 @@ export const commandStages = Object.freeze([
|
|
|
23
24
|
export const commandGroups = Object.freeze([
|
|
24
25
|
{ name: 'work', summary: 'Accept, check, grade and close work orders' },
|
|
25
26
|
{ name: 'browser', summary: 'Drive, inspect and capture the declared application in a browser' },
|
|
27
|
+
{ name: 'docs', summary: 'Check living documents and decision records against the repositories' },
|
|
26
28
|
]);
|
|
27
29
|
/**
|
|
28
30
|
* The public command an argv names, and the arguments after it. A group word takes the next word
|
|
@@ -42,6 +44,7 @@ const FLAG_DESCRIPTIONS = Object.freeze({
|
|
|
42
44
|
'accept-preview': 'Accept the displayed init preview with its exact SHA-256 digest',
|
|
43
45
|
'accept-reset': 'Accept the displayed answer reset with its exact SHA-256 digest',
|
|
44
46
|
abandon: 'Close the order as abandoned; --note says why',
|
|
47
|
+
clear: 'Serve the sibling from its declared checkout again',
|
|
45
48
|
adopt: 'Move one existing harness settings path under cockpit ownership',
|
|
46
49
|
adoption: 'Path to the legacy adoption shell data',
|
|
47
50
|
answers: 'Path to the init answers file',
|
|
@@ -50,6 +53,7 @@ const FLAG_DESCRIPTIONS = Object.freeze({
|
|
|
50
53
|
command: 'With --scaffold-compose: the command that starts the app, as words separated by spaces',
|
|
51
54
|
config: 'Path to cockpit.toml',
|
|
52
55
|
create: 'Create a new local sibling repository',
|
|
56
|
+
decision: 'With work note: record the text as a decision the order made, resolved with work decide before signoff. With work decide: number of the decision to resolve, as work status lists it',
|
|
53
57
|
criteria: 'Path to a file whose YAML front-matter lists the criteria to grade',
|
|
54
58
|
dir: 'Directory for the sibling checkout',
|
|
55
59
|
evaluator: 'Grade with only this agent tool (codexcli, claudecode or opencode), with no fallback to another',
|
|
@@ -66,16 +70,24 @@ const FLAG_DESCRIPTIONS = Object.freeze({
|
|
|
66
70
|
note: 'What was checked, or what was and was not done',
|
|
67
71
|
partial: 'Close the order as partly done; --note says what is left',
|
|
68
72
|
port: 'With --scaffold-compose: the port the app listens on inside its container',
|
|
73
|
+
profile: 'Compose profile to activate beside environment.profiles; repeat it for several',
|
|
69
74
|
'probe-specifications': 'Path to the harness probe specifications; with --write-probe-specifications, the earlier file whose filled rows for unchanged cells carry forward',
|
|
70
75
|
purpose: 'Short purpose of the sibling repository',
|
|
71
76
|
question: 'Number of the open question to answer, as work status lists it',
|
|
72
77
|
registry: 'Path to the legacy registry input',
|
|
78
|
+
reason: 'With --none: why the decision is not a lasting one',
|
|
79
|
+
record: 'Write a new decision record under [docs] decisions for the decision',
|
|
80
|
+
'record-file': 'An existing decision record under [docs] decisions that cites the order',
|
|
81
|
+
supersedes: 'With --record: the number of the decision record the new one supersedes, which is marked superseded',
|
|
82
|
+
none: 'Resolve the decision as not lasting; --reason says why',
|
|
73
83
|
'reset-answer': 'Preview clearing one recorded init answer field',
|
|
74
84
|
remote: 'Repository remote URL recorded for the sibling',
|
|
75
85
|
role: 'Declared browser role',
|
|
86
|
+
'fail-on-findings': 'Exit 2 when a route has console errors, uncaught errors or failed requests, for a criterion that wants a clean route',
|
|
87
|
+
route: 'Declared browser route to visit, by name or path; every declared route when omitted',
|
|
76
88
|
'scaffold-compose': 'Write a starting compose.yml for the app in this directory, relative to the root, once; it refuses when a Compose file exists',
|
|
77
89
|
select: 'Optional subset of the gate to run',
|
|
78
|
-
service: 'Declared service
|
|
90
|
+
service: 'Declared Compose service the command acts on',
|
|
79
91
|
'siblings-root': 'Root directory containing sibling checkouts',
|
|
80
92
|
since: 'Collect logs no older than this duration',
|
|
81
93
|
tail: 'Maximum number of log records to collect',
|
|
@@ -97,6 +109,13 @@ export function describePublicOption(name) {
|
|
|
97
109
|
}
|
|
98
110
|
// Mirrors EvaluatorTool in schema.ts, which this data table may not import.
|
|
99
111
|
const EVALUATOR_TOOLS = ['codexcli', 'claudecode', 'opencode'];
|
|
112
|
+
// Mirrors UNCONFIRMABLE_ANSWERS in schema.ts. Every answer is admitted, so confirm itself names the
|
|
113
|
+
// person an answer needs instead of the guard refusing it unexplained.
|
|
114
|
+
const UNCONFIRMED_ANSWER_WORDS = Object.freeze(['cockpit.name', 'sibling', 'estate.runnable', 'ports.bases',
|
|
115
|
+
'environment.install_policies', 'environment.test_data', 'environment.compose_files', 'environment.machine', 'environment.posture',
|
|
116
|
+
'testing', 'guards']);
|
|
117
|
+
// A declared route's name or its path; the command checks it against the declared routes.
|
|
118
|
+
const ROUTE_SELECTOR = /^(?:[a-z0-9][a-z0-9_-]{0,63}|\/(?!\/)[^\s#\x00-\x1f\x7f]{0,511})$/;
|
|
100
119
|
const EXACT_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
101
120
|
const flag = Object.freeze({ kind: 'flag', role: 'boolean', repetition: 'singleton' });
|
|
102
121
|
function value(role, required = false) {
|
|
@@ -108,6 +127,8 @@ const configFlag = str(value('resolved-config', true));
|
|
|
108
127
|
const jsonFlag = bool(flag);
|
|
109
128
|
const configured = { config: configFlag, json: jsonFlag };
|
|
110
129
|
const workOrderFlag = str(value('identifier', true));
|
|
130
|
+
const profileFlag = { type: 'string', multiple: true, agent: { kind: 'value', role: 'compose-profile', repetition: 'repeated' } };
|
|
131
|
+
const serviceFlag = str(value('declared-service', true));
|
|
111
132
|
// The flags and their roles are filled in from `flags` when the table is built.
|
|
112
133
|
function ordinary(command, extra = {}) {
|
|
113
134
|
return { availability: 'available', executableRole: 'installed-cockpit', command,
|
|
@@ -150,6 +171,28 @@ const sources = [
|
|
|
150
171
|
ordinary: ordinary('setup', { forbidden: Object.freeze({ presence: Object.freeze(['write-instruction-pointers', 'write-instruction-files']) }) }),
|
|
151
172
|
flags: { config: configFlag, 'write-instruction-pointers': bool(), 'write-instruction-files': bool(), json: jsonFlag },
|
|
152
173
|
},
|
|
174
|
+
{
|
|
175
|
+
name: 'use',
|
|
176
|
+
stage: 'set-up',
|
|
177
|
+
summary: 'Serve a sibling from another of its checkouts, such as a worktree',
|
|
178
|
+
usage: '<slug> <checkout> | <slug> --clear [options]',
|
|
179
|
+
handler: 'use',
|
|
180
|
+
visibility: 'public',
|
|
181
|
+
// The checkout must already lie inside a directory the guard lets the agent write, so an agent
|
|
182
|
+
// can point the stack at its own worktree but never widen its reach to a new directory.
|
|
183
|
+
ordinary: ordinary('use', { positional: Object.freeze({ min: 1, max: 2, roles: Object.freeze(['slug', 'confined-root']) }) }),
|
|
184
|
+
flags: { config: configFlag, clear: bool(flag), json: jsonFlag },
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'pull',
|
|
188
|
+
stage: 'set-up',
|
|
189
|
+
summary: 'Fast-forward the cockpit and its siblings to their upstreams',
|
|
190
|
+
handler: 'pull',
|
|
191
|
+
visibility: 'public',
|
|
192
|
+
// Fetch and a fast-forward only: it never merges, rebases, stashes or touches a modified checkout.
|
|
193
|
+
ordinary: ordinary('pull'),
|
|
194
|
+
flags: configured,
|
|
195
|
+
},
|
|
153
196
|
{
|
|
154
197
|
name: 'sibling',
|
|
155
198
|
stage: 'set-up',
|
|
@@ -168,9 +211,25 @@ const sources = [
|
|
|
168
211
|
usage: `add|remove <${SURFACE_TARGETS.join('|')}> [options]`,
|
|
169
212
|
handler: 'tool',
|
|
170
213
|
visibility: 'public',
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
214
|
+
// tool add edits only [surfaces] tools, and an added tool never widens what agents may do, so the
|
|
215
|
+
// agent syncs it as [autonomy] sync allows. tool remove deletes that tool's guard, so it is a person's.
|
|
216
|
+
ordinary: ordinary('tool', { positional: Object.freeze({ min: 2, max: 2, roles: Object.freeze(['operation', 'surface-target']),
|
|
217
|
+
operations: Object.freeze(['add']) }) }),
|
|
218
|
+
flags: { config: configFlag, 'dry-run': bool(flag), json: jsonFlag },
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'confirm',
|
|
222
|
+
stage: 'set-up',
|
|
223
|
+
summary: 'Confirm an init answer no person stated by running the observation that verifies it',
|
|
224
|
+
usage: '<answer> [options]',
|
|
225
|
+
handler: 'confirm',
|
|
226
|
+
visibility: 'public',
|
|
227
|
+
// It removes an entry from unconfirmed only after an observation of this machine and the running
|
|
228
|
+
// stack passes, and records that observation where only cockpit writes; the guard then treats the
|
|
229
|
+
// removal as non-widening. It refuses the answers a person must confirm.
|
|
230
|
+
ordinary: ordinary('confirm', { positional: Object.freeze({ min: 1, max: 1, roles: Object.freeze(['operation']),
|
|
231
|
+
operations: UNCONFIRMED_ANSWER_WORDS }) }),
|
|
232
|
+
flags: configured,
|
|
174
233
|
},
|
|
175
234
|
{
|
|
176
235
|
name: 'registry-resolve',
|
|
@@ -204,7 +263,8 @@ const sources = [
|
|
|
204
263
|
// Adoption rewrites cockpit.toml and moves a team's harness settings, so it stays a person's. A
|
|
205
264
|
// real sync rewrites the guard's own files from a configuration the guard already pins and
|
|
206
265
|
// holds every call after an edit to, so it is an agent's only where [autonomy] sync = "agent".
|
|
207
|
-
ordinary: ordinary('sync', { forbidden: Object.freeze({ presence: Object.freeze(['interactive', 'adopt']) }), requires: 'autonomy-sync'
|
|
266
|
+
ordinary: ordinary('sync', { forbidden: Object.freeze({ presence: Object.freeze(['interactive', 'adopt']) }), requires: 'autonomy-sync',
|
|
267
|
+
admittedWhileUnsynced: true }),
|
|
208
268
|
flags: { config: configFlag, 'dry-run': bool(flag), adopt: { type: 'string', multiple: true }, interactive: bool(), json: jsonFlag },
|
|
209
269
|
},
|
|
210
270
|
{
|
|
@@ -213,7 +273,7 @@ const sources = [
|
|
|
213
273
|
summary: 'Report generated files that no longer match cockpit.toml',
|
|
214
274
|
handler: 'check',
|
|
215
275
|
visibility: 'public',
|
|
216
|
-
ordinary: ordinary('check'),
|
|
276
|
+
ordinary: ordinary('check', { admittedWhileUnsynced: true }),
|
|
217
277
|
flags: configured,
|
|
218
278
|
},
|
|
219
279
|
{
|
|
@@ -247,7 +307,7 @@ const sources = [
|
|
|
247
307
|
handler: 'up',
|
|
248
308
|
visibility: 'public',
|
|
249
309
|
ordinary: ordinary('up'),
|
|
250
|
-
flags: configured,
|
|
310
|
+
flags: { ...configured, profile: profileFlag },
|
|
251
311
|
},
|
|
252
312
|
{
|
|
253
313
|
name: 'preflight',
|
|
@@ -265,9 +325,10 @@ const sources = [
|
|
|
265
325
|
usage: '[options] -- <command> [args...]',
|
|
266
326
|
handler: 'session',
|
|
267
327
|
visibility: 'public',
|
|
268
|
-
// The guard admits `session --config <file> -- <command>` only when it
|
|
328
|
+
// The guard admits `session --config <file> [--profile <name>]... -- <command>` only when it
|
|
329
|
+
// admits the inner command.
|
|
269
330
|
ordinary: unavailable('session wraps an arbitrary foreground command'),
|
|
270
|
-
flags: { config: str() },
|
|
331
|
+
flags: { config: str(), profile: { type: 'string', multiple: true } },
|
|
271
332
|
},
|
|
272
333
|
{
|
|
273
334
|
name: 'logs',
|
|
@@ -276,9 +337,49 @@ const sources = [
|
|
|
276
337
|
handler: 'logs',
|
|
277
338
|
visibility: 'public',
|
|
278
339
|
ordinary: ordinary('logs'),
|
|
279
|
-
flags: { config: configFlag, service:
|
|
340
|
+
flags: { config: configFlag, service: serviceFlag, tail: str(value('tail'), '100'), since: str(value('since'), '15m'),
|
|
280
341
|
follow: bool(flag), json: jsonFlag },
|
|
281
342
|
},
|
|
343
|
+
{
|
|
344
|
+
name: 'ps',
|
|
345
|
+
stage: 'run-the-stack',
|
|
346
|
+
summary: 'Show the state and health of each service in this checkout\'s stack',
|
|
347
|
+
handler: 'ps',
|
|
348
|
+
visibility: 'public',
|
|
349
|
+
ordinary: ordinary('ps'),
|
|
350
|
+
flags: configured,
|
|
351
|
+
},
|
|
352
|
+
// The service verbs act only on this checkout's own leased stack, as up does, so an agent runs
|
|
353
|
+
// them as it runs up; reset, which deletes data, stays gated.
|
|
354
|
+
{
|
|
355
|
+
name: 'restart',
|
|
356
|
+
stage: 'run-the-stack',
|
|
357
|
+
summary: 'Restart one service of this checkout\'s stack and wait until it is healthy',
|
|
358
|
+
handler: 'restart',
|
|
359
|
+
visibility: 'public',
|
|
360
|
+
ordinary: ordinary('restart'),
|
|
361
|
+
flags: { ...configured, service: serviceFlag },
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'rebuild',
|
|
365
|
+
stage: 'run-the-stack',
|
|
366
|
+
summary: 'Rebuild one service\'s image, replace its container and wait until it is healthy',
|
|
367
|
+
handler: 'rebuild',
|
|
368
|
+
visibility: 'public',
|
|
369
|
+
ordinary: ordinary('rebuild'),
|
|
370
|
+
flags: { ...configured, service: serviceFlag },
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'exec',
|
|
374
|
+
stage: 'run-the-stack',
|
|
375
|
+
summary: 'Run a command inside one service\'s container of this checkout\'s stack',
|
|
376
|
+
usage: '--service <name> [options] -- <command> [args...]',
|
|
377
|
+
handler: 'exec',
|
|
378
|
+
visibility: 'public',
|
|
379
|
+
// The grammar admits the spelling; the evaluator judges the container command.
|
|
380
|
+
ordinary: ordinary('exec'),
|
|
381
|
+
flags: { config: configFlag, service: serviceFlag },
|
|
382
|
+
},
|
|
282
383
|
{
|
|
283
384
|
name: 'sleep',
|
|
284
385
|
stage: 'run-the-stack',
|
|
@@ -308,6 +409,21 @@ const sources = [
|
|
|
308
409
|
ordinary: ordinary('reset', { requires: 'autonomy-reset' }),
|
|
309
410
|
flags: { config: configFlag, interactive: bool(), apply: bool(Object.freeze({ ...flag, required: true })), json: jsonFlag },
|
|
310
411
|
},
|
|
412
|
+
{
|
|
413
|
+
name: 'stacks',
|
|
414
|
+
stage: 'run-the-stack',
|
|
415
|
+
summary: 'List every stack on this machine, or preview and remove those whose checkout is gone (stacks prune)',
|
|
416
|
+
usage: '[prune] [options]',
|
|
417
|
+
handler: 'stacks',
|
|
418
|
+
visibility: 'public',
|
|
419
|
+
// Listing and the prune preview change nothing. --apply removes only stacks whose checkout is
|
|
420
|
+
// gone; the command itself refuses one that deletes volumes unless [autonomy] reset allows it.
|
|
421
|
+
ordinary: ordinary('stacks', {
|
|
422
|
+
positional: Object.freeze({ min: 0, max: 1, roles: Object.freeze(['operation']), operations: Object.freeze(['prune']) }),
|
|
423
|
+
...personFlag,
|
|
424
|
+
}),
|
|
425
|
+
flags: { config: configFlag, apply: bool(flag), interactive: bool(), json: jsonFlag },
|
|
426
|
+
},
|
|
311
427
|
{
|
|
312
428
|
name: 'doctor',
|
|
313
429
|
stage: 'diagnose',
|
|
@@ -333,12 +449,20 @@ const sources = [
|
|
|
333
449
|
{ name: 'work check', handler: 'work-check', stage: 'test-and-grade', summary: "Run every criterion's check and the order's gates", visibility: 'public',
|
|
334
450
|
ordinary: ordinary('work-check'),
|
|
335
451
|
flags: { ...configured, 'work-order': workOrderFlag } },
|
|
336
|
-
{ name: 'work note', handler: 'work-note', stage: 'test-and-grade', summary: 'Record a note on an order,
|
|
452
|
+
{ name: 'work note', handler: 'work-note', stage: 'test-and-grade', summary: 'Record a note on an order, an open question with --ask, or a decision with --decision', visibility: 'public',
|
|
337
453
|
ordinary: ordinary('work-note'),
|
|
338
|
-
flags: { ...configured, 'work-order': workOrderFlag, text: str(value('label', true)), ask: bool(flag) } },
|
|
454
|
+
flags: { ...configured, 'work-order': workOrderFlag, text: str(value('label', true)), ask: bool(flag), decision: bool(flag) } },
|
|
455
|
+
// Resolving a decision passes no gate of its own: a --none resolution is shown in the signoff prompt, which the signoff gate covers.
|
|
456
|
+
{ name: 'work decide', handler: 'work-decide', stage: 'test-and-grade', summary: 'Resolve a recorded decision: write or cite its decision record, or say it is not lasting', visibility: 'public',
|
|
457
|
+
ordinary: ordinary('work-decide'),
|
|
458
|
+
flags: { ...configured, 'work-order': workOrderFlag, decision: str(value('tail', true)), record: bool(flag), 'record-file': str(value('label')),
|
|
459
|
+
supersedes: str(value('label')), none: bool(flag), reason: str(value('label')) } },
|
|
339
460
|
{ name: 'work answer', handler: 'work-answer', stage: 'test-and-grade', summary: 'Answer an open question on an order, as [work.gates] allows', visibility: 'public',
|
|
340
461
|
ordinary: ordinary('work-answer', personFlag),
|
|
341
462
|
flags: { ...configured, 'work-order': workOrderFlag, question: str(value('tail', true)), text: str(value('label', true)), interactive: bool() } },
|
|
463
|
+
{ name: 'work acknowledge', handler: 'work-acknowledge', stage: 'test-and-grade', summary: 'Acknowledge that an order weakened tests, as [work.gates] allows', visibility: 'public',
|
|
464
|
+
ordinary: ordinary('work-acknowledge', personFlag),
|
|
465
|
+
flags: { ...configured, 'work-order': workOrderFlag, note: str(value('label', true)), interactive: bool() } },
|
|
342
466
|
// Grading hands the order to an evaluator running apart from the agent that did the work, unless
|
|
343
467
|
// [work] grading names checks or self.
|
|
344
468
|
{ name: 'work grade', handler: 'work-grade', stage: 'test-and-grade', summary: 'Grade an accepted order the way [work] grading says', visibility: 'public',
|
|
@@ -347,12 +471,11 @@ const sources = [
|
|
|
347
471
|
{ name: 'work signoff', handler: 'work-signoff', stage: 'test-and-grade', summary: 'Close a graded order as done, or any open one as partial or abandoned', visibility: 'public',
|
|
348
472
|
ordinary: ordinary('work-signoff', personFlag),
|
|
349
473
|
flags: { ...configured, 'work-order': workOrderFlag, partial: bool(flag), abandon: bool(flag), note: str(value('label')), interactive: bool() } },
|
|
350
|
-
// Inspection only reports the holder. --apply
|
|
351
|
-
//
|
|
352
|
-
// on another host that shares the checkout looks dead to it, so a person confirms it is gone.
|
|
474
|
+
// Inspection only reports the holder. --apply removes the lock only once the command has shown its
|
|
475
|
+
// holder gone where a pid probe cannot see: the lock's host and the containers cockpit labels.
|
|
353
476
|
{ name: 'work recover', handler: 'work-recover', stage: 'test-and-grade', summary: "Inspect or remove a crashed command's work-order lock", visibility: 'public',
|
|
354
|
-
ordinary: ordinary('work-recover'
|
|
355
|
-
flags: { ...configured, 'work-order': workOrderFlag, apply: bool() } },
|
|
477
|
+
ordinary: ordinary('work-recover'),
|
|
478
|
+
flags: { ...configured, 'work-order': workOrderFlag, apply: bool(flag) } },
|
|
356
479
|
{ name: 'work verify', handler: 'work-verify', stage: 'test-and-grade', summary: 'Check every record against its order; in CI, pass the steps gated on ci', visibility: 'public',
|
|
357
480
|
ordinary: ordinary('work-verify'),
|
|
358
481
|
flags: configured },
|
|
@@ -363,10 +486,15 @@ const sources = [
|
|
|
363
486
|
// person wrote down as safe for that.
|
|
364
487
|
{ name: 'browser drive', handler: 'browser-drive', stage: 'test-and-grade', summary: 'Drive the declared application browser surfaces', visibility: 'public',
|
|
365
488
|
ordinary: ordinary('browser-drive', browserAdmission),
|
|
366
|
-
|
|
489
|
+
// Findings are evidence by default; this makes them fail the command, for a criterion that wants a clean route.
|
|
490
|
+
flags: { ...configured, role: str(value('identifier')), route: str(value('route')), 'fail-on-findings': bool(flag) } },
|
|
367
491
|
{ name: 'browser screenshot', handler: 'browser-screenshot', stage: 'test-and-grade', summary: 'Capture the declared application surfaces', visibility: 'public',
|
|
368
492
|
ordinary: ordinary('browser-screenshot', browserAdmission),
|
|
369
|
-
flags: { ...configured, role: str(value('identifier')) } },
|
|
493
|
+
flags: { ...configured, role: str(value('identifier')), route: str(value('route')) } },
|
|
494
|
+
// Reads documents and runs read-only Git, like check; it writes nothing, so an agent runs it freely.
|
|
495
|
+
{ name: 'docs check', handler: 'docs-check', stage: 'test-and-grade', summary: 'Report removed paths and make targets living docs still cite, and broken decision records', visibility: 'public',
|
|
496
|
+
ordinary: ordinary('docs-check'),
|
|
497
|
+
flags: configured },
|
|
370
498
|
];
|
|
371
499
|
export const commands = sources.map(({ flags, ...command }) => {
|
|
372
500
|
const entries = Object.entries(flags);
|
|
@@ -384,6 +512,8 @@ export const internalCommands = Object.freeze([Object.freeze({
|
|
|
384
512
|
const ORDINARY_GRANTS = ['browser-preapproval', 'autonomy-sync', 'autonomy-reset'];
|
|
385
513
|
// An image reference or ID as Docker spells it; the command itself refuses one that is not local.
|
|
386
514
|
const LOCAL_IMAGE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]{0,255}$/;
|
|
515
|
+
// The spelling environment.profiles accepts.
|
|
516
|
+
const COMPOSE_PROFILE = /^[a-z0-9][a-z0-9_.-]{0,63}$/;
|
|
387
517
|
// A selected value that starts with `-` would reach the product's tool as an option of its own.
|
|
388
518
|
const GATE_SELECTOR = /^(?:spec|file|test|route):[^-]/;
|
|
389
519
|
function canonicalPath(path) {
|
|
@@ -394,19 +524,31 @@ function canonicalPath(path) {
|
|
|
394
524
|
function record(value) {
|
|
395
525
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
396
526
|
}
|
|
527
|
+
/** The commands the guard admits while a non-widening edit of cockpit.toml waits for sync. */
|
|
528
|
+
export const UNSYNCED_ADMITTED_COMMANDS = Object.freeze(commands.flatMap(row => row.ordinary.availability === 'available' && row.ordinary.admittedWhileUnsynced ? [row.name] : []));
|
|
529
|
+
/** The same commands as prose, for the hold that names them: "cockpit sync and check". */
|
|
530
|
+
export const UNSYNCED_ADMITTED_PROSE = `cockpit ${new Intl.ListFormat('en', { type: 'conjunction' }).format(UNSYNCED_ADMITTED_COMMANDS)}`;
|
|
397
531
|
/**
|
|
398
532
|
* The inner command of an exact `session --config <configured file> -- <command…>` run from the
|
|
399
533
|
* cockpit root, or undefined. The guard admits the session only when it admits the inner command,
|
|
400
534
|
* so a session never runs what the agent could not run directly.
|
|
401
535
|
*/
|
|
402
536
|
export function ordinarySessionCommand(argv, cwd, authority) {
|
|
403
|
-
const
|
|
404
|
-
if (argv[0] !== 'session' ||
|
|
537
|
+
const inner = innerCommand(argv);
|
|
538
|
+
if (argv[0] !== 'session' || !inner || cwd !== authority.canonicalRoot)
|
|
405
539
|
return undefined;
|
|
406
|
-
const options = argv.slice(1,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
540
|
+
const options = argv.slice(1, argv.length - inner.words.length - 1);
|
|
541
|
+
let config;
|
|
542
|
+
for (let index = 0; index < options.length; index++) {
|
|
543
|
+
const [name, inline] = options[index].startsWith('--') && options[index].includes('=')
|
|
544
|
+
? [options[index].slice(0, options[index].indexOf('=')), options[index].slice(options[index].indexOf('=') + 1)]
|
|
545
|
+
: [options[index], options[++index]];
|
|
546
|
+
if (name === '--config' && config === undefined && inline !== undefined)
|
|
547
|
+
config = inline;
|
|
548
|
+
else if (name !== '--profile' || inline === undefined || !authority.profiles.includes(inline))
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
return config && resolve(cwd, config) === authority.configPath ? inner.words : undefined;
|
|
410
552
|
}
|
|
411
553
|
/** Classify one exact candidate; the caller authenticates filesystem and executable authority. */
|
|
412
554
|
export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
@@ -415,7 +557,7 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
415
557
|
if (!record(candidate)
|
|
416
558
|
|| Object.keys(candidate).sort().join(',') !== 'argv,cwd,executable'
|
|
417
559
|
|| !record(captured)
|
|
418
|
-
|| Object.keys(captured).sort().join(',') !== 'allowedRoots,canonicalRoot,configPath,executable,gates,grants,services')
|
|
560
|
+
|| Object.keys(captured).sort().join(',') !== 'allowedRoots,canonicalRoot,configPath,executable,gates,grants,profiles,services')
|
|
419
561
|
return undefined;
|
|
420
562
|
const source = captured;
|
|
421
563
|
if (!canonicalPath(source.executable) || !canonicalPath(source.canonicalRoot) || !canonicalPath(source.configPath)
|
|
@@ -424,6 +566,7 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
424
566
|
|| !Array.isArray(source.services) || !source.services.every(service => typeof service === 'string' && /^[a-z0-9][a-z0-9_.-]{0,127}$/.test(service))
|
|
425
567
|
|| !Array.isArray(source.gates) || !source.gates.every(gate => typeof gate === 'string' && /^[a-z][a-z0-9-]{0,63}$/.test(gate))
|
|
426
568
|
|| !Array.isArray(source.grants) || !source.grants.every(grant => ORDINARY_GRANTS.includes(grant))
|
|
569
|
+
|| !Array.isArray(source.profiles) || !source.profiles.every(profile => typeof profile === 'string' && COMPOSE_PROFILE.test(profile))
|
|
427
570
|
|| candidate.executable !== source.executable || candidate.cwd !== source.canonicalRoot
|
|
428
571
|
|| !Array.isArray(candidate.argv) || candidate.argv.length === 0 || candidate.argv.length > GUARD_INPUT_LIMITS.words
|
|
429
572
|
|| !candidate.argv.every(arg => typeof arg === 'string' && arg.length > 0
|
|
@@ -452,7 +595,12 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
452
595
|
const reads = [];
|
|
453
596
|
const positionals = [];
|
|
454
597
|
const options = new Map();
|
|
455
|
-
const
|
|
598
|
+
const wraps = WRAPPING_COMMANDS.has(definition.name);
|
|
599
|
+
const separator = wraps ? spelling.rest.indexOf('--') : -1;
|
|
600
|
+
// The wrapped command is required and is the only thing after `--`.
|
|
601
|
+
if (wraps && (separator < 0 || separator === spelling.rest.length - 1))
|
|
602
|
+
return undefined;
|
|
603
|
+
const words = separator < 0 ? spelling.rest : spelling.rest.slice(0, separator);
|
|
456
604
|
for (let index = 0; index < words.length; index++) {
|
|
457
605
|
const arg = words[index];
|
|
458
606
|
if (!arg.startsWith('-')) {
|
|
@@ -464,7 +612,7 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
464
612
|
const equal = arg.indexOf('=');
|
|
465
613
|
const name = arg.slice(2, equal < 0 ? undefined : equal);
|
|
466
614
|
const option = grammar.options[name];
|
|
467
|
-
if (!option || options.has(name) || grammar.forbidden.presence.includes(name))
|
|
615
|
+
if (!option || options.has(name) && option.repetition !== 'repeated' || grammar.forbidden.presence.includes(name))
|
|
468
616
|
return undefined;
|
|
469
617
|
if (option.kind === 'flag') {
|
|
470
618
|
if (equal >= 0)
|
|
@@ -484,15 +632,17 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
484
632
|
: option.role === 'gate-mode' ? ['canonical', 'full', 'staged'].includes(text)
|
|
485
633
|
: option.role === 'gate-selector' ? GATE_SELECTOR.test(text)
|
|
486
634
|
: option.role === 'identifier' ? WORK_ORDER_ID.test(text)
|
|
487
|
-
: option.role === '
|
|
488
|
-
: option.role === '
|
|
489
|
-
: option.role === '
|
|
490
|
-
: option.role === '
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
: option.role === '
|
|
494
|
-
: option.role === '
|
|
495
|
-
: option.role === '
|
|
635
|
+
: option.role === 'route' ? ROUTE_SELECTOR.test(text)
|
|
636
|
+
: option.role === 'label' ? true
|
|
637
|
+
: option.role === 'field' ? isRecordedAnswerField(text)
|
|
638
|
+
: option.role === 'tail' ? /^[1-9]\d{0,4}$/.test(text) && Number(text) <= 10_000
|
|
639
|
+
: option.role === 'since' ? text.length <= 64 && (/^\d+(?:\.\d+)?[smhd]$/.test(text)
|
|
640
|
+
|| /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)$/.test(text) && Number.isFinite(Date.parse(text)))
|
|
641
|
+
: option.role === 'surface-target' ? text.split(',').every(tool => SURFACE_TARGETS.some(target => target === tool))
|
|
642
|
+
: option.role === 'evaluator-tool' ? EVALUATOR_TOOLS.includes(text)
|
|
643
|
+
: option.role === 'local-image' ? LOCAL_IMAGE.test(text)
|
|
644
|
+
: option.role === 'compose-profile' ? source.profiles.includes(text)
|
|
645
|
+
: option.role === 'sha256' && /^[a-f0-9]{64}$/.test(text);
|
|
496
646
|
if (!valid)
|
|
497
647
|
return undefined;
|
|
498
648
|
options.set(name, text);
|
|
@@ -501,7 +651,9 @@ export function resolveOrdinaryInvocation(input, authority, inspectPath) {
|
|
|
501
651
|
|| positionals.some((word, index) => grammar.positional.roles[index] === 'operation'
|
|
502
652
|
? !grammar.positional.operations?.includes(word)
|
|
503
653
|
: grammar.positional.roles[index] === 'version' ? !EXACT_VERSION.test(word)
|
|
504
|
-
: grammar.positional.roles[index]
|
|
654
|
+
: grammar.positional.roles[index] === 'slug' ? !/^[a-z0-9][a-z0-9-]{0,62}$/.test(word)
|
|
655
|
+
: grammar.positional.roles[index] === 'surface-target' ? !SURFACE_TARGETS.some(target => target === word)
|
|
656
|
+
: grammar.positional.roles[index] !== 'confined-root' || !confined(word))
|
|
505
657
|
|| Object.entries(grammar.options).some(([name, option]) => option.required && !options.has(name))
|
|
506
658
|
// A dry run writes nothing, so no grant gates it.
|
|
507
659
|
|| grammar.requires !== undefined && !options.has('dry-run') && !source.grants.includes(grammar.requires)
|
package/dist/config/load.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type ConfigSourceSnapshot, type EnvironmentCommandOutcome, type EnvironmentDiagnostic, type SourceProvenance } from '../environment/contract.js';
|
|
2
2
|
import { type EffectivePolicy } from '../autonomy.js';
|
|
3
|
-
import { CockpitConfig, type EvaluatorTool } from '../schema.js';
|
|
3
|
+
import { browserDataQualifies, CockpitConfig, type EvaluatorTool } from '../schema.js';
|
|
4
|
+
/** An explicit request_preapproval wins; otherwise preapproval follows the declared data posture. */
|
|
5
|
+
export declare const browserPreapprovalRequested: (browser: Readonly<{
|
|
6
|
+
request_preapproval?: boolean | undefined;
|
|
7
|
+
data: Parameters<typeof browserDataQualifies>[0];
|
|
8
|
+
}>) => boolean;
|
|
4
9
|
import { type BoundedRead } from '../bounded-read.js';
|
|
5
10
|
export declare const STRUCTURED_INPUT_BYTE_LIMIT = 1048576;
|
|
6
|
-
|
|
11
|
+
type BoundedInputRead = BoundedRead;
|
|
7
12
|
export declare function readBoundedInput(path: string): Promise<BoundedInputRead>;
|
|
8
13
|
declare const loadedConfigProjectionBrand: unique symbol;
|
|
9
14
|
export type LoadedConfigProjection = LoadedCockpitConfig;
|
|
@@ -29,7 +34,7 @@ export interface LoadedCockpitConfig {
|
|
|
29
34
|
export interface MachineFloorConfigProjection {
|
|
30
35
|
readonly kind: 'machine-floor-config-projection';
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
interface ConsumedMachineFloorConfig {
|
|
33
38
|
readonly cockpitName: string;
|
|
34
39
|
readonly source: SourceProvenance;
|
|
35
40
|
readonly runtimeSha256: string;
|
|
@@ -39,7 +44,7 @@ export interface ConsumedMachineFloorConfig {
|
|
|
39
44
|
freeDiskMb: number;
|
|
40
45
|
}>;
|
|
41
46
|
}
|
|
42
|
-
|
|
47
|
+
interface WorkEnvironmentConfigProjection {
|
|
43
48
|
readonly kind: 'work-environment-config-projection';
|
|
44
49
|
}
|
|
45
50
|
export interface ConsumedWorkEnvironmentConfig {
|
|
@@ -66,6 +71,10 @@ export interface ConsumedWorkEnvironmentConfig {
|
|
|
66
71
|
readonly generatedHostPaths: readonly string[];
|
|
67
72
|
readonly browser?: ReturnType<typeof projectBrowserConfig>;
|
|
68
73
|
readonly testing?: ReturnType<typeof projectTestingConfig>;
|
|
74
|
+
readonly docs?: Readonly<{
|
|
75
|
+
decisions?: string;
|
|
76
|
+
decisionsIndex?: string;
|
|
77
|
+
}>;
|
|
69
78
|
}
|
|
70
79
|
declare function projectBrowserConfig(declaredBrowser: NonNullable<CockpitConfig['browser']>): {
|
|
71
80
|
engine: "chromium" | "firefox" | "webkit";
|
|
@@ -139,6 +148,7 @@ declare function projectTestingConfig(declaredTesting: NonNullable<CockpitConfig
|
|
|
139
148
|
}[];
|
|
140
149
|
};
|
|
141
150
|
nativeHookInstall: "explicit-native" | "inspect";
|
|
151
|
+
testFiles?: string[] | undefined;
|
|
142
152
|
nativeHookPolicy?: {
|
|
143
153
|
path: string;
|
|
144
154
|
sha256: string;
|
|
@@ -162,6 +172,8 @@ export declare function loadAutonomyPolicy(configPath: string): Promise<Readonly
|
|
|
162
172
|
}> | Readonly<{
|
|
163
173
|
refusal: EnvironmentDiagnostic;
|
|
164
174
|
}>>;
|
|
175
|
+
/** The configuration `bytes` declare, or undefined when they are not UTF-8 TOML the schema accepts. */
|
|
176
|
+
export declare function parseCockpitConfigBytes(bytes: Uint8Array): CockpitConfig | undefined;
|
|
165
177
|
export declare function loadCockpitConfig(inputPath: string): Promise<EnvironmentCommandOutcome<LoadedCockpitConfig>>;
|
|
166
178
|
/**
|
|
167
179
|
* The evaluators work grade and cockpit grade try, in order. Left unset, they are the listed agent
|