@guilz-dev/belay 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -10
- package/dist/adapters/shared/gate-runtime.d.ts +5 -0
- package/dist/adapters/shared/gate-runtime.js +237 -33
- package/dist/bundle/claude-runtime.mjs +4082 -1230
- package/dist/bundle/codex-runtime.mjs +4057 -1205
- package/dist/bundle/cursor-runtime.mjs +4057 -1205
- package/dist/commands/classify-for-report.js +5 -0
- package/dist/commands/doctor.js +73 -6
- package/dist/commands/explain.js +3 -2
- package/dist/commands/metrics.js +42 -0
- package/dist/commands/recovery-checkpoints.d.ts +8 -1
- package/dist/commands/recovery-checkpoints.js +44 -11
- package/dist/commands/session.d.ts +5 -0
- package/dist/commands/session.js +8 -1
- package/dist/config-io.js +2 -2
- package/dist/conformance/contained-execution-guarantee.d.ts +121 -0
- package/dist/conformance/contained-execution-guarantee.js +121 -0
- package/dist/conformance/guarantee-table.js +7 -0
- package/dist/core/audit-io.js +8 -0
- package/dist/core/audit-metrics.d.ts +11 -0
- package/dist/core/audit-metrics.js +14 -0
- package/dist/core/audit-recovery-metrics.d.ts +33 -0
- package/dist/core/audit-recovery-metrics.js +146 -0
- package/dist/core/audit-types.d.ts +7 -1
- package/dist/core/audit-types.js +1 -1
- package/dist/core/bounded-output.d.ts +11 -0
- package/dist/core/bounded-output.js +26 -0
- package/dist/core/capability/attestation.d.ts +56 -0
- package/dist/core/capability/attestation.js +110 -3
- package/dist/core/capability/boundary-session.d.ts +5 -0
- package/dist/core/capability/boundary-session.js +38 -1
- package/dist/core/capability/index.d.ts +1 -1
- package/dist/core/capability/index.js +1 -1
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +89 -10
- package/dist/core/contained-execution/docker-policy.d.ts +18 -0
- package/dist/core/contained-execution/docker-policy.js +97 -0
- package/dist/core/contained-execution/docker.d.ts +223 -0
- package/dist/core/contained-execution/docker.js +846 -0
- package/dist/core/contained-execution/eligibility.d.ts +9 -0
- package/dist/core/contained-execution/eligibility.js +103 -0
- package/dist/core/contained-execution/failure.d.ts +18 -0
- package/dist/core/contained-execution/failure.js +105 -0
- package/dist/core/contained-execution/mirror.d.ts +54 -0
- package/dist/core/contained-execution/mirror.js +587 -0
- package/dist/core/contained-execution/policy.d.ts +10 -0
- package/dist/core/contained-execution/policy.js +20 -0
- package/dist/core/effect-ir/index.d.ts +1 -1
- package/dist/core/effect-ir/index.js +1 -1
- package/dist/core/effect-ir/normalize.d.ts +5 -0
- package/dist/core/effect-ir/normalize.js +17 -7
- package/dist/core/effect-ir/shell-build.js +2 -14
- package/dist/core/effect-ir/shell-lower.js +100 -25
- package/dist/core/gate-contract.d.ts +4 -1
- package/dist/core/gate-contract.js +2 -0
- package/dist/core/process-runner.d.ts +10 -3
- package/dist/core/process-runner.js +66 -10
- package/dist/core/recovery/checkpoint.d.ts +3 -0
- package/dist/core/recovery/checkpoint.js +33 -4
- package/dist/core/recovery/types.d.ts +1 -0
- package/dist/core/scrub.d.ts +17 -0
- package/dist/core/scrub.js +499 -5
- package/dist/core/standing-allow.d.ts +0 -20
- package/dist/core/standing-allow.js +0 -71
- package/dist/core/transactional/apply-observed-changes.d.ts +1 -0
- package/dist/core/transactional/apply-observed-changes.js +11 -0
- package/dist/core/transactional/backend-selector.d.ts +2 -1
- package/dist/core/transactional/backend-selector.js +32 -29
- package/dist/core/transactional/backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +242 -54
- package/dist/core/transactional/file-checkpoint-git.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-git.js +53 -0
- package/dist/core/transactional/file-checkpoint-isolation.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-isolation.js +16 -0
- package/dist/core/transactional/git-worktree-backend.js +3 -0
- package/dist/core/transactional/git-worktree.d.ts +1 -0
- package/dist/core/transactional/git-worktree.js +1 -0
- package/dist/core/transactional/runner.js +59 -13
- package/dist/core/types.d.ts +13 -0
- package/dist/core/verdict/parser.d.ts +10 -0
- package/dist/core/verdict/parser.js +271 -40
- package/dist/core/verdict/shell-semantics.js +21 -0
- package/dist/corpus/adversarial-probe.d.ts +1 -1
- package/dist/corpus/adversarial-probe.js +3 -3
- package/dist/corpus/benign-probe-cores.d.ts +6 -0
- package/dist/corpus/{must-allow-commands.js → benign-probe-cores.js} +8 -2
- package/dist/corpus/coverage-compare.d.ts +36 -0
- package/dist/corpus/coverage-compare.js +197 -0
- package/dist/corpus/coverage-contexts.d.ts +17 -0
- package/dist/corpus/coverage-contexts.js +88 -0
- package/dist/corpus/coverage-matrix.d.ts +37 -0
- package/dist/corpus/coverage-matrix.js +159 -0
- package/dist/corpus/coverage-probe.d.ts +91 -0
- package/dist/corpus/coverage-probe.js +341 -0
- package/dist/corpus/evaluate.d.ts +1 -1
- package/dist/corpus/evaluate.js +1 -1
- package/dist/corpus/structural-fixture-root.d.ts +2 -0
- package/dist/corpus/structural-fixture-root.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/skills/belay/SKILL.md +19 -0
- package/skills/belay/belay-status.md +5 -0
- package/dist/corpus/must-allow-commands.d.ts +0 -6
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { classifierOptionsFromConfig } from '../core/config.js';
|
|
5
|
+
import { classifyShell } from '../core/verdict/adapter.js';
|
|
6
|
+
import { CoverageCompareError, compareBaselineWarnings, compareCoverageReports, formatCoverageCompareReport, parseCoverageProbeReportForCompare, } from './coverage-compare.js';
|
|
7
|
+
import { buildCoverageEvalContexts, hashStableJson, resolvedConfigHash, } from './coverage-contexts.js';
|
|
8
|
+
import { assertKnownContextIds, CoverageMatrixSchemaError, DEFAULT_PROBE_CONTEXT_IDS, defaultCoverageMatrixPath, flattenCoverageCases, loadCoverageMatrix, } from './coverage-matrix.js';
|
|
9
|
+
export class CoverageProbeCliError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'CoverageProbeCliError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export const COVERAGE_PROBE_REPORT_SCHEMA_VERSION = 2;
|
|
16
|
+
function defaultRepoRoot() {
|
|
17
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
18
|
+
}
|
|
19
|
+
export const defaultClassifyFn = async (command, evalContext) => {
|
|
20
|
+
const options = {
|
|
21
|
+
...classifierOptionsFromConfig(evalContext.config),
|
|
22
|
+
...evalContext.options,
|
|
23
|
+
};
|
|
24
|
+
return classifyShell(command, evalContext.cwd, evalContext.repoRoot, evalContext.config, options);
|
|
25
|
+
};
|
|
26
|
+
function matchesExpectation(expectation, actual) {
|
|
27
|
+
if (actual.verdict !== expectation.verdict) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (expectation.reason !== undefined && actual.reason !== expectation.reason) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function caseMatchesFilter(testCase, filters) {
|
|
36
|
+
if (filters.length === 0) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const filterSet = new Set(filters);
|
|
40
|
+
if (filterSet.has(testCase.groupId) || filterSet.has(testCase.id)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return testCase.tags.some((tag) => filterSet.has(tag));
|
|
44
|
+
}
|
|
45
|
+
async function readPackageVersion(repoRoot) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = JSON.parse(await readFile(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
48
|
+
return raw.version ?? null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function readGitSha() {
|
|
55
|
+
return process.env.GITHUB_SHA ?? process.env.GIT_SHA ?? null;
|
|
56
|
+
}
|
|
57
|
+
function initBucket() {
|
|
58
|
+
return { total: 0, matched: 0, mismatched: 0, observeOnly: 0 };
|
|
59
|
+
}
|
|
60
|
+
function recordBucket(bucket, result) {
|
|
61
|
+
bucket.total += 1;
|
|
62
|
+
if (result.observeOnly) {
|
|
63
|
+
bucket.observeOnly += 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (result.match) {
|
|
67
|
+
bucket.matched += 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
bucket.mismatched += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function evaluateCoverageMatrix(matrix, options = {}) {
|
|
74
|
+
const classifyFn = options.classifyFn ?? defaultClassifyFn;
|
|
75
|
+
const contextIds = options.contextIds ?? [...DEFAULT_PROBE_CONTEXT_IDS];
|
|
76
|
+
const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot());
|
|
77
|
+
const filters = options.filters ?? [];
|
|
78
|
+
const contexts = options.evalContexts ?? (await buildCoverageEvalContexts(contextIds, repoRoot));
|
|
79
|
+
const cases = flattenCoverageCases(matrix).filter((testCase) => caseMatchesFilter(testCase, filters));
|
|
80
|
+
const results = [];
|
|
81
|
+
for (const testCase of cases) {
|
|
82
|
+
for (const evalContext of contexts) {
|
|
83
|
+
const expectation = testCase.expectations?.[evalContext.id];
|
|
84
|
+
const observeOnly = expectation === undefined;
|
|
85
|
+
const actual = await classifyFn(testCase.command, evalContext);
|
|
86
|
+
const match = observeOnly || expectation === undefined ? null : matchesExpectation(expectation, actual);
|
|
87
|
+
results.push({
|
|
88
|
+
caseId: testCase.id,
|
|
89
|
+
groupId: testCase.groupId,
|
|
90
|
+
command: testCase.command,
|
|
91
|
+
commandHash: hashStableJson(testCase.command),
|
|
92
|
+
context: evalContext.id,
|
|
93
|
+
tags: testCase.tags,
|
|
94
|
+
observeOnly,
|
|
95
|
+
expectation,
|
|
96
|
+
expectationHash: expectation ? hashStableJson(expectation) : null,
|
|
97
|
+
actual: {
|
|
98
|
+
verdict: actual.verdict,
|
|
99
|
+
reason: actual.reason,
|
|
100
|
+
fingerprint: actual.fingerprint,
|
|
101
|
+
},
|
|
102
|
+
match,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
function summarizeResults(results, filters) {
|
|
109
|
+
const summary = {
|
|
110
|
+
total: results.length,
|
|
111
|
+
observeOnly: 0,
|
|
112
|
+
matched: 0,
|
|
113
|
+
mismatched: 0,
|
|
114
|
+
filteredEmpty: filters.length > 0 && results.length === 0,
|
|
115
|
+
byGroup: {},
|
|
116
|
+
byTag: {},
|
|
117
|
+
};
|
|
118
|
+
for (const result of results) {
|
|
119
|
+
if (result.observeOnly) {
|
|
120
|
+
summary.observeOnly += 1;
|
|
121
|
+
}
|
|
122
|
+
else if (result.match) {
|
|
123
|
+
summary.matched += 1;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
summary.mismatched += 1;
|
|
127
|
+
}
|
|
128
|
+
if (!summary.byGroup[result.groupId]) {
|
|
129
|
+
summary.byGroup[result.groupId] = initBucket();
|
|
130
|
+
}
|
|
131
|
+
recordBucket(summary.byGroup[result.groupId], result);
|
|
132
|
+
for (const tag of result.tags) {
|
|
133
|
+
if (!summary.byTag[tag]) {
|
|
134
|
+
summary.byTag[tag] = initBucket();
|
|
135
|
+
}
|
|
136
|
+
recordBucket(summary.byTag[tag], result);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return summary;
|
|
140
|
+
}
|
|
141
|
+
function resultsSignature(results) {
|
|
142
|
+
return hashStableJson(results.map((result) => ({
|
|
143
|
+
caseId: result.caseId,
|
|
144
|
+
context: result.context,
|
|
145
|
+
verdict: result.actual.verdict,
|
|
146
|
+
reason: result.actual.reason,
|
|
147
|
+
fingerprint: result.actual.fingerprint,
|
|
148
|
+
})));
|
|
149
|
+
}
|
|
150
|
+
export async function runCoverageProbe(options = {}) {
|
|
151
|
+
const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot());
|
|
152
|
+
const matrixPath = path.resolve(options.matrixPath ?? defaultCoverageMatrixPath(repoRoot));
|
|
153
|
+
const matrix = await loadCoverageMatrix(matrixPath);
|
|
154
|
+
const contextIds = options.contextIds ?? [...DEFAULT_PROBE_CONTEXT_IDS];
|
|
155
|
+
const contexts = await buildCoverageEvalContexts(contextIds, repoRoot);
|
|
156
|
+
const repeat = Math.max(1, options.repeat ?? 1);
|
|
157
|
+
const filters = options.filters ?? [];
|
|
158
|
+
const probeOptions = { ...options, evalContexts: contexts };
|
|
159
|
+
let results = await evaluateCoverageMatrix(matrix, probeOptions);
|
|
160
|
+
let driftRuns = 0;
|
|
161
|
+
const firstResults = results;
|
|
162
|
+
const firstSignature = resultsSignature(results);
|
|
163
|
+
for (let run = 2; run <= repeat; run += 1) {
|
|
164
|
+
const nextResults = await evaluateCoverageMatrix(matrix, probeOptions);
|
|
165
|
+
if (resultsSignature(nextResults) !== firstSignature) {
|
|
166
|
+
driftRuns += 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
results = firstResults;
|
|
170
|
+
const mismatches = results.filter((result) => !result.observeOnly && result.match === false);
|
|
171
|
+
return {
|
|
172
|
+
reportSchemaVersion: COVERAGE_PROBE_REPORT_SCHEMA_VERSION,
|
|
173
|
+
generatedAt: new Date().toISOString(),
|
|
174
|
+
packageVersion: await readPackageVersion(repoRoot),
|
|
175
|
+
gitSha: readGitSha(),
|
|
176
|
+
matrixPath,
|
|
177
|
+
matrixHash: hashStableJson(matrix),
|
|
178
|
+
repeat,
|
|
179
|
+
driftRuns,
|
|
180
|
+
contexts: contexts.map((evalContext) => ({
|
|
181
|
+
id: evalContext.id,
|
|
182
|
+
cwd: evalContext.cwd,
|
|
183
|
+
repoRoot: evalContext.repoRoot,
|
|
184
|
+
resolvedConfigHash: resolvedConfigHash(evalContext),
|
|
185
|
+
configProvenance: evalContext.configProvenance,
|
|
186
|
+
})),
|
|
187
|
+
summary: summarizeResults(results, filters),
|
|
188
|
+
results,
|
|
189
|
+
mismatches,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export function formatCoverageProbeSummary(report) {
|
|
193
|
+
const lines = [
|
|
194
|
+
'Coverage probe',
|
|
195
|
+
` cases: ${report.summary.total} (observe-only: ${report.summary.observeOnly})`,
|
|
196
|
+
` matched: ${report.summary.matched}`,
|
|
197
|
+
` mismatched: ${report.summary.mismatched}`,
|
|
198
|
+
` repeat: ${report.repeat} (drift runs: ${report.driftRuns})`,
|
|
199
|
+
' contexts:',
|
|
200
|
+
];
|
|
201
|
+
for (const evalContext of report.contexts) {
|
|
202
|
+
lines.push(` ${evalContext.id}: cwd=${evalContext.cwd} configHash=${evalContext.resolvedConfigHash.slice(0, 12)}…`);
|
|
203
|
+
}
|
|
204
|
+
if (report.mismatches.length > 0) {
|
|
205
|
+
lines.push(' mismatches:');
|
|
206
|
+
for (const mismatch of report.mismatches.slice(0, 15)) {
|
|
207
|
+
lines.push(` - [${mismatch.context}] ${mismatch.caseId}: expected=${JSON.stringify(mismatch.expectation)} actual=${mismatch.actual.verdict}/${mismatch.actual.reason}`);
|
|
208
|
+
}
|
|
209
|
+
if (report.mismatches.length > 15) {
|
|
210
|
+
lines.push(` … and ${report.mismatches.length - 15} more`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (report.summary.filteredEmpty) {
|
|
214
|
+
lines.push(' warning: filter matched zero cases');
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
export function parseCoverageProbeCliArgs(argv) {
|
|
219
|
+
let repoRoot;
|
|
220
|
+
let matrixPath;
|
|
221
|
+
let contextIds;
|
|
222
|
+
let filters = [];
|
|
223
|
+
let repeat = 1;
|
|
224
|
+
let strict = false;
|
|
225
|
+
let outputDir;
|
|
226
|
+
let comparePath;
|
|
227
|
+
let json = false;
|
|
228
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
229
|
+
const arg = argv[i];
|
|
230
|
+
if (arg === '--repo-root' && argv[i + 1]) {
|
|
231
|
+
repoRoot = argv[++i];
|
|
232
|
+
}
|
|
233
|
+
else if (arg === '--matrix' && argv[i + 1]) {
|
|
234
|
+
matrixPath = argv[++i];
|
|
235
|
+
}
|
|
236
|
+
else if (arg === '--context' && i + 1 < argv.length) {
|
|
237
|
+
const rawContexts = argv[++i]
|
|
238
|
+
.split(',')
|
|
239
|
+
.map((value) => value.trim())
|
|
240
|
+
.filter(Boolean);
|
|
241
|
+
contextIds = assertKnownContextIds(rawContexts);
|
|
242
|
+
}
|
|
243
|
+
else if (arg === '--filter' && i + 1 < argv.length) {
|
|
244
|
+
filters = argv[++i]
|
|
245
|
+
.split(',')
|
|
246
|
+
.map((value) => value.trim())
|
|
247
|
+
.filter(Boolean);
|
|
248
|
+
if (filters.length === 0) {
|
|
249
|
+
throw new CoverageProbeCliError('--filter requires at least one tag, group id, or case id');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (arg === '--repeat' && argv[i + 1]) {
|
|
253
|
+
repeat = Number.parseInt(argv[++i], 10);
|
|
254
|
+
if (!Number.isFinite(repeat) || repeat < 1) {
|
|
255
|
+
throw new CoverageProbeCliError('--repeat must be a positive integer');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else if (arg === '--output-dir' && argv[i + 1]) {
|
|
259
|
+
outputDir = argv[++i];
|
|
260
|
+
}
|
|
261
|
+
else if (arg === '--compare' && argv[i + 1]) {
|
|
262
|
+
comparePath = argv[++i];
|
|
263
|
+
}
|
|
264
|
+
else if (arg === '--strict') {
|
|
265
|
+
strict = true;
|
|
266
|
+
}
|
|
267
|
+
else if (arg === '--json') {
|
|
268
|
+
json = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { repoRoot, matrixPath, contextIds, filters, repeat, strict, outputDir, comparePath, json };
|
|
272
|
+
}
|
|
273
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = parseCoverageProbeCliArgs(argv);
|
|
276
|
+
const report = await runCoverageProbe({
|
|
277
|
+
repoRoot: parsed.repoRoot,
|
|
278
|
+
matrixPath: parsed.matrixPath,
|
|
279
|
+
contextIds: parsed.contextIds,
|
|
280
|
+
filters: parsed.filters,
|
|
281
|
+
repeat: parsed.repeat,
|
|
282
|
+
outputDir: parsed.outputDir,
|
|
283
|
+
json: parsed.json,
|
|
284
|
+
});
|
|
285
|
+
if (parsed.json) {
|
|
286
|
+
console.log(JSON.stringify(report, null, 2));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log(formatCoverageProbeSummary(report));
|
|
290
|
+
}
|
|
291
|
+
if (parsed.outputDir) {
|
|
292
|
+
await mkdir(parsed.outputDir, { recursive: true });
|
|
293
|
+
const stamp = report.generatedAt.replace(/[:.]/g, '-');
|
|
294
|
+
const outputPath = path.join(parsed.outputDir, `run-${stamp}.json`);
|
|
295
|
+
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
296
|
+
if (!parsed.json) {
|
|
297
|
+
console.log(`\nWrote ${outputPath}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (parsed.comparePath) {
|
|
301
|
+
let baselineRaw;
|
|
302
|
+
try {
|
|
303
|
+
baselineRaw = JSON.parse(await readFile(path.resolve(parsed.comparePath), 'utf8'));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
throw new CoverageCompareError(`--compare baseline is not valid JSON: ${parsed.comparePath}`);
|
|
307
|
+
}
|
|
308
|
+
const baseline = parseCoverageProbeReportForCompare(baselineRaw);
|
|
309
|
+
const compareReport = compareCoverageReports(baseline, report);
|
|
310
|
+
for (const warning of compareBaselineWarnings(baseline, report)) {
|
|
311
|
+
console.warn(`\nWARNING: compare baseline mismatch: ${warning}`);
|
|
312
|
+
}
|
|
313
|
+
if (!parsed.json) {
|
|
314
|
+
console.log(`\n${formatCoverageCompareReport(compareReport)}`);
|
|
315
|
+
}
|
|
316
|
+
if (compareReport.configDrift.length > 0) {
|
|
317
|
+
console.warn(`\nWARNING: config drift detected for ${compareReport.configDrift.length} context(s); compare may include classifier drift from config changes`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (report.driftRuns > 0 && !parsed.json) {
|
|
321
|
+
console.warn(`\nWARNING: classifier drift detected in ${report.driftRuns}/${report.repeat} repeats; report uses first run results`);
|
|
322
|
+
}
|
|
323
|
+
if (report.summary.filteredEmpty) {
|
|
324
|
+
console.warn('\nWARNING: --filter matched zero cases; check tag/group/case ids');
|
|
325
|
+
return 2;
|
|
326
|
+
}
|
|
327
|
+
if (parsed.strict && report.summary.mismatched > 0) {
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (error instanceof CoverageProbeCliError ||
|
|
334
|
+
error instanceof CoverageMatrixSchemaError ||
|
|
335
|
+
error instanceof CoverageCompareError) {
|
|
336
|
+
console.error(error.message);
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -49,7 +49,7 @@ export interface FpCommandBatchResult {
|
|
|
49
49
|
mutatorId: string;
|
|
50
50
|
isFp: boolean;
|
|
51
51
|
}
|
|
52
|
-
/** False-positive probe:
|
|
52
|
+
/** False-positive probe: benign probe cores that receive deny_pending_approval count as FP. */
|
|
53
53
|
export declare function evaluateFpCommandBatch(cases: Array<{
|
|
54
54
|
command: string;
|
|
55
55
|
core: string;
|
package/dist/corpus/evaluate.js
CHANGED
|
@@ -102,7 +102,7 @@ export async function evaluateCommandBatch(cases, repoRoot = defaultCorpusEvalPa
|
|
|
102
102
|
}
|
|
103
103
|
return results;
|
|
104
104
|
}
|
|
105
|
-
/** False-positive probe:
|
|
105
|
+
/** False-positive probe: benign probe cores that receive deny_pending_approval count as FP. */
|
|
106
106
|
export async function evaluateFpCommandBatch(cases, repoRoot = defaultCorpusEvalPaths().repoRoot) {
|
|
107
107
|
const { cwd } = defaultCorpusEvalPaths(repoRoot);
|
|
108
108
|
const options = classifierOptionsFromConfig(DEFAULT_CONFIG_V3);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "0.
|
|
1
|
+
export declare const PACKAGE_VERSION = "0.9.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/sync-version.mjs — do not edit.
|
|
2
|
-
export const PACKAGE_VERSION = '0.
|
|
2
|
+
export const PACKAGE_VERSION = '0.9.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guilz-dev/belay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Belay-style approval and audit gating for agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"test:stable": "pnpm build && vitest run && vitest run && vitest run",
|
|
61
61
|
"corpus": "pnpm build && node scripts/corpus.mjs",
|
|
62
62
|
"probe:adversarial": "pnpm build && node scripts/adversarial-probe.mjs",
|
|
63
|
+
"probe:coverage": "pnpm build && node scripts/coverage-probe.mjs",
|
|
64
|
+
"probe:coverage:repeat": "pnpm build && node scripts/coverage-probe.mjs --repeat 3",
|
|
63
65
|
"corpus:ratchet": "pnpm build && node scripts/corpus-ratchet.mjs"
|
|
64
66
|
},
|
|
65
67
|
"devDependencies": {
|
package/skills/belay/SKILL.md
CHANGED
|
@@ -53,10 +53,28 @@ To restore the legacy two-step UX (approve, then always retry manually), set
|
|
|
53
53
|
|
|
54
54
|
For why it was blocked, use `/belay why <command>` or `belay explain --command "<command>"`.
|
|
55
55
|
For the latest pending ask, use `/belay explain` or `belay explain`.
|
|
56
|
+
|
|
57
|
+
**Do not use command allowlists** (`overrides.allow`) or legacy standing-allow records
|
|
58
|
+
(shell, tool, or subagent) to fix blocks; none of them change runtime authorization.
|
|
59
|
+
Improve EffectPlan semantics, approve once with `/belay-approve`, or use an exact
|
|
60
|
+
resource-scoped grant. See ADR-005 in the belay repository.
|
|
56
61
|
For install health and audit visibility, use `/belay status` or `belay status`.
|
|
57
62
|
For audit-only summary, use `/belay report` or `belay report`.
|
|
58
63
|
For recovery advice after a block, use `/belay recover` or `belay recover`.
|
|
59
64
|
|
|
65
|
+
## Contained unknown execution
|
|
66
|
+
|
|
67
|
+
When an operator has explicitly enabled Docker contained execution, an eligible repository-local
|
|
68
|
+
`unknown_local_effect` may run once in a disposable mirror instead of using a command allowlist.
|
|
69
|
+
The route has no network, never mounts the source workspace or control plane, discards all guest
|
|
70
|
+
changes, and always applies mandatory credential scrubbing to its 16 KiB stdout/stderr tails.
|
|
71
|
+
|
|
72
|
+
If Belay reports that the contained capability, image, or Docker substrate no longer matches, ask
|
|
73
|
+
the operator to run `belay session start`; do not silently run it from this skill. Only typed Docker
|
|
74
|
+
substrate/daemon unavailability returns to the normal one-shot approval flow. Missing or stale
|
|
75
|
+
capability/image, mirror/lease, create/inspect/start, timeout, and cleanup failures deny without
|
|
76
|
+
approval or host replay.
|
|
77
|
+
|
|
60
78
|
## Install or repair
|
|
61
79
|
|
|
62
80
|
- Interactive setup: `belay config`
|
|
@@ -75,4 +93,5 @@ Do not run init, config, or doctor implicitly from this skill — only when the
|
|
|
75
93
|
| Status / dogfood / audit visibility | `belay status` |
|
|
76
94
|
| Audit summary (read-only) | `belay report` |
|
|
77
95
|
| Recovery advice (advisory only) | `belay recover` |
|
|
96
|
+
| Refresh contained Docker attestation | `belay session start` |
|
|
78
97
|
| Approve once | `belay approve <id>` |
|
|
@@ -6,4 +6,9 @@ belay status
|
|
|
6
6
|
|
|
7
7
|
For audit-only summary: `belay report`. For recovery advice: `belay recover`.
|
|
8
8
|
|
|
9
|
+
For opt-in contained execution, report the configured and attested Docker capability separately
|
|
10
|
+
from L1-full. If the image, executable, Unix socket, daemon, or limits no longer match, suggest
|
|
11
|
+
`belay session start`. Do not run it implicitly. Contained guest output is always credential-
|
|
12
|
+
scrubbed and capped even when ordinary audit redaction switches are disabled.
|
|
13
|
+
|
|
9
14
|
If hooks are missing, suggest `npx @guilz-dev/belay init` and `belay doctor`.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural-suite MUST-ALLOW CI expectations.
|
|
3
|
-
* These commands guard classifier availability in tests and never grant runtime authority.
|
|
4
|
-
* @see src/__tests__/verdict/structural-suite.test.ts
|
|
5
|
-
*/
|
|
6
|
-
export declare const MUST_ALLOW_SHELL_COMMANDS: readonly ["npm test", "npm run build", "pnpm test", "pnpm build", "pnpm vitest run src/example.test.ts", "bash -lc 'git status'", "belay approve belay_deadbeef1234"];
|