@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
|
@@ -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,20 +4,24 @@ 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';
|
|
23
27
|
import { readInstalledRuntimeProvenance } from '../runtime-provenance.js';
|
|
@@ -74,7 +78,7 @@ export async function doctorProject(options = {}) {
|
|
|
74
78
|
...(loadedConfig.overrides.external.length > 0 ? ['overrides.external'] : []),
|
|
75
79
|
];
|
|
76
80
|
if (ignoredShellOverrideLists.length > 0) {
|
|
77
|
-
|
|
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.`);
|
|
78
82
|
}
|
|
79
83
|
if (loadedConfig.version !== 4) {
|
|
80
84
|
warnings.push(`Config version is ${loadedConfig.version}; expected 4. Run belay upgrade to migrate.`);
|
|
@@ -233,6 +237,23 @@ export async function doctorProject(options = {}) {
|
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
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
|
+
}
|
|
236
257
|
const cleanup = await cleanupOrphanApprovalState(repoRoot, loadedConfig, {
|
|
237
258
|
dryRun: options.dryRun === true,
|
|
238
259
|
});
|
|
@@ -291,10 +312,27 @@ export async function doctorProject(options = {}) {
|
|
|
291
312
|
else if (dogfood.unknownLocalEffect === 'deny' && dogfood.mode !== 'audit') {
|
|
292
313
|
notes.push('Fail-closed policy is enabled in enforce mode.');
|
|
293
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
|
+
}
|
|
294
330
|
if (loadedConfig.policy.transactional.enabled) {
|
|
295
|
-
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.');
|
|
296
|
-
|
|
297
|
-
|
|
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.');
|
|
298
336
|
}
|
|
299
337
|
}
|
|
300
338
|
const fileCheckpoint = loadedConfig.policy.transactional.fileCheckpoint;
|
|
@@ -307,6 +345,35 @@ export async function doctorProject(options = {}) {
|
|
|
307
345
|
if (!loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
308
346
|
warnings.push('File checkpoint is enabled but durable Recovery checkpointing is disabled; backend selection will fail closed.');
|
|
309
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'}.`);
|
|
310
377
|
}
|
|
311
378
|
if (loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
312
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
|
@@ -29,6 +29,42 @@ export async function metricsProject(options = {}) {
|
|
|
29
29
|
activeCohort,
|
|
30
30
|
});
|
|
31
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
|
+
}
|
|
32
68
|
export function formatMetricsReport(report) {
|
|
33
69
|
const lines = [
|
|
34
70
|
`belay metrics for ${report.auditLogPath}`,
|
|
@@ -37,6 +73,7 @@ export function formatMetricsReport(report) {
|
|
|
37
73
|
`All-time would-block: ${report.wouldBlockCount} (${(report.wouldBlockRate * 100).toFixed(1)}%)`,
|
|
38
74
|
`All-time classifier-quality would-block: ${report.classifierWouldBlockCount} (${(report.classifierWouldBlockRate * 100).toFixed(1)}%)`,
|
|
39
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}`,
|
|
40
77
|
];
|
|
41
78
|
lines.push('', 'Current readiness cohort:');
|
|
42
79
|
if (report.currentCohort.identity) {
|
|
@@ -51,6 +88,7 @@ export function formatMetricsReport(report) {
|
|
|
51
88
|
lines.push(`- would-block: ${report.currentCohort.wouldBlockCount} (${(report.currentCohort.wouldBlockRate * 100).toFixed(1)}%)`);
|
|
52
89
|
lines.push(`- classifier-quality would-block: ${report.currentCohort.classifierWouldBlockCount} (${(report.currentCohort.classifierWouldBlockRate * 100).toFixed(1)}%)`);
|
|
53
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}`);
|
|
54
92
|
lines.push(`- availability-caused asks: ${report.currentCohort.availabilityAsks.total}`);
|
|
55
93
|
if (Object.keys(report.currentCohort.wouldBlockByReason).length > 0) {
|
|
56
94
|
lines.push('', 'Current-cohort would-block by reason:');
|
|
@@ -152,5 +190,9 @@ export function formatMetricsReport(report) {
|
|
|
152
190
|
}
|
|
153
191
|
lines.push('', report.dogfood.readyForEnforce ? 'Ready for enforce: yes' : 'Ready for enforce: not yet');
|
|
154
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
|
+
}));
|
|
155
197
|
return `${lines.join('\n')}\n`;
|
|
156
198
|
}
|
|
@@ -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/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);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS } from '../core/contained-execution/policy.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normative contained-execution capability contract.
|
|
4
|
+
*
|
|
5
|
+
* This is intentionally not a LayerProfileId: it describes one opt-in mediated execution and
|
|
6
|
+
* never upgrades the surrounding configuration to L1-full.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContainedUnknownExecutionGuarantee {
|
|
9
|
+
id: 'contained-unknown-execution-v1';
|
|
10
|
+
optIn: true;
|
|
11
|
+
runtime: 'docker-only';
|
|
12
|
+
l1Full: false;
|
|
13
|
+
materializesGrants: false;
|
|
14
|
+
deniesUngrantedEffects: false;
|
|
15
|
+
authority: {
|
|
16
|
+
shell: 'effect-plan-only';
|
|
17
|
+
commandIdentityEligibility: false;
|
|
18
|
+
forbiddenInputs: readonly [
|
|
19
|
+
'executable',
|
|
20
|
+
'prefix',
|
|
21
|
+
'fingerprint',
|
|
22
|
+
'corpus-membership',
|
|
23
|
+
'framework-identity'
|
|
24
|
+
];
|
|
25
|
+
};
|
|
26
|
+
audit: {
|
|
27
|
+
wouldMediate: true;
|
|
28
|
+
containedRouteExecution: 'none';
|
|
29
|
+
readsAttestation: false;
|
|
30
|
+
preparesMirror: false;
|
|
31
|
+
startsContainer: false;
|
|
32
|
+
gatePermission: 'allow';
|
|
33
|
+
hostExecution: 'delegated-to-host';
|
|
34
|
+
};
|
|
35
|
+
enforce: {
|
|
36
|
+
originalHostCommand: 'deny';
|
|
37
|
+
mirror: 'file_copy';
|
|
38
|
+
startsAtMostOnce: true;
|
|
39
|
+
workspaceChanges: 'discard';
|
|
40
|
+
output: {
|
|
41
|
+
scrub: 'mandatory';
|
|
42
|
+
tailBytes: 16384;
|
|
43
|
+
userRedactionCanDisable: false;
|
|
44
|
+
};
|
|
45
|
+
audit: 'safe-metadata-only';
|
|
46
|
+
};
|
|
47
|
+
attestation: {
|
|
48
|
+
signed: true;
|
|
49
|
+
fresh: true;
|
|
50
|
+
immutableImageId: true;
|
|
51
|
+
configuredDockerBinary: 'absolute-bound';
|
|
52
|
+
configuredDockerSocket: 'local-unix-bound';
|
|
53
|
+
daemonIdentity: true;
|
|
54
|
+
configurationBound: true;
|
|
55
|
+
};
|
|
56
|
+
boundary: {
|
|
57
|
+
network: 'none';
|
|
58
|
+
readOnlyRoot: true;
|
|
59
|
+
sanitizedHostEnvironment: true;
|
|
60
|
+
resourceLimits: true;
|
|
61
|
+
logDriver: 'none';
|
|
62
|
+
mount: 'one-private-mirror-at-original-guest-path';
|
|
63
|
+
excluded: readonly [
|
|
64
|
+
'host-source',
|
|
65
|
+
'git-metadata',
|
|
66
|
+
'control-plane',
|
|
67
|
+
'docker-socket',
|
|
68
|
+
'devices',
|
|
69
|
+
'unrelated-host-paths'
|
|
70
|
+
];
|
|
71
|
+
cleanupConfirmed: true;
|
|
72
|
+
};
|
|
73
|
+
fallback: {
|
|
74
|
+
approvalOnly: typeof CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS;
|
|
75
|
+
};
|
|
76
|
+
failure: {
|
|
77
|
+
/** Every setup failure other than the exact approval fallback set denies before host replay. */
|
|
78
|
+
setup: {
|
|
79
|
+
categories: readonly [
|
|
80
|
+
'boundary',
|
|
81
|
+
'capability',
|
|
82
|
+
'image',
|
|
83
|
+
'mirror',
|
|
84
|
+
'lease',
|
|
85
|
+
'container-lifecycle',
|
|
86
|
+
'cleanup'
|
|
87
|
+
];
|
|
88
|
+
outcome: 'deny';
|
|
89
|
+
hostExecution: 'deny';
|
|
90
|
+
approval: 'none';
|
|
91
|
+
approvalStateMutation: 'none';
|
|
92
|
+
};
|
|
93
|
+
/** A started container that times out or exits nonzero is terminal, never an approval. */
|
|
94
|
+
command: {
|
|
95
|
+
timeout: 'contained_execution_failed';
|
|
96
|
+
nonzero: 'contained_execution_failed';
|
|
97
|
+
hostExecution: 'deny';
|
|
98
|
+
approval: 'none';
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
outcomes: {
|
|
102
|
+
success: {
|
|
103
|
+
exitCode: 0;
|
|
104
|
+
outcome: 'contained_execution_complete';
|
|
105
|
+
hostExecution: 'deny';
|
|
106
|
+
approval: 'none';
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export type ContainedUnknownExecutionScenario = {
|
|
111
|
+
id: string;
|
|
112
|
+
kind: 'eligible-unknown-local';
|
|
113
|
+
command: string;
|
|
114
|
+
} | {
|
|
115
|
+
id: string;
|
|
116
|
+
kind: 'ineligible-network';
|
|
117
|
+
command: string;
|
|
118
|
+
};
|
|
119
|
+
export declare const CONTAINED_UNKNOWN_EXECUTION_GUARANTEE: ContainedUnknownExecutionGuarantee;
|
|
120
|
+
/** Executable EffectPlan scenarios; routing and output use the gate/executor suites. */
|
|
121
|
+
export declare const CONTAINED_UNKNOWN_EXECUTION_SCENARIOS: readonly ContainedUnknownExecutionScenario[];
|