@guilz-dev/belay 0.6.0 → 0.8.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 +39 -20
- package/dist/adapters/claude/runtime-entry.js +54 -25
- package/dist/adapters/shared/gate-runtime.d.ts +7 -0
- package/dist/adapters/shared/gate-runtime.js +443 -225
- package/dist/bundle/claude-runtime.mjs +13027 -7271
- package/dist/bundle/codex-runtime.mjs +12700 -6977
- package/dist/bundle/cursor-runtime.mjs +12695 -6977
- package/dist/cli.js +37 -1
- package/dist/commands/approve.js +34 -10
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/dogfood.js +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/harvest.d.ts +0 -1
- package/dist/commands/harvest.js +1 -4
- package/dist/commands/metrics.js +6 -0
- package/dist/commands/quality.js +1 -3
- package/dist/commands/recovery-checkpoints.d.ts +164 -0
- package/dist/commands/recovery-checkpoints.js +347 -0
- package/dist/commands/revoke.js +19 -7
- package/dist/commands/status.js +7 -0
- package/dist/config-io.js +49 -18
- package/dist/conformance/guarantee-table.js +137 -9
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval-replay.d.ts +1 -1
- package/dist/core/approval-replay.js +4 -1
- package/dist/core/approval-service.d.ts +6 -1
- package/dist/core/approval-service.js +86 -27
- package/dist/core/approval.d.ts +2 -0
- package/dist/core/approval.js +6 -3
- package/dist/core/audit-metrics.d.ts +1 -0
- package/dist/core/audit-metrics.js +12 -4
- package/dist/core/audit-types.d.ts +16 -0
- package/dist/core/capability/approval-state-mutation.d.ts +19 -0
- package/dist/core/capability/approval-state-mutation.js +76 -3
- package/dist/core/capability/approval-v3.d.ts +18 -1
- package/dist/core/capability/approval-v3.js +63 -23
- package/dist/core/capability/attestation.d.ts +3 -0
- package/dist/core/capability/attestation.js +7 -0
- package/dist/core/capability/boundary-driver-container.d.ts +12 -0
- package/dist/core/capability/boundary-driver-container.js +101 -34
- package/dist/core/capability/boundary-driver.js +1 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
- package/dist/core/capability/boundary-grant-materialize.js +15 -0
- package/dist/core/capability/boundary-run.d.ts +25 -0
- package/dist/core/capability/boundary-run.js +37 -9
- package/dist/core/capability/boundary-session.d.ts +4 -0
- package/dist/core/capability/boundary-session.js +9 -0
- package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
- package/dist/core/capability/boundary-workspace-mount.js +68 -0
- package/dist/core/capability/gate-policy-shadow.js +23 -1
- package/dist/core/capability/grant-consumption.d.ts +4 -0
- package/dist/core/capability/grant-consumption.js +11 -0
- package/dist/core/capability/grant-lease.d.ts +36 -1
- package/dist/core/capability/grant-lease.js +213 -16
- package/dist/core/capability/grant-match.js +3 -0
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +1 -1
- package/dist/core/capability/policy-engine.d.ts +38 -1
- package/dist/core/capability/policy-engine.js +388 -15
- package/dist/core/capability/request.d.ts +12 -0
- package/dist/core/capability/resolver.d.ts +0 -1
- package/dist/core/capability/resolver.js +0 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +62 -2
- package/dist/core/effect-ir/audit.d.ts +9 -0
- package/dist/core/effect-ir/audit.js +87 -0
- package/dist/core/effect-ir/build.d.ts +31 -0
- package/dist/core/effect-ir/build.js +281 -0
- package/dist/core/effect-ir/index.d.ts +13 -0
- package/dist/core/effect-ir/index.js +9 -0
- package/dist/core/effect-ir/normalize.d.ts +4 -0
- package/dist/core/effect-ir/normalize.js +110 -0
- package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
- package/dist/core/effect-ir/package-exec-eval.js +152 -0
- package/dist/core/effect-ir/package-exec.d.ts +29 -0
- package/dist/core/effect-ir/package-exec.js +292 -0
- package/dist/core/effect-ir/policy.d.ts +14 -0
- package/dist/core/effect-ir/policy.js +164 -0
- package/dist/core/effect-ir/shell-build.d.ts +43 -0
- package/dist/core/effect-ir/shell-build.js +77 -0
- package/dist/core/effect-ir/shell-lower.d.ts +14 -0
- package/dist/core/effect-ir/shell-lower.js +1509 -0
- package/dist/core/effect-ir/types.d.ts +62 -0
- package/dist/core/effect-ir/types.js +1 -0
- package/dist/core/egress/allowlist.d.ts +1 -0
- package/dist/core/egress/allowlist.js +8 -0
- package/dist/core/egress-approval.js +45 -51
- package/dist/core/gate-contract.d.ts +2 -0
- package/dist/core/gate-contract.js +2 -0
- package/dist/core/gate-engine.js +82 -25
- package/dist/core/git-resource-identity.d.ts +26 -0
- package/dist/core/git-resource-identity.js +284 -0
- package/dist/core/harvest.d.ts +3 -7
- package/dist/core/harvest.js +4 -27
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +3 -1
- package/dist/core/judge-cloud-consent.js +29 -15
- package/dist/core/network-endpoint.d.ts +10 -0
- package/dist/core/network-endpoint.js +51 -0
- package/dist/core/path-utils.js +42 -23
- package/dist/core/process-runner.d.ts +10 -0
- package/dist/core/process-runner.js +124 -0
- package/dist/core/recover-advice.js +2 -1
- package/dist/core/recovery/artifact-store.d.ts +29 -0
- package/dist/core/recovery/artifact-store.js +336 -0
- package/dist/core/recovery/checkpoint.d.ts +25 -0
- package/dist/core/recovery/checkpoint.js +260 -0
- package/dist/core/recovery/index.d.ts +3 -1
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/operator-guidance.d.ts +7 -0
- package/dist/core/recovery/operator-guidance.js +39 -0
- package/dist/core/recovery/reconcile.d.ts +3 -0
- package/dist/core/recovery/reconcile.js +48 -0
- package/dist/core/recovery/resource-identity.d.ts +4 -0
- package/dist/core/recovery/resource-identity.js +34 -0
- package/dist/core/recovery/restore.d.ts +13 -0
- package/dist/core/recovery/restore.js +114 -0
- package/dist/core/recovery/snapshot-node.d.ts +27 -0
- package/dist/core/recovery/snapshot-node.js +261 -0
- package/dist/core/recovery/types.d.ts +85 -1
- package/dist/core/shell-substitution.d.ts +1 -0
- package/dist/core/shell-substitution.js +61 -0
- package/dist/core/standing-allow.d.ts +1 -2
- package/dist/core/standing-allow.js +3 -20
- package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
- package/dist/core/transactional/apply-observed-changes.js +275 -0
- package/dist/core/transactional/backend-selector.d.ts +8 -0
- package/dist/core/transactional/backend-selector.js +118 -0
- package/dist/core/transactional/backend.d.ts +55 -0
- package/dist/core/transactional/backend.js +1 -0
- package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +278 -0
- package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
- package/dist/core/transactional/file-checkpoint-git.js +217 -0
- package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
- package/dist/core/transactional/file-checkpoint-staging.js +54 -0
- package/dist/core/transactional/file-clone.d.ts +15 -0
- package/dist/core/transactional/file-clone.js +139 -0
- package/dist/core/transactional/file-tree-path.d.ts +7 -0
- package/dist/core/transactional/file-tree-path.js +50 -0
- package/dist/core/transactional/file-tree.d.ts +41 -0
- package/dist/core/transactional/file-tree.js +186 -0
- package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
- package/dist/core/transactional/git-worktree-backend.js +50 -0
- package/dist/core/transactional/git-worktree.d.ts +9 -11
- package/dist/core/transactional/git-worktree.js +24 -119
- package/dist/core/transactional/index.d.ts +11 -0
- package/dist/core/transactional/index.js +10 -0
- package/dist/core/transactional/reasons.js +1 -0
- package/dist/core/transactional/runner.js +157 -25
- package/dist/core/transactional/snapshot-node.d.ts +26 -0
- package/dist/core/transactional/snapshot-node.js +87 -0
- package/dist/core/transactional/types.d.ts +17 -1
- package/dist/core/types.d.ts +13 -1
- package/dist/core/verdict/adapter.js +23 -15
- package/dist/core/verdict/containment.js +4 -0
- package/dist/core/verdict/egress-classify.d.ts +12 -0
- package/dist/core/verdict/egress-classify.js +746 -0
- package/dist/core/verdict/git-classifier.d.ts +12 -0
- package/dist/core/verdict/git-classifier.js +320 -0
- package/dist/core/verdict/launcher-resolve.js +79 -37
- package/dist/core/verdict/parser.d.ts +2 -0
- package/dist/core/verdict/parser.js +95 -1
- package/dist/core/verdict/types.d.ts +6 -2
- package/dist/core/verdict/verdict.js +103 -935
- package/dist/corpus/must-allow-commands.d.ts +2 -1
- package/dist/corpus/must-allow-commands.js +2 -1
- package/dist/corpus/runtime-match.d.ts +2 -1
- package/dist/corpus/runtime-match.js +2 -1
- package/dist/corpus/types.d.ts +2 -2
- package/dist/templates.js +7 -2
- package/dist/types.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/belay/SKILL.md +8 -3
- package/skills/belay/belay-approve.md +10 -7
- package/dist/core/verdict/overrides.d.ts +0 -7
- package/dist/core/verdict/overrides.js +0 -37
- package/dist/core/verdict/shell-policy.d.ts +0 -25
- package/dist/core/verdict/shell-policy.js +0 -40
- package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
- package/dist/corpus/standing-allow-catalog.generated.js +0 -99
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { formatHarvestReport, harvestApplyProject, harvestListProject } from './
|
|
|
10
10
|
import { formatMetricsReport, metricsProject } from './commands/metrics.js';
|
|
11
11
|
import { formatQualityReport, qualityCheck } from './commands/quality.js';
|
|
12
12
|
import { formatRecoverReport, recoverProject } from './commands/recover.js';
|
|
13
|
+
import { formatRecoveryCheckpointResult, recoveryCheckpointCommand, } from './commands/recovery-checkpoints.js';
|
|
13
14
|
import { formatReport, reportProject } from './commands/report.js';
|
|
14
15
|
import { revokeApproval } from './commands/revoke.js';
|
|
15
16
|
import { formatSessionStatusReport, sessionStartProject, sessionStatusProject, } from './commands/session.js';
|
|
@@ -433,6 +434,23 @@ function parseArgs(argv) {
|
|
|
433
434
|
}
|
|
434
435
|
throw new Error('harvest requires subcommand: list or apply');
|
|
435
436
|
}
|
|
437
|
+
if (command === 'recover' && !options.recoverSubcommand) {
|
|
438
|
+
if (token === 'advice' ||
|
|
439
|
+
token === 'status' ||
|
|
440
|
+
token === 'list' ||
|
|
441
|
+
token === 'show' ||
|
|
442
|
+
token === 'apply') {
|
|
443
|
+
options.recoverSubcommand = token;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
throw new Error('recover requires subcommand: advice, status, list, show, or apply');
|
|
447
|
+
}
|
|
448
|
+
if (command === 'recover' &&
|
|
449
|
+
(options.recoverSubcommand === 'show' || options.recoverSubcommand === 'apply') &&
|
|
450
|
+
!options.recoverCheckpointId) {
|
|
451
|
+
options.recoverCheckpointId = token;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
436
454
|
if (command === 'judge' && !options.judgeSubcommand) {
|
|
437
455
|
if (token === 'status' ||
|
|
438
456
|
token === 'list' ||
|
|
@@ -527,8 +545,10 @@ Usage:
|
|
|
527
545
|
${c} metrics [--target <dir>] [--json]
|
|
528
546
|
${c} quality [--target <dir>] [--corpus <path>] [--json]
|
|
529
547
|
${c} report [--target <dir>] [--since <iso>] [--until <iso>] [--limit <n>] [--json]
|
|
530
|
-
${c} recover [--target <dir>] [--since <iso>] [--fingerprint <fp>] [--command "<text>"] [--limit <n>] [--json]
|
|
548
|
+
${c} recover [advice] [--target <dir>] [--since <iso>] [--fingerprint <fp>] [--command "<text>"] [--limit <n>] [--json]
|
|
531
549
|
(--limit picks the Nth recover candidate after priority ranking: local_mutation first, then recency; 1 = highest priority, default 1)
|
|
550
|
+
${c} recover <status|list> [--target <dir>] [--json]
|
|
551
|
+
${c} recover <show|apply> <checkpoint-id> [--target <dir>] [--json]
|
|
532
552
|
${c} audit <query|summarize|replay> [--target <dir>] [--json] [--since <iso>] [--until <iso>] [--verdict <v>] [--reason <r>] [--kind <k>] [--fingerprint <fp>] [--event <e>] [--location <v>] [--opacity <v>] [--effect <v>] [--confidence <v>] [--limit <n>] [--config <path>]
|
|
533
553
|
${c} simulate --config <path> [--target <dir>] [--json]
|
|
534
554
|
${c} status [--target <dir>] [--json]
|
|
@@ -825,6 +845,22 @@ async function main() {
|
|
|
825
845
|
throw new Error('harvest requires subcommand: list or apply');
|
|
826
846
|
}
|
|
827
847
|
if (command === 'recover') {
|
|
848
|
+
if (options.recoverSubcommand && options.recoverSubcommand !== 'advice') {
|
|
849
|
+
const result = await recoveryCheckpointCommand({
|
|
850
|
+
targetDir: options.targetDir,
|
|
851
|
+
subcommand: options.recoverSubcommand,
|
|
852
|
+
checkpointId: options.recoverCheckpointId,
|
|
853
|
+
});
|
|
854
|
+
if (options.json) {
|
|
855
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
process.stdout.write(formatRecoveryCheckpointResult(result));
|
|
859
|
+
}
|
|
860
|
+
if (!result.ok)
|
|
861
|
+
process.exitCode = 2;
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
828
864
|
const report = await recoverProject({
|
|
829
865
|
targetDir: options.targetDir,
|
|
830
866
|
since: options.since,
|
package/dist/commands/approve.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadApprovalState, loadConfigFile } from '../config-io.js';
|
|
3
|
-
import { canAutoReplay, getExecutionLeaseMs
|
|
4
|
-
import {
|
|
3
|
+
import { canAutoReplay, getExecutionLeaseMs } from '../core/approval-replay.js';
|
|
4
|
+
import { claimApprovedForReplay, createGateApprovalStore, recordApproval, } from '../core/approval-service.js';
|
|
5
|
+
import { runBoundaryAgentCommand } from '../core/capability/boundary-session.js';
|
|
5
6
|
import { JUDGE_CLOUD_CONSENT_REASON } from '../core/capability/reasons.js';
|
|
6
7
|
import { recordCapabilityApproval } from '../core/capability-approval.js';
|
|
7
8
|
import { recordEgressApproval } from '../core/egress-approval.js';
|
|
9
|
+
import { RECOVERY_RESTORE_REASON } from '../core/recovery/checkpoint.js';
|
|
8
10
|
import { createEgressApprovalStore } from '../services/egress-service.js';
|
|
9
11
|
import { createCapabilityApprovalStore } from '../services/sandbox-service.js';
|
|
10
12
|
export async function approvePending(options) {
|
|
@@ -12,6 +14,19 @@ export async function approvePending(options) {
|
|
|
12
14
|
const config = await loadConfigFile(repoRoot);
|
|
13
15
|
const pending = await loadApprovalState(repoRoot, 'pending-approvals.json', config);
|
|
14
16
|
const match = pending.approvals.find((approval) => approval.approvalId === options.approvalId);
|
|
17
|
+
if (match?.reason === RECOVERY_RESTORE_REASON) {
|
|
18
|
+
if (options.scope && options.scope !== 'once') {
|
|
19
|
+
return { ok: false, message: 'Recovery restore approvals support one-shot scope only.' };
|
|
20
|
+
}
|
|
21
|
+
const result = await recordApproval({
|
|
22
|
+
approvalId: options.approvalId,
|
|
23
|
+
config,
|
|
24
|
+
token: options.token,
|
|
25
|
+
requireSignedToken: true,
|
|
26
|
+
store: createGateApprovalStore(repoRoot, config),
|
|
27
|
+
});
|
|
28
|
+
return { ok: result.ok, message: result.message };
|
|
29
|
+
}
|
|
15
30
|
if (match?.reason === JUDGE_CLOUD_CONSENT_REASON) {
|
|
16
31
|
const result = await recordCapabilityApproval({
|
|
17
32
|
approvalId: options.approvalId,
|
|
@@ -73,22 +88,31 @@ export async function approvePending(options) {
|
|
|
73
88
|
message: 'CLI replay is only supported for shell approvals. Retry the original tool or subagent action manually.',
|
|
74
89
|
};
|
|
75
90
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
91
|
+
const claimed = await claimApprovedForReplay({
|
|
92
|
+
approvalId: options.approvalId,
|
|
93
|
+
store: approvalStore,
|
|
94
|
+
});
|
|
95
|
+
if (!claimed) {
|
|
96
|
+
return { ok: false, message: 'Approval could not be claimed for replay.' };
|
|
97
|
+
}
|
|
98
|
+
const replayResult = await runBoundaryAgentCommand({
|
|
99
|
+
repoRoot,
|
|
100
|
+
config,
|
|
101
|
+
command: claimed.input ?? approval.input,
|
|
102
|
+
cwd: claimed.cwd ?? repoRoot,
|
|
103
|
+
timeoutMs: getExecutionLeaseMs(config),
|
|
104
|
+
runOptions: { mountReadOnly: false },
|
|
105
|
+
});
|
|
78
106
|
if (replayResult.exitCode === 0) {
|
|
79
|
-
await consumeApprovedAfterCliReplay({
|
|
80
|
-
approvalId: options.approvalId,
|
|
81
|
-
store: approvalStore,
|
|
82
|
-
});
|
|
83
107
|
return {
|
|
84
108
|
ok: true,
|
|
85
|
-
message: `Belay replay succeeded for ${options.approvalId}. Do not retry via hooks; the one-shot grant was consumed
|
|
109
|
+
message: `Belay replay succeeded for ${options.approvalId}. Do not retry via hooks; the one-shot grant was consumed.`,
|
|
86
110
|
};
|
|
87
111
|
}
|
|
88
112
|
const timeoutNote = replayResult.timedOut ? ' Replay timed out.' : '';
|
|
89
113
|
return {
|
|
90
114
|
ok: false,
|
|
91
|
-
message: `Belay replay failed for ${options.approvalId} (exit ${replayResult.exitCode}).${timeoutNote}
|
|
115
|
+
message: `Belay replay failed for ${options.approvalId} (exit ${replayResult.exitCode}).${timeoutNote} The one-shot approval was consumed before replay and was not re-armed.`,
|
|
92
116
|
};
|
|
93
117
|
}
|
|
94
118
|
return { ok: result.ok, message: result.message };
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getAdapterLayout } from '../adapters/layouts/index.js';
|
|
|
7
7
|
import { resolveScopedPaths } from '../adapters/layouts/scope.js';
|
|
8
8
|
import { cleanupOrphanApprovalState } from '../cleanup-orphans.js';
|
|
9
9
|
import { approvedApprovalsPath, belayStateDir, detectAdapterName, loadLayeredConfig, pendingApprovalsPath, repoLocalStateDirFor, } from '../config-io.js';
|
|
10
|
+
import { approvalSigningKeyPath } from '../core/approval-token.js';
|
|
10
11
|
import { detectFenceDrift, summarizeAuditVisibility } from '../core/audit-summary.js';
|
|
11
12
|
import { inspectBoundaryAttestationFile } from '../core/capability/boundary-attestation-sign.js';
|
|
12
13
|
import { boundaryAttestationPath, boundarySessionStatus, } from '../core/capability/boundary-session.js';
|
|
@@ -14,6 +15,9 @@ import { defaultControlPlaneDir } from '../core/config.js';
|
|
|
14
15
|
import { verifyIntegrityManifest } from '../core/integrity.js';
|
|
15
16
|
import { diagnoseJudge, stopJudgeSessionBrokers } from '../core/judge-doctor.js';
|
|
16
17
|
import { resolveJudgeTransport } from '../core/judge-runtime-detection.js';
|
|
18
|
+
import { listRecoveryCheckpoints } from '../core/recovery/checkpoint.js';
|
|
19
|
+
import { recoveryApprovalSetupNotes, recoveryNotificationConfigured, recoveryNotificationSetupWarning, summarizeRecoveryCheckpointDiagnostics, } from '../core/recovery/operator-guidance.js';
|
|
20
|
+
import { probeFileCloneStrategy } from '../core/transactional/file-clone.js';
|
|
17
21
|
import { getManagedHookEntries } from '../defaults.js';
|
|
18
22
|
import { resolveNodeBinary } from '../node-resolution.js';
|
|
19
23
|
import { egressStatus } from '../services/egress-service.js';
|
|
@@ -78,6 +82,13 @@ export async function doctorProject(options = {}) {
|
|
|
78
82
|
for (const entry of layered.provenance) {
|
|
79
83
|
notes.push(`Config layer [${entry.source}]: ${entry.path}`);
|
|
80
84
|
}
|
|
85
|
+
const ignoredShellOverrideLists = [
|
|
86
|
+
...(loadedConfig.overrides.allow.length > 0 ? ['overrides.allow'] : []),
|
|
87
|
+
...(loadedConfig.overrides.external.length > 0 ? ['overrides.external'] : []),
|
|
88
|
+
];
|
|
89
|
+
if (ignoredShellOverrideLists.length > 0) {
|
|
90
|
+
warnings.push(`Legacy shell ${ignoredShellOverrideLists.join(' and ')} ${ignoredShellOverrideLists.length === 1 ? 'is' : 'are'} deprecated and ignored. Remove the command list; inspect the EffectPlan and correct effect semantics or resource scope instead.`);
|
|
91
|
+
}
|
|
81
92
|
if (loadedConfig.version !== 4) {
|
|
82
93
|
warnings.push(`Config version is ${loadedConfig.version}; expected 4. Run belay upgrade to migrate.`);
|
|
83
94
|
}
|
|
@@ -290,6 +301,45 @@ export async function doctorProject(options = {}) {
|
|
|
290
301
|
warnings.push('Transactional execution is enabled but this directory is not a git repository. Transactional worktrees will be skipped until git is available.');
|
|
291
302
|
}
|
|
292
303
|
}
|
|
304
|
+
const fileCheckpoint = loadedConfig.policy.transactional.fileCheckpoint;
|
|
305
|
+
if (fileCheckpoint.enabled) {
|
|
306
|
+
const copyStrategy = await probeFileCloneStrategy();
|
|
307
|
+
notes.push(`File checkpoint: enabled (copyStrategy=${copyStrategy}, non-Git=${fileCheckpoint.allowNonGit}, maxFiles=${fileCheckpoint.maxFiles}, maxSourceBytes=${fileCheckpoint.maxSourceBytes}, maxWorkspaceBytes=${fileCheckpoint.maxWorkspaceBytes}, prepareTimeoutMs=${fileCheckpoint.prepareTimeoutMs}, copyConcurrency=${fileCheckpoint.copyConcurrency}).`);
|
|
308
|
+
if (!loadedConfig.policy.transactional.enabled) {
|
|
309
|
+
warnings.push('File checkpoint is enabled but transactional execution is disabled; dirty Git workspaces cannot use the backend.');
|
|
310
|
+
}
|
|
311
|
+
if (!loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
312
|
+
warnings.push('File checkpoint is enabled but durable Recovery checkpointing is disabled; backend selection will fail closed.');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (loadedConfig.policy.transactional.checkpoint?.enabled) {
|
|
316
|
+
notes.push('Recovery checkpoint: enabled — repo-local pre-images are persisted before observed-safe transactional apply.');
|
|
317
|
+
notes.push(...recoveryApprovalSetupNotes());
|
|
318
|
+
if (!recoveryNotificationConfigured(loadedConfig)) {
|
|
319
|
+
warnings.push(recoveryNotificationSetupWarning());
|
|
320
|
+
}
|
|
321
|
+
const signingKeyPath = approvalSigningKeyPath(loadedConfig.controlPlane.enabled
|
|
322
|
+
? belayStateDir(loadedConfig, repoLocalDir)
|
|
323
|
+
: (loadedConfig.controlPlane.configDir ?? defaultControlPlaneDir()));
|
|
324
|
+
if (!existsSync(signingKeyPath)) {
|
|
325
|
+
notes.push(`Approval signing key not yet created at ${signingKeyPath}; it will be generated on the first signed recovery approval request.`);
|
|
326
|
+
}
|
|
327
|
+
if (!loadedConfig.policy.transactional.enabled) {
|
|
328
|
+
warnings.push('Recovery checkpoint is enabled but transactional execution is disabled; checkpoints will not be created until transactional execution is enabled.');
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const stateDir = belayStateDir(loadedConfig, repoLocalDir);
|
|
332
|
+
const checkpoints = await listRecoveryCheckpoints(stateDir, repoRoot);
|
|
333
|
+
if (checkpoints.length > 0) {
|
|
334
|
+
notes.push(`Recovery checkpoints in this repository: ${checkpoints.length}.`);
|
|
335
|
+
}
|
|
336
|
+
const advisories = summarizeRecoveryCheckpointDiagnostics(checkpoints);
|
|
337
|
+
warnings.push(...advisories);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Checkpoint inspection is advisory only.
|
|
341
|
+
}
|
|
342
|
+
}
|
|
293
343
|
if (loadedConfig.sandbox.enabled) {
|
|
294
344
|
const sandbox = await sandboxStatus({ targetDir: repoRoot });
|
|
295
345
|
notes.push(`Sandbox capability broker: enabled (runtime=${loadedConfig.sandbox.runtime}, fs-scope entries=${sandbox.fsScopeAllowlistCount}, full-isolation=${sandbox.l1FullActive}).`);
|
|
@@ -305,6 +355,10 @@ export async function doctorProject(options = {}) {
|
|
|
305
355
|
}
|
|
306
356
|
const boundaryDriver = loadedConfig.capability?.boundaryDriver ??
|
|
307
357
|
(loadedConfig.sandbox.runtime === 'container' ? 'container' : null);
|
|
358
|
+
if (loadedConfig.policy.transactional.fileCheckpoint.enabled &&
|
|
359
|
+
boundaryDriver !== 'container') {
|
|
360
|
+
warnings.push('File checkpoint requires an attested workspace-isolating boundary driver; configure the container boundary and run belay session start.');
|
|
361
|
+
}
|
|
308
362
|
if (loadedConfig.capability || boundaryDriver === 'container') {
|
|
309
363
|
const attestationPath = boundaryAttestationPath(repoRoot, loadedConfig);
|
|
310
364
|
const attestationFormat = await inspectBoundaryAttestationFile(attestationPath);
|
|
@@ -323,6 +377,10 @@ export async function doctorProject(options = {}) {
|
|
|
323
377
|
else if (!session.fresh) {
|
|
324
378
|
warnings.push('Boundary attestation is stale or expired. Run belay session start to refresh.');
|
|
325
379
|
}
|
|
380
|
+
else if (loadedConfig.policy.transactional.fileCheckpoint.enabled &&
|
|
381
|
+
session.attestation.isolatesWorkspaceMounts !== true) {
|
|
382
|
+
warnings.push('File checkpoint is enabled but the fresh boundary attestation does not prove workspace-mount isolation. Run belay session start with a compatible boundary.');
|
|
383
|
+
}
|
|
326
384
|
else if (session.attestation.probeSignals.includes('repo-mount-ro-default')) {
|
|
327
385
|
notes.push('Container boundary defaults to read-only directory mounts; predicted writes use read-write mounts for the working directory only.');
|
|
328
386
|
}
|
package/dist/commands/dogfood.js
CHANGED
|
@@ -45,7 +45,7 @@ async function promoteDogfoodToEnforce(repoRoot, configPath, force, adapter = 'c
|
|
|
45
45
|
message: [
|
|
46
46
|
'Dogfood metrics do not recommend enforce yet.',
|
|
47
47
|
...metrics.dogfood.notes,
|
|
48
|
-
'Re-run belay metrics,
|
|
48
|
+
'Re-run belay metrics, correct EffectPlan semantics or resource scope, or pass --force to override.',
|
|
49
49
|
].join(' '),
|
|
50
50
|
};
|
|
51
51
|
}
|
package/dist/commands/explain.js
CHANGED
|
@@ -106,8 +106,8 @@ export function formatExplainReport(report) {
|
|
|
106
106
|
report.policy.transactional.enabled
|
|
107
107
|
? `Transactional band: [${report.policy.transactional.minConfidence}, ${report.policy.transactional.maxConfidence})`
|
|
108
108
|
: 'Transactional band: not configured',
|
|
109
|
-
`
|
|
110
|
-
`
|
|
109
|
+
`Legacy overrides allow (deprecated, ignored): ${report.overrides.allow.join(', ') || '(none)'}`,
|
|
110
|
+
`Legacy overrides external (deprecated, ignored): ${report.overrides.external.join(', ') || '(none)'}`,
|
|
111
111
|
'',
|
|
112
112
|
`Verdict: ${result.verdict}`,
|
|
113
113
|
`Reason: ${result.reason}`,
|
|
@@ -17,7 +17,6 @@ export declare function harvestListProject(options?: HarvestListOptions): Promis
|
|
|
17
17
|
export declare function harvestReportFromRecords(records: AuditRecord[], options?: {
|
|
18
18
|
since?: string;
|
|
19
19
|
until?: string;
|
|
20
|
-
allowPatterns?: string[];
|
|
21
20
|
}): HarvestReport;
|
|
22
21
|
export declare function formatHarvestReport(report: HarvestReport): string;
|
|
23
22
|
export declare function harvestApplyProject(options: HarvestApplyOptions): Promise<{
|
package/dist/commands/harvest.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { loadConfigFile } from '../config-io.js';
|
|
4
3
|
import { parseAuditNdjson } from '../core/audit-metrics.js';
|
|
5
4
|
import { toAuditRecord } from '../core/audit-query.js';
|
|
6
5
|
import { applyHarvestReview, buildHarvestReport, filterRecordsForHarvest, } from '../core/harvest.js';
|
|
@@ -8,16 +7,14 @@ import { parseCorpusCases } from '../corpus/types.js';
|
|
|
8
7
|
import { loadAuditRecords } from './audit.js';
|
|
9
8
|
export async function harvestListProject(options = {}) {
|
|
10
9
|
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
11
|
-
const config = await loadConfigFile(repoRoot);
|
|
12
10
|
const records = await loadAuditRecords(repoRoot);
|
|
13
11
|
return harvestReportFromRecords(records, {
|
|
14
12
|
since: options.since,
|
|
15
13
|
until: options.until,
|
|
16
|
-
allowPatterns: config.overrides.allow,
|
|
17
14
|
});
|
|
18
15
|
}
|
|
19
16
|
export function harvestReportFromRecords(records, options = {}) {
|
|
20
|
-
return buildHarvestReport(filterRecordsForHarvest(records, { since: options.since, until: options.until })
|
|
17
|
+
return buildHarvestReport(filterRecordsForHarvest(records, { since: options.since, until: options.until }));
|
|
21
18
|
}
|
|
22
19
|
export function formatHarvestReport(report) {
|
|
23
20
|
const lines = [
|
package/dist/commands/metrics.js
CHANGED
|
@@ -38,6 +38,12 @@ export function formatMetricsReport(report) {
|
|
|
38
38
|
if (report.approvalLatency.count > 0) {
|
|
39
39
|
lines.push(`Approval latency: median ${report.approvalLatency.medianMs ?? 0}ms, p95 ${report.approvalLatency.p95Ms ?? 0}ms (${report.approvalLatency.count} samples)`);
|
|
40
40
|
}
|
|
41
|
+
if (Object.keys(report.gateEventsByRuntime).length > 0) {
|
|
42
|
+
lines.push('', 'Gate events by runtime:');
|
|
43
|
+
for (const [runtime, count] of Object.entries(report.gateEventsByRuntime).sort((left, right) => right[1] - left[1])) {
|
|
44
|
+
lines.push(`- ${runtime}: ${count}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
41
47
|
if (report.bypassAttemptCount > 0) {
|
|
42
48
|
lines.push(`Bypass attempts detected: ${report.bypassAttemptCount}`);
|
|
43
49
|
}
|
package/dist/commands/quality.js
CHANGED
|
@@ -18,9 +18,7 @@ export async function qualityCheck(options = {}) {
|
|
|
18
18
|
mode: config.mode,
|
|
19
19
|
unknownLocalEffect: config.policy.unknownLocalEffect,
|
|
20
20
|
});
|
|
21
|
-
const harvest = harvestReportFromRecords(auditRecords
|
|
22
|
-
allowPatterns: config.overrides.allow,
|
|
23
|
-
});
|
|
21
|
+
const harvest = harvestReportFromRecords(auditRecords);
|
|
24
22
|
const notes = [
|
|
25
23
|
'Overall ok reflects corpus hard gates only; audit and harvest signals are advisory.',
|
|
26
24
|
'Recursive quality loop: corpus hard gates are the FN/FP safety boundary.',
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type RecoveryCheckpointSubcommand = 'status' | 'list' | 'show' | 'apply';
|
|
2
|
+
export declare function recoveryCheckpointCommand(options: {
|
|
3
|
+
targetDir?: string;
|
|
4
|
+
subcommand: RecoveryCheckpointSubcommand;
|
|
5
|
+
checkpointId?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
ok: boolean;
|
|
8
|
+
subcommand: string;
|
|
9
|
+
repoRoot: string;
|
|
10
|
+
backend: string;
|
|
11
|
+
availableBackends: string[];
|
|
12
|
+
backendCounts: {
|
|
13
|
+
[k: string]: number;
|
|
14
|
+
};
|
|
15
|
+
fileCheckpoint: {
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
allowNonGit: boolean;
|
|
18
|
+
isolation: string | null;
|
|
19
|
+
copyStrategy: import("../core/transactional/file-clone.js").FileCloneStrategy | undefined;
|
|
20
|
+
probe: string;
|
|
21
|
+
};
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
checkpointCount: number;
|
|
24
|
+
storageBytes: number;
|
|
25
|
+
recoverableCount: number;
|
|
26
|
+
needsManualRepairCount: number;
|
|
27
|
+
verifiedReceiptCount: number;
|
|
28
|
+
states: {
|
|
29
|
+
[k: string]: number;
|
|
30
|
+
};
|
|
31
|
+
advisories: string[];
|
|
32
|
+
limits: {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
appliedRetentionHours: number;
|
|
35
|
+
restoredRetentionHours: number;
|
|
36
|
+
maxCheckpoints: number;
|
|
37
|
+
maxBytes: number;
|
|
38
|
+
};
|
|
39
|
+
checkpoints?: undefined;
|
|
40
|
+
manifest?: undefined;
|
|
41
|
+
state?: undefined;
|
|
42
|
+
manifestHash?: undefined;
|
|
43
|
+
receipt?: undefined;
|
|
44
|
+
verdict?: undefined;
|
|
45
|
+
checkpointId?: undefined;
|
|
46
|
+
approvalId?: undefined;
|
|
47
|
+
paths?: undefined;
|
|
48
|
+
auditRecorded?: undefined;
|
|
49
|
+
message?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
subcommand: string;
|
|
53
|
+
repoRoot: string;
|
|
54
|
+
checkpoints: import("../core/index.js").RecoveryCheckpointSummary[];
|
|
55
|
+
backend?: undefined;
|
|
56
|
+
availableBackends?: undefined;
|
|
57
|
+
backendCounts?: undefined;
|
|
58
|
+
fileCheckpoint?: undefined;
|
|
59
|
+
enabled?: undefined;
|
|
60
|
+
checkpointCount?: undefined;
|
|
61
|
+
storageBytes?: undefined;
|
|
62
|
+
recoverableCount?: undefined;
|
|
63
|
+
needsManualRepairCount?: undefined;
|
|
64
|
+
verifiedReceiptCount?: undefined;
|
|
65
|
+
states?: undefined;
|
|
66
|
+
advisories?: undefined;
|
|
67
|
+
limits?: undefined;
|
|
68
|
+
manifest?: undefined;
|
|
69
|
+
state?: undefined;
|
|
70
|
+
manifestHash?: undefined;
|
|
71
|
+
receipt?: undefined;
|
|
72
|
+
verdict?: undefined;
|
|
73
|
+
checkpointId?: undefined;
|
|
74
|
+
approvalId?: undefined;
|
|
75
|
+
paths?: undefined;
|
|
76
|
+
auditRecorded?: undefined;
|
|
77
|
+
message?: undefined;
|
|
78
|
+
} | {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
subcommand: string;
|
|
81
|
+
manifest: import("../core/index.js").RecoveryCheckpointManifest;
|
|
82
|
+
state: import("../core/recovery/types.js").RecoveryCheckpointStateV1;
|
|
83
|
+
manifestHash: string;
|
|
84
|
+
receipt: import("../core/index.js").RecoveryReceiptV1 | undefined;
|
|
85
|
+
repoRoot?: undefined;
|
|
86
|
+
backend?: undefined;
|
|
87
|
+
availableBackends?: undefined;
|
|
88
|
+
backendCounts?: undefined;
|
|
89
|
+
fileCheckpoint?: undefined;
|
|
90
|
+
enabled?: undefined;
|
|
91
|
+
checkpointCount?: undefined;
|
|
92
|
+
storageBytes?: undefined;
|
|
93
|
+
recoverableCount?: undefined;
|
|
94
|
+
needsManualRepairCount?: undefined;
|
|
95
|
+
verifiedReceiptCount?: undefined;
|
|
96
|
+
states?: undefined;
|
|
97
|
+
advisories?: undefined;
|
|
98
|
+
limits?: undefined;
|
|
99
|
+
checkpoints?: undefined;
|
|
100
|
+
verdict?: undefined;
|
|
101
|
+
checkpointId?: undefined;
|
|
102
|
+
approvalId?: undefined;
|
|
103
|
+
paths?: undefined;
|
|
104
|
+
auditRecorded?: undefined;
|
|
105
|
+
message?: undefined;
|
|
106
|
+
} | {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
subcommand: string;
|
|
109
|
+
verdict: string;
|
|
110
|
+
checkpointId: string;
|
|
111
|
+
approvalId: string;
|
|
112
|
+
manifestHash: string;
|
|
113
|
+
paths: string[];
|
|
114
|
+
auditRecorded: boolean;
|
|
115
|
+
message: string;
|
|
116
|
+
repoRoot?: undefined;
|
|
117
|
+
backend?: undefined;
|
|
118
|
+
availableBackends?: undefined;
|
|
119
|
+
backendCounts?: undefined;
|
|
120
|
+
fileCheckpoint?: undefined;
|
|
121
|
+
enabled?: undefined;
|
|
122
|
+
checkpointCount?: undefined;
|
|
123
|
+
storageBytes?: undefined;
|
|
124
|
+
recoverableCount?: undefined;
|
|
125
|
+
needsManualRepairCount?: undefined;
|
|
126
|
+
verifiedReceiptCount?: undefined;
|
|
127
|
+
states?: undefined;
|
|
128
|
+
advisories?: undefined;
|
|
129
|
+
limits?: undefined;
|
|
130
|
+
checkpoints?: undefined;
|
|
131
|
+
manifest?: undefined;
|
|
132
|
+
state?: undefined;
|
|
133
|
+
receipt?: undefined;
|
|
134
|
+
} | {
|
|
135
|
+
auditRecorded: boolean;
|
|
136
|
+
message: string;
|
|
137
|
+
manifestHash: string;
|
|
138
|
+
changeCount: number;
|
|
139
|
+
ok: boolean;
|
|
140
|
+
subcommand: string;
|
|
141
|
+
verdict: string;
|
|
142
|
+
checkpointId: string;
|
|
143
|
+
repoRoot?: undefined;
|
|
144
|
+
backend?: undefined;
|
|
145
|
+
availableBackends?: undefined;
|
|
146
|
+
backendCounts?: undefined;
|
|
147
|
+
fileCheckpoint?: undefined;
|
|
148
|
+
enabled?: undefined;
|
|
149
|
+
checkpointCount?: undefined;
|
|
150
|
+
storageBytes?: undefined;
|
|
151
|
+
recoverableCount?: undefined;
|
|
152
|
+
needsManualRepairCount?: undefined;
|
|
153
|
+
verifiedReceiptCount?: undefined;
|
|
154
|
+
states?: undefined;
|
|
155
|
+
advisories?: undefined;
|
|
156
|
+
limits?: undefined;
|
|
157
|
+
checkpoints?: undefined;
|
|
158
|
+
manifest?: undefined;
|
|
159
|
+
state?: undefined;
|
|
160
|
+
receipt?: undefined;
|
|
161
|
+
approvalId?: undefined;
|
|
162
|
+
paths?: undefined;
|
|
163
|
+
}>;
|
|
164
|
+
export declare function formatRecoveryCheckpointResult(result: Record<string, unknown>): string;
|