@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
|
@@ -2,66 +2,20 @@
|
|
|
2
2
|
// derived from the record and the current file, and partial and abandoned as closures. Each command
|
|
3
3
|
// that passes a gate asks the autonomy policy who may pass it; a person gate returns a continuation
|
|
4
4
|
// the CLI completes only with the digest a person types in a terminal. It also routes the browser and
|
|
5
|
-
// test-gate operations that share the work composition.
|
|
6
|
-
|
|
7
|
-
import { posix } from 'node:path';
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
import { decide, policyWeakening, runningInCi } from '../autonomy.js';
|
|
5
|
+
// test-gate operations that share the work composition. Decisions, grading and closure are owned by the
|
|
6
|
+
// application-* modules beside it, on the shared steps application-context.ts binds.
|
|
10
7
|
import { executeBrowserCommand } from '../browser/commands.js';
|
|
11
8
|
import { evaluateUiGate } from '../browser/ui-gate.js';
|
|
9
|
+
import { policyWeakening } from '../autonomy.js';
|
|
12
10
|
import { errorCause, RefusalError } from '../environment/contract.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { describeWeakening, testWeakeningDigest } from '../testing/weakening.js';
|
|
12
|
+
import { checksBindsField, contractAmendment, workOrderDigest } from './contract.js';
|
|
15
13
|
import { isWorkApplicationComposition } from './composition.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
z.object({ operation: z.literal('work-accept'), workOrderId: id }).strict(),
|
|
22
|
-
z.object({ operation: z.literal('work-check'), workOrderId: id }).strict(),
|
|
23
|
-
z.object({ operation: z.literal('work-note'), workOrderId: id, text: note, question: z.boolean() }).strict(),
|
|
24
|
-
z.object({ operation: z.literal('work-answer'), workOrderId: id, question: z.number().int().min(1), text: note }).strict(),
|
|
25
|
-
z.object({ operation: z.literal('work-grade'), workOrderId: id, verdict: z.enum(['met', 'unmet']).optional(), note: note.optional() }).strict(),
|
|
26
|
-
z.object({ operation: z.literal('work-signoff'), workOrderId: id, close: z.enum(['done', 'partial', 'abandoned']), note: note.optional() }).strict(),
|
|
27
|
-
z.object({ operation: z.literal('work-recover'), workOrderId: id, apply: z.boolean() }).strict(),
|
|
28
|
-
z.object({ operation: z.literal('work-verify') }).strict(),
|
|
29
|
-
z.object({ operation: z.literal('grade-criteria'), criteriaFile: relativeFile }).strict(),
|
|
30
|
-
z.object({ operation: z.enum(['browser-drive', 'browser-screenshot']), role: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).optional() }).strict(),
|
|
31
|
-
z.object({ operation: z.literal('test-run'), gate: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/), mode: z.enum(['canonical', 'full', 'staged']),
|
|
32
|
-
select: z.object({ kind: z.enum(['spec', 'file', 'test', 'route']), value: z.string().min(1).refine(value => Buffer.byteLength(value) <= 4_096 && !/[\x00-\x1f\x7f]/.test(value)) }).strict().optional() }).strict(),
|
|
33
|
-
]);
|
|
34
|
-
export function captureWorkApplicationRequest(value) {
|
|
35
|
-
const parsed = Request.safeParse(value);
|
|
36
|
-
return parsed.success ? parsed.data : undefined;
|
|
37
|
-
}
|
|
38
|
-
const refusal = (operation, reason) => ({ kind: 'refusal', operation, reason });
|
|
39
|
-
const complete = (operation, value) => ({ kind: 'complete', operation, value });
|
|
40
|
-
const unsatisfied = (operation, value, reason) => ({ kind: 'unsatisfied', operation, value, reason });
|
|
41
|
-
const now = () => new Date().toISOString();
|
|
42
|
-
// Agent-written text is shown to a person with its control and bidirectional-override characters
|
|
43
|
-
// replaced, so it can neither drive their terminal nor read in an order other than the one recorded.
|
|
44
|
-
const shown = (value) => value.replace(/[\x00-\x09\x0b-\x1f\x7f-\x9f--]/g, '�');
|
|
45
|
-
const TERMINAL = new Set(['done', 'partial', 'abandoned']);
|
|
46
|
-
const CLOSURE = Object.freeze({ independent: 'graded', checks: 'checks-passed', self: 'self-reported' });
|
|
47
|
-
function displayState(row, attemptCap) {
|
|
48
|
-
if (!row.record)
|
|
49
|
-
return row.order ? 'draft' : 'invalid';
|
|
50
|
-
if (TERMINAL.has(row.record.state))
|
|
51
|
-
return row.record.state;
|
|
52
|
-
if (!row.order || row.digest !== row.record.accepted.digest)
|
|
53
|
-
return 'stale';
|
|
54
|
-
if (openQuestions(row.record).length || row.record.attempts >= attemptCap)
|
|
55
|
-
return 'blocked';
|
|
56
|
-
return row.record.state;
|
|
57
|
-
}
|
|
58
|
-
function openQuestions(record) {
|
|
59
|
-
return record.notes.flatMap((row, index) => row.kind === 'question' && !record.notes.some(answer => answer.answers === index + 1) ? [index + 1] : []);
|
|
60
|
-
}
|
|
61
|
-
const pendingCi = (record) => [
|
|
62
|
-
...record.accepted.by === 'ci' ? ['accept'] : [], ...record.amendments.filter(row => row.by === 'ci').map(() => 'amend'),
|
|
63
|
-
...record.notes.filter(row => row.kind === 'answer' && row.by === 'ci').map(() => 'answer'), ...record.closure?.by === 'ci' ? ['signoff'] : [],
|
|
64
|
-
];
|
|
14
|
+
import { captureWorkApplicationRequest, complete, displayState, now, openQuestions, refusal, TERMINAL, unsatisfied, workContext } from './application-context.js';
|
|
15
|
+
import { pendingCi, signoff, verify } from './application-closure.js';
|
|
16
|
+
import { decideOrder, DECISIONS_UNDECLARED, relatedDecisions, unresolvedDecisions } from './application-decisions.js';
|
|
17
|
+
import { gradeCriteriaFile, gradeOrder, testRun } from './application-grading.js';
|
|
18
|
+
export { captureWorkApplicationRequest } from './application-context.js';
|
|
65
19
|
export async function runWorkApplication(request, composition) {
|
|
66
20
|
const captured = captureWorkApplicationRequest(request);
|
|
67
21
|
if (!captured)
|
|
@@ -72,21 +26,10 @@ export async function runWorkApplication(request, composition) {
|
|
|
72
26
|
try {
|
|
73
27
|
if (captured.operation === 'browser-drive' || captured.operation === 'browser-screenshot') {
|
|
74
28
|
const kind = captured.operation === 'browser-screenshot' ? 'screenshot' : 'drive';
|
|
75
|
-
|
|
76
|
-
if (result.kind === 'refusal')
|
|
77
|
-
return refusal(operation, result.reason);
|
|
78
|
-
if (result.kind === 'complete') {
|
|
79
|
-
const gate = evaluateUiGate({ activating: kind, receipts: [result.receipt] });
|
|
80
|
-
if (gate.kind === 'refusal')
|
|
81
|
-
return refusal(operation, gate.reason);
|
|
82
|
-
}
|
|
83
|
-
return complete(operation, result);
|
|
84
|
-
}
|
|
85
|
-
if (captured.operation === 'test-run') {
|
|
86
|
-
const prepared = await composition.gate(captured);
|
|
87
|
-
const run = await runGate(prepared.registry, prepared.request, prepared.dependencies);
|
|
88
|
-
return run.satisfiesGate ? complete(operation, run) : unsatisfied(operation, run, `${run.verdict}: ${run.reasons.join('; ') || 'declared gate was not satisfied'}`);
|
|
29
|
+
return browserOutcome(captured.operation, await executeBrowserCommand(await composition.browser(kind, captured.role, captured.route)), captured.failOnFindings === true);
|
|
89
30
|
}
|
|
31
|
+
if (captured.operation === 'test-run')
|
|
32
|
+
return await testRun(captured, composition);
|
|
90
33
|
// A policy weakened in cockpit.toml since the last sync is not in force until a person accepts it there.
|
|
91
34
|
const recorded = await composition.recordedPolicy();
|
|
92
35
|
const weakened = recorded ? policyWeakening(recorded, composition.policy) : [];
|
|
@@ -100,87 +43,48 @@ export async function runWorkApplication(request, composition) {
|
|
|
100
43
|
return refusal(operation, errorCause(error));
|
|
101
44
|
}
|
|
102
45
|
}
|
|
46
|
+
// Findings are evidence for the agent and the grader to weigh, so they fail the command only for a criterion that demands a clean route.
|
|
47
|
+
export function browserOutcome(operation, result, failOnFindings) {
|
|
48
|
+
if (result.kind === 'refusal')
|
|
49
|
+
return refusal(operation, result.reason);
|
|
50
|
+
const gate = evaluateUiGate({ activating: operation === 'browser-screenshot' ? 'screenshot' : 'drive', receipts: [result.receipt] });
|
|
51
|
+
if (gate.kind === 'refusal')
|
|
52
|
+
return refusal(operation, gate.reason);
|
|
53
|
+
if (failOnFindings && result.receipt.verdict === 'findings')
|
|
54
|
+
return unsatisfied(operation, result, browserFindingsReason(result.receipt));
|
|
55
|
+
return complete(operation, result);
|
|
56
|
+
}
|
|
57
|
+
function browserFindingsReason(receipt) {
|
|
58
|
+
return receipt.surfaces.flatMap(row => {
|
|
59
|
+
const found = row.diagnostics;
|
|
60
|
+
if (!found || row.verdict !== 'findings')
|
|
61
|
+
return [];
|
|
62
|
+
const parts = [[found.consoleErrors, 'console error'], [found.pageErrors, 'uncaught page error'], [found.failedRequests, 'failed request']]
|
|
63
|
+
.flatMap(([count, noun]) => count ? [`${count} ${noun}${count === 1 ? '' : 's'}`] : []);
|
|
64
|
+
return [`${row.name}: ${parts.join(', ')}`];
|
|
65
|
+
}).join('; ');
|
|
66
|
+
}
|
|
103
67
|
async function work(request, composition) {
|
|
104
|
-
const
|
|
105
|
-
const operation =
|
|
106
|
-
const observe = async (orderId) => {
|
|
107
|
-
const [parsed, record] = await Promise.all([composition.readOrder(orderId), store.read(orderId)]);
|
|
108
|
-
if (!parsed.ok)
|
|
109
|
-
return { id: orderId, problems: parsed.problems, ...(record ? { record } : {}) };
|
|
110
|
-
// A source that cannot be read leaves this order without a digest, and so stale, not the whole listing refused.
|
|
111
|
-
let sources;
|
|
112
|
-
try {
|
|
113
|
-
sources = await composition.sourceDigests(parsed.value);
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
if (!(error instanceof RefusalError))
|
|
117
|
-
throw error;
|
|
118
|
-
return { id: orderId, order: parsed.value, problems: [`a cited source cannot be read: ${error.message}`], ...(record ? { record } : {}) };
|
|
119
|
-
}
|
|
120
|
-
const base = record?.accepted.base ?? await composition.heads();
|
|
121
|
-
return { id: orderId, order: parsed.value, problems: [], ...(record ? { record } : {}), digest: workOrderDigest(parsed.value, sources, base) };
|
|
122
|
-
};
|
|
123
|
-
const wip = async () => (await store.list()).filter(record => !TERMINAL.has(record.state)).length;
|
|
124
|
-
// Who passed a gate: the agent; a person, after typing the digest of exactly what they were shown;
|
|
125
|
-
// or CI, which locally records the step as awaiting the `work verify` job and passes it there.
|
|
126
|
-
const gated = async (gate, decider, lines, verb, apply) => {
|
|
127
|
-
const decision = decide(decider, composition.env);
|
|
128
|
-
if (decision === 'proceed')
|
|
129
|
-
return apply(decider === 'ci' ? 'ci' : 'agent');
|
|
130
|
-
if (decision === 'ci-only')
|
|
131
|
-
return apply('ci');
|
|
132
|
-
const digest = createHash('sha256').update(JSON.stringify({ gate, lines })).digest('hex');
|
|
133
|
-
let used = false;
|
|
134
|
-
return { kind: 'human-needed', operation, continuation: { digest,
|
|
135
|
-
prompt: [...lines.map(shown), `[work.gates] ${gate} = person. To ${verb} exactly this, type ${verb} ${digest}`].join('\n'),
|
|
136
|
-
decide: async (answer) => {
|
|
137
|
-
if (used)
|
|
138
|
-
return refusal(operation, `this prompt was already answered; rerun work ${operation.slice('work-'.length)}`);
|
|
139
|
-
used = true;
|
|
140
|
-
return answer.trim() === `${verb} ${digest}` ? apply('person') : refusal(operation, `Nothing was recorded: the person did not type ${verb} ${digest}`);
|
|
141
|
-
} } };
|
|
142
|
-
};
|
|
143
|
-
const current = async (orderId) => {
|
|
144
|
-
const row = await observe(orderId);
|
|
145
|
-
if (!row.record)
|
|
146
|
-
throw new RefusalError(`${orderId} is not accepted yet; run npx --no cockpit work accept --config <your cockpit.toml> --work-order ${orderId}`);
|
|
147
|
-
const state = displayState(row, composition.caps.attempts);
|
|
148
|
-
if (TERMINAL.has(row.record.state))
|
|
149
|
-
throw new RefusalError(`${orderId} is closed (${row.record.state})`);
|
|
150
|
-
if (state === 'stale')
|
|
151
|
-
throw new RefusalError(`${orderId} changed since it was accepted${row.problems.length ? ` and does not parse: ${row.problems.join('; ')}` : ''}; `
|
|
152
|
-
+ `run npx --no cockpit work accept --config <your cockpit.toml> --work-order ${orderId} to accept the change`);
|
|
153
|
-
if (state === 'blocked')
|
|
154
|
-
throw new RefusalError(openQuestions(row.record).length
|
|
155
|
-
? `${orderId} has open question ${openQuestions(row.record).join(', ')}; it is answered with work answer`
|
|
156
|
-
: `${orderId} reached the attempt cap (${composition.caps.attempts}); record the root cause with work note, then a person closes it with work signoff --partial or --abandon, or amends the contract`);
|
|
157
|
-
return { row, record: row.record, order: row.order };
|
|
158
|
-
};
|
|
159
|
-
const runChecks = async (order) => {
|
|
160
|
-
const results = [];
|
|
161
|
-
// Checks share the product's state, so they run in the order the contract lists them.
|
|
162
|
-
for (const criterion of order.criteria)
|
|
163
|
-
if (criterion.check)
|
|
164
|
-
results.push(await composition.runCheck(criterion.id, criterion.check));
|
|
165
|
-
for (const gate of order.gates)
|
|
166
|
-
results.push(await composition.runCheck(`gate:${gate}`, { gate }));
|
|
167
|
-
return results;
|
|
168
|
-
};
|
|
68
|
+
const context = workContext(composition, request.operation);
|
|
69
|
+
const { operation, policy, store, observe, wip, gated, current, runChecks } = context;
|
|
169
70
|
if (request.operation === 'work-status') {
|
|
170
71
|
const ids = request.workOrderId ? [request.workOrderId] : [...new Set([...await composition.orderIds(), ...(await store.list()).map(row => row.id)])].sort();
|
|
171
72
|
const rows = await Promise.all(ids.map(observe));
|
|
172
73
|
const closed = new Set(rows.filter(row => row.record?.state === 'done').map(row => row.id));
|
|
173
74
|
return complete(operation, {
|
|
174
75
|
grading: policy.grading, gates: policy.gates, wip: { open: await wip(), cap: composition.caps.wip },
|
|
175
|
-
orders: rows.map(row => ({
|
|
76
|
+
orders: await Promise.all(rows.map(async (row) => ({
|
|
176
77
|
id: row.id, state: displayState(row, composition.caps.attempts),
|
|
177
78
|
...row.record?.closure ? { closedAs: row.record.closure.how, closedBy: row.record.closure.by } : {},
|
|
178
79
|
...row.record ? { attempts: row.record.attempts, acceptedBy: row.record.accepted.by, openQuestions: openQuestions(row.record),
|
|
80
|
+
acknowledgements: row.record.acknowledgements.map(({ by, note, findings }) => ({ by, note, findings })),
|
|
179
81
|
awaitingCi: pendingCi(row.record), ...row.record.grade ? { grade: { mode: row.record.grade.mode, passed: row.record.grade.passed,
|
|
180
82
|
criteria: row.record.grade.criteria, outOfScope: row.record.grade.outOfScope, ...row.record.grade.evaluator ? { evaluator: row.record.grade.evaluator } : {} } } : {} } : {},
|
|
181
83
|
...row.order ? { waitingOn: row.order.depends_on.filter(dependency => !closed.has(dependency)) } : {},
|
|
84
|
+
...row.record && !TERMINAL.has(row.record.state) ? { openDecisions: await unresolvedDecisions(row.record, composition) } : {},
|
|
85
|
+
...row.order && !TERMINAL.has(row.record?.state ?? 'accepted') ? await relatedDecisions(context, row.order) : {},
|
|
182
86
|
...row.problems.length ? { problems: row.problems } : {},
|
|
183
|
-
})),
|
|
87
|
+
}))),
|
|
184
88
|
});
|
|
185
89
|
}
|
|
186
90
|
if (request.operation === 'work-accept') {
|
|
@@ -200,8 +104,8 @@ async function work(request, composition) {
|
|
|
200
104
|
if (existing)
|
|
201
105
|
throw new RefusalError(`${order.id} was accepted while this prompt was open; rerun work status`);
|
|
202
106
|
const record = { format: 'cockpit-work-order/v1', id: order.id, state: 'accepted',
|
|
203
|
-
accepted: { at: now(), by, digest, order, sources, base }, amendments: [], attempts: 0, notes: [] };
|
|
204
|
-
return { record, value: complete(operation, { id: order.id, state: 'accepted', by, digest, ...by === 'ci' ? { awaitingCi: ['accept'] } : {} }) };
|
|
107
|
+
accepted: { at: now(), by, digest, order, sources, base }, amendments: [], acknowledgements: [], attempts: 0, notes: [] };
|
|
108
|
+
return { record, value: complete(operation, { id: order.id, state: 'accepted', by, digest, ...by === 'ci' ? { awaitingCi: ['accept'] } : {}, ...await relatedDecisions(context, order) }) };
|
|
205
109
|
}));
|
|
206
110
|
}
|
|
207
111
|
const record = row.record;
|
|
@@ -209,19 +113,27 @@ async function work(request, composition) {
|
|
|
209
113
|
return refusal(operation, `${order.id} is closed (${record.state}); write a new order for further work`);
|
|
210
114
|
if (row.digest === record.accepted.digest)
|
|
211
115
|
return refusal(operation, `${order.id} is already accepted and unchanged since`);
|
|
212
|
-
const weakened =
|
|
213
|
-
// Removing
|
|
214
|
-
|
|
116
|
+
const { removed: weakened, review } = contractAmendment(record.accepted.order, order);
|
|
117
|
+
// Removing a criterion or gate lowers the bar in every reading, so an agent never certifies it. A rewrite may
|
|
118
|
+
// only clarify: where a model grades, the next grade judges it; under checks nothing could, so a person does.
|
|
119
|
+
// So does the amendment after a grade that found the agent's earlier ones lowered the bar.
|
|
120
|
+
const unjudgeable = policy.grading === 'checks' ? review.filter(row => checksBindsField(row.field)) : [];
|
|
121
|
+
const failedReview = record.amendmentsFailedReview;
|
|
122
|
+
const decider = policy.gates.amend === 'agent' && (weakened.length || unjudgeable.length || failedReview) ? 'person' : policy.gates.amend;
|
|
215
123
|
const sources = await composition.sourceDigests(order);
|
|
216
124
|
const digest = workOrderDigest(order, sources, record.accepted.base);
|
|
217
125
|
return gated('amend', decider, [`Amend ${order.id} (digest ${record.accepted.digest} -> ${digest})`,
|
|
218
|
-
...weakened.length ? [`This weakens the accepted contract: ${weakened.join('; ')}`] : [],
|
|
126
|
+
...weakened.length ? [`This weakens the accepted contract: ${weakened.join('; ')}`] : [],
|
|
127
|
+
...failedReview ? [`A grade found the agent's earlier amendments lower the bar: ${failedReview.note}`] : [],
|
|
128
|
+
...review.map(row => `Changed ${row.field}: "${row.before}" -> "${row.after}"`), ...shownOrder], 'amend', async (by) => store.update(order.id, async (existing) => {
|
|
219
129
|
if (!existing || existing.accepted.digest !== record.accepted.digest)
|
|
220
130
|
throw new RefusalError(`${order.id} changed while this prompt was open; rerun work accept`);
|
|
221
131
|
const next = { ...existing, state: 'accepted', attempts: 0, checks: undefined, grade: undefined,
|
|
132
|
+
...by === 'agent' ? {} : { amendmentsFailedReview: undefined },
|
|
222
133
|
accepted: { at: now(), by, digest, order, sources, base: existing.accepted.base },
|
|
223
|
-
amendments: [...existing.amendments, { at: now(), by, from: existing.accepted.digest, to: digest, weakened: [...weakened] }] };
|
|
224
|
-
return { record: next, value: complete(operation, { id: order.id, state: 'accepted', by, digest, amended: true, weakened
|
|
134
|
+
amendments: [...existing.amendments, { at: now(), by, from: existing.accepted.digest, to: digest, weakened: [...weakened], review: [...review] }] };
|
|
135
|
+
return { record: next, value: complete(operation, { id: order.id, state: 'accepted', by, digest, amended: true, weakened,
|
|
136
|
+
...by === 'agent' && review.length && policy.grading !== 'checks' ? { judgedAtGrade: review.map(row => row.field) } : {}, ...await relatedDecisions(context, order) }) };
|
|
225
137
|
}));
|
|
226
138
|
}
|
|
227
139
|
if (request.operation === 'work-check') {
|
|
@@ -240,6 +152,8 @@ async function work(request, composition) {
|
|
|
240
152
|
return passed ? complete(operation, value) : unsatisfied(operation, value, `${results.filter(row => !row.passed).map(row => `${row.criterion}: ${row.outcome}`).join('; ')}`);
|
|
241
153
|
}
|
|
242
154
|
if (request.operation === 'work-note') {
|
|
155
|
+
if (request.kind === 'decision' && !composition.docs?.decisions)
|
|
156
|
+
return refusal(operation, DECISIONS_UNDECLARED);
|
|
243
157
|
await current(request.workOrderId).catch(async (error) => {
|
|
244
158
|
// A note on a stale or blocked order is how its cause gets recorded, so only a closed or unaccepted one refuses.
|
|
245
159
|
const record = await store.read(request.workOrderId);
|
|
@@ -249,10 +163,13 @@ async function work(request, composition) {
|
|
|
249
163
|
const index = await store.update(request.workOrderId, async (existing) => {
|
|
250
164
|
if (!existing)
|
|
251
165
|
throw new RefusalError(`${request.workOrderId} is not accepted`);
|
|
252
|
-
return { record: { ...existing, notes: [...existing.notes, { at: now(), by: 'agent', kind: request.
|
|
166
|
+
return { record: { ...existing, notes: [...existing.notes, { at: now(), by: 'agent', kind: request.kind, text: request.text }] }, value: existing.notes.length + 1 };
|
|
253
167
|
});
|
|
254
|
-
return complete(operation, { id: request.workOrderId, note: index, ...request.question ? { question: index, state: 'blocked' } : {}
|
|
168
|
+
return complete(operation, { id: request.workOrderId, note: index, ...request.kind === 'question' ? { question: index, state: 'blocked' } : {},
|
|
169
|
+
...request.kind === 'decision' ? { decision: index, resolveWith: `npx --no cockpit work decide --config <your cockpit.toml> --work-order ${request.workOrderId} --decision ${index} --record` } : {} });
|
|
255
170
|
}
|
|
171
|
+
if (request.operation === 'work-decide')
|
|
172
|
+
return decideOrder(request, context);
|
|
256
173
|
if (request.operation === 'work-answer') {
|
|
257
174
|
const record = await store.read(request.workOrderId);
|
|
258
175
|
if (!record || TERMINAL.has(record.state))
|
|
@@ -267,117 +184,40 @@ async function work(request, composition) {
|
|
|
267
184
|
value: complete(operation, { id: request.workOrderId, answered: request.question, by }) };
|
|
268
185
|
}));
|
|
269
186
|
}
|
|
270
|
-
if (request.operation === 'work-grade')
|
|
187
|
+
if (request.operation === 'work-grade')
|
|
188
|
+
return gradeOrder(request, context);
|
|
189
|
+
if (request.operation === 'work-acknowledge') {
|
|
271
190
|
const { order, record } = await current(request.workOrderId);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Checks and scope bind in every mode, so an agent's own `met` covers neither a failed check nor a
|
|
282
|
-
// change outside scope. Changes are read after the checks, so files a check writes count too.
|
|
283
|
-
const checks = await runChecks(order);
|
|
284
|
-
const changes = await composition.changes(record.accepted.base);
|
|
285
|
-
const outOfScope = changes.map(change => change.path).filter(path => !scopeMatches(order.scope, path));
|
|
286
|
-
if (policy.grading === 'self') {
|
|
287
|
-
verdicts = order.criteria.map(criterion => ({ id: criterion.id, verdict: request.verdict, note: request.note }));
|
|
288
|
-
}
|
|
289
|
-
else if (policy.grading === 'checks') {
|
|
290
|
-
verdicts = order.criteria.map(criterion => {
|
|
291
|
-
const result = checks.find(row => row.criterion === criterion.id);
|
|
292
|
-
return { id: criterion.id, verdict: result.passed ? 'met' : 'unmet', note: `cockpit ran the check: ${result.outcome}` };
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
const graded = await composition.evaluate({ objective: order.objective, criteria: order.criteria, nonGoals: order.non_goals, scope: order.scope, checks, changes, outOfScope });
|
|
297
|
-
if ('refusal' in graded)
|
|
298
|
-
return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
|
|
299
|
-
verdicts = graded.criteria;
|
|
300
|
-
evaluator = { tool: graded.evaluator.tool, fallbacks: graded.evaluator.fallbacks.map(row => ({ ...row })) };
|
|
301
|
-
}
|
|
302
|
-
const passed = verdicts.every(row => row.verdict === 'met') && checks.every(row => row.passed) && outOfScope.length === 0;
|
|
303
|
-
const grade = { at: now(), digest: record.accepted.digest, mode: policy.grading, passed,
|
|
304
|
-
criteria: [...verdicts], outOfScope: [...outOfScope], ...evaluator ? { evaluator } : {}, ...request.note ? { note: request.note } : {} };
|
|
305
|
-
await store.update(order.id, async (existing) => {
|
|
191
|
+
const weak = await composition.testWeakening(record.accepted.base);
|
|
192
|
+
if (!weak.findings.length)
|
|
193
|
+
return refusal(operation, `${order.id} weakens no tests since its base, so there is nothing to acknowledge`);
|
|
194
|
+
const digest = testWeakeningDigest(weak.findings);
|
|
195
|
+
if (record.acknowledgements.some(row => row.digest === digest))
|
|
196
|
+
return refusal(operation, `this weakening of ${order.id} is already acknowledged; run work grade`);
|
|
197
|
+
const rows = weak.findings.map(describeWeakening);
|
|
198
|
+
return gated('acknowledge', policy.gates.acknowledge, [`Acknowledge that ${order.id} weakens tests since its base (test files from ${weak.patterns.source}):`,
|
|
199
|
+
...rows.map(row => ` ${row}`), 'Reason given:', ` ${request.note}`], 'acknowledge', async (by) => store.update(order.id, async (existing) => {
|
|
306
200
|
if (!existing || existing.accepted.digest !== record.accepted.digest)
|
|
307
|
-
throw new RefusalError(`${order.id} changed while
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
});
|
|
311
|
-
const value = { id: order.id, state: passed ? 'graded' : 'accepted', mode: policy.grading, criteria: verdicts, checks, outOfScope, ...evaluator ? { evaluator } : {} };
|
|
312
|
-
if (passed)
|
|
313
|
-
return complete(operation, value);
|
|
314
|
-
return unsatisfied(operation, value, [...verdicts.filter(row => row.verdict !== 'met').map(row => `${row.id} ${row.verdict}: ${row.note}`),
|
|
315
|
-
...checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`),
|
|
316
|
-
...outOfScope.length ? [`changed outside scope: ${outOfScope.join(', ')}`] : []].join('; '));
|
|
317
|
-
}
|
|
318
|
-
if (request.operation === 'work-signoff') {
|
|
319
|
-
const record = await store.read(request.workOrderId);
|
|
320
|
-
if (!record)
|
|
321
|
-
return refusal(operation, `${request.workOrderId} is not accepted`);
|
|
322
|
-
if (TERMINAL.has(record.state))
|
|
323
|
-
return refusal(operation, `${request.workOrderId} is already closed (${record.state})`);
|
|
324
|
-
let how;
|
|
325
|
-
if (request.close === 'done') {
|
|
326
|
-
const { order } = await current(request.workOrderId);
|
|
327
|
-
if (record.state !== 'graded' || !record.grade?.passed || record.grade.digest !== record.accepted.digest) {
|
|
328
|
-
return refusal(operation, `${order.id} is ${record.state}, not graded; run npx --no cockpit work grade --config <your cockpit.toml> --work-order ${order.id}`);
|
|
201
|
+
throw new RefusalError(`${order.id} changed while this prompt was open; rerun work acknowledge`);
|
|
202
|
+
if (testWeakeningDigest((await composition.testWeakening(existing.accepted.base)).findings) !== digest) {
|
|
203
|
+
throw new RefusalError(`the test changes of ${order.id} changed while this prompt was open; rerun work acknowledge`);
|
|
329
204
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return refusal(operation, `${order.id} depends on ${open.filter(Boolean).join(', ')}, which ${open.filter(Boolean).length === 1 ? 'is' : 'are'} not done`);
|
|
333
|
-
how = CLOSURE[record.grade.mode];
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
if (!request.note)
|
|
337
|
-
return refusal(operation, `closing ${request.workOrderId} as ${request.close} needs --note saying what was and was not done`);
|
|
338
|
-
how = request.close;
|
|
339
|
-
}
|
|
340
|
-
return gated('signoff', policy.gates.signoff, [`Close ${request.workOrderId} as ${request.close} (${how})`, ...request.note ? [`Note: ${request.note}`] : [],
|
|
341
|
-
...record.grade ? record.grade.criteria.map(row => `${row.id}: ${row.verdict} — ${row.note}`) : []], 'signoff', async (by) => store.update(request.workOrderId, async (existing) => {
|
|
342
|
-
if (!existing || existing.state !== record.state || existing.accepted.digest !== record.accepted.digest)
|
|
343
|
-
throw new RefusalError(`${request.workOrderId} changed while this prompt was open`);
|
|
344
|
-
const state = request.close;
|
|
345
|
-
return { record: { ...existing, state, closure: { at: now(), by, how, ...request.note ? { note: request.note } : {} } },
|
|
346
|
-
value: complete(operation, { id: request.workOrderId, state, closedAs: how, by }) };
|
|
205
|
+
return { record: { ...existing, acknowledgements: [...existing.acknowledgements, { at: now(), by, digest, findings: rows, note: request.note }] },
|
|
206
|
+
value: complete(operation, { id: order.id, acknowledged: rows, by, digest }) };
|
|
347
207
|
}));
|
|
348
208
|
}
|
|
349
|
-
if (request.operation === 'work-
|
|
350
|
-
return
|
|
351
|
-
if (request.operation === 'work-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
problems.push(`${record.id} is done without a passing grade of its accepted contract`);
|
|
360
|
-
awaiting.push(...pendingCi(record).map(step => `${record.id} ${step}`));
|
|
361
|
-
}
|
|
362
|
-
const ci = runningInCi(composition.env);
|
|
363
|
-
const value = { ci, problems, ...ci ? { confirmed: awaiting } : { awaitingCi: awaiting } };
|
|
364
|
-
return problems.length ? unsatisfied(operation, value, problems.join('; ')) : complete(operation, value);
|
|
209
|
+
if (request.operation === 'work-signoff')
|
|
210
|
+
return signoff(request, context);
|
|
211
|
+
if (request.operation === 'work-recover') {
|
|
212
|
+
// A pid probe sees only this machine's processes, so the command looks where it cannot first: the
|
|
213
|
+
// lock's host and the containers cockpit labels. Applied, it removes nothing it has not shown gone.
|
|
214
|
+
const recovered = await store.recover(request.workOrderId, request.apply, composition.containers);
|
|
215
|
+
if (!request.apply || recovered.lock === 'none' || recovered.lock === 'removed')
|
|
216
|
+
return complete(operation, recovered);
|
|
217
|
+
return refusal(operation, `the lock on ${request.workOrderId} was left in place: its holder ${recovered.lock === 'live' ? 'is still running' : 'cannot be shown gone'} `
|
|
218
|
+
+ `(${recovered.checked?.join('; ')}); wait for it to finish, or stop it on the host that holds it, then rerun work recover --apply`);
|
|
365
219
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return refusal(operation, `${request.criteriaFile} is not a criteria file: ${parsed.problems.join('; ')}`);
|
|
370
|
-
const file = parsed.value;
|
|
371
|
-
const checks = await runChecks(file);
|
|
372
|
-
// The criteria file is the request, not part of the change it grades.
|
|
373
|
-
const changes = (await composition.changes(await composition.heads())).filter(change => change.path !== posix.normalize(request.criteriaFile));
|
|
374
|
-
const outOfScope = file.scope.length ? changes.map(change => change.path).filter(path => !scopeMatches(file.scope, path)) : [];
|
|
375
|
-
const subject = { ...file.objective ? { objective: file.objective } : {}, criteria: file.criteria, nonGoals: [], scope: file.scope, checks, changes, outOfScope };
|
|
376
|
-
const graded = await composition.evaluate(subject);
|
|
377
|
-
if ('refusal' in graded)
|
|
378
|
-
return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
|
|
379
|
-
const passed = graded.criteria.every(row => row.verdict === 'met') && checks.every(row => row.passed) && outOfScope.length === 0;
|
|
380
|
-
const value = { criteria: graded.criteria, checks, outOfScope, evaluator: graded.evaluator };
|
|
381
|
-
return passed ? complete(operation, value) : unsatisfied(operation, value, graded.criteria.filter(row => row.verdict !== 'met').map(row => `${row.id} ${row.verdict}: ${row.note}`)
|
|
382
|
-
.concat(checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`), outOfScope.length ? [`changed outside scope: ${outOfScope.join(', ')}`] : []).join('; '));
|
|
220
|
+
if (request.operation === 'work-verify')
|
|
221
|
+
return verify(context);
|
|
222
|
+
return gradeCriteriaFile(request, context);
|
|
383
223
|
}
|
|
@@ -1,52 +1,12 @@
|
|
|
1
1
|
import { type EvaluatorTool } from '../schema.js';
|
|
2
|
-
import type { ConsumedWorkEnvironmentConfig } from '../config/load.js';
|
|
3
2
|
import { type EffectivePolicy } from '../autonomy.js';
|
|
4
|
-
import { type EvaluatorChain } from '../grading/evaluate.js';
|
|
5
3
|
import { type ChangedFile, type GradingSubject, type Verdict } from '../grading/grade.js';
|
|
6
|
-
import { type BrowserCommandInput, type BrowserCommandKind
|
|
7
|
-
import { type
|
|
8
|
-
import { type BrowserProjectClient } from '../browser/surfaces.js';
|
|
9
|
-
import type { EnvironmentSnapshot, RuntimeEvidence } from '../environment/evidence.js';
|
|
4
|
+
import { type BrowserCommandInput, type BrowserCommandKind } from '../browser/commands.js';
|
|
5
|
+
import { type RepositoryWeakening, type TestFilePatterns } from '../testing/weakening.js';
|
|
10
6
|
import { type GateDependencies, type GateRegistry, type GateRequest } from '../testing/gates.js';
|
|
11
7
|
import { parseCriteriaFile, type ParsedFile, type WorkOrder, type WorkOrderCheck } from './contract.js';
|
|
12
|
-
import { type CheckResult, type WorkStore } from './store.js';
|
|
13
|
-
|
|
14
|
-
type BrowserAuthority = Readonly<{
|
|
15
|
-
runtime: BrowserRuntimeProjection;
|
|
16
|
-
client: BrowserProjectClient;
|
|
17
|
-
policy: BrowserRoutePolicy;
|
|
18
|
-
state?: BrowserCommandInput['state'];
|
|
19
|
-
}>;
|
|
20
|
-
type EvaluatorAuthority = Readonly<{
|
|
21
|
-
path?: string;
|
|
22
|
-
bytes: string;
|
|
23
|
-
sha256: string;
|
|
24
|
-
restriction: 'instructed';
|
|
25
|
-
executableDirectories?: readonly string[];
|
|
26
|
-
chain: EvaluatorChain;
|
|
27
|
-
environment?: Readonly<Record<string, string | undefined>>;
|
|
28
|
-
}>;
|
|
29
|
-
export interface WorkApplicationAuthorities {
|
|
30
|
-
readonly browser?: BrowserAuthority;
|
|
31
|
-
readonly resolveBrowser?: () => Promise<BrowserAuthority>;
|
|
32
|
-
readonly testing?: Readonly<{
|
|
33
|
-
registry: GateRegistry;
|
|
34
|
-
dependencies: GateDependencies;
|
|
35
|
-
runtime?: () => Promise<Readonly<{
|
|
36
|
-
stack: NonNullable<GateRequest['stack']>;
|
|
37
|
-
evidence?: Readonly<{
|
|
38
|
-
snapshot: EnvironmentSnapshot;
|
|
39
|
-
capture: () => Promise<RuntimeEvidence>;
|
|
40
|
-
}>;
|
|
41
|
-
}>>;
|
|
42
|
-
}>;
|
|
43
|
-
readonly evaluator?: EvaluatorAuthority;
|
|
44
|
-
readonly resolveEvaluator?: () => Promise<EvaluatorAuthority>;
|
|
45
|
-
readonly executableDirectories?: readonly string[];
|
|
46
|
-
readonly declaredEnvironment?: Readonly<Record<string, string>>;
|
|
47
|
-
readonly env: Readonly<Record<string, string | undefined>>;
|
|
48
|
-
}
|
|
49
|
-
export type GradingResult = Readonly<{
|
|
8
|
+
import { type CheckResult, type ContainerEvidence, type WorkStore } from './store.js';
|
|
9
|
+
type GradingResult = Readonly<{
|
|
50
10
|
criteria: readonly Verdict[];
|
|
51
11
|
evaluator: Readonly<{
|
|
52
12
|
tool: string;
|
|
@@ -78,16 +38,43 @@ export interface WorkApplicationComposition {
|
|
|
78
38
|
readonly sourceDigests: (order: Pick<WorkOrder, 'criteria'>) => Promise<Readonly<Record<string, string>>>;
|
|
79
39
|
readonly heads: () => Promise<Readonly<Record<string, string>>>;
|
|
80
40
|
readonly runCheck: (criterion: string, check: WorkOrderCheck) => Promise<CheckResult>;
|
|
81
|
-
readonly changes: (base: Readonly<Record<string, string>>) => Promise<readonly ChangedFile[]>;
|
|
82
41
|
readonly evaluate: (subject: GradingSubject) => Promise<GradingResult>;
|
|
83
|
-
|
|
42
|
+
/** Test weakening in each checkout since `base`, over the files this cockpit treats as tests. */
|
|
43
|
+
readonly testWeakening: (base: Readonly<Record<string, string>>) => Promise<TestWeakeningObservation>;
|
|
44
|
+
/** What changed in each checkout since `base` (committed, staged, unstaged and untracked, each with its diff), and its test weakening, from one listing of untracked files. */
|
|
45
|
+
readonly review: (base: Readonly<Record<string, string>>) => Promise<Readonly<{
|
|
46
|
+
changes: readonly ChangedFile[];
|
|
47
|
+
weakening: TestWeakeningObservation;
|
|
48
|
+
}>>;
|
|
49
|
+
readonly browser: (kind: BrowserCommandKind, role?: string, route?: string) => Promise<BrowserCommandInput>;
|
|
50
|
+
/** The [docs] table as declared; absent when the cockpit declares none. */
|
|
51
|
+
readonly docs?: Readonly<{
|
|
52
|
+
decisions?: string;
|
|
53
|
+
decisionsIndex?: string;
|
|
54
|
+
}>;
|
|
55
|
+
/** Each Markdown file in [docs] decisions, by reference, with its text; empty when the directory does not exist yet. */
|
|
56
|
+
readonly decisionRecords: () => Promise<ReadonlyMap<string, string>>;
|
|
57
|
+
/** A decision record's or the index's text, or undefined when the file does not exist. */
|
|
58
|
+
readonly readDocsFile: (reference: string) => Promise<string | undefined>;
|
|
59
|
+
/** Writes each record, a new one exclusively and an existing one replaced, then appends `indexLine` to [docs] decisions_index. */
|
|
60
|
+
readonly writeDecisionRecords: (writes: readonly Readonly<{
|
|
61
|
+
reference: string;
|
|
62
|
+
text: string;
|
|
63
|
+
create: boolean;
|
|
64
|
+
}>[], indexLine?: string) => Promise<void>;
|
|
84
65
|
readonly gate: (request: WorkApplicationGateRequest) => Promise<Readonly<{
|
|
85
66
|
registry: GateRegistry;
|
|
86
67
|
request: GateRequest;
|
|
87
68
|
dependencies: GateDependencies;
|
|
88
69
|
}>>;
|
|
70
|
+
/** The containers cockpit labels on this machine's local Docker daemon, where a lock's holder may run unseen by a pid probe. */
|
|
71
|
+
readonly containers: () => Promise<ContainerEvidence>;
|
|
89
72
|
}
|
|
90
|
-
|
|
73
|
+
type TestWeakeningObservation = Readonly<{
|
|
74
|
+
patterns: TestFilePatterns;
|
|
75
|
+
findings: readonly RepositoryWeakening[];
|
|
76
|
+
}>;
|
|
77
|
+
interface WorkApplicationGateRequest {
|
|
91
78
|
readonly operation: 'test-run';
|
|
92
79
|
readonly gate: string;
|
|
93
80
|
readonly mode: 'canonical' | 'full' | 'staged';
|
|
@@ -96,18 +83,8 @@ export interface WorkApplicationGateRequest {
|
|
|
96
83
|
value: string;
|
|
97
84
|
}>;
|
|
98
85
|
}
|
|
99
|
-
export type VerificationObservation = Readonly<{
|
|
100
|
-
kind: 'executed';
|
|
101
|
-
exitCode: number;
|
|
102
|
-
stdout: string;
|
|
103
|
-
stderr: string;
|
|
104
|
-
truncated: boolean;
|
|
105
|
-
}> | Readonly<{
|
|
106
|
-
kind: 'error' | 'unavailable';
|
|
107
|
-
reason: string;
|
|
108
|
-
}>;
|
|
109
86
|
export declare function isWorkApplicationComposition(value: unknown): value is WorkApplicationComposition;
|
|
110
|
-
|
|
87
|
+
type ConfiguredWorkApplicationCompositionResult = Readonly<{
|
|
111
88
|
kind: 'complete';
|
|
112
89
|
composition: WorkApplicationComposition;
|
|
113
90
|
}> | Readonly<{
|