@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/browser/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readBoundedFile } from '../bounded-read.js';
|
|
|
4
4
|
import { isIP } from 'node:net';
|
|
5
5
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
6
6
|
import { errorCause, snapshotJson, RefusalError, hasErrorCode } from '../environment/contract.js';
|
|
7
|
+
import { redactSensitiveMessage } from '../environment/sensitive.js';
|
|
7
8
|
import {} from '../environment/state.js';
|
|
8
9
|
import { cockpitCommand } from '../fix-catalogue.js';
|
|
9
10
|
import { assertBrowserStorageStateCurrent, browserRuntimeControlAuthorities, browserRuntimeControlPorts } from './runtime.js';
|
|
@@ -164,7 +165,81 @@ function publicAuthentication(value) {
|
|
|
164
165
|
return value.posture !== 'real' ? { posture: value.posture }
|
|
165
166
|
: { posture: 'real', role: value.role, policy: value.policy };
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
+
const MAX_DIAGNOSTIC_ENTRIES = 20;
|
|
169
|
+
const MAX_EXCERPT_MESSAGES = 5;
|
|
170
|
+
const MAX_EXCERPT_MESSAGE_BYTES = 240;
|
|
171
|
+
const EXCERPT_OMITTED = 'browser.data is not isolated, non-production and non-restored, so page message text is never read; the counts and entries above still apply';
|
|
172
|
+
const isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
173
|
+
const exactKeys = (row, keys) => Object.keys(row).sort().join(',') === keys.sort().join(',');
|
|
174
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
175
|
+
const diagnosticPath = (value) => typeof value === 'string' && value.length <= 200 && /^\/[^\s?#\x00-\x1f\x7f]*$/.test(value);
|
|
176
|
+
function parseDiagnosticEntry(value) {
|
|
177
|
+
if (!isRecord(value))
|
|
178
|
+
return undefined;
|
|
179
|
+
if (value.kind === 'console') {
|
|
180
|
+
const keys = ['kind', ...value.path !== undefined ? ['path'] : [], ...value.line !== undefined ? ['line'] : []];
|
|
181
|
+
if (!exactKeys(value, keys) || value.path !== undefined && !diagnosticPath(value.path) || value.line !== undefined && !isCount(value.line))
|
|
182
|
+
return undefined;
|
|
183
|
+
return { kind: 'console', ...value.path !== undefined ? { path: value.path } : {}, ...value.line !== undefined ? { line: value.line } : {} };
|
|
184
|
+
}
|
|
185
|
+
if (value.kind === 'pageerror') {
|
|
186
|
+
return exactKeys(value, ['kind', 'name']) && typeof value.name === 'string' && /^[A-Za-z0-9_$.]{1,64}$/.test(value.name)
|
|
187
|
+
? { kind: 'pageerror', name: value.name } : undefined;
|
|
188
|
+
}
|
|
189
|
+
if (value.kind !== 'request' || typeof value.method !== 'string' || !/^[A-Z]{1,16}$/.test(value.method) || !diagnosticPath(value.path))
|
|
190
|
+
return undefined;
|
|
191
|
+
if (exactKeys(value, ['kind', 'method', 'path', 'status'])) {
|
|
192
|
+
return Number.isInteger(value.status) && value.status >= 400 && value.status <= 599
|
|
193
|
+
? { kind: 'request', method: value.method, path: value.path, status: value.status } : undefined;
|
|
194
|
+
}
|
|
195
|
+
return exactKeys(value, ['kind', 'method', 'path', 'failure']) && typeof value.failure === 'string' && /^ERR_[A-Z0-9_]{1,64}$/.test(value.failure)
|
|
196
|
+
? { kind: 'request', method: value.method, path: value.path, failure: value.failure } : undefined;
|
|
197
|
+
}
|
|
198
|
+
// Page text is kept as work-order evidence, so a URL loses its query string and fragment and anything
|
|
199
|
+
// secret-shaped is redacted; text that cannot be redacted is withheld, never kept raw.
|
|
200
|
+
function sanitizeExcerpt(text) {
|
|
201
|
+
const redacted = redactSensitiveMessage(text.replace(/(\b[a-z][a-z0-9+.-]*:\/\/[^\s?#]*)[?#]\S*/gi, '$1')) ?? '[withheld]';
|
|
202
|
+
let bounded = redacted;
|
|
203
|
+
while (Buffer.byteLength(bounded) > MAX_EXCERPT_MESSAGE_BYTES)
|
|
204
|
+
bounded = bounded.slice(0, -1);
|
|
205
|
+
return bounded;
|
|
206
|
+
}
|
|
207
|
+
function parseMessages(value) {
|
|
208
|
+
if (!Array.isArray(value) || value.length > MAX_EXCERPT_MESSAGES)
|
|
209
|
+
return undefined;
|
|
210
|
+
const messages = [];
|
|
211
|
+
for (const row of value) {
|
|
212
|
+
if (!isRecord(row) || !exactKeys(row, ['kind', 'text']) || row.kind !== 'console' && row.kind !== 'pageerror' || typeof row.text !== 'string'
|
|
213
|
+
|| row.text.length === 0 || Buffer.byteLength(row.text) > MAX_EXCERPT_MESSAGE_BYTES || /[\x00-\x1f\x7f]/.test(row.text))
|
|
214
|
+
return undefined;
|
|
215
|
+
messages.push({ kind: row.kind, text: sanitizeExcerpt(row.text) });
|
|
216
|
+
}
|
|
217
|
+
return { trust: 'untrusted-page-content', messages };
|
|
218
|
+
}
|
|
219
|
+
// The operation reads message text only when asked to, so its presence must match the posture exactly.
|
|
220
|
+
function parseDiagnostics(value, excerptAllowed) {
|
|
221
|
+
if (!isRecord(value) || !exactKeys(value, ['consoleErrors', 'consoleWarnings', 'pageErrors', 'failedRequests', 'abortedRequests', 'thirdParty', 'entries', 'omitted', ...excerptAllowed ? ['messages'] : []]))
|
|
222
|
+
return undefined;
|
|
223
|
+
const { consoleErrors, consoleWarnings, pageErrors, failedRequests, abortedRequests, thirdParty, entries, omitted } = value;
|
|
224
|
+
if (![consoleErrors, consoleWarnings, pageErrors, failedRequests, abortedRequests, omitted].every(isCount)
|
|
225
|
+
|| !isRecord(thirdParty) || !exactKeys(thirdParty, ['requests', 'failed']) || !isCount(thirdParty.requests) || !isCount(thirdParty.failed)
|
|
226
|
+
|| !Array.isArray(entries) || entries.length > MAX_DIAGNOSTIC_ENTRIES)
|
|
227
|
+
return undefined;
|
|
228
|
+
const parsed = entries.map(parseDiagnosticEntry);
|
|
229
|
+
if (parsed.some(entry => entry === undefined))
|
|
230
|
+
return undefined;
|
|
231
|
+
const excerpt = excerptAllowed ? parseMessages(value.messages) : { omitted: EXCERPT_OMITTED };
|
|
232
|
+
if (!excerpt)
|
|
233
|
+
return undefined;
|
|
234
|
+
// Every finding is either listed or counted as omitted, so the listing can never hide one.
|
|
235
|
+
if (parsed.length + omitted !== consoleErrors + pageErrors + failedRequests)
|
|
236
|
+
return undefined;
|
|
237
|
+
return { consoleErrors: consoleErrors, consoleWarnings: consoleWarnings, pageErrors: pageErrors,
|
|
238
|
+
failedRequests: failedRequests, abortedRequests: abortedRequests,
|
|
239
|
+
thirdParty: { requests: thirdParty.requests, failed: thirdParty.failed }, entries: parsed, omitted: omitted, excerpt };
|
|
240
|
+
}
|
|
241
|
+
const routeVerdict = (diagnostics) => diagnostics.consoleErrors + diagnostics.pageErrors + diagnostics.failedRequests > 0 ? 'findings' : 'clean';
|
|
242
|
+
function parseObservation(value, kind, origin, excerptAllowed) {
|
|
168
243
|
let parsed;
|
|
169
244
|
// Child stdout can be quoted by a parse error; the caller refuses with its own fixed message.
|
|
170
245
|
try {
|
|
@@ -176,7 +251,7 @@ function parseObservation(value, kind, origin) {
|
|
|
176
251
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
177
252
|
return undefined;
|
|
178
253
|
const row = parsed;
|
|
179
|
-
const expected = kind === 'drive' ? ['finalUrl', 'operation', 'schema'] : ['evidence', 'finalUrl', 'operation', 'schema'];
|
|
254
|
+
const expected = kind === 'drive' ? ['diagnostics', 'finalUrl', 'operation', 'schema'] : ['evidence', 'finalUrl', 'operation', 'schema'];
|
|
180
255
|
if (Object.keys(row).sort().join(',') !== expected.sort().join(',') || row.schema !== 'cockpit-browser-observation/v1' || row.operation !== kind || typeof row.finalUrl !== 'string')
|
|
181
256
|
return undefined;
|
|
182
257
|
let final;
|
|
@@ -189,8 +264,10 @@ function parseObservation(value, kind, origin) {
|
|
|
189
264
|
}
|
|
190
265
|
if (final.origin !== origin || final.username || final.password || final.hash)
|
|
191
266
|
return undefined;
|
|
192
|
-
if (kind === 'drive')
|
|
193
|
-
|
|
267
|
+
if (kind === 'drive') {
|
|
268
|
+
const diagnostics = parseDiagnostics(row.diagnostics, excerptAllowed);
|
|
269
|
+
return diagnostics ? { finalUrl: final.href, diagnostics } : undefined;
|
|
270
|
+
}
|
|
194
271
|
if (!row.evidence || typeof row.evidence !== 'object' || Array.isArray(row.evidence))
|
|
195
272
|
return undefined;
|
|
196
273
|
const evidence = row.evidence;
|
|
@@ -249,14 +326,14 @@ function ensureArtifactRoot(root) {
|
|
|
249
326
|
}
|
|
250
327
|
return undefined;
|
|
251
328
|
}
|
|
252
|
-
async function run(input, client, policy, auth, deadline, authorization) {
|
|
329
|
+
async function run(input, client, surfaces, policy, auth, deadline, authorization) {
|
|
253
330
|
const receipts = [];
|
|
254
331
|
if (input.kind === 'screenshot') {
|
|
255
332
|
const unavailable = ensureArtifactRoot(client.artifactRoot);
|
|
256
333
|
if (unavailable)
|
|
257
334
|
return refusal(unavailable);
|
|
258
335
|
}
|
|
259
|
-
for (const surface of
|
|
336
|
+
for (const surface of surfaces) {
|
|
260
337
|
// Only the runtime's own fixed-text TypeError escapes the revalidation (it replaces filesystem
|
|
261
338
|
// errors with that text), so the refusal names the condition instead.
|
|
262
339
|
try {
|
|
@@ -285,7 +362,7 @@ async function run(input, client, policy, auth, deadline, authorization) {
|
|
|
285
362
|
if (authenticated.execution.exitCode !== 0) {
|
|
286
363
|
return refusal(`Browser ${input.kind} operation failed without a valid bounded diagnostic for ${surface.name}`);
|
|
287
364
|
}
|
|
288
|
-
const observation = parseObservation(authenticated.stdout, input.kind, policy.origin);
|
|
365
|
+
const observation = parseObservation(authenticated.stdout, input.kind, policy.origin, input.runtime.dataQualifies);
|
|
289
366
|
if (!observation)
|
|
290
367
|
return refusal(`Browser ${input.kind} returned no bound application-driving observation for ${surface.name}`);
|
|
291
368
|
try {
|
|
@@ -309,13 +386,37 @@ async function run(input, client, policy, auth, deadline, authorization) {
|
|
|
309
386
|
receipts.push({ ...base, screenshot: { path, ...observation.evidence } });
|
|
310
387
|
}
|
|
311
388
|
else
|
|
312
|
-
receipts.push(base);
|
|
389
|
+
receipts.push({ ...base, diagnostics: observation.diagnostics, verdict: routeVerdict(observation.diagnostics) });
|
|
313
390
|
}
|
|
314
391
|
const receipt = { kind: input.kind, activation: 'application',
|
|
315
|
-
authentication: publicAuthentication(auth), surfaces: receipts
|
|
392
|
+
authentication: publicAuthentication(auth), surfaces: receipts,
|
|
393
|
+
...input.kind === 'drive' ? { verdict: receipts.some(row => row.verdict === 'findings') ? 'findings' : 'clean' } : {} };
|
|
316
394
|
issuedReceipts.add(receipt);
|
|
317
395
|
return { kind: 'complete', receipt };
|
|
318
396
|
}
|
|
397
|
+
// A route is chosen by its declared name or its declared path; anything else is refused with the choices.
|
|
398
|
+
function selectRoutes(input) {
|
|
399
|
+
if (input.route === undefined)
|
|
400
|
+
return input.policy.surfaces;
|
|
401
|
+
const route = input.route;
|
|
402
|
+
const origin = new URL(input.policy.origin);
|
|
403
|
+
let href;
|
|
404
|
+
if (route.startsWith('/') && !route.startsWith('//')) {
|
|
405
|
+
// `new URL` throws TypeError for an address it cannot parse; such a route can only match by name.
|
|
406
|
+
try {
|
|
407
|
+
href = new URL(route, origin).href;
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
if (!(error instanceof TypeError))
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const surface = input.policy.surfaces.find(row => row.name === route || href !== undefined && row.url === href);
|
|
415
|
+
if (surface)
|
|
416
|
+
return [surface];
|
|
417
|
+
// The route is the caller's own input, so it is safe to echo back.
|
|
418
|
+
return `Browser route ${JSON.stringify(route)} is not declared; declared routes are ${input.policy.surfaces.map(row => `${row.name} (${new URL(row.url).pathname + new URL(row.url).search})`).join(', ')}`;
|
|
419
|
+
}
|
|
319
420
|
export async function executeBrowserCommand(value) {
|
|
320
421
|
const deadline = Date.now() + COMMAND_TIMEOUT_MS;
|
|
321
422
|
const input = value;
|
|
@@ -338,12 +439,15 @@ export async function executeBrowserCommand(value) {
|
|
|
338
439
|
+ 'a remote browser reaches host addresses only through that exposure, so it would look for the application inside its own container. '
|
|
339
440
|
+ 'Use a 127.0.0.1, localhost or .localhost origin (an IPv6 literal is never tunnelled), or widen network_exposure with a network_exposure_reason');
|
|
340
441
|
}
|
|
442
|
+
const selected = selectRoutes(input);
|
|
443
|
+
if (typeof selected === 'string')
|
|
444
|
+
return refusal(selected);
|
|
341
445
|
const auth = authentication(input);
|
|
342
446
|
if (!auth)
|
|
343
447
|
return refusal(input.runtime.authentication.posture === 'real'
|
|
344
448
|
? 'Browser command requires a declared seeded role and matching coordination authority' : 'Bypassed browser authentication cannot accept role or state input');
|
|
345
449
|
const admitted = await withBrowserProjectOperationAdmission(client, input.runtime, {
|
|
346
450
|
origin: input.policy.origin, ...(auth.posture === 'real' ? { role: auth.role } : {}), ...(input.state ? { state: input.state } : {}),
|
|
347
|
-
}, authorization => run(input, client, input.policy, auth, deadline, authorization));
|
|
451
|
+
}, authorization => run(input, client, selected, input.policy, auth, deadline, authorization));
|
|
348
452
|
return admitted.kind === 'admitted' ? admitted.value : refusal(admitted.reason);
|
|
349
453
|
}
|
|
@@ -100,6 +100,7 @@ export declare function resolveBrowserRuntime(input: BrowserRuntimeInput, coordi
|
|
|
100
100
|
states: any[];
|
|
101
101
|
claim: "exclusive" | "per-worker";
|
|
102
102
|
};
|
|
103
|
+
dataQualifies: boolean;
|
|
103
104
|
preapproval: "eligible" | "not-requested";
|
|
104
105
|
networkExposure: string;
|
|
105
106
|
networkExposureReason?: string | undefined;
|
package/dist/browser/runtime.js
CHANGED
|
@@ -123,7 +123,8 @@ export function resolveBrowserRuntime(input, coordinationNamespace) {
|
|
|
123
123
|
|| typeof input.data.reachesProduction !== 'boolean' || typeof input.requestPreapproval !== 'boolean') {
|
|
124
124
|
throw new RefusalError('browser data posture must be explicit');
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
const dataQualifies = input.data.mode !== 'restored' && input.data.isolated && !input.data.reachesProduction;
|
|
127
|
+
if (input.requestPreapproval && !dataQualifies) {
|
|
127
128
|
throw new RefusalError('browser preapproval requires isolated empty or seeded data with no production reach');
|
|
128
129
|
}
|
|
129
130
|
const projection = {
|
|
@@ -141,6 +142,8 @@ export function resolveBrowserRuntime(input, coordinationNamespace) {
|
|
|
141
142
|
authentication: signedOut
|
|
142
143
|
? { posture: signedOut, policy: 'none', states: [] }
|
|
143
144
|
: { posture: 'real', policy: input.authentication.policy, states: states.map((state) => ({ ...state })), claim: input.authentication.policy === 'serialized' ? 'exclusive' : 'per-worker' },
|
|
145
|
+
// The posture alone, whatever request_preapproval says: it also gates message excerpts in drive receipts.
|
|
146
|
+
dataQualifies,
|
|
144
147
|
preapproval: input.requestPreapproval ? 'eligible' : 'not-requested',
|
|
145
148
|
networkExposure: networkExposure.value,
|
|
146
149
|
...(networkExposure.reason ? { networkExposureReason: networkExposure.reason } : {}),
|
|
@@ -3,9 +3,9 @@ import { type ExecuteProbeOutcome } from '../environment-exec/execute.js';
|
|
|
3
3
|
import { type StateNamespace } from '../environment/state.js';
|
|
4
4
|
import type { RenderedSurfaceFile } from '../surfaces/contract.js';
|
|
5
5
|
import { type GuardSurfaceTarget } from '../surfaces/harness-registry.js';
|
|
6
|
-
import { type
|
|
6
|
+
import { type McpStdioDeclaration } from '../surfaces/mcp.js';
|
|
7
7
|
import { type BrowserRuntimeProjection } from './runtime.js';
|
|
8
|
-
|
|
8
|
+
type BrowserTestPackage = Readonly<{
|
|
9
9
|
directory: string;
|
|
10
10
|
manifest: {
|
|
11
11
|
readonly bytes: string;
|
|
@@ -34,12 +34,12 @@ export interface BrowserSurfaceInput {
|
|
|
34
34
|
readonly target: GuardSurfaceTarget;
|
|
35
35
|
readonly version: string;
|
|
36
36
|
}[];
|
|
37
|
-
readonly mcp:
|
|
37
|
+
readonly mcp: McpStdioDeclaration;
|
|
38
38
|
readonly approved: boolean;
|
|
39
39
|
readonly testPackage: BrowserTestPackage | null;
|
|
40
40
|
readonly skillTemplate: string;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
interface BrowserTransportRow {
|
|
43
43
|
readonly target: GuardSurfaceTarget;
|
|
44
44
|
readonly version: string;
|
|
45
45
|
readonly transport: 'mcp' | 'cli';
|
|
@@ -84,7 +84,7 @@ export interface BrowserProjectClient {
|
|
|
84
84
|
maxBytes: number;
|
|
85
85
|
}>;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
interface BrowserProjectOperation {
|
|
88
88
|
readonly kind: 'drive' | 'screenshot';
|
|
89
89
|
readonly surface: Readonly<{
|
|
90
90
|
name: string;
|
|
@@ -97,7 +97,7 @@ export interface BrowserProjectOperation {
|
|
|
97
97
|
export interface BrowserProjectOperationAuthorization {
|
|
98
98
|
readonly kind: 'browser-project-operation-authorization';
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
interface BrowserProjectOperationAdmissionInput {
|
|
101
101
|
readonly origin: string;
|
|
102
102
|
readonly role?: string;
|
|
103
103
|
readonly state?: Readonly<{
|
|
@@ -114,7 +114,7 @@ export declare function observeBrowserProjectPackage(input: Readonly<{
|
|
|
114
114
|
artifactRoot: string;
|
|
115
115
|
}>): BrowserTestPackage;
|
|
116
116
|
export declare function authenticateBrowserProjectClient(value: unknown, runtime: BrowserRuntimeProjection): BrowserProjectClient | undefined;
|
|
117
|
-
|
|
117
|
+
type BrowserProjectOperationNotAdmitted = Readonly<{
|
|
118
118
|
kind: 'unadmitted';
|
|
119
119
|
cause: string;
|
|
120
120
|
}>;
|
package/dist/browser/surfaces.js
CHANGED
|
@@ -238,6 +238,7 @@ function operationStep(client, runtime, operation, remaining, origin, statePath,
|
|
|
238
238
|
'--control-ports', JSON.stringify(controlPorts),
|
|
239
239
|
'--url', operation.surface.url, '--origin', origin, '--timeout', String(remaining),
|
|
240
240
|
...(statePath ? ['--state-proof', statePath] : []),
|
|
241
|
+
...(operation.kind === 'drive' && runtime.dataQualifies ? ['--messages'] : []),
|
|
241
242
|
...(operation.output && artifactIdentity ? ['--output', operation.output, '--output-name', basename(operation.output),
|
|
242
243
|
'--artifact-root', client.artifactRoot, '--artifact-dev', String(artifactIdentity.dev), '--artifact-ino', String(artifactIdentity.ino),
|
|
243
244
|
'--artifact-birthtime-ns', artifactIdentity.birthtimeNs] : [])];
|
|
@@ -489,6 +490,8 @@ export function renderBrowserSurfaces(input) {
|
|
|
489
490
|
const server = collected.result.servers[0];
|
|
490
491
|
if (server.name !== 'playwright')
|
|
491
492
|
throw new RefusalError('browser MCP declaration must be the playwright server');
|
|
493
|
+
if (!('command' in server))
|
|
494
|
+
throw new RefusalError('the playwright MCP server must be a local command, not a remote URL');
|
|
492
495
|
if (server.cwd !== data.cockpitDirectory) {
|
|
493
496
|
throw new RefusalError('the playwright MCP declaration must come from a sibling whose dir is the cockpit directory itself (dir = "."), so the server starts there');
|
|
494
497
|
}
|
|
@@ -537,6 +540,8 @@ export function renderBrowserSurfaces(input) {
|
|
|
537
540
|
continue;
|
|
538
541
|
if (ownRows.some(row => row.transport === 'mcp' && row.declaration === 'instructed')) {
|
|
539
542
|
const native = collected.result.harnesses.find(row => row.target === harness.target).servers[0];
|
|
543
|
+
if (!('command' in native))
|
|
544
|
+
throw new RefusalError('the playwright MCP server must be a local command, not a remote URL');
|
|
540
545
|
files.push(nativeMcp(harness.target, native.command, native.args));
|
|
541
546
|
}
|
|
542
547
|
files.push(renderSkill(data.skillTemplate, harness.target, ownRows, runtime));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** JSON with object keys sorted at every depth, so equal values serialise to identical bytes whatever order their keys were written in. */
|
|
2
|
+
export function canonicalJson(value) {
|
|
3
|
+
return JSON.stringify(value, (_key, item) => item !== null && typeof item === 'object' && !Array.isArray(item)
|
|
4
|
+
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : item);
|
|
5
|
+
}
|
package/dist/cli-main.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type EvaluatorTool } from './schema.js';
|
|
|
2
2
|
import { type ScaffoldBackingService } from './init/scaffold.js';
|
|
3
3
|
import { type ReleaseMetadataTransport } from './freshness.js';
|
|
4
4
|
import { type HandlerIdentifier, type WorkHandlerIdentifier } from './command-table.js';
|
|
5
|
-
import type
|
|
5
|
+
import { type FixContext } from './fix-catalogue.js';
|
|
6
6
|
import { type Diagnostic } from './diagnostics.js';
|
|
7
7
|
import { type UnattendedInit } from './init/command.js';
|
|
8
8
|
import { type InitApplicationInput, type InitApplicationOutcome, type InitResult } from './environment/init-application.js';
|
|
@@ -10,14 +10,17 @@ import { type RegistryResolveCommandInput } from './registry-resolve/command.js'
|
|
|
10
10
|
import { type SetupCommandInput } from './setup/command.js';
|
|
11
11
|
import { type DoctorCommandInput } from './environment/application/doctor.js';
|
|
12
12
|
import { type PreflightCommandInput } from './environment/application/health.js';
|
|
13
|
+
import { type ConfirmCommandInput } from './environment/application/confirm.js';
|
|
13
14
|
import { type DownCommandInput, type ResetCommandInput, type SleepCommandInput, type UpCommandInput } from './environment/application/lifecycle-context.js';
|
|
14
15
|
import { type SiblingAddInput, type SiblingRemoveInput } from './environment/sibling-registration.js';
|
|
15
16
|
import { type ToolChangeInput } from './environment/tool-registration.js';
|
|
17
|
+
import { type PullCommandInput, type UseCommandInput } from './environment/application/siblings.js';
|
|
16
18
|
import { type SetupRefusalEvidence } from './environment/contract.js';
|
|
17
19
|
import { type GeneratedEnvironmentCommandInput } from './generated-environment/command.js';
|
|
18
20
|
import type { GeneratedStaleRemovalApproval, GeneratedStaleRemovalApprovalRequest } from './environment/generated-application/shared.js';
|
|
19
21
|
import { type SurfaceSelftestDraftInput, type SurfaceSelftestExecutableCandidate, type SurfaceSelftestProbeSpecificationsCandidate } from './environment/selftest-application.js';
|
|
20
|
-
import { loadOperatorPolicy } from './environment/operator-application.js';
|
|
22
|
+
import { loadOperatorPolicy, type StacksPruneOperatorSession } from './environment/operator-application.js';
|
|
23
|
+
import { type StacksPruneScope } from './environment/stack-inventory-prune.js';
|
|
21
24
|
import type { WorkApplicationRequest } from './work-orders/application.js';
|
|
22
25
|
export interface CliIo {
|
|
23
26
|
stdout: Pick<NodeJS.WriteStream, 'write'>;
|
|
@@ -33,6 +36,7 @@ export interface CliIo {
|
|
|
33
36
|
confirmSelftestExecutable?: (candidate: SurfaceSelftestExecutableCandidate) => Promise<string | undefined>;
|
|
34
37
|
confirmSelftestProbeSpecifications?: (candidate: SurfaceSelftestProbeSpecificationsCandidate) => Promise<string | undefined>;
|
|
35
38
|
confirmStaleRemoval?: (request: GeneratedStaleRemovalApprovalRequest) => Promise<string | undefined>;
|
|
39
|
+
confirmStacksPrune?: (preview: CommandOutcome) => Promise<string | undefined>;
|
|
36
40
|
}
|
|
37
41
|
export interface CliRuntime {
|
|
38
42
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
@@ -55,11 +59,31 @@ interface WorkCommandInput {
|
|
|
55
59
|
}
|
|
56
60
|
interface HandlerInputById extends Record<WorkHandlerIdentifier, WorkCommandInput> {
|
|
57
61
|
check: GeneratedEnvironmentCommandInput;
|
|
62
|
+
'docs-check': Readonly<{
|
|
63
|
+
configPath: string;
|
|
64
|
+
}>;
|
|
65
|
+
confirm: ConfirmCommandInput;
|
|
58
66
|
init: InitApplicationInput & Readonly<{
|
|
59
67
|
unattended?: UnattendedInit;
|
|
60
68
|
}>;
|
|
61
69
|
doctor: DoctorCommandInput;
|
|
62
70
|
down: DownCommandInput;
|
|
71
|
+
exec: Readonly<{
|
|
72
|
+
configPath: string;
|
|
73
|
+
service: string;
|
|
74
|
+
argv: readonly [string, ...string[]];
|
|
75
|
+
}>;
|
|
76
|
+
ps: Readonly<{
|
|
77
|
+
configPath: string;
|
|
78
|
+
}>;
|
|
79
|
+
restart: Readonly<{
|
|
80
|
+
configPath: string;
|
|
81
|
+
service: string;
|
|
82
|
+
}>;
|
|
83
|
+
rebuild: Readonly<{
|
|
84
|
+
configPath: string;
|
|
85
|
+
service: string;
|
|
86
|
+
}>;
|
|
63
87
|
logs: Readonly<{
|
|
64
88
|
configPath: string;
|
|
65
89
|
service: string;
|
|
@@ -95,19 +119,28 @@ interface HandlerInputById extends Record<WorkHandlerIdentifier, WorkCommandInpu
|
|
|
95
119
|
configPath: string;
|
|
96
120
|
argv: readonly [string, ...string[]];
|
|
97
121
|
sessionId: string;
|
|
122
|
+
profiles: readonly string[];
|
|
98
123
|
}>;
|
|
99
124
|
sleep: SleepCommandInput;
|
|
125
|
+
stacks: Readonly<{
|
|
126
|
+
configPath: string;
|
|
127
|
+
operation: 'list' | 'prune';
|
|
128
|
+
}>;
|
|
100
129
|
sync: GeneratedEnvironmentCommandInput & Readonly<{
|
|
101
130
|
dryRun?: boolean;
|
|
102
131
|
adopt?: readonly string[];
|
|
103
132
|
authorizeStaleRemoval?: (request: GeneratedStaleRemovalApprovalRequest) => Promise<GeneratedStaleRemovalApproval | undefined>;
|
|
104
133
|
}>;
|
|
105
|
-
up: UpCommandInput
|
|
134
|
+
up: UpCommandInput & Readonly<{
|
|
135
|
+
profiles: readonly string[];
|
|
136
|
+
}>;
|
|
106
137
|
upgrade: Readonly<{
|
|
107
138
|
configPath: string;
|
|
108
139
|
version?: string;
|
|
109
140
|
dryRun: boolean;
|
|
110
141
|
}>;
|
|
142
|
+
use: Omit<UseCommandInput, 'env'>;
|
|
143
|
+
pull: Omit<PullCommandInput, 'env'>;
|
|
111
144
|
}
|
|
112
145
|
export type CommandHandlers = {
|
|
113
146
|
[Identifier in HandlerIdentifier]: (input: HandlerInputById[Identifier]) => Promise<Identifier extends 'init' ? InitApplicationOutcome : CommandOutcome>;
|
|
@@ -124,6 +157,10 @@ interface OperatorWorkBinding {
|
|
|
124
157
|
export type CliHandlerOverrides = Partial<CommandHandlers> & Readonly<{
|
|
125
158
|
beginOperatorWorkApplication?: (input: WorkCommandInput) => Promise<OperatorWorkBinding | CommandOutcome>;
|
|
126
159
|
beginOperatorReset?: (input: ResetCommandInput) => Promise<OperatorResetBinding | CommandOutcome>;
|
|
160
|
+
beginStacksPrune?: (input: Readonly<{
|
|
161
|
+
configPath: string;
|
|
162
|
+
scope: StacksPruneScope;
|
|
163
|
+
}>) => Promise<StacksPruneOperatorSession | CommandOutcome>;
|
|
127
164
|
loadPolicy?: typeof loadOperatorPolicy;
|
|
128
165
|
scaffoldCompose?: (input: ComposeScaffoldRequest) => Promise<CommandOutcome>;
|
|
129
166
|
}>;
|