@entrorove/skill 0.1.23 → 0.1.25
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/AGREEMENT.md +6 -6
- package/CAPABILITY-MAP.md +6 -0
- package/CHANGELOG.md +10 -0
- package/SKILL.md +18 -4
- package/archguard-runtime-build.mjs +15 -0
- package/blueprint-criterion-runner.mjs +110 -0
- package/blueprint-criterion-wire.mjs +78 -0
- package/calctool-runtime-build.mjs +59 -0
- package/calctool-runtime-entry.mjs +92 -0
- package/cli.mjs +35 -83
- package/local-approved-plan.mjs +74 -15
- package/local-archguard-ast.mjs +371 -0
- package/local-archguard-checkpoint.mjs +123 -0
- package/local-archguard-components.mjs +177 -0
- package/local-archguard-contract.mjs +109 -0
- package/local-archguard-contracts.mjs +42 -0
- package/local-archguard-evaluate.mjs +120 -0
- package/local-archguard-language-ast.mjs +165 -0
- package/local-archguard-mutation-fence.mjs +28 -0
- package/local-archguard-operations.mjs +25 -0
- package/local-archguard-scope.mjs +64 -0
- package/local-archguard-signing.mjs +52 -0
- package/local-archguard-snapshot.mjs +126 -0
- package/local-archguard-state.mjs +200 -0
- package/local-archguard.mjs +74 -0
- package/local-budget-git-attributes.mjs +49 -0
- package/local-budget-git-loader.mjs +61 -0
- package/local-budget-git-native-attributes.mjs +81 -0
- package/local-budget-git-native-darwin.mjs +104 -0
- package/local-budget-git-native-linux.mjs +93 -0
- package/local-budget-git-native-reads.mjs +88 -0
- package/local-budget-git-native-request.mjs +53 -0
- package/local-budget-git-native-runtime.mjs +18 -0
- package/local-budget-git-native.c +313 -0
- package/local-budget-git-native.mjs +109 -0
- package/local-budget-git-outcome.mjs +33 -0
- package/local-budget-git-scratch-loader.mjs +158 -0
- package/local-budget-git-scratch.mjs +112 -0
- package/local-budget-git-system-attributes.mjs +44 -0
- package/local-budget-git.mjs +92 -0
- package/local-budget-grants.mjs +82 -0
- package/local-budget-handoff-completion.mjs +70 -0
- package/local-budget-handoff-contracts.mjs +53 -0
- package/local-budget-handoff.mjs +229 -0
- package/local-budget-lock.mjs +55 -0
- package/local-budget-reads.mjs +135 -0
- package/local-budget-renewal.mjs +86 -34
- package/local-budget-source-bindings.mjs +51 -0
- package/local-budget-store.mjs +131 -0
- package/local-budget-transition.mjs +46 -0
- package/local-budget.mjs +185 -0
- package/local-calctool-cli.mjs +24 -0
- package/local-calctool-contracts.mjs +42 -0
- package/local-calctool-runtime.mjs +10 -0
- package/local-calctool-storage.mjs +101 -0
- package/local-calctool.mjs +103 -0
- package/local-chain-bindings.mjs +93 -0
- package/local-chain-broker.mjs +61 -0
- package/local-chain-contracts.mjs +21 -0
- package/local-chain-controller.mjs +321 -0
- package/local-chain-human.mjs +48 -0
- package/local-chain-lock.mjs +54 -0
- package/local-chain-operations.mjs +7 -0
- package/local-chain-plan.mjs +232 -0
- package/local-chain-release-labels.mjs +12 -0
- package/local-chain-results.mjs +75 -0
- package/local-cli.mjs +65 -24
- package/local-contract-types.mjs +46 -0
- package/local-contracts.mjs +126 -0
- package/local-credential.mjs +101 -0
- package/local-decimal.mjs +85 -0
- package/local-error.mjs +23 -0
- package/local-evidence-verification.mjs +199 -0
- package/local-execution.mjs +115 -304
- package/local-final-gate16-bundle.mjs +21 -0
- package/local-final-gate16-checks.mjs +88 -0
- package/local-final-gate16-contract.mjs +15 -0
- package/local-final-gate16-plan.mjs +66 -0
- package/local-final-gate16-proof.mjs +75 -0
- package/local-final-gate16-release.mjs +17 -0
- package/local-final-gate16-state.mjs +70 -0
- package/local-final-gate16-worker.mjs +19 -0
- package/local-final-gate16.mjs +137 -0
- package/local-formula-ast.mjs +106 -0
- package/local-formula-engine.mjs +125 -0
- package/local-formula-tests.mjs +61 -0
- package/local-formula.mjs +25 -125
- package/local-integrity.mjs +22 -41
- package/local-invocations.mjs +142 -0
- package/local-ledger.mjs +15 -9
- package/local-merge-archguard-verification.d.mts +44 -0
- package/local-merge-archguard-verification.mjs +240 -0
- package/local-merge-archguard.mjs +138 -0
- package/local-merge-artifacts.mjs +86 -0
- package/local-merge-authority.mjs +89 -0
- package/local-merge-contracts.mjs +56 -0
- package/local-merge-controller.mjs +170 -0
- package/local-merge-head.mjs +56 -0
- package/local-merge-journal.mjs +77 -0
- package/local-merge-mutation.mjs +97 -0
- package/local-merge-rollback.mjs +126 -0
- package/local-merge-snapshot-rollback.mjs +26 -0
- package/local-merge-storage.mjs +103 -0
- package/local-merge-targets.mjs +47 -0
- package/local-merge-transaction.mjs +133 -0
- package/local-mutation-audit.mjs +34 -0
- package/local-mutation-authority.mjs +103 -0
- package/local-mutation-batch-receipt.mjs +84 -0
- package/local-mutation-batch.mjs +143 -0
- package/local-mutation-binding.mjs +89 -0
- package/local-mutation-files.mjs +270 -0
- package/local-mutation-pass-store.mjs +63 -0
- package/local-mutation-snapshot.mjs +200 -0
- package/local-mutation-wire.mjs +89 -0
- package/local-probe.mjs +133 -11
- package/local-scope-candidate.mjs +28 -0
- package/local-scope-context.mjs +28 -0
- package/local-scope-files.mjs +106 -0
- package/local-scope-git-attributes.mjs +200 -0
- package/local-scope-git-comparison.mjs +8 -0
- package/local-scope-git-history.mjs +72 -0
- package/local-scope-git-reads.mjs +66 -0
- package/local-scope-git.mjs +124 -0
- package/local-scope-repository.mjs +249 -0
- package/local-scope-signature.mjs +70 -0
- package/local-scope-snapshot.mjs +39 -0
- package/local-scope-state.mjs +204 -0
- package/local-scope-workspaces.mjs +83 -0
- package/local-swarm-authority.mjs +143 -0
- package/local-swarm-baseline.mjs +32 -0
- package/local-swarm-completion.mjs +102 -0
- package/local-swarm-contracts.mjs +47 -0
- package/local-swarm-mutation.mjs +142 -0
- package/local-swarm-priority.mjs +237 -0
- package/local-swarm-state.mjs +102 -0
- package/local-swarm-storage.mjs +135 -0
- package/local-swarm.mjs +179 -0
- package/local-task-handoff-completion-wire.mjs +71 -0
- package/local-task-handoff-contracts.mjs +66 -0
- package/local-task-handoff-fence.mjs +478 -0
- package/local-task-handoff-observation.mjs +41 -0
- package/local-task-handoff-overlay-wire.mjs +94 -0
- package/local-task-handoff-read-authority.mjs +236 -0
- package/local-task-handoff-storage.mjs +73 -0
- package/local-task-handoff-write-archive.mjs +80 -0
- package/local-task-handoff-write-constraints.mjs +66 -0
- package/local-task-handoff-write-receipts.mjs +99 -0
- package/local-task-handoff-write-wire.mjs +99 -0
- package/local-task-routing.mjs +202 -22
- package/manifest.json +1 -1
- package/package.json +164 -3
- package/release-state.json +164 -18
- package/release.mjs +242 -4
- package/releases.json +12 -0
- package/remote-client.mjs +127 -36
- package/remote-transport.mjs +80 -0
- package/review-client.mjs +38 -0
- package/runtime/archguard-runtime-v7.0.44.mjs +314 -0
- package/runtime/calctool-runtime-v7.0.43.mjs +1910 -0
- package/runtime/git-native-darwin-v1.mjs +5 -0
- package/runtime/git-native-linux-v1.mjs +5 -0
- package/task-message-cli.mjs +47 -0
- package/task-message-entry.mjs +159 -0
- package/workflow-local-client.mjs +35 -0
- package/workflow-local-contracts.mjs +58 -0
- package/workflow-local-request.mjs +53 -0
- package/workflow-local-runner.mjs +350 -0
package/local-execution.mjs
CHANGED
|
@@ -1,29 +1,42 @@
|
|
|
1
|
+
import { mergeVerified as executeVerifiedMerge, rollbackMerge } from './local-merge-controller.mjs';
|
|
2
|
+
import { peerCoordinate, spawnBind, completePeer, verifyCoordinationLease } from './local-swarm.mjs';
|
|
3
|
+
export { peerCoordinate, spawnBind, completePeer, verifyCoordinationLease };
|
|
4
|
+
import { snapshotPlan14, snapshotVerify14, snapshotAuthority14, loadSnapshotReceipt14, readSnapshotCopy14 } from './local-mutation-snapshot.mjs';
|
|
5
|
+
import { mutateGate14, issueMutationPass14, verifyMutationPass14, guardedWrite14 } from './local-mutation-authority.mjs';
|
|
6
|
+
import { writeMutationProjectFile14 } from './local-mutation-files.mjs';
|
|
7
|
+
import { auditMutation14, withMutationAudit14 } from './local-mutation-audit.mjs';
|
|
1
8
|
import { createHash, createPublicKey, generateKeyPairSync, sign, verify, randomUUID } from 'node:crypto';
|
|
2
|
-
import { constants } from 'node:fs';
|
|
3
9
|
import { promises as fs } from 'node:fs';
|
|
4
10
|
import os from 'node:os';
|
|
5
11
|
import path from 'node:path';
|
|
6
12
|
import { formulaFinalGateFile, formulaFinalGate, evaluateFormula, evaluateFormulaGraph, Decimal } from './local-formula.mjs';
|
|
7
|
-
import { LOCAL_LEDGER_OPERATIONS,
|
|
8
|
-
import {
|
|
13
|
+
import { LOCAL_LEDGER_OPERATIONS, branchCreate, branchList, branchSwitch, diffReport, preflight, ledgerQuery, chainInit, chainResume, chainStatus, chainAnswer, appendLedgerRecord } from './local-ledger.mjs';
|
|
14
|
+
import { runHandoffTrustedWrite } from './local-task-handoff-write-receipts.mjs';
|
|
15
|
+
import { snapshotHandoffBinding, verifyArchguardPreflight, verifyReceipt, verifySnapshotManifest, verifyTrustedEvidence } from './local-integrity.mjs';
|
|
16
|
+
import { verifyTestEvidence } from './local-evidence-verification.mjs';
|
|
9
17
|
import { RUNNER_CAPABILITIES, runApprovedPlan } from './local-approved-plan.mjs';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
18
|
+
import { WORKFLOW_LOCAL_CAPABILITIES, runWorkflowLocalStep } from './workflow-local-runner.mjs';
|
|
19
|
+
import { LOCAL_RENEWAL_OPERATIONS, budgetAutoRenewRequest, budgetAutoRenew, budgetAutoRenewStop } from './local-budget-renewal.mjs';
|
|
20
|
+
import { LOCAL_PROBE_OPERATIONS, probe, reassess } from './local-probe.mjs';
|
|
21
|
+
import { LOCAL_TASK_ROUTING_OPERATIONS, taskDescribe, messageRoute, messageAccept, taskResume } from './local-task-routing.mjs';
|
|
13
22
|
import { withStateLock } from './local-state-lock.mjs';
|
|
23
|
+
import { readBudgetedProjectFile } from './local-budget-reads.mjs';
|
|
24
|
+
import { receiverClaim } from './local-task-handoff-overlay-wire.mjs';
|
|
25
|
+
import { assertTaskHandoffActive, assertTaskHandoffSnapshotBinding, readTaskHandoffSnapshotBinding,
|
|
26
|
+
withTaskHandoffIssueFence, withTaskHandoffWriteFence } from './local-task-handoff-fence.mjs';
|
|
27
|
+
import { LOCAL_OPERATION_SCHEMAS } from './local-contracts.mjs';
|
|
28
|
+
export { LOCAL_OPERATION_SCHEMAS };
|
|
14
29
|
export const LOCAL_SCHEMA = 'entrorove.local-execution/1.0';
|
|
15
30
|
export const SNAPSHOT_SCHEMA = 'entrorove.snapshot/1.0';
|
|
16
31
|
export const RECEIPT_SCHEMA = 'entrorove.snapshot-receipt/1.0';
|
|
17
32
|
export const PASS_SCHEMA = 'entrorove.mutation-pass/1.0';
|
|
18
33
|
export const EVIDENCE_SCHEMA = 'entrorove.test-evidence/2.0';
|
|
19
34
|
export const EVIDENCE_RECEIPT_SCHEMA = 'entrorove.runner-receipt/1.0';
|
|
20
|
-
export const BUDGET_SCHEMA = 'entrorove.read-budget/1.0';
|
|
21
35
|
export const COORDINATION_SCHEMA = 'entrorove.coordination/1.0';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
36
|
+
import { BUDGET_SCHEMA, READ_BUDGETS, budgetInit, budgetContext, budgetStatus, budgetRead, budgetExtend } from './local-budget.mjs';
|
|
37
|
+
export { BUDGET_SCHEMA, READ_BUDGETS, budgetInit, budgetContext, budgetStatus, budgetRead, budgetExtend };
|
|
38
|
+
export { budgetTransition, LOCAL_BUDGET_TRANSITION_OPERATIONS } from './local-budget-transition.mjs';
|
|
39
|
+
export { budgetHandoff, LOCAL_BUDGET_HANDOFF_OPERATIONS } from './local-budget-handoff.mjs';
|
|
27
40
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
28
41
|
const SHA256 = /^[0-9a-f]{64}$/;
|
|
29
42
|
const MISSING_SHA256 = sha256('entrorove.missing-file/1.0');
|
|
@@ -150,248 +163,89 @@ async function keyPair(home, root) {
|
|
|
150
163
|
return { privateKey: pair.privateKey, publicKey: pair.publicKey, publicDer, keyId: sha256(publicDer) };
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
|
-
async function fileDigest(file) { return file.exists ? sha256(await
|
|
166
|
+
async function fileDigest(file, input) { return file.exists ? sha256(await readBudgetedProjectFile(input, file.target, { sourcePath: file.path })) : MISSING_SHA256; }
|
|
154
167
|
export async function snapshotPlan(input) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const snapshotId = id(input.snapshotId ?? `snap-${randomUUID()}`, 'snapshotId');
|
|
159
|
-
const directory = path.dirname(statePath(input.home, 'snapshots', root, flowId, 'manifest.json'));
|
|
160
|
-
const snapshotRoot = path.join(directory, snapshotId);
|
|
161
|
-
await privateDir(snapshotRoot);
|
|
162
|
-
const files = [];
|
|
163
|
-
for (const relative of paths) {
|
|
164
|
-
const source = await projectFile(root, relative, true);
|
|
165
|
-
const digest = await fileDigest(source);
|
|
166
|
-
const target = path.join(snapshotRoot, relative);
|
|
167
|
-
if (source.exists) { await privateDir(path.dirname(target)); await fs.copyFile(source.target, target, constants.COPYFILE_EXCL); }
|
|
168
|
-
files.push({ path: relative, exists: source.exists, sha256: digest, mode: source.exists ? source.stat.mode & 0o777 : null });
|
|
169
|
-
}
|
|
170
|
-
const contractDigest = input.contractDigest ?? null;
|
|
171
|
-
if (contractDigest !== null && !SHA256.test(contractDigest)) fail('ENTROROVE_CONTRACT_DIGEST', 'contractDigest must be a lowercase SHA-256 digest');
|
|
172
|
-
const manifest = { schemaVersion: SNAPSHOT_SCHEMA, snapshotId, flowId, stepId, repositoryDigest: repoKey(root), paths, files, contractDigest,
|
|
173
|
-
createdAt: new Date().toISOString(), snapshotDigest: sha256(canonical({ snapshotId, flowId, stepId, paths, files, contractDigest })) };
|
|
174
|
-
const manifestPath = path.join(snapshotRoot, 'manifest.json');
|
|
175
|
-
await atomicJson(manifestPath, manifest);
|
|
176
|
-
await audit(input.home, root, 'snapshot-plan', { flowId, stepId, snapshotId });
|
|
177
|
-
return { schemaVersion: LOCAL_SCHEMA, snapshotId, snapshotRoot, manifestPath, paths, contractDigest, snapshotDigest: manifest.snapshotDigest, forbidGitBranch: true, forbidWorktree: true };
|
|
168
|
+
const result = await snapshotPlan14(input, { handoffBinding: await readTaskHandoffSnapshotBinding(input) });
|
|
169
|
+
await auditMutation14(input, 'snapshot-plan', { snapshotId: result.snapshotId });
|
|
170
|
+
return result;
|
|
178
171
|
}
|
|
179
172
|
export async function snapshotVerify(input) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!receipt || receipt.schemaVersion !== RECEIPT_SCHEMA || !SHA256.test(receipt.snapshotDigest) || !SHA256.test(receipt.receiptDigest)
|
|
204
|
-
|| !Array.isArray(receipt.paths) || receipt.receiptDigest !== sha256(canonical({ schemaVersion: receipt.schemaVersion, snapshotId: receipt.snapshotId,
|
|
205
|
-
flowId: receipt.flowId, stepId: receipt.stepId, repositoryDigest: receipt.repositoryDigest, snapshotDigest: receipt.snapshotDigest,
|
|
206
|
-
contractDigest: receipt.contractDigest ?? null, paths: receipt.paths, fileManifestDigest: receipt.fileManifestDigest }))) fail('ENTROROVE_RECEIPT_INVALID', 'snapshot receipt is invalid');
|
|
207
|
-
return receipt;
|
|
208
|
-
}
|
|
209
|
-
async function snapshotAuthority(input, root, receipt) {
|
|
210
|
-
const manifestPath = path.join(path.dirname(statePath(input.home, 'snapshots', root, receipt.flowId, 'manifest.json')), receipt.snapshotId, 'manifest.json'); const manifest = await readJson(manifestPath);
|
|
211
|
-
const check = verifySnapshotManifest(manifest, { snapshotId: receipt.snapshotId, flowId: receipt.flowId, stepId: receipt.stepId, repositoryDigest: repoKey(root) });
|
|
212
|
-
if (!check.verified || manifest.snapshotDigest !== receipt.snapshotDigest || canonical(manifest.paths) !== canonical(receipt.paths) || (manifest.contractDigest ?? null) !== (receipt.contractDigest ?? null)) fail('ENTROROVE_SNAPSHOT_BINDING', check.reason ?? 'snapshot receipt does not match its manifest');
|
|
213
|
-
return { manifest, manifestPath };
|
|
214
|
-
}
|
|
215
|
-
export async function mutateGate(input) {
|
|
216
|
-
if (input.accepted !== true) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', allowed: false, code: 'ENTROROVE_MUTATE_NOT_ACCEPTED' };
|
|
217
|
-
id(input.flowId, 'flowId'); id(input.stepId, 'stepId');
|
|
218
|
-
const receipt = await loadReceipt(input); const root = await repositoryRoot(input.repositoryRoot);
|
|
219
|
-
if (receipt.flowId !== input.flowId || receipt.stepId !== input.stepId || receipt.repositoryDigest !== repoKey(root)) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', allowed: false, code: 'ENTROROVE_RECEIPT_SCOPE' };
|
|
220
|
-
await snapshotAuthority(input, root, receipt);
|
|
221
|
-
if (input.paths && canonical(normalizePaths(input.paths)) !== canonical(normalizePaths(receipt.paths))) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', allowed: false, code: 'ENTROROVE_RECEIPT_PATHS' };
|
|
222
|
-
if (input.coordinationRequired === true) {
|
|
223
|
-
try { await verifyCoordinationLease({ ...input, paths: receipt.paths }); } catch (error) { return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', allowed: false, code: error.code ?? 'ENTROROVE_LEASE_INVALID' }; }
|
|
173
|
+
const result = await snapshotVerify14(input);
|
|
174
|
+
await auditMutation14(input, 'snapshot-verify', { snapshotId: input.snapshotId, receiptDigest: result.receipt.receiptDigest });
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
async function loadReceipt(input) { return loadSnapshotReceipt14(input.receipt); }
|
|
178
|
+
async function snapshotAuthority(input, root, receipt) { return snapshotAuthority14(input, root, receipt); }
|
|
179
|
+
export async function mutateGate(input) { return mutateGate14(input); }
|
|
180
|
+
export async function issueMutationPass(input) { return issueMutationPass14(input); }
|
|
181
|
+
export async function verifyMutationPass(input, options) { return verifyMutationPass14(input, options); }
|
|
182
|
+
async function writeSnapshotFile(input, root, targetPath, baseline) {
|
|
183
|
+
if (!baseline || input.expectedSha256 && input.expectedSha256 !== baseline.sha256) fail('ENTROROVE_WRITE_BASELINE', 'target does not match its exact expected snapshot baseline');
|
|
184
|
+
const content = typeof input.content === 'string' ? input.content : JSON.stringify(input.content);
|
|
185
|
+
if (typeof content !== 'string') fail('ENTROROVE_CONTENT_INVALID', 'content must be text or JSON');
|
|
186
|
+
const written = await writeMutationProjectFile14(input, root, targetPath, content, baseline);
|
|
187
|
+
const record = { flowId: input.flowId, stepId: input.stepId, targetPath, sha256: written.sha256, ...(input.handoffId ? { handoffId: input.handoffId } : {}) };
|
|
188
|
+
await auditMutation14(input, 'guarded-write', record);
|
|
189
|
+
await appendLedgerRecord({ home: input.home, repositoryRoot: root }, 'guarded-write', record);
|
|
190
|
+
return { schemaVersion: LOCAL_SCHEMA, status: 'verified', written: true, targetPath, sha256: written.sha256 };
|
|
191
|
+
}
|
|
192
|
+
async function guardedHandoffWrite(input) {
|
|
193
|
+
const required = ['repositoryRoot', 'flowId', 'stepId', 'targetPath', 'content', 'handoffId', 'epoch', 'handoffPass', 'provisionalLease'];
|
|
194
|
+
if (required.some(key => !Object.hasOwn(input, key)) || Object.keys(input).some(key => ![...required, 'home', 'expectedSha256'].includes(key))) {
|
|
195
|
+
fail('ENTROROVE_HANDOFF_WRITE_INPUT', 'takeover write requires only its exact original cloud pass, lease and target fields');
|
|
224
196
|
}
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
export async function issueMutationPass(input) {
|
|
228
|
-
const gate = await mutateGate(input); if (!gate.allowed) return gate;
|
|
229
|
-
const root = await repositoryRoot(input.repositoryRoot); const authority = await keyPair(input.home, root); const now = Date.now();
|
|
230
|
-
const ttlSeconds = input.ttlSeconds ?? 300;
|
|
231
|
-
if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > 300) fail('ENTROROVE_PASS_TTL_INVALID', 'ttlSeconds must be 1..300');
|
|
232
|
-
const pass = { schemaVersion: PASS_SCHEMA, passId: `pass-${randomUUID()}`, flowId: input.flowId, stepId: input.stepId,
|
|
233
|
-
repositoryDigest: repoKey(root), snapshotId: gate.snapshotId, snapshotReceiptDigest: gate.receiptDigest, snapshotReceipt: input.receipt, paths: gate.paths, coordinationRequired: gate.coordinationRequired,
|
|
234
|
-
issuedAt: new Date(now).toISOString(), expiresAt: new Date(now + ttlSeconds * 1000).toISOString(), keyId: authority.keyId, nonce: randomUUID() };
|
|
235
|
-
const signature = sign(null, Buffer.from(canonical(pass)), authority.privateKey).toString('base64url');
|
|
236
|
-
const signedPass = { ...pass, signature }; const file = statePath(input.home, 'gates', root, input.flowId, `${pass.passId}.json`);
|
|
237
|
-
await atomicJson(file, signedPass); await audit(input.home, root, 'mutation-pass-issued', { flowId: input.flowId, stepId: input.stepId, passId: pass.passId });
|
|
238
|
-
return { schemaVersion: LOCAL_SCHEMA, gatePassPath: file, pass: signedPass };
|
|
239
|
-
}
|
|
240
|
-
async function verifyPass(input) {
|
|
197
|
+
const root = await repositoryRoot(input.repositoryRoot), target = safePath(input.targetPath, 'targetPath');
|
|
241
198
|
id(input.flowId, 'flowId'); id(input.stepId, 'stepId');
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
199
|
+
const lock = path.join(homeRoot(input.home), 'write-locks', repoKey(root), `${sha256(target)}.lock`);
|
|
200
|
+
return withStateLock(lock, () => withTaskHandoffWriteFence({ ...input, repositoryRoot: root,
|
|
201
|
+
passId: input.handoffPass?.passId, leaseId: input.provisionalLease?.leaseId }, async (state, capability) => {
|
|
202
|
+
if (!state.receiverClaim || state.receiverRestoration) fail('ENTROROVE_HANDOFF_WRITE_REVOKED', 'takeover scope is absent or already restored');
|
|
203
|
+
const supplied = { pass: input.handoffPass, lease: input.provisionalLease, challenge: state.receiverClaim.challenge };
|
|
204
|
+
const { pass, lease, challenge } = receiverClaim(supplied, { flowId: input.flowId, repo: repoKey(root) });
|
|
205
|
+
if (canonical(supplied) !== canonical(state.receiverClaim) || input.handoffId !== pass.handoffId
|
|
206
|
+
|| input.epoch !== pass.receiverEpoch || input.stepId !== lease.stepId || !pass.paths.includes(target)) {
|
|
207
|
+
fail('ENTROROVE_HANDOFF_WRITE_BINDING', 'takeover write changed its actual receiver, signed grant or exact path scope');
|
|
208
|
+
}
|
|
209
|
+
const manifestPath = path.join(path.dirname(statePath(input.home, 'snapshots', root, input.flowId, 'manifest.json')), pass.snapshotId, 'manifest.json');
|
|
210
|
+
const manifest = await readJson(manifestPath);
|
|
211
|
+
const integrity = verifySnapshotManifest(manifest, { snapshotId: pass.snapshotId, flowId: input.flowId, stepId: lease.stepId, repositoryDigest: repoKey(root) });
|
|
212
|
+
if (!integrity.verified || manifest.snapshotDigest !== pass.scopeSnapshotDigest || canonical(manifest.paths) !== canonical(pass.paths)
|
|
213
|
+
|| manifest.handoffEpoch !== pass.receiverEpoch || manifest.handoffBaselineReceiptDigest !== challenge.baselineReceiptDigest) {
|
|
214
|
+
fail('ENTROROVE_HANDOFF_WRITE_SNAPSHOT', 'takeover write requires its original fresh snapshot and actual release baseline');
|
|
215
|
+
}
|
|
216
|
+
const baseline = manifest.files.find(file => file.path === target);
|
|
217
|
+
if (!baseline || (baseline.exists && sha256((await readSnapshotCopy14(input, path.dirname(manifestPath), manifest, target)).content) !== baseline.sha256)) {
|
|
218
|
+
fail('ENTROROVE_HANDOFF_WRITE_SNAPSHOT', 'takeover snapshot copy changed before the actual physical write');
|
|
219
|
+
}
|
|
220
|
+
return runHandoffTrustedWrite({ input: { ...input, targetPath: target }, ...capability, manifest,
|
|
221
|
+
snapshotDirectory: path.dirname(manifestPath),
|
|
222
|
+
performActualWrite: ({ input: actualInput, baseline: current }) => writeSnapshotFile(actualInput, root, target, current) });
|
|
223
|
+
}));
|
|
257
224
|
}
|
|
258
|
-
export async function verifyMutationPass(input) { const verified = await verifyPass(input); return { schemaVersion: LOCAL_SCHEMA, verified: true, pass: verified.pass, targetPath: verified.target }; }
|
|
259
225
|
export async function guardedWrite(input) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await fs.writeFile(temporary, content, { flag: 'wx', mode: target.exists ? target.stat.mode & 0o777 : 0o600 }); await fs.rename(temporary, target.target);
|
|
273
|
-
await audit(input.home, verified.root, 'guarded-write', { flowId: input.flowId, stepId: input.stepId, targetPath: verified.target });
|
|
274
|
-
await appendLedgerRecord({ home: input.home, repositoryRoot: verified.root }, 'guarded-write', { flowId: input.flowId, stepId: input.stepId, targetPath: verified.target, sha256: sha256(content) });
|
|
275
|
-
return { schemaVersion: LOCAL_SCHEMA, status: 'verified', written: true, targetPath: verified.target, sha256: sha256(content) };
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
function enforced(context) {
|
|
279
|
-
if (!context || typeof context !== 'object' || Array.isArray(context)) fail('ENTROROVE_CONTEXT_REQUIRED', 'executionContext is required');
|
|
280
|
-
if (Object.keys(context).length === 1 && context.executionIsolation === 'local') return false;
|
|
281
|
-
if (Object.keys(context).length !== 2 || !['direct', 'sandbox'].includes(context.executionIsolation) || typeof context.cloudSandboxEnabled !== 'boolean') fail('ENTROROVE_CONTEXT_INVALID', 'executionContext is invalid');
|
|
282
|
-
if (context.executionIsolation === 'sandbox' && context.cloudSandboxEnabled !== true) fail('ENTROROVE_CONTEXT_INVALID', 'sandbox requires cloudSandboxEnabled=true');
|
|
283
|
-
return context.executionIsolation === 'sandbox';
|
|
284
|
-
}
|
|
285
|
-
async function budgetFile(input) { const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId'); return { root, file: statePath(input.home, 'budgets', root, flowId, 'budget.json') }; }
|
|
286
|
-
function budgetView(state, now = Date.now()) {
|
|
287
|
-
const elapsedMs = state.enforced ? state.elapsedMs + now - Date.parse(state.startedAt) : state.elapsedMs;
|
|
288
|
-
return { ...state, elapsedMs, remainingFiles: state.enforced ? Math.max(0, state.maxFiles - state.files.length) : null,
|
|
289
|
-
remainingTokenEstimate: state.enforced && state.maxTokenEstimate !== null ? Math.max(0, state.maxTokenEstimate - state.tokenEstimate) : null,
|
|
290
|
-
remainingDurationMs: state.enforced ? Math.max(0, state.maxDurationMs - elapsedMs) : null,
|
|
291
|
-
enforcement: state.enforced ? 'cloud-sandbox' : 'continuous' };
|
|
292
|
-
}
|
|
293
|
-
export async function budgetInit(input) {
|
|
294
|
-
const mode = input.mode; if (!READ_BUDGETS[mode]) fail('ENTROROVE_MODE_INVALID', 'mode must be lock, probe, or swarm');
|
|
295
|
-
const { root, file } = await budgetFile(input); const limits = READ_BUDGETS[mode]; const isEnforced = enforced(input.executionContext);
|
|
296
|
-
return withStateLock(file, async () => {
|
|
297
|
-
try { const existing = await readJson(file); if (existing.schemaVersion !== BUDGET_SCHEMA || existing.flowId !== input.flowId || existing.repositoryDigest !== repoKey(root) || existing.mode !== mode || canonical(existing.executionContext) !== canonical(input.executionContext)) fail('ENTROROVE_BUDGET_REINITIALIZE', 'budget is already initialized with a different scope'); return budgetView(existing); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
298
|
-
const state = { schemaVersion: BUDGET_SCHEMA, flowId: input.flowId, repositoryDigest: repoKey(root), mode, executionContext: input.executionContext,
|
|
299
|
-
enforced: isEnforced, startedAt: new Date().toISOString(), elapsedMs: 0, ...limits, files: [], readCalls: 0, tokenEstimate: 0, extensions: [] };
|
|
300
|
-
await atomicJson(file, state); await audit(input.home, root, 'budget-init', { flowId: input.flowId, mode }); return budgetView(state);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
export async function budgetContext(input) {
|
|
304
|
-
const { root, file } = await budgetFile(input);
|
|
305
|
-
return withStateLock(file, async () => {
|
|
306
|
-
const state = await readJson(file); const isEnforced = enforced(input.executionContext); if (state.enforced && !isEnforced) fail('ENTROROVE_BUDGET_DOWNGRADE', 'an enforced sandbox budget cannot be downgraded');
|
|
307
|
-
const updated = { ...state, executionContext: input.executionContext, enforced: isEnforced }; await atomicJson(file, updated); return budgetView(updated);
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
export async function budgetStatus(input) { const { file } = await budgetFile(input); return budgetView(await readJson(file)); }
|
|
311
|
-
export async function budgetRead(input) {
|
|
312
|
-
const { root, file } = await budgetFile(input);
|
|
313
|
-
return withStateLock(file, async () => {
|
|
314
|
-
const relative = safePath(input.path); const originalState = await readJson(file); const state = await consumeBudgetRenewal({ file, state: originalState }); const before = budgetView(state);
|
|
315
|
-
const source = await projectFile(root, relative); const bytes = source.stat.size; const tokens = Math.ceil(bytes / 4); const fresh = !state.files.includes(relative);
|
|
316
|
-
if (state.enforced && fresh && before.remainingFiles <= 0) fail('ENTROROVE_BUDGET_EXHAUSTED', 'read file budget exhausted');
|
|
317
|
-
if (state.enforced && before.remainingTokenEstimate !== null && tokens > before.remainingTokenEstimate) fail('ENTROROVE_BUDGET_EXHAUSTED', 'read token budget exhausted');
|
|
318
|
-
if (state.enforced && before.remainingDurationMs <= 0) fail('ENTROROVE_BUDGET_EXHAUSTED', 'read duration budget exhausted');
|
|
319
|
-
const updated = { ...state, files: fresh ? [...state.files, relative] : state.files, readCalls: state.readCalls + 1, tokenEstimate: state.tokenEstimate + tokens };
|
|
320
|
-
await atomicJson(file, updated); await audit(input.home, root, 'budget-read', { flowId: input.flowId, path: relative, tokens });
|
|
321
|
-
return { schemaVersion: LOCAL_SCHEMA, path: relative, content: await fs.readFile(source.target, 'utf8'), budget: budgetView(updated) };
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
export async function budgetExtend(input) {
|
|
325
|
-
const { root, file } = await budgetFile(input);
|
|
326
|
-
return withStateLock(file, async () => {
|
|
327
|
-
const state = await readJson(file); if (!state.enforced) fail('ENTROROVE_BUDGET_NOT_APPLICABLE', 'budget extension applies only to a cloud sandbox');
|
|
328
|
-
const confirmation = input.confirmation; const additions = input.additions;
|
|
329
|
-
if (confirmation?.answer !== 'approve' || typeof confirmation.requestId !== 'string') fail('ENTROROVE_CONFIRMATION_REQUIRED', 'explicit budget confirmation is required');
|
|
330
|
-
if (!additions || !Number.isSafeInteger(additions.files) || !Number.isSafeInteger(additions.tokenEstimate) || !Number.isSafeInteger(additions.durationMs)
|
|
331
|
-
|| additions.files < 0 || additions.tokenEstimate < 0 || additions.durationMs < 0 || additions.files + additions.tokenEstimate + additions.durationMs === 0) fail('ENTROROVE_EXTENSION_INVALID', 'budget additions must be positive integers');
|
|
332
|
-
if (state.extensions.some(item => item.requestId === confirmation.requestId)) fail('ENTROROVE_CONFIRMATION_REPLAYED', 'budget confirmation was already applied');
|
|
333
|
-
const updated = { ...state, maxFiles: state.maxFiles + additions.files, maxTokenEstimate: state.maxTokenEstimate === null ? additions.tokenEstimate : state.maxTokenEstimate + additions.tokenEstimate,
|
|
334
|
-
maxDurationMs: state.maxDurationMs + additions.durationMs, extensions: [...state.extensions, { requestId: confirmation.requestId, additions, at: new Date().toISOString() }] };
|
|
335
|
-
await atomicJson(file, updated); await audit(input.home, root, 'budget-extend', { flowId: input.flowId, requestId: confirmation.requestId }); return budgetView(updated);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
async function coordinationState(input, create = false) {
|
|
339
|
-
const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId'); const file = statePath(input.home, 'coordination', root, flowId, 'state.json');
|
|
340
|
-
if (create) { try { return { root, file, state: await readJson(file) }; } catch (error) { if (error.code !== 'ENOENT') throw error; const state = { schemaVersion: COORDINATION_SCHEMA, flowId, repositoryDigest: repoKey(root), workers: [], leases: [], events: [] }; await atomicJson(file, state); return { root, file, state }; } }
|
|
341
|
-
return { root, file, state: await readJson(file) };
|
|
342
|
-
}
|
|
343
|
-
async function coordinateUnlocked(input) {
|
|
344
|
-
const workerCount = input.workerCount ?? 0; if (!Number.isSafeInteger(workerCount) || workerCount < 0 || workerCount > 50) fail('ENTROROVE_WORKER_LIMIT', 'workerCount must be between 0 and 50');
|
|
345
|
-
if (input.priority !== undefined && !['urgent', 'normal', 'dependency', 'background'].includes(input.priority)) fail('ENTROROVE_PRIORITY_INVALID', 'priority is invalid');
|
|
346
|
-
const authority = await coordinationState(input, true); const updated = { ...authority.state, maxWorkers: workerCount, priority: input.priority ?? 'normal', events: [...authority.state.events, { event: 'coordinate', at: new Date().toISOString() }] };
|
|
347
|
-
await atomicJson(authority.file, updated); await audit(input.home, authority.root, 'peer-coordinate', { flowId: input.flowId, workerCount }); return { schemaVersion: LOCAL_SCHEMA, status: 'verified', maxWorkers: workerCount, priority: updated.priority };
|
|
348
|
-
}
|
|
349
|
-
export async function peerCoordinate(input) {
|
|
350
|
-
const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId');
|
|
351
|
-
return withStateLock(statePath(input.home, 'coordination', root, flowId, 'state.json'), () => coordinateUnlocked(input));
|
|
352
|
-
}
|
|
353
|
-
async function spawnUnlocked(input) {
|
|
354
|
-
const paths = normalizePaths(input.paths); const priority = input.priority ?? 'normal'; if (!['urgent', 'normal', 'dependency', 'background'].includes(priority)) fail('ENTROROVE_PRIORITY_INVALID', 'priority is invalid');
|
|
355
|
-
const authority = await coordinationState(input, true); const active = authority.state.workers.filter(worker => worker.status === 'active'); if (active.length >= (authority.state.maxWorkers ?? 0)) fail('ENTROROVE_WORKER_LIMIT', 'coordination worker budget is exhausted');
|
|
356
|
-
if (input.parentWorkerId) fail('ENTROROVE_RECURSION_FORBIDDEN', 'derived workers cannot recursively spawn workers');
|
|
357
|
-
const workerId = id(input.workerId ?? `worker-${randomUUID()}`, 'workerId'); const leaseId = `lease-${randomUUID()}`; const expiresAt = new Date(Date.now() + 300_000).toISOString();
|
|
358
|
-
const worker = { workerId, leaseId, flowId: input.flowId, paths, priority, status: 'active', expiresAt, createdAt: new Date().toISOString() };
|
|
359
|
-
await atomicJson(authority.file, { ...authority.state, workers: [...authority.state.workers, worker], leases: [...authority.state.leases, worker], events: [...authority.state.events, { event: 'spawn-bind', workerId, at: new Date().toISOString() }] });
|
|
360
|
-
return { schemaVersion: LOCAL_SCHEMA, status: 'verified', workerId, leaseId, paths, expiresAt };
|
|
361
|
-
}
|
|
362
|
-
export async function spawnBind(input) {
|
|
363
|
-
const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId');
|
|
364
|
-
return withStateLock(statePath(input.home, 'coordination', root, flowId, 'state.json'), () => spawnUnlocked(input));
|
|
365
|
-
}
|
|
366
|
-
async function completeUnlocked(input) {
|
|
367
|
-
const authority = await coordinationState(input); const worker = authority.state.workers.find(item => item.workerId === input.workerId && item.leaseId === input.leaseId && item.status === 'active'); if (!worker) fail('ENTROROVE_LEASE_INVALID', 'coordination lease is invalid');
|
|
368
|
-
if (Date.parse(worker.expiresAt) <= Date.now()) fail('ENTROROVE_LEASE_EXPIRED', 'coordination lease has expired');
|
|
369
|
-
const status = input.status ?? 'completed'; if (!['completed', 'failed', 'blocked'].includes(status)) fail('ENTROROVE_STATUS_INVALID', 'peer status is invalid');
|
|
370
|
-
const updatedWorker = { ...worker, status, completedAt: new Date().toISOString() }; const state = { ...authority.state, workers: authority.state.workers.map(item => item.workerId === worker.workerId ? updatedWorker : item), leases: authority.state.leases.map(item => item.workerId === worker.workerId ? updatedWorker : item) };
|
|
371
|
-
await atomicJson(authority.file, state); return { schemaVersion: LOCAL_SCHEMA, status: 'verified', workerId: worker.workerId, state: status };
|
|
372
|
-
}
|
|
373
|
-
export async function completePeer(input) {
|
|
374
|
-
const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId');
|
|
375
|
-
return withStateLock(statePath(input.home, 'coordination', root, flowId, 'state.json'), () => completeUnlocked(input));
|
|
376
|
-
}
|
|
226
|
+
if (Object.hasOwn(input, 'handoffPass') || Object.hasOwn(input, 'provisionalLease')) {
|
|
227
|
+
return withMutationAudit14(input, 'guarded-write', () => guardedHandoffWrite(input));
|
|
228
|
+
}
|
|
229
|
+
return guardedWrite14(input);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
377
238
|
async function verifyEvidenceValue(evidence, input, requireBinding = false) {
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const subject = Object.fromEntries(Object.entries(evidence).filter(([key]) => key !== 'receipt'));
|
|
387
|
-
try { if (receipt.schemaVersion === 'validator.execution-receipt/1.0') { if (typeof input.receiptPublicKey !== 'string' || receipt.subjectDigest !== evidence.subjectDigest || receipt.result?.runner !== 'trusted-runner' || receipt.result.exitCode !== evidence.exitCode || receipt.result.durationMs !== evidence.durationMs || receipt.result.summary !== evidence.summary) throw new Error('Trusted execution receipt is not bound'); const unsigned = Object.fromEntries(Object.entries(receipt).filter(([key]) => key !== 'signature')); verifyReceipt(receipt, unsigned, { schemaVersion: 'validator.execution-receipt/1.0', publicKey: input.receiptPublicKey }); } else verifyReceipt(receipt, subject); return { verified: true, evidence }; } catch (error) { return { verified: false, reason: error instanceof Error ? error.message : String(error) }; }
|
|
388
|
-
}
|
|
389
|
-
export async function verifyCoordinationLease(input) {
|
|
390
|
-
const authority = await coordinationState(input); const worker = authority.state.workers.find(item => item.workerId === input.workerId && item.leaseId === input.leaseId && item.status === 'active');
|
|
391
|
-
if (!worker || Date.parse(worker.expiresAt) <= Date.now()) fail('ENTROROVE_LEASE_INVALID', 'coordination lease is invalid or expired');
|
|
392
|
-
const paths = normalizePaths(input.paths ?? []); if (paths.some(target => !worker.paths.some(scope => target === scope || target.startsWith(`${scope}/`)))) fail('ENTROROVE_LEASE_SCOPE', 'coordination lease does not cover target paths');
|
|
393
|
-
return { schemaVersion: LOCAL_SCHEMA, verified: true, workerId: worker.workerId, leaseId: worker.leaseId, paths: worker.paths, expiresAt: worker.expiresAt };
|
|
239
|
+
const publicKey = process.env.ENTROROVE_VALIDATOR_RECEIPT_PUBLIC_KEY?.trim();
|
|
240
|
+
if (publicKey && input.receiptPublicKey !== undefined && input.receiptPublicKey !== publicKey) {
|
|
241
|
+
return { verified: false, authentic: false, successful: false, status: 'blocked', evidenceState: 'blocked',
|
|
242
|
+
code: 'ENTROROVE_EVIDENCE_KEY', reason: 'Supplied receipt key does not match independent verifier configuration' };
|
|
243
|
+
}
|
|
244
|
+
const binding = { flowId: input.flowId, stepId: input.stepId, snapshotDigest: input.snapshotDigest,
|
|
245
|
+
contractDigest: input.contractDigest, receiptPublicKey: input.receiptPublicKey };
|
|
246
|
+
return requireBinding ? verifyTrustedEvidence(evidence, binding) : verifyTestEvidence(evidence, { publicKey, binding });
|
|
394
247
|
}
|
|
248
|
+
|
|
395
249
|
export async function signEvidence(input) {
|
|
396
250
|
const root = await repositoryRoot(input.repositoryRoot); const authority = await keyPair(input.home, root); const evidence = { ...input.evidence, schemaVersion: EVIDENCE_SCHEMA, runner: input.evidence?.runner ?? 'local' };
|
|
397
251
|
if (typeof evidence.evidenceId !== 'string' || !evidence.evidenceId.trim() || evidence.evidenceId.length > 200) fail('ENTROROVE_EVIDENCE_ID', 'evidenceId must be a non-empty string up to 200 characters');
|
|
@@ -402,69 +256,26 @@ export async function signEvidence(input) {
|
|
|
402
256
|
return { ...evidence, receipt };
|
|
403
257
|
}
|
|
404
258
|
export async function verifyEvidence(input) { return verifyEvidenceValue(input.evidence, input); }
|
|
405
|
-
export async function mergeVerified(input) {
|
|
406
|
-
|
|
407
|
-
if (!input.snapshotReceipt) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_SNAPSHOT_REQUIRED' };
|
|
408
|
-
const snapshotReceipt = await loadReceipt({ receipt: input.snapshotReceipt });
|
|
409
|
-
if (snapshotReceipt.flowId !== input.flowId || snapshotReceipt.stepId !== input.stepId || snapshotReceipt.repositoryDigest !== repoKey(root)) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_SNAPSHOT_SCOPE' };
|
|
410
|
-
let manifest;
|
|
411
|
-
try { ({ manifest } = await snapshotAuthority(input, root, snapshotReceipt)); } catch (error) { return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: error.code ?? 'ENTROROVE_SNAPSHOT_BINDING' }; }
|
|
412
|
-
const binding = { flowId: input.flowId, stepId: input.stepId, snapshotDigest: snapshotReceipt.snapshotDigest, contractDigest: snapshotReceipt.contractDigest, receiptPublicKey: input.receiptPublicKey };
|
|
413
|
-
const preflight = verifyArchguardPreflight(input.preflight, binding);
|
|
414
|
-
if (!preflight.verified) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_PREFLIGHT_BLOCKED', reason: preflight.reason };
|
|
415
|
-
if (!SHA256.test(binding.contractDigest ?? '')) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_CONTRACT_DIGEST' };
|
|
416
|
-
if (!Array.isArray(input.evidence) || input.evidence.length === 0) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_EVIDENCE_REQUIRED' };
|
|
417
|
-
const checks = await Promise.all(input.evidence.map(evidence => verifyEvidenceValue(evidence, binding, true))); if (checks.some(check => !check.verified) || input.evidence.some(evidence => evidence.exitCode !== 0)) return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', code: 'ENTROROVE_EVIDENCE_UNVERIFIED', checks };
|
|
418
|
-
let written = 0; for (const change of input.changes ?? []) { await guardedWrite({ ...change, repositoryRoot: root, home: input.home, flowId: input.flowId, stepId: input.stepId }); written += 1; }
|
|
419
|
-
await audit(input.home, root, 'merge-verified', { flowId: input.flowId, stepId: input.stepId, sourceRef: input.sourceRef, targetRef: input.targetRef });
|
|
420
|
-
await appendLedgerRecord({ home: input.home, repositoryRoot: root }, 'merge-verified', { flowId: input.flowId, stepId: input.stepId, sourceRef: input.sourceRef, targetRef: input.targetRef, snapshotDigest: binding.snapshotDigest, contractDigest: binding.contractDigest });
|
|
421
|
-
return { schemaVersion: LOCAL_SCHEMA, status: 'verified', merged: true, changes: written, sourceRef: input.sourceRef, targetRef: input.targetRef, preflightKeyId: preflight.keyId };
|
|
422
|
-
}
|
|
423
|
-
export async function rollback(input) {
|
|
424
|
-
const root = await repositoryRoot(input.repositoryRoot); const flowId = id(input.flowId, 'flowId'); const snapshotId = id(input.snapshotId, 'snapshotId'); const manifestPath = path.join(path.dirname(statePath(input.home, 'snapshots', root, flowId, 'manifest.json')), snapshotId, 'manifest.json'); const manifest = await readJson(manifestPath);
|
|
425
|
-
const integrity = verifySnapshotManifest(manifest, { snapshotId, flowId, stepId: input.stepId ?? manifest.stepId, repositoryDigest: repoKey(root) });
|
|
426
|
-
if (!integrity.verified) fail('ENTROROVE_SNAPSHOT_CORRUPT', integrity.reason ?? 'snapshot manifest is invalid');
|
|
427
|
-
for (const entry of manifest.files) { const target = await projectFile(root, entry.path, true); const copy = path.join(path.dirname(manifestPath), entry.path); if (entry.exists) { if (sha256(await fs.readFile(copy)) !== entry.sha256) fail('ENTROROVE_SNAPSHOT_CORRUPT', `${entry.path} snapshot copy is invalid`); await privateDir(path.dirname(target.target)); await fs.copyFile(copy, target.target); } else if (target.exists) await fs.unlink(target.target); }
|
|
428
|
-
await audit(input.home, root, 'rollback', { flowId, stepId: input.stepId ?? null, snapshotId }); return { schemaVersion: LOCAL_SCHEMA, status: 'verified', rolledBack: true, snapshotId };
|
|
429
|
-
}
|
|
259
|
+
export async function mergeVerified(input) { return executeVerifiedMerge(input); }
|
|
260
|
+
export async function rollback(input) { return rollbackMerge(input); }
|
|
430
261
|
export async function finalGate(input) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const preflight = bound ? verifyArchguardPreflight(input.preflight, binding) : { verified: true };
|
|
436
|
-
const validation = evidence.length ? await Promise.all(evidence.map(item => verifyEvidenceValue(item, bound ? binding : input, bound))) : [];
|
|
437
|
-
const trusted = evidence.length > 0 && preflight.verified && validation.every(item => item.verified) && evidence.every(evidenceItem => evidenceItem.exitCode === 0);
|
|
438
|
-
const complete = passed && trusted;
|
|
262
|
+
if (input.gateId) {
|
|
263
|
+
const { runFinalGate16 } = await import('./local-final-gate16.mjs');
|
|
264
|
+
return runFinalGate16(input);
|
|
265
|
+
}
|
|
439
266
|
if (input.repositoryRoot) {
|
|
440
267
|
const root = await repositoryRoot(input.repositoryRoot);
|
|
441
|
-
await appendLedgerRecord({ home: input.home, repositoryRoot: root }, 'final-gate',
|
|
268
|
+
await appendLedgerRecord({ home: input.home, repositoryRoot: root }, 'final-gate',
|
|
269
|
+
{ flowId: input.flowId ?? null, stepId: input.stepId ?? null, status: 'blocked',
|
|
270
|
+
code: 'ENTROROVE_FINAL_GATE_ACTUAL_CHECKS_REQUIRED' });
|
|
442
271
|
}
|
|
443
|
-
return { schemaVersion: LOCAL_SCHEMA, status:
|
|
272
|
+
return { schemaVersion: LOCAL_SCHEMA, status: 'blocked', authority: 'local-evidence',
|
|
273
|
+
code: 'ENTROROVE_FINAL_GATE_ACTUAL_CHECKS_REQUIRED',
|
|
274
|
+
checks: { audit: false, test: false, operations: false },
|
|
275
|
+
guarantee: 'Original engine, frozen role plans and actual three-party checks are required' };
|
|
444
276
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
['mutate-gate', ['repositoryRoot', 'flowId', 'stepId', 'accepted', 'receipt']], ['gate-issue', ['repositoryRoot', 'flowId', 'stepId', 'accepted', 'receipt']],
|
|
448
|
-
['gate-verify', ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath']], ['guarded-write', ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath', 'content']],
|
|
449
|
-
['budget-init', ['repositoryRoot', 'flowId', 'mode', 'executionContext']], ['budget-context', ['repositoryRoot', 'flowId', 'executionContext']], ['budget-read', ['repositoryRoot', 'flowId', 'path']], ['budget-status', ['repositoryRoot', 'flowId']], ['budget-extend', ['repositoryRoot', 'flowId', 'confirmation', 'additions']],
|
|
450
|
-
['peer-coordinate', ['repositoryRoot', 'flowId']], ['coordinate', ['repositoryRoot', 'flowId']], ['spawn-bind', ['repositoryRoot', 'flowId', 'paths']], ['peer-complete', ['repositoryRoot', 'flowId', 'workerId', 'leaseId']], ['complete', ['repositoryRoot', 'flowId', 'workerId', 'leaseId']], ['lease-verify', ['repositoryRoot', 'flowId', 'workerId', 'leaseId', 'paths']],
|
|
451
|
-
['evidence-sign', ['repositoryRoot', 'evidence']], ['evidence-verify', ['repositoryRoot', 'evidence']], ['merge-verified', ['repositoryRoot', 'flowId', 'stepId', 'sourceRef', 'targetRef', 'snapshotReceipt', 'evidence', 'preflight']], ['rollback', ['repositoryRoot', 'flowId', 'snapshotId']], ['final-gate', ['repositoryRoot', 'audit', 'test', 'operations', 'evidence']],
|
|
452
|
-
['formula-evaluate', ['formula']], ['formula-graph', ['formulas']], ['formula-final-gate', ['engine']], ['calculation-final-gate', ['engine']],
|
|
453
|
-
['run-approved-plan', ['repositoryRoot', 'planPath', 'approvalPath', 'signerConfigPath']],
|
|
454
|
-
].map(([operation, required]) => [operation, { type: 'object', additionalProperties: false, required }]));
|
|
455
|
-
Object.assign(LOCAL_OPERATION_SCHEMAS, LOCAL_LEDGER_SCHEMAS);
|
|
456
|
-
Object.assign(LOCAL_OPERATION_SCHEMAS, LOCAL_RENEWAL_SCHEMAS, LOCAL_PROBE_SCHEMAS);
|
|
457
|
-
Object.assign(LOCAL_OPERATION_SCHEMAS, LOCAL_TASK_ROUTING_SCHEMAS);
|
|
458
|
-
Object.freeze(LOCAL_OPERATION_SCHEMAS);
|
|
459
|
-
export const LOCAL_CAPABILITIES = Object.freeze({ schemaVersion: LOCAL_SCHEMA, operations: Object.freeze([
|
|
460
|
-
'capabilities', 'snapshot-plan', 'snapshot-verify', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write',
|
|
461
|
-
'budget-init', 'budget-context', 'budget-read', 'budget-status', 'budget-extend', 'peer-coordinate', 'spawn-bind',
|
|
462
|
-
'peer-complete', 'complete', 'lease-verify', 'evidence-sign', 'evidence-verify', 'merge-verified', 'rollback', 'final-gate',
|
|
463
|
-
'formula-evaluate', 'formula-graph', 'formula-final-gate', 'calculation-final-gate', 'run-approved-plan',
|
|
464
|
-
...Object.keys(LOCAL_LEDGER_OPERATIONS),
|
|
465
|
-
...Object.keys(LOCAL_RENEWAL_OPERATIONS), ...Object.keys(LOCAL_PROBE_OPERATIONS),
|
|
466
|
-
...Object.keys(LOCAL_TASK_ROUTING_OPERATIONS),
|
|
467
|
-
]), operationSchemas: LOCAL_OPERATION_SCHEMAS, stateDirectory: '~/.entrorove', forbidGitBranch: true, forbidWorktree: true, evidenceSchema: EVIDENCE_SCHEMA, budgets: READ_BUDGETS, validatorRunner: RUNNER_CAPABILITIES });
|
|
277
|
+
|
|
278
|
+
export const LOCAL_CAPABILITIES = Object.freeze({ schemaVersion: LOCAL_SCHEMA, operations: Object.freeze(Object.keys(LOCAL_OPERATION_SCHEMAS)), operationSchemas: LOCAL_OPERATION_SCHEMAS, stateDirectory: '~/.entrorove', forbidGitBranch: true, forbidWorktree: true, evidenceSchema: EVIDENCE_SCHEMA, budgets: READ_BUDGETS, validatorRunner: RUNNER_CAPABILITIES, workflowRunner: WORKFLOW_LOCAL_CAPABILITIES });
|
|
468
279
|
export function capabilities() { return LOCAL_CAPABILITIES; }
|
|
469
280
|
export const guardedWriteFile = guardedWrite;
|
|
470
281
|
export const verifyMutationPassFile = verifyMutationPass;
|
|
@@ -474,7 +285,7 @@ export const readFileWithinBudget = budgetRead;
|
|
|
474
285
|
export const readBudgetStatus = budgetStatus;
|
|
475
286
|
export const extendReadBudget = budgetExtend;
|
|
476
287
|
export const runLocalFinalGate = finalGate;
|
|
477
|
-
export { Decimal, evaluateFormula, evaluateFormulaGraph, formulaFinalGate, formulaFinalGateFile, runApprovedPlan };
|
|
288
|
+
export { Decimal, evaluateFormula, evaluateFormulaGraph, formulaFinalGate, formulaFinalGateFile, runApprovedPlan, runWorkflowLocalStep };
|
|
478
289
|
export { LOCAL_LEDGER_OPERATIONS };
|
|
479
290
|
export { LOCAL_RENEWAL_OPERATIONS, LOCAL_PROBE_OPERATIONS };
|
|
480
291
|
export { branchCreate, branchList, branchSwitch, diffReport, preflight, ledgerQuery, appendLedgerRecord, chainInit, chainResume, chainStatus, chainAnswer };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { FINAL_GATE16_BUNDLE_PREFIX } from './local-final-gate16-contract.mjs';
|
|
3
|
+
import { fail16, sha16 } from './local-final-gate16-checks.mjs';
|
|
4
|
+
|
|
5
|
+
export const FINAL_GATE16_CHECKER_PINS = Object.freeze({
|
|
6
|
+
'local-final-gate16-worker.mjs': '344d64b6c1c5552b96960e0d8514a876a3dbfa9762ab9eb749038e8351727121',
|
|
7
|
+
'local-final-gate16-checks.mjs': '1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a',
|
|
8
|
+
'local-decimal.mjs': '93580b0cbdac2e881942658c1cd10394b432219f8657afaaaf4cb1c35c91d644',
|
|
9
|
+
'local-formula-ast.mjs': '768ec4aa88a127588035d5b94aa2721df00e3e0576dc7e341baaead44795f314',
|
|
10
|
+
'local-formula-engine.mjs': 'cfc44f03bb8dc4797f9f9e859a64a2dd5e4dd316db45624c03f7f8fad3202ede',
|
|
11
|
+
'local-formula-tests.mjs': 'd6d2809572c46955e6b556023ed6a12362c8acd0ad0d5be3b1b2dd64439215c0',
|
|
12
|
+
});
|
|
13
|
+
export async function finalGate16Bundle() {
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const [name, sha256] of Object.entries(FINAL_GATE16_CHECKER_PINS)) {
|
|
16
|
+
const bytes = await readFile(new URL(name, import.meta.url));
|
|
17
|
+
if (sha16(bytes) !== sha256) fail16('FINAL_GATE16_CHECKER_PIN', 'The installed checker closure differs from its reviewed source');
|
|
18
|
+
files.push({ path: FINAL_GATE16_BUNDLE_PREFIX + name, sha256, bytes });
|
|
19
|
+
}
|
|
20
|
+
return files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
21
|
+
}
|