@guilz-dev/belay 0.8.0 → 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 +90 -27
- package/dist/commands/explain.js +3 -2
- package/dist/commands/metrics.js +75 -4
- package/dist/commands/quality.js +2 -6
- 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/commands/status.js +1 -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 +34 -0
- package/dist/core/audit-metrics.js +87 -15
- 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/operational-insights.d.ts +3 -0
- package/dist/operational-insights.js +7 -3
- package/dist/runtime-provenance.d.ts +8 -0
- package/dist/runtime-provenance.js +34 -0
- package/dist/types.d.ts +3 -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
|
@@ -3,6 +3,7 @@ import { getAdapter } from '../adapters/registry.js';
|
|
|
3
3
|
import { repoShellClassifierOptions } from '../adapters/shared/gate-runtime.js';
|
|
4
4
|
import { detectAdapterName, loadConfigFile } from '../config-io.js';
|
|
5
5
|
import { isCapabilityBrokerDemotionActive } from '../core/capability/broker.js';
|
|
6
|
+
import { isContainedUnknownExecutionEligible } from '../core/contained-execution/eligibility.js';
|
|
6
7
|
import { classifySubagent, classifyToolUse } from '../core/index.js';
|
|
7
8
|
import { isTransactionalEligible } from '../core/transactional/index.js';
|
|
8
9
|
import { classifyShell } from '../core/verdict/adapter.js';
|
|
@@ -26,6 +27,10 @@ export async function classifyForReport(params) {
|
|
|
26
27
|
throw new Error('classify-for-report requires a command for shell classification.');
|
|
27
28
|
}
|
|
28
29
|
result = await classifyShell(params.command, cwd, repoRoot, config, classifierOptions);
|
|
30
|
+
result = {
|
|
31
|
+
...result,
|
|
32
|
+
wouldMediate: isContainedUnknownExecutionEligible(config, { kind: 'shell', repoRoot }, result),
|
|
33
|
+
};
|
|
29
34
|
input = params.command;
|
|
30
35
|
}
|
|
31
36
|
else if (kind === 'subagent') {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,42 +4,33 @@ import path from 'node:path';
|
|
|
4
4
|
import { getClaudeManagedHookEntries } from '../adapters/claude/hooks.js';
|
|
5
5
|
import { getCodexManagedHookEntries } from '../adapters/codex/hooks.js';
|
|
6
6
|
import { getAdapterLayout } from '../adapters/layouts/index.js';
|
|
7
|
+
import { protectedArtifactRoots } from '../adapters/layouts/protected-paths.js';
|
|
7
8
|
import { resolveScopedPaths } from '../adapters/layouts/scope.js';
|
|
8
9
|
import { cleanupOrphanApprovalState } from '../cleanup-orphans.js';
|
|
9
|
-
import { approvedApprovalsPath, belayStateDir, detectAdapterName, loadLayeredConfig, pendingApprovalsPath, repoLocalStateDirFor, } from '../config-io.js';
|
|
10
|
+
import { approvedApprovalsPath, belayStateDir, detectAdapterName, loadLayeredConfig, pendingApprovalsPath, repoLocalStateDirFor, writeConfigFile, } from '../config-io.js';
|
|
10
11
|
import { approvalSigningKeyPath } from '../core/approval-token.js';
|
|
11
12
|
import { detectFenceDrift, summarizeAuditVisibility } from '../core/audit-summary.js';
|
|
12
13
|
import { inspectBoundaryAttestationFile } from '../core/capability/boundary-attestation-sign.js';
|
|
13
14
|
import { boundaryAttestationPath, boundarySessionStatus, } from '../core/capability/boundary-session.js';
|
|
14
|
-
import { defaultControlPlaneDir } from '../core/config.js';
|
|
15
|
+
import { configuredControlPlaneDir, defaultControlPlaneDir, hasForbiddenShellOverrideLists, stripForbiddenShellOverrideLists, } from '../core/config.js';
|
|
15
16
|
import { verifyIntegrityManifest } from '../core/integrity.js';
|
|
16
17
|
import { diagnoseJudge, stopJudgeSessionBrokers } from '../core/judge-doctor.js';
|
|
17
18
|
import { resolveJudgeTransport } from '../core/judge-runtime-detection.js';
|
|
18
19
|
import { listRecoveryCheckpoints } from '../core/recovery/checkpoint.js';
|
|
19
20
|
import { recoveryApprovalSetupNotes, recoveryNotificationConfigured, recoveryNotificationSetupWarning, summarizeRecoveryCheckpointDiagnostics, } from '../core/recovery/operator-guidance.js';
|
|
21
|
+
import { probeFileCheckpointBackend } from '../core/transactional/backend-selector.js';
|
|
22
|
+
import { fileCheckpointIsolationReason } from '../core/transactional/file-checkpoint-isolation.js';
|
|
20
23
|
import { probeFileCloneStrategy } from '../core/transactional/file-clone.js';
|
|
24
|
+
import { isGitWorktreeAvailable } from '../core/transactional/git-worktree.js';
|
|
21
25
|
import { getManagedHookEntries } from '../defaults.js';
|
|
22
26
|
import { resolveNodeBinary } from '../node-resolution.js';
|
|
27
|
+
import { readInstalledRuntimeProvenance } from '../runtime-provenance.js';
|
|
23
28
|
import { egressStatus } from '../services/egress-service.js';
|
|
24
29
|
import { sandboxStatus } from '../services/sandbox-service.js';
|
|
25
30
|
import { PACKAGE_VERSION } from '../version.js';
|
|
26
31
|
import { loadAuditRecords } from './audit.js';
|
|
27
32
|
import { collectHealthSnapshot } from './health-snapshot.js';
|
|
28
33
|
import { metricsProject } from './metrics.js';
|
|
29
|
-
async function readRuntimeVersion(corePath) {
|
|
30
|
-
try {
|
|
31
|
-
const content = await readFile(corePath, 'utf8');
|
|
32
|
-
const stampMatch = content.match(/RUNTIME_BUILD_STAMP\s*=\s*"([^"]+)"/);
|
|
33
|
-
const versionMatch = content.match(/RUNTIME_PACKAGE_VERSION\s*=\s*"([^"]+)"/);
|
|
34
|
-
return {
|
|
35
|
-
stamp: stampMatch?.[1],
|
|
36
|
-
version: versionMatch?.[1],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return {};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
34
|
function resolveDoctorAdapter(options, configAdapter) {
|
|
44
35
|
if (options.adapter) {
|
|
45
36
|
return options.adapter;
|
|
@@ -87,7 +78,7 @@ export async function doctorProject(options = {}) {
|
|
|
87
78
|
...(loadedConfig.overrides.external.length > 0 ? ['overrides.external'] : []),
|
|
88
79
|
];
|
|
89
80
|
if (ignoredShellOverrideLists.length > 0) {
|
|
90
|
-
|
|
81
|
+
issues.push(`Legacy shell ${ignoredShellOverrideLists.join(' and ')} ${ignoredShellOverrideLists.length === 1 ? 'is' : 'are'} forbidden (ADR-005). Remove the command list (or run belay doctor --fix); improve EffectPlan semantics, use one-shot approval, or approve an exact resource scope instead.`);
|
|
91
82
|
}
|
|
92
83
|
if (loadedConfig.version !== 4) {
|
|
93
84
|
warnings.push(`Config version is ${loadedConfig.version}; expected 4. Run belay upgrade to migrate.`);
|
|
@@ -234,7 +225,7 @@ export async function doctorProject(options = {}) {
|
|
|
234
225
|
notes.push(`Node resolved at ${nodeResolution.path}`);
|
|
235
226
|
}
|
|
236
227
|
if (existsSync(corePath)) {
|
|
237
|
-
const runtimeVersions = await
|
|
228
|
+
const runtimeVersions = await readInstalledRuntimeProvenance(corePath);
|
|
238
229
|
if (runtimeVersions.stamp && !runtimeVersions.stamp.startsWith(`${PACKAGE_VERSION}@`)) {
|
|
239
230
|
warnings.push(`Installed runtime stamp (${runtimeVersions.stamp}) differs from package (${PACKAGE_VERSION}). Run belay upgrade.`);
|
|
240
231
|
}
|
|
@@ -246,6 +237,23 @@ export async function doctorProject(options = {}) {
|
|
|
246
237
|
}
|
|
247
238
|
}
|
|
248
239
|
if (options.fix && loadedConfig) {
|
|
240
|
+
if (hasForbiddenShellOverrideLists(loadedConfig)) {
|
|
241
|
+
if (options.dryRun !== true) {
|
|
242
|
+
const stripped = stripForbiddenShellOverrideLists(loadedConfig);
|
|
243
|
+
await writeConfigFile(repoRoot, stripped, adapterName);
|
|
244
|
+
loadedConfig = stripped;
|
|
245
|
+
notes.push('Removed forbidden legacy shell override lists (overrides.allow / overrides.external).');
|
|
246
|
+
for (let index = issues.length - 1; index >= 0; index -= 1) {
|
|
247
|
+
const issue = issues[index] ?? '';
|
|
248
|
+
if (issue.includes('overrides.allow') || issue.includes('overrides.external')) {
|
|
249
|
+
issues.splice(index, 1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
notes.push('Dry run: would remove forbidden legacy shell override lists.');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
249
257
|
const cleanup = await cleanupOrphanApprovalState(repoRoot, loadedConfig, {
|
|
250
258
|
dryRun: options.dryRun === true,
|
|
251
259
|
});
|
|
@@ -269,25 +277,34 @@ export async function doctorProject(options = {}) {
|
|
|
269
277
|
let dogfood = null;
|
|
270
278
|
if (loadedConfig) {
|
|
271
279
|
const auditRecords = await loadAuditRecords(repoRoot);
|
|
272
|
-
const
|
|
280
|
+
const metrics = await metricsProject({ targetDir: repoRoot });
|
|
281
|
+
const cohortIdentity = metrics.currentCohort.identity;
|
|
282
|
+
const cohortAuditRecords = cohortIdentity
|
|
283
|
+
? auditRecords.filter((record) => record.runtimeBuildStamp === cohortIdentity.runtimeBuildStamp &&
|
|
284
|
+
record.configFingerprint === cohortIdentity.configFingerprint)
|
|
285
|
+
: [];
|
|
286
|
+
const auditVisibility = summarizeAuditVisibility(cohortAuditRecords);
|
|
273
287
|
const drift = detectFenceDrift(auditVisibility, {
|
|
274
288
|
threshold: loadedConfig.policy.fenceWarnThreshold,
|
|
275
289
|
});
|
|
276
290
|
warnings.push(...drift.warnings);
|
|
277
291
|
notes.push(...drift.notes);
|
|
278
|
-
const
|
|
292
|
+
const cohort = metrics.currentCohort;
|
|
279
293
|
dogfood = {
|
|
280
294
|
active: loadedConfig.mode === 'audit' && loadedConfig.policy.unknownLocalEffect === 'deny',
|
|
281
295
|
mode: loadedConfig.mode,
|
|
282
296
|
unknownLocalEffect: loadedConfig.policy.unknownLocalEffect,
|
|
283
297
|
readyForEnforce: metrics.dogfood.readyForEnforce,
|
|
284
|
-
gateEvents:
|
|
285
|
-
wouldBlockCount:
|
|
286
|
-
wouldBlockRate:
|
|
298
|
+
gateEvents: cohort.gateEvents,
|
|
299
|
+
wouldBlockCount: cohort.wouldBlockCount,
|
|
300
|
+
wouldBlockRate: cohort.wouldBlockRate,
|
|
301
|
+
excludedGateEvents: cohort.excludedGateEvents,
|
|
302
|
+
runtimeBuildStamp: cohort.identity?.runtimeBuildStamp,
|
|
303
|
+
configFingerprint: cohort.identity?.configFingerprint,
|
|
287
304
|
notes: metrics.dogfood.notes,
|
|
288
305
|
};
|
|
289
306
|
if (dogfood.active) {
|
|
290
|
-
notes.push(`Dogfood active: ${dogfood.gateEvents} gate events, ${dogfood.wouldBlockCount} would-block (${(dogfood.wouldBlockRate * 100).toFixed(1)}%).`);
|
|
307
|
+
notes.push(`Dogfood active cohort: ${dogfood.gateEvents} gate events, ${dogfood.wouldBlockCount} would-block (${(dogfood.wouldBlockRate * 100).toFixed(1)}%); ${dogfood.excludedGateEvents} historical/mismatched event(s) excluded.`);
|
|
291
308
|
if (dogfood.readyForEnforce) {
|
|
292
309
|
notes.push('Dogfood metrics suggest enforce mode is ready (belay dogfood --enforce).');
|
|
293
310
|
}
|
|
@@ -295,10 +312,27 @@ export async function doctorProject(options = {}) {
|
|
|
295
312
|
else if (dogfood.unknownLocalEffect === 'deny' && dogfood.mode !== 'audit') {
|
|
296
313
|
notes.push('Fail-closed policy is enabled in enforce mode.');
|
|
297
314
|
}
|
|
315
|
+
const recoveryCohort = metrics.currentCohortRecovery;
|
|
316
|
+
const restoreTotal = recoveryCohort.restore.applied +
|
|
317
|
+
recoveryCohort.restore.conflict +
|
|
318
|
+
recoveryCohort.restore.rejected;
|
|
319
|
+
if (recoveryCohort.snapshot.attempts > 0 || restoreTotal > 0) {
|
|
320
|
+
notes.push(`Recovery cohort metrics: ${recoveryCohort.snapshot.attempts} snapshot attempt(s) (${recoveryCohort.snapshot.applied} applied, ${recoveryCohort.snapshot.skipped} skipped); restore outcomes ${recoveryCohort.restore.applied} applied / ${recoveryCohort.restore.conflict} conflict / ${recoveryCohort.restore.rejected} rejected.`);
|
|
321
|
+
if (recoveryCohort.snapshot.prepareSampleCount > 0) {
|
|
322
|
+
notes.push(`Recovery snapshot prepare latency (cohort): p50 ${recoveryCohort.snapshot.prepareMsP50 ?? 0}ms, p95 ${recoveryCohort.snapshot.prepareMsP95 ?? 0}ms (${recoveryCohort.snapshot.prepareSampleCount} samples).`);
|
|
323
|
+
}
|
|
324
|
+
if (Object.keys(recoveryCohort.snapshot.failuresByReason).length > 0) {
|
|
325
|
+
notes.push(`Recovery snapshot failures (cohort): ${Object.entries(recoveryCohort.snapshot.failuresByReason)
|
|
326
|
+
.map(([reason, count]) => `${reason}=${count}`)
|
|
327
|
+
.join(', ')}.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
298
330
|
if (loadedConfig.policy.transactional.enabled) {
|
|
299
|
-
notes.push('Transactional execution: enabled — low-confidence shell mutations run in an isolated git worktree; observed-safe effects are applied once and the hook denies re-execution.');
|
|
300
|
-
|
|
301
|
-
|
|
331
|
+
notes.push('Transactional execution: enabled — low-confidence shell mutations run in an isolated git worktree or file-checkpoint mirror; observed-safe effects are applied once and the hook denies re-execution.');
|
|
332
|
+
const fileCheckpointEnabled = loadedConfig.policy.transactional.fileCheckpoint.enabled;
|
|
333
|
+
const allowNonGit = loadedConfig.policy.transactional.fileCheckpoint.allowNonGit;
|
|
334
|
+
if (!existsSync(path.join(repoRoot, '.git')) && !(fileCheckpointEnabled && allowNonGit)) {
|
|
335
|
+
warnings.push('Transactional execution is enabled but this directory is not a git repository. Enable file checkpoint with allowNonGit or initialize git before transactional recovery can run.');
|
|
302
336
|
}
|
|
303
337
|
}
|
|
304
338
|
const fileCheckpoint = loadedConfig.policy.transactional.fileCheckpoint;
|
|
@@ -311,6 +345,35 @@ export async function doctorProject(options = {}) {
|
|
|
311
345
|
if (!loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
312
346
|
warnings.push('File checkpoint is enabled but durable Recovery checkpointing is disabled; backend selection will fail closed.');
|
|
313
347
|
}
|
|
348
|
+
const checkpointConfig = loadedConfig.policy.transactional.checkpoint;
|
|
349
|
+
const configuredBoundary = loadedConfig.capability?.boundaryDriver ??
|
|
350
|
+
(loadedConfig.sandbox.runtime === 'container' ? 'container' : null);
|
|
351
|
+
let attestation = null;
|
|
352
|
+
if (configuredBoundary) {
|
|
353
|
+
try {
|
|
354
|
+
const session = await boundarySessionStatus({ repoRoot, config: loadedConfig });
|
|
355
|
+
attestation = session.attestation;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
attestation = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const gitAvailable = await isGitWorktreeAvailable(repoRoot);
|
|
362
|
+
const backendContext = {
|
|
363
|
+
repoRoot,
|
|
364
|
+
stateDir: belayStateDir(loadedConfig, repoLocalDir),
|
|
365
|
+
cwd: repoRoot,
|
|
366
|
+
dirtyIgnoreRoots: protectedArtifactRoots(activeLayout, repoRoot, loadedConfig.controlPlane.enabled ? configuredControlPlaneDir(loadedConfig) : null),
|
|
367
|
+
fileCheckpoint,
|
|
368
|
+
durableCheckpointEnabled: checkpointConfig?.enabled === true,
|
|
369
|
+
boundaryAttestation: attestation,
|
|
370
|
+
boundaryAttestationFresh: false,
|
|
371
|
+
boundaryDriverId: configuredBoundary ?? undefined,
|
|
372
|
+
};
|
|
373
|
+
const isolationAvailable = fileCheckpointIsolationReason(backendContext) === null;
|
|
374
|
+
const fileCheckpointProbe = await probeFileCheckpointBackend(backendContext);
|
|
375
|
+
const fileCheckpointAvailable = loadedConfig.policy.transactional.enabled && fileCheckpointProbe.eligible;
|
|
376
|
+
notes.push(`File checkpoint eligibility: probe=${fileCheckpointAvailable ? 'available' : 'unavailable'}, workspace=${gitAvailable ? 'git' : 'non-git'}, isolation=${isolationAvailable ? (configuredBoundary ?? 'attested') : 'unavailable'}.`);
|
|
314
377
|
}
|
|
315
378
|
if (loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
316
379
|
notes.push('Recovery checkpoint: enabled — repo-local pre-images are persisted before observed-safe transactional apply.');
|
package/dist/commands/explain.js
CHANGED
|
@@ -103,11 +103,12 @@ export function formatExplainReport(report) {
|
|
|
103
103
|
: 'Egress proxy: not configured',
|
|
104
104
|
`Sandbox (L1 broker): ${report.sandbox.enabled ? 'enabled' : 'disabled'} (runtime=${report.sandbox.runtime}, fs broker active=${report.sandboxBrokerActive}, L1-full=${report.l1FullActive})`,
|
|
105
105
|
`Transactional (L2): ${report.policy.transactional.enabled ? 'enabled' : 'disabled'} (eligible for this command=${report.transactionalEligible})`,
|
|
106
|
+
`Contained unknown execution: ${result.wouldMediate ? 'would mediate' : 'not eligible'}`,
|
|
106
107
|
report.policy.transactional.enabled
|
|
107
108
|
? `Transactional band: [${report.policy.transactional.minConfidence}, ${report.policy.transactional.maxConfidence})`
|
|
108
109
|
: 'Transactional band: not configured',
|
|
109
|
-
`Legacy overrides allow (
|
|
110
|
-
`Legacy overrides external (
|
|
110
|
+
`Legacy overrides allow (forbidden, ignored): ${report.overrides.allow.join(', ') || '(none)'}`,
|
|
111
|
+
`Legacy overrides external (forbidden, ignored): ${report.overrides.external.join(', ') || '(none)'}`,
|
|
111
112
|
'',
|
|
112
113
|
`Verdict: ${result.verdict}`,
|
|
113
114
|
`Reason: ${result.reason}`,
|
package/dist/commands/metrics.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { loadConfigFile } from '../config-io.js';
|
|
4
4
|
import { computeAuditMetrics, parseAuditNdjson } from '../core/audit-metrics.js';
|
|
5
|
+
import { resolveActiveAuditCohort } from '../runtime-provenance.js';
|
|
5
6
|
function formatFingerprintPreview(fingerprint) {
|
|
6
7
|
if (fingerprint.length <= 12) {
|
|
7
8
|
return fingerprint;
|
|
@@ -20,21 +21,87 @@ export async function metricsProject(options = {}) {
|
|
|
20
21
|
raw = '';
|
|
21
22
|
}
|
|
22
23
|
const records = parseAuditNdjson(raw);
|
|
24
|
+
const activeCohort = await resolveActiveAuditCohort(repoRoot, config);
|
|
23
25
|
return computeAuditMetrics(records, {
|
|
24
26
|
auditLogPath: config.audit.logPath,
|
|
25
27
|
mode: config.mode,
|
|
26
28
|
unknownLocalEffect: config.policy.unknownLocalEffect,
|
|
29
|
+
activeCohort,
|
|
27
30
|
});
|
|
28
31
|
}
|
|
32
|
+
function formatRecoveryMetricsSection(label, metrics, excluded) {
|
|
33
|
+
const lines = [
|
|
34
|
+
'',
|
|
35
|
+
label,
|
|
36
|
+
`Snapshot attempts: ${metrics.snapshot.attempts} (${metrics.snapshot.applied} applied, ${metrics.snapshot.skipped} skipped)`,
|
|
37
|
+
];
|
|
38
|
+
if (excluded) {
|
|
39
|
+
lines.push(`- excluded historical/mismatched snapshot attempts: ${excluded.snapshotAttempts}`, `- excluded historical/mismatched restore events: ${excluded.restoreEvents}`);
|
|
40
|
+
}
|
|
41
|
+
if (metrics.snapshot.prepareSampleCount > 0) {
|
|
42
|
+
lines.push(`Snapshot prepare latency: p50 ${metrics.snapshot.prepareMsP50 ?? 0}ms, p95 ${metrics.snapshot.prepareMsP95 ?? 0}ms (${metrics.snapshot.prepareSampleCount} samples)`);
|
|
43
|
+
}
|
|
44
|
+
if (Object.keys(metrics.snapshot.byBackend).length > 0) {
|
|
45
|
+
lines.push('Snapshot backends:');
|
|
46
|
+
for (const [backend, count] of Object.entries(metrics.snapshot.byBackend).sort((left, right) => right[1] - left[1])) {
|
|
47
|
+
lines.push(`- ${backend}: ${count}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (Object.keys(metrics.snapshot.byResourceKind).length > 0) {
|
|
51
|
+
lines.push('Snapshot resource kinds:');
|
|
52
|
+
for (const [resourceKind, count] of Object.entries(metrics.snapshot.byResourceKind).sort((left, right) => right[1] - left[1])) {
|
|
53
|
+
lines.push(`- ${resourceKind}: ${count}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (Object.keys(metrics.snapshot.failuresByReason).length > 0) {
|
|
57
|
+
lines.push('Snapshot failures by reason:');
|
|
58
|
+
for (const [reason, count] of Object.entries(metrics.snapshot.failuresByReason).sort((left, right) => right[1] - left[1])) {
|
|
59
|
+
lines.push(`- ${reason}: ${count}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const restoreTotal = metrics.restore.applied + metrics.restore.conflict + metrics.restore.rejected;
|
|
63
|
+
if (restoreTotal > 0) {
|
|
64
|
+
lines.push(`Restore outcomes: ${metrics.restore.applied} applied, ${metrics.restore.conflict} conflict, ${metrics.restore.rejected} rejected`);
|
|
65
|
+
}
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
29
68
|
export function formatMetricsReport(report) {
|
|
30
69
|
const lines = [
|
|
31
70
|
`belay metrics for ${report.auditLogPath}`,
|
|
32
71
|
`Schema: v${report.schemaVersion}`,
|
|
33
|
-
`
|
|
34
|
-
`
|
|
35
|
-
`
|
|
36
|
-
`
|
|
72
|
+
`All-time gate events: ${report.gateEvents}`,
|
|
73
|
+
`All-time would-block: ${report.wouldBlockCount} (${(report.wouldBlockRate * 100).toFixed(1)}%)`,
|
|
74
|
+
`All-time classifier-quality would-block: ${report.classifierWouldBlockCount} (${(report.classifierWouldBlockRate * 100).toFixed(1)}%)`,
|
|
75
|
+
`All-time approvals recorded during audit: ${report.approvalRecordedCount}`,
|
|
76
|
+
`Contained execution: would mediate ${report.containedExecution.wouldMediate}; complete ${report.containedExecution.complete}; failed ${report.containedExecution.failed}; timed out ${report.containedExecution.timedOut}`,
|
|
37
77
|
];
|
|
78
|
+
lines.push('', 'Current readiness cohort:');
|
|
79
|
+
if (report.currentCohort.identity) {
|
|
80
|
+
lines.push(`- runtime build: ${report.currentCohort.identity.runtimeBuildStamp}`);
|
|
81
|
+
lines.push(`- config fingerprint: ${formatFingerprintPreview(report.currentCohort.identity.configFingerprint)}`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
lines.push('- identity: unavailable');
|
|
85
|
+
}
|
|
86
|
+
lines.push(`- matching gate events: ${report.currentCohort.gateEvents}`);
|
|
87
|
+
lines.push(`- excluded historical/mismatched gate events: ${report.currentCohort.excludedGateEvents}`);
|
|
88
|
+
lines.push(`- would-block: ${report.currentCohort.wouldBlockCount} (${(report.currentCohort.wouldBlockRate * 100).toFixed(1)}%)`);
|
|
89
|
+
lines.push(`- classifier-quality would-block: ${report.currentCohort.classifierWouldBlockCount} (${(report.currentCohort.classifierWouldBlockRate * 100).toFixed(1)}%)`);
|
|
90
|
+
lines.push(`- approvals recorded during audit: ${report.currentCohort.approvalRecordedCount}`);
|
|
91
|
+
lines.push(`- contained execution: would mediate ${report.currentCohort.containedExecution.wouldMediate}; complete ${report.currentCohort.containedExecution.complete}; failed ${report.currentCohort.containedExecution.failed}; timed out ${report.currentCohort.containedExecution.timedOut}`);
|
|
92
|
+
lines.push(`- availability-caused asks: ${report.currentCohort.availabilityAsks.total}`);
|
|
93
|
+
if (Object.keys(report.currentCohort.wouldBlockByReason).length > 0) {
|
|
94
|
+
lines.push('', 'Current-cohort would-block by reason:');
|
|
95
|
+
for (const [reason, count] of Object.entries(report.currentCohort.wouldBlockByReason).sort((left, right) => right[1] - left[1])) {
|
|
96
|
+
lines.push(`- ${reason}: ${count}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (report.currentCohort.topWouldBlockSummaries.length > 0) {
|
|
100
|
+
lines.push('', 'Current-cohort top would-block summaries:');
|
|
101
|
+
for (const entry of report.currentCohort.topWouldBlockSummaries) {
|
|
102
|
+
lines.push(`- [${entry.reason}] x${entry.count}: ${entry.summary}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
38
105
|
if (report.approvalLatency.count > 0) {
|
|
39
106
|
lines.push(`Approval latency: median ${report.approvalLatency.medianMs ?? 0}ms, p95 ${report.approvalLatency.p95Ms ?? 0}ms (${report.approvalLatency.count} samples)`);
|
|
40
107
|
}
|
|
@@ -123,5 +190,9 @@ export function formatMetricsReport(report) {
|
|
|
123
190
|
}
|
|
124
191
|
lines.push('', report.dogfood.readyForEnforce ? 'Ready for enforce: yes' : 'Ready for enforce: not yet');
|
|
125
192
|
}
|
|
193
|
+
lines.push(...formatRecoveryMetricsSection('All-time recovery metrics:', report.recovery), ...formatRecoveryMetricsSection('Current-cohort recovery metrics:', report.currentCohortRecovery, {
|
|
194
|
+
snapshotAttempts: report.currentCohortRecovery.excludedSnapshotAttempts,
|
|
195
|
+
restoreEvents: report.currentCohortRecovery.excludedRestoreEvents,
|
|
196
|
+
}));
|
|
126
197
|
return `${lines.join('\n')}\n`;
|
|
127
198
|
}
|
package/dist/commands/quality.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadConfigFile } from '../config-io.js';
|
|
3
|
-
import { computeAuditMetrics } from '../core/audit-metrics.js';
|
|
4
3
|
import { runCorpusEvaluation } from '../corpus/evaluate.js';
|
|
5
4
|
import { passesHardGates } from '../corpus/gates.js';
|
|
6
5
|
import { loadAuditRecords } from './audit.js';
|
|
7
6
|
import { harvestReportFromRecords } from './harvest.js';
|
|
7
|
+
import { metricsProject } from './metrics.js';
|
|
8
8
|
export const QUALITY_REPORT_SCHEMA_VERSION = 1;
|
|
9
9
|
export async function qualityCheck(options = {}) {
|
|
10
10
|
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
@@ -13,11 +13,7 @@ export async function qualityCheck(options = {}) {
|
|
|
13
13
|
const corpusMetrics = await runCorpusEvaluation(corpusDir);
|
|
14
14
|
const hardGatesOk = passesHardGates(corpusMetrics.gates);
|
|
15
15
|
const auditRecords = await loadAuditRecords(repoRoot);
|
|
16
|
-
const metrics =
|
|
17
|
-
auditLogPath: config.audit.logPath,
|
|
18
|
-
mode: config.mode,
|
|
19
|
-
unknownLocalEffect: config.policy.unknownLocalEffect,
|
|
20
|
-
});
|
|
16
|
+
const metrics = await metricsProject({ targetDir: repoRoot });
|
|
21
17
|
const harvest = harvestReportFromRecords(auditRecords);
|
|
22
18
|
const notes = [
|
|
23
19
|
'Overall ok reflects corpus hard gates only; audit and harvest signals are advisory.',
|
|
@@ -12,10 +12,13 @@ export declare function recoveryCheckpointCommand(options: {
|
|
|
12
12
|
backendCounts: {
|
|
13
13
|
[k: string]: number;
|
|
14
14
|
};
|
|
15
|
+
resourceKindCounts: {
|
|
16
|
+
[k: string]: number;
|
|
17
|
+
};
|
|
15
18
|
fileCheckpoint: {
|
|
16
19
|
enabled: boolean;
|
|
17
20
|
allowNonGit: boolean;
|
|
18
|
-
isolation:
|
|
21
|
+
isolation: import("../core/capability/attestation.js").BoundaryDriverId | null;
|
|
19
22
|
copyStrategy: import("../core/transactional/file-clone.js").FileCloneStrategy | undefined;
|
|
20
23
|
probe: string;
|
|
21
24
|
};
|
|
@@ -55,6 +58,7 @@ export declare function recoveryCheckpointCommand(options: {
|
|
|
55
58
|
backend?: undefined;
|
|
56
59
|
availableBackends?: undefined;
|
|
57
60
|
backendCounts?: undefined;
|
|
61
|
+
resourceKindCounts?: undefined;
|
|
58
62
|
fileCheckpoint?: undefined;
|
|
59
63
|
enabled?: undefined;
|
|
60
64
|
checkpointCount?: undefined;
|
|
@@ -86,6 +90,7 @@ export declare function recoveryCheckpointCommand(options: {
|
|
|
86
90
|
backend?: undefined;
|
|
87
91
|
availableBackends?: undefined;
|
|
88
92
|
backendCounts?: undefined;
|
|
93
|
+
resourceKindCounts?: undefined;
|
|
89
94
|
fileCheckpoint?: undefined;
|
|
90
95
|
enabled?: undefined;
|
|
91
96
|
checkpointCount?: undefined;
|
|
@@ -117,6 +122,7 @@ export declare function recoveryCheckpointCommand(options: {
|
|
|
117
122
|
backend?: undefined;
|
|
118
123
|
availableBackends?: undefined;
|
|
119
124
|
backendCounts?: undefined;
|
|
125
|
+
resourceKindCounts?: undefined;
|
|
120
126
|
fileCheckpoint?: undefined;
|
|
121
127
|
enabled?: undefined;
|
|
122
128
|
checkpointCount?: undefined;
|
|
@@ -144,6 +150,7 @@ export declare function recoveryCheckpointCommand(options: {
|
|
|
144
150
|
backend?: undefined;
|
|
145
151
|
availableBackends?: undefined;
|
|
146
152
|
backendCounts?: undefined;
|
|
153
|
+
resourceKindCounts?: undefined;
|
|
147
154
|
fileCheckpoint?: undefined;
|
|
148
155
|
enabled?: undefined;
|
|
149
156
|
checkpointCount?: undefined;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { getAdapterLayout } from '../adapters/layouts/index.js';
|
|
4
|
+
import { protectedArtifactRoots } from '../adapters/layouts/protected-paths.js';
|
|
3
5
|
import { ensureBelayStateDir, loadConfigFile } from '../config-io.js';
|
|
4
6
|
import { compactApprovals, createApprovalRecordWithEnvelope } from '../core/approval.js';
|
|
5
7
|
import { createGateApprovalStore } from '../core/approval-service.js';
|
|
@@ -12,6 +14,8 @@ import { notifyDeny } from '../core/notify.js';
|
|
|
12
14
|
import { canonicalPath } from '../core/path-utils.js';
|
|
13
15
|
import { listRecoveryCheckpoints, RECOVERY_CHECKPOINT_CORRUPT, RECOVERY_RESTORE_CONFLICT, RECOVERY_RESTORE_REASON, recoveryCheckpointStorageBytes, recoveryRestoreBinding, restoreRecoveryCheckpoint, showRecoveryCheckpoint, } from '../core/recovery/checkpoint.js';
|
|
14
16
|
import { formatRecoveryStateDiagnostic, recoveryNotificationConfigured, recoveryNotificationSetupWarning, summarizeRecoveryCheckpointDiagnostics, } from '../core/recovery/operator-guidance.js';
|
|
17
|
+
import { probeFileCheckpointBackend } from '../core/transactional/backend-selector.js';
|
|
18
|
+
import { fileCheckpointIsolationReason } from '../core/transactional/file-checkpoint-isolation.js';
|
|
15
19
|
import { probeFileCloneStrategy } from '../core/transactional/file-clone.js';
|
|
16
20
|
async function appendRecoveryAudit(repoRoot, config, event) {
|
|
17
21
|
try {
|
|
@@ -116,28 +120,43 @@ export async function recoveryCheckpointCommand(options) {
|
|
|
116
120
|
backend,
|
|
117
121
|
checkpoints.filter((checkpoint) => checkpoint.backend === backend).length,
|
|
118
122
|
]));
|
|
123
|
+
const resourceKindCounts = Object.fromEntries([
|
|
124
|
+
...new Set(checkpoints.map((checkpoint) => checkpoint.resourceKind ?? 'git_repository')),
|
|
125
|
+
].map((resourceKind) => [
|
|
126
|
+
resourceKind,
|
|
127
|
+
checkpoints.filter((checkpoint) => (checkpoint.resourceKind ?? 'git_repository') === resourceKind).length,
|
|
128
|
+
]));
|
|
119
129
|
const checkpointBackends = Object.keys(backendCounts);
|
|
120
130
|
const fileCheckpointConfig = config.policy.transactional.fileCheckpoint;
|
|
121
131
|
const configuredBoundary = config.capability?.boundaryDriver ??
|
|
122
132
|
(config.sandbox.runtime === 'container' ? 'container' : null);
|
|
123
|
-
let
|
|
124
|
-
let isolationAvailable = false;
|
|
133
|
+
let attestation = null;
|
|
125
134
|
if (configuredBoundary) {
|
|
126
135
|
try {
|
|
127
136
|
const session = await boundarySessionStatus({ repoRoot, config });
|
|
128
|
-
|
|
129
|
-
session.fresh === true && session.attestation?.isolatesWorkspaceMounts === true;
|
|
130
|
-
isolation = isolationAvailable ? (session.attestation?.driver ?? configuredBoundary) : null;
|
|
137
|
+
attestation = session.attestation;
|
|
131
138
|
}
|
|
132
139
|
catch {
|
|
133
|
-
|
|
140
|
+
attestation = null;
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
const copyStrategy = fileCheckpointConfig.enabled ? await probeFileCloneStrategy() : null;
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
const adapterLayout = getAdapterLayout(config.adapter);
|
|
145
|
+
const backendContext = {
|
|
146
|
+
repoRoot,
|
|
147
|
+
stateDir,
|
|
148
|
+
cwd: repoRoot,
|
|
149
|
+
dirtyIgnoreRoots: protectedArtifactRoots(adapterLayout, repoRoot, config.controlPlane.enabled ? configuredControlPlaneDir(config) : null),
|
|
150
|
+
fileCheckpoint: fileCheckpointConfig,
|
|
151
|
+
durableCheckpointEnabled: checkpointConfig.enabled,
|
|
152
|
+
boundaryAttestation: attestation,
|
|
153
|
+
boundaryAttestationFresh: false,
|
|
154
|
+
boundaryDriverId: configuredBoundary ?? undefined,
|
|
155
|
+
};
|
|
156
|
+
const isolationAvailable = fileCheckpointIsolationReason(backendContext) === null;
|
|
157
|
+
const fileCheckpointProbe = await probeFileCheckpointBackend(backendContext);
|
|
158
|
+
const fileCheckpointAvailable = config.policy.transactional.enabled && fileCheckpointProbe.eligible;
|
|
159
|
+
const isolation = isolationAvailable ? (attestation?.driver ?? configuredBoundary) : null;
|
|
141
160
|
return {
|
|
142
161
|
ok: true,
|
|
143
162
|
subcommand: 'status',
|
|
@@ -149,6 +168,7 @@ export async function recoveryCheckpointCommand(options) {
|
|
|
149
168
|
: 'git_worktree',
|
|
150
169
|
availableBackends: ['git_worktree', 'file_checkpoint'],
|
|
151
170
|
backendCounts,
|
|
171
|
+
resourceKindCounts,
|
|
152
172
|
fileCheckpoint: {
|
|
153
173
|
enabled: fileCheckpointConfig.enabled,
|
|
154
174
|
allowNonGit: fileCheckpointConfig.allowNonGit,
|
|
@@ -304,9 +324,13 @@ export async function recoveryCheckpointCommand(options) {
|
|
|
304
324
|
}
|
|
305
325
|
export function formatRecoveryCheckpointResult(result) {
|
|
306
326
|
if (result.subcommand === 'status') {
|
|
327
|
+
const fileCheckpoint = result.fileCheckpoint;
|
|
328
|
+
const backendCounts = result.backendCounts;
|
|
329
|
+
const resourceKindCounts = result.resourceKindCounts;
|
|
307
330
|
const lines = [
|
|
308
331
|
`belay recover status for ${result.repoRoot}`,
|
|
309
332
|
`Backend: ${result.backend}`,
|
|
333
|
+
`Available backends: ${result.availableBackends?.join(', ') ?? 'git_worktree, file_checkpoint'}`,
|
|
310
334
|
`Checkpointing: ${result.enabled ? 'enabled' : 'disabled'}`,
|
|
311
335
|
`Checkpoints: ${result.checkpointCount} (${result.recoverableCount} recoverable)`,
|
|
312
336
|
`Needs manual repair: ${result.needsManualRepairCount ?? 0}`,
|
|
@@ -314,6 +338,15 @@ export function formatRecoveryCheckpointResult(result) {
|
|
|
314
338
|
`Storage: ${result.storageBytes} bytes`,
|
|
315
339
|
`States: ${JSON.stringify(result.states)}`,
|
|
316
340
|
];
|
|
341
|
+
if (backendCounts && Object.keys(backendCounts).length > 0) {
|
|
342
|
+
lines.push(`Backend counts: ${JSON.stringify(backendCounts)}`);
|
|
343
|
+
}
|
|
344
|
+
if (resourceKindCounts && Object.keys(resourceKindCounts).length > 0) {
|
|
345
|
+
lines.push(`Resource kind counts: ${JSON.stringify(resourceKindCounts)}`);
|
|
346
|
+
}
|
|
347
|
+
if (fileCheckpoint) {
|
|
348
|
+
lines.push(`File checkpoint: enabled=${fileCheckpoint.enabled ? 'yes' : 'no'}, allowNonGit=${fileCheckpoint.allowNonGit ? 'yes' : 'no'}, probe=${fileCheckpoint.probe ?? 'unknown'}, isolation=${fileCheckpoint.isolation ?? 'none'}${fileCheckpoint.copyStrategy ? `, copyStrategy=${fileCheckpoint.copyStrategy}` : ''}`);
|
|
349
|
+
}
|
|
317
350
|
const advisories = result.advisories;
|
|
318
351
|
if (advisories && advisories.length > 0) {
|
|
319
352
|
lines.push('', 'Advisories:');
|
|
@@ -330,7 +363,7 @@ export function formatRecoveryCheckpointResult(result) {
|
|
|
330
363
|
return 'No recovery checkpoints found.\n';
|
|
331
364
|
const lines = checkpoints.map((item) => {
|
|
332
365
|
const diagnostic = formatRecoveryStateDiagnostic(item.state, item.stateDetail);
|
|
333
|
-
const base = `${item.checkpointId}\t${item.state}\t${item.changeCount} changes\treceipt:${item.receiptHash?.slice(0, 12) ?? '-'}\t${item.createdAt}`;
|
|
366
|
+
const base = `${item.checkpointId}\t${item.state}\t${item.backend}\t${item.resourceKind}\t${item.changeCount} changes\treceipt:${item.receiptHash?.slice(0, 12) ?? '-'}\t${item.createdAt}`;
|
|
334
367
|
return diagnostic ? `${base}\n ${diagnostic}` : base;
|
|
335
368
|
});
|
|
336
369
|
return `${lines.join('\n')}\n`;
|
|
@@ -15,6 +15,11 @@ export declare function sessionStatusProject(params: {
|
|
|
15
15
|
ok: boolean;
|
|
16
16
|
attestationPath: string;
|
|
17
17
|
fresh: boolean;
|
|
18
|
+
containedExecutionFresh?: boolean;
|
|
18
19
|
attestation: Awaited<ReturnType<typeof boundarySessionStatus>>['attestation'];
|
|
19
20
|
}>;
|
|
21
|
+
export declare function sessionStatusOk(status: {
|
|
22
|
+
fresh: boolean;
|
|
23
|
+
containedExecutionFresh?: boolean;
|
|
24
|
+
}): boolean;
|
|
20
25
|
export declare function formatSessionStatusReport(report: Awaited<ReturnType<typeof sessionStatusProject>>): string;
|
package/dist/commands/session.js
CHANGED
|
@@ -31,12 +31,16 @@ export async function sessionStatusProject(params) {
|
|
|
31
31
|
const config = await loadConfigFile(repoRoot);
|
|
32
32
|
const status = await boundarySessionStatus({ repoRoot, config });
|
|
33
33
|
return {
|
|
34
|
-
ok: status
|
|
34
|
+
ok: sessionStatusOk(status),
|
|
35
35
|
attestationPath: status.attestationPath,
|
|
36
36
|
fresh: status.fresh,
|
|
37
|
+
containedExecutionFresh: status.containedExecutionFresh,
|
|
37
38
|
attestation: status.attestation,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
export function sessionStatusOk(status) {
|
|
42
|
+
return status.fresh || status.containedExecutionFresh === true;
|
|
43
|
+
}
|
|
40
44
|
export function formatSessionStatusReport(report) {
|
|
41
45
|
if (!report.attestation) {
|
|
42
46
|
return `No boundary attestation at ${report.attestationPath}. Run belay session start.`;
|
|
@@ -45,6 +49,9 @@ export function formatSessionStatusReport(report) {
|
|
|
45
49
|
`Attestation: ${report.attestationPath}`,
|
|
46
50
|
`Driver: ${report.attestation.driver}`,
|
|
47
51
|
`Fresh: ${report.fresh ? 'yes' : 'no'}`,
|
|
52
|
+
...(report.containedExecutionFresh === undefined
|
|
53
|
+
? []
|
|
54
|
+
: [`Contained execution fresh: ${report.containedExecutionFresh ? 'yes' : 'no'}`]),
|
|
48
55
|
`Expires: ${report.attestation.expiresAt}`,
|
|
49
56
|
`Materializes grants: ${report.attestation.materializesGrants ? 'yes' : 'no'}`,
|
|
50
57
|
].join('\n');
|
package/dist/commands/status.js
CHANGED
|
@@ -54,7 +54,7 @@ export function formatStatusReport(report) {
|
|
|
54
54
|
`Approved (awaiting use): ${report.approved.length}`,
|
|
55
55
|
`Expired pending (not yet compacted): ${report.expiredPendingCount}`,
|
|
56
56
|
`Dogfood: ${report.dogfood.active ? 'active' : 'inactive'} (mode=${report.dogfood.mode}, unknownLocalEffect=${report.dogfood.unknownLocalEffect})`,
|
|
57
|
-
`
|
|
57
|
+
`Current cohort metrics: ${report.dogfood.gateEvents} gate events, ${report.dogfood.wouldBlockCount} would-block (${(report.dogfood.wouldBlockRate * 100).toFixed(1)}%), ${report.dogfood.excludedGateEvents} historical/mismatched excluded`,
|
|
58
58
|
`Ready for enforce: ${report.dogfood.readyForEnforce ? 'yes' : 'not yet'}`,
|
|
59
59
|
`File checkpoint: ${report.fileCheckpoint.enabled ? 'enabled' : 'disabled'} (transactional=${report.fileCheckpoint.transactionalEnabled}, durable=${report.fileCheckpoint.durableCheckpointEnabled}, nonGit=${report.fileCheckpoint.allowNonGit})`,
|
|
60
60
|
`File checkpoint limits: files=${report.fileCheckpoint.maxFiles}, sourceBytes=${report.fileCheckpoint.maxSourceBytes}, workspaceBytes=${report.fileCheckpoint.maxWorkspaceBytes}, prepareTimeoutMs=${report.fileCheckpoint.prepareTimeoutMs}, copyConcurrency=${report.fileCheckpoint.copyConcurrency}`,
|
package/dist/config-io.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { getAdapterLayout } from './adapters/layouts/index.js';
|
|
6
6
|
import { compactApprovals, isExpired, mergeApprovalStates } from './core/approval.js';
|
|
7
7
|
import { mutateApprovalStateWithRetry } from './core/capability/approval-state-mutation.js';
|
|
8
|
-
import { approvedApprovalsFile, belayStateDir, configuredControlPlaneDir, mergeConfig, pendingApprovalsFile, } from './core/config.js';
|
|
8
|
+
import { approvedApprovalsFile, belayStateDir, configuredControlPlaneDir, mergeConfig, pendingApprovalsFile, stripForbiddenShellOverrideLists, } from './core/config.js';
|
|
9
9
|
import { resolveLayeredConfig, teamConfigPath, } from './core/config-layers.js';
|
|
10
10
|
export function resolveAdapterName(config) {
|
|
11
11
|
if (config.adapter === 'claude') {
|
|
@@ -210,7 +210,7 @@ export async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoR
|
|
|
210
210
|
export async function writeConfigFile(repoRoot, config, adapter = resolveAdapterName(config)) {
|
|
211
211
|
const configPath = configPathFor(repoRoot, adapter);
|
|
212
212
|
await mkdir(path.dirname(configPath), { recursive: true });
|
|
213
|
-
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
213
|
+
await writeFile(configPath, `${JSON.stringify(stripForbiddenShellOverrideLists(config), null, 2)}\n`, 'utf8');
|
|
214
214
|
}
|
|
215
215
|
export async function mergeAndWriteConfig(repoRoot, adapter = 'cursor') {
|
|
216
216
|
const layout = getAdapterLayout(adapter);
|