@voybio/ace-swarm 2.4.8 → 2.4.10
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/CHANGELOG.md +52 -14
- package/README.md +14 -10
- package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
- package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
- package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
- package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
- package/assets/agent-state/ACE_WORKFLOW.md +25 -2
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
- package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
- package/assets/eval-battery/battery.json +1 -1
- package/assets/instructions/ACE_Coder.instructions.md +9 -13
- package/assets/scripts/ace-hook-dispatch.mjs +16 -411
- package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
- package/assets/vendor/README.md +9 -0
- package/assets/vendor/vericify-reader.mjs +272 -0
- package/assets/vendor/vericify-reader.provenance.json +10 -0
- package/dist/ace-autonomy.d.ts +58 -0
- package/dist/ace-autonomy.js +211 -6
- package/dist/ace-internal-tools.d.ts +1 -0
- package/dist/ace-internal-tools.js +3 -3
- package/dist/ace-server-instructions.d.ts +3 -3
- package/dist/ace-server-instructions.js +46 -192
- package/dist/cli.js +44 -6
- package/dist/experience-reflector.d.ts +7 -0
- package/dist/experience-reflector.js +39 -0
- package/dist/goal-engine.d.ts +64 -7
- package/dist/goal-engine.js +197 -41
- package/dist/handoff-registry.d.ts +10 -0
- package/dist/handoff-registry.js +59 -0
- package/dist/helpers/artifacts.d.ts +6 -2
- package/dist/helpers/artifacts.js +8 -6
- package/dist/helpers/bootstrap.js +13 -20
- package/dist/helpers/path-utils.js +2 -0
- package/dist/helpers/store-resolution.js +18 -3
- package/dist/helpers/workspace-root.d.ts +2 -0
- package/dist/helpers/workspace-root.js +9 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +1 -0
- package/dist/hermes/bridge-protocol.d.ts +1 -1
- package/dist/hermes/session-manager.d.ts +17 -0
- package/dist/hermes/session-manager.js +67 -14
- package/dist/host-goal-engine.d.ts +138 -0
- package/dist/host-goal-engine.js +793 -0
- package/dist/host-goal-status.d.ts +113 -0
- package/dist/host-goal-status.js +175 -0
- package/dist/host-learning.d.ts +68 -0
- package/dist/host-learning.js +115 -0
- package/dist/host-observation.d.ts +12 -0
- package/dist/host-observation.js +91 -0
- package/dist/host-plan-revision.d.ts +14 -0
- package/dist/host-plan-revision.js +101 -0
- package/dist/host-verification-job.d.ts +12 -0
- package/dist/host-verification-job.js +183 -0
- package/dist/host-work-context.d.ts +12 -0
- package/dist/host-work-context.js +8 -0
- package/dist/job-scheduler.d.ts +58 -13
- package/dist/job-scheduler.js +298 -48
- package/dist/job-worker.d.ts +3 -1
- package/dist/job-worker.js +10 -3
- package/dist/kanban.d.ts +4 -1
- package/dist/kanban.js +44 -16
- package/dist/learned-artifacts.d.ts +64 -0
- package/dist/learned-artifacts.js +321 -0
- package/dist/learning-controls.d.ts +142 -0
- package/dist/learning-controls.js +135 -0
- package/dist/learning-input-context.d.ts +9 -0
- package/dist/learning-input-context.js +62 -0
- package/dist/learning-lifecycle.d.ts +10 -0
- package/dist/learning-lifecycle.js +11 -0
- package/dist/learning-overlays.d.ts +3 -0
- package/dist/learning-overlays.js +17 -0
- package/dist/learning-policy.d.ts +40 -0
- package/dist/learning-policy.js +38 -0
- package/dist/learning-records.d.ts +35 -0
- package/dist/learning-records.js +17 -0
- package/dist/learning-scorecard.d.ts +82 -0
- package/dist/learning-scorecard.js +86 -0
- package/dist/learning-serving.d.ts +29 -0
- package/dist/learning-serving.js +134 -0
- package/dist/linked-work.d.ts +9 -0
- package/dist/linked-work.js +28 -0
- package/dist/local-model-runtime.js +1 -1
- package/dist/mcp-command-progress.d.ts +16 -0
- package/dist/mcp-command-progress.js +70 -0
- package/dist/model-bridge.d.ts +14 -13
- package/dist/model-bridge.js +73 -41
- package/dist/orchestrator-supervisor.d.ts +111 -6
- package/dist/orchestrator-supervisor.js +67 -43
- package/dist/plan-proposal.js +3 -3
- package/dist/playbook-curator.d.ts +1 -0
- package/dist/playbook-curator.js +11 -9
- package/dist/process-identity.d.ts +8 -0
- package/dist/process-identity.js +38 -0
- package/dist/progress-command.d.ts +34 -0
- package/dist/progress-command.js +153 -0
- package/dist/prompts.js +15 -15
- package/dist/resources.js +2 -2
- package/dist/runtime-command.d.ts +8 -1
- package/dist/runtime-command.js +48 -7
- package/dist/runtime-executor.js +33 -11
- package/dist/runtime-profile-syntax.d.ts +6 -0
- package/dist/runtime-profile-syntax.js +121 -0
- package/dist/runtime-profile.d.ts +37 -5
- package/dist/runtime-profile.js +36 -129
- package/dist/runtime-tool-specs.js +1 -1
- package/dist/safe-edit.d.ts +7 -3
- package/dist/safe-edit.js +40 -47
- package/dist/schemas.js +4 -2
- package/dist/server.d.ts +2 -16
- package/dist/server.js +14 -20
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +2 -0
- package/dist/skill-auditor.d.ts +19 -0
- package/dist/skill-auditor.js +32 -2
- package/dist/skill-catalog.d.ts +4 -1
- package/dist/skill-catalog.js +6 -5
- package/dist/store/ace-packed-store.d.ts +5 -1
- package/dist/store/ace-packed-store.js +33 -1
- package/dist/store/bootstrap-store.js +19 -12
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +94 -27
- package/dist/store/importer.js +11 -20
- package/dist/store/knowledge-bake.d.ts +3 -1
- package/dist/store/knowledge-bake.js +10 -1
- package/dist/store/legacy-vericify-posts.d.ts +8 -0
- package/dist/store/legacy-vericify-posts.js +76 -0
- package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
- package/dist/store/materializers/hook-context-materializer.js +6 -2
- package/dist/store/materializers/host-file-materializer.js +2 -2
- package/dist/store/materializers/projection-manager.d.ts +4 -1
- package/dist/store/materializers/projection-manager.js +10 -3
- package/dist/store/materializers/todo-syncer.d.ts +39 -20
- package/dist/store/materializers/todo-syncer.js +208 -110
- package/dist/store/materializers/vericify-projector.js +15 -26
- package/dist/store/repositories/goal-repository.d.ts +51 -2
- package/dist/store/repositories/goal-repository.js +114 -48
- package/dist/store/repositories/playbook-repository.d.ts +41 -3
- package/dist/store/repositories/playbook-repository.js +61 -5
- package/dist/store/repositories/todo-repository.d.ts +8 -2
- package/dist/store/repositories/todo-repository.js +1 -1
- package/dist/store/repositories/vericify-repository.d.ts +4 -0
- package/dist/store/repositories/vericify-repository.js +46 -0
- package/dist/store/skills-install.d.ts +1 -0
- package/dist/store/skills-install.js +20 -16
- package/dist/store/source-preservation.d.ts +24 -0
- package/dist/store/source-preservation.js +79 -0
- package/dist/store/store-artifacts.js +8 -1
- package/dist/store/test-write-boundary.d.ts +16 -0
- package/dist/store/test-write-boundary.js +175 -0
- package/dist/store/types.d.ts +5 -1
- package/dist/store/write-coordinator.js +16 -0
- package/dist/task-plan-context.d.ts +11 -0
- package/dist/task-plan-context.js +59 -0
- package/dist/todo-state.d.ts +19 -9
- package/dist/todo-state.js +204 -321
- package/dist/tools-agent.d.ts +1 -1
- package/dist/tools-agent.js +38 -62
- package/dist/tools-files.d.ts +0 -3
- package/dist/tools-files.js +21 -11
- package/dist/tools-framework.js +16 -25
- package/dist/tools-goal.d.ts +2 -4
- package/dist/tools-goal.js +119 -16
- package/dist/tools-handoff.d.ts +3 -2
- package/dist/tools-handoff.js +53 -23
- package/dist/tools-lifecycle.js +1 -1
- package/dist/tools-memory.d.ts +0 -3
- package/dist/tools-memory.js +51 -6
- package/dist/tools-scheduler.js +20 -17
- package/dist/tools-todo.js +160 -148
- package/dist/vericify-bridge.d.ts +8 -0
- package/dist/vericify-bridge.js +18 -0
- package/dist/vericify-context.d.ts +4 -0
- package/dist/vericify-context.js +26 -5
- package/dist/vericify-native.d.ts +101 -0
- package/dist/vericify-native.js +221 -0
- package/dist/verification-worker.d.ts +2 -0
- package/dist/verification-worker.js +31 -0
- package/docs/mcp-tool-reference.md +50 -10
- package/docs/troubleshooting.md +29 -0
- package/package.json +19 -6
- package/scripts/dev/run-tests-isolated.mjs +165 -0
- package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
- package/scripts/dev/test-isolation-guard.mjs +20 -0
- package/scripts/dev/test-preload.mjs +153 -0
- package/scripts/dev/test-source-inventory.mjs +31 -0
- package/docs/development.md +0 -46
package/dist/safe-edit.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Prevents bricking by never leaving core files in a broken state.
|
|
4
4
|
*/
|
|
5
5
|
import { copyFileSync, cpSync, existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { basename, dirname, relative, resolve } from "node:path";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
|
+
import { runProgressCommand } from "./progress-command.js";
|
|
10
11
|
import { WORKSPACE_ROOT, safeRead, wsPath } from "./helpers.js";
|
|
11
12
|
import { isInside, isReadError, normalizeRelPath } from "./shared.js";
|
|
12
13
|
import { appendSourceEditEvent, SourceEditLeaseBusyError, withSourceMutationCoordinatorSync, } from "./source-mutation-coordinator.js";
|
|
@@ -176,38 +177,31 @@ export function gcStaging(input = {}) {
|
|
|
176
177
|
errors,
|
|
177
178
|
};
|
|
178
179
|
}
|
|
179
|
-
function runValidation(validationCmd, testCmd, cwd = WORKSPACE_ROOT) {
|
|
180
|
+
async function runValidation(validationCmd, testCmd, cwd = WORKSPACE_ROOT, policy = {}) {
|
|
180
181
|
let validation_passed;
|
|
181
182
|
let validation_output;
|
|
182
183
|
let test_passed;
|
|
183
184
|
let test_output;
|
|
185
|
+
let reason_code;
|
|
184
186
|
if (validationCmd) {
|
|
185
|
-
const res =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
190
|
-
});
|
|
191
|
-
validation_passed = res.status === 0;
|
|
192
|
-
validation_output = ((res.stdout ?? "") + (res.stderr ?? "")).slice(0, 4000);
|
|
187
|
+
const res = await runProgressCommand(validationCmd, cwd, policy);
|
|
188
|
+
validation_passed = res.ok;
|
|
189
|
+
validation_output = res.output.slice(-4000);
|
|
190
|
+
reason_code = res.reason_code;
|
|
193
191
|
}
|
|
194
192
|
if (testCmd && (validation_passed === undefined || validation_passed)) {
|
|
195
|
-
const res =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
200
|
-
});
|
|
201
|
-
test_passed = res.status === 0;
|
|
202
|
-
test_output = ((res.stdout ?? "") + (res.stderr ?? "")).slice(0, 4000);
|
|
193
|
+
const res = await runProgressCommand(testCmd, cwd, policy);
|
|
194
|
+
test_passed = res.ok;
|
|
195
|
+
test_output = res.output.slice(-4000);
|
|
196
|
+
reason_code = res.reason_code;
|
|
203
197
|
}
|
|
204
198
|
const passed = (validation_passed ?? true) && (test_passed ?? true);
|
|
205
|
-
return { passed, validation_passed, validation_output, test_passed, test_output };
|
|
199
|
+
return { passed, validation_passed, validation_output, test_passed, test_output, reason_code };
|
|
206
200
|
}
|
|
207
201
|
// ────────────────────────────────────────────────────────────────────
|
|
208
202
|
// Safe edit (copy → modify → validate → swap)
|
|
209
203
|
// ────────────────────────────────────────────────────────────────────
|
|
210
|
-
export function safeEditFile(input) {
|
|
204
|
+
export async function safeEditFile(input) {
|
|
211
205
|
const target = resolveWorkspaceTarget(input.path);
|
|
212
206
|
if ("error" in target) {
|
|
213
207
|
return {
|
|
@@ -227,7 +221,7 @@ export function safeEditFile(input) {
|
|
|
227
221
|
const originalHash = isNew ? "none" : fileHash(originalContent);
|
|
228
222
|
const stagedNewHash = fileHash(input.content);
|
|
229
223
|
// Step 1: Create staging directory
|
|
230
|
-
const stagingDir = wsPath(STAGING_DIR, `${timestamp}-${isNew ? "new" : originalHash.slice(0, 8)}`);
|
|
224
|
+
const stagingDir = wsPath(STAGING_DIR, `${timestamp}-${isNew ? "new" : originalHash.slice(0, 8)}-${randomUUID()}`);
|
|
231
225
|
ensureDir(stagingDir);
|
|
232
226
|
const needsValidationWorkspace = Boolean(input.validation_command || input.test_command);
|
|
233
227
|
const flatName = relPath.replace(/\//g, "__");
|
|
@@ -260,7 +254,7 @@ export function safeEditFile(input) {
|
|
|
260
254
|
restored: true,
|
|
261
255
|
};
|
|
262
256
|
}
|
|
263
|
-
check = runValidation(input.validation_command, input.test_command, stagedWorkspace);
|
|
257
|
+
check = await runValidation(input.validation_command, input.test_command, stagedWorkspace, input.command_policy);
|
|
264
258
|
if (!check.passed) {
|
|
265
259
|
return {
|
|
266
260
|
ok: false,
|
|
@@ -273,7 +267,8 @@ export function safeEditFile(input) {
|
|
|
273
267
|
validation_output: check.validation_output,
|
|
274
268
|
test_passed: check.test_passed,
|
|
275
269
|
test_output: check.test_output,
|
|
276
|
-
error:
|
|
270
|
+
error: `Validation/test failed — staged change not promoted${check.reason_code ? ` (${check.reason_code})` : ""}`,
|
|
271
|
+
reason_code: check.reason_code,
|
|
277
272
|
restored: true,
|
|
278
273
|
};
|
|
279
274
|
}
|
|
@@ -281,17 +276,18 @@ export function safeEditFile(input) {
|
|
|
281
276
|
try {
|
|
282
277
|
return withSourceMutationCoordinatorSync(relPath, (lease) => {
|
|
283
278
|
// Step 4: Promote staged content to the real target only after checks pass.
|
|
284
|
-
|
|
279
|
+
{
|
|
280
|
+
const expectedBaseHash = input.expected_base_hash ?? (isNew ? "none" : fullFileHash(originalContent));
|
|
285
281
|
const currentContent = safeRead(input.path);
|
|
286
282
|
const observedHash = isReadError(currentContent) ? "none" : fullFileHash(currentContent);
|
|
287
|
-
if (isReadError(currentContent) || !hashMatchesExpected(
|
|
283
|
+
if (observedHash !== expectedBaseHash && (isReadError(currentContent) || !hashMatchesExpected(expectedBaseHash, currentContent))) {
|
|
288
284
|
appendSourceEditEvent({
|
|
289
285
|
event: "SOURCE_EDIT_CONFLICT",
|
|
290
286
|
target_path: relPath,
|
|
291
287
|
lock_path: lease.lockPath,
|
|
292
288
|
waited_ms: lease.waitedMs,
|
|
293
289
|
base_hash: originalHash,
|
|
294
|
-
expected_hash:
|
|
290
|
+
expected_hash: expectedBaseHash,
|
|
295
291
|
observed_hash: observedHash,
|
|
296
292
|
reason_code: "source_edit_conflict",
|
|
297
293
|
});
|
|
@@ -301,7 +297,7 @@ export function safeEditFile(input) {
|
|
|
301
297
|
backupPath,
|
|
302
298
|
originalHash,
|
|
303
299
|
newHash: stagedNewHash,
|
|
304
|
-
expectedHash:
|
|
300
|
+
expectedHash: expectedBaseHash,
|
|
305
301
|
observedHash,
|
|
306
302
|
validationCheck: check,
|
|
307
303
|
leaseLockPath: lease.lockPath,
|
|
@@ -384,21 +380,16 @@ function detectTestCommand() {
|
|
|
384
380
|
}
|
|
385
381
|
return "echo 'No test command detected' && exit 1";
|
|
386
382
|
}
|
|
387
|
-
export function runTestSuite(command) {
|
|
383
|
+
export async function runTestSuite(command, options = {}) {
|
|
388
384
|
const cmd = command ?? detectTestCommand();
|
|
389
|
-
const
|
|
390
|
-
const res = spawnSync("sh", ["-c", cmd], {
|
|
391
|
-
cwd: WORKSPACE_ROOT,
|
|
392
|
-
encoding: "utf-8",
|
|
393
|
-
timeout: 300_000,
|
|
394
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
395
|
-
});
|
|
385
|
+
const res = await runProgressCommand(cmd, WORKSPACE_ROOT, options);
|
|
396
386
|
return {
|
|
397
|
-
ok: res.
|
|
398
|
-
exit_code: res.
|
|
399
|
-
output:
|
|
387
|
+
ok: res.ok,
|
|
388
|
+
exit_code: res.exit_code ?? -1,
|
|
389
|
+
output: res.output.slice(-8000),
|
|
400
390
|
command: cmd,
|
|
401
|
-
duration_ms:
|
|
391
|
+
duration_ms: res.duration_ms,
|
|
392
|
+
reason_code: res.reason_code,
|
|
402
393
|
};
|
|
403
394
|
}
|
|
404
395
|
// ────────────────────────────────────────────────────────────────────
|
|
@@ -507,7 +498,7 @@ export function diffFiles(pathA, pathB) {
|
|
|
507
498
|
}
|
|
508
499
|
return diffContents(contentA, contentB);
|
|
509
500
|
}
|
|
510
|
-
export function applyPatch(input) {
|
|
501
|
+
export async function applyPatch(input) {
|
|
511
502
|
const target = resolveWorkspaceTarget(input.path);
|
|
512
503
|
if ("error" in target) {
|
|
513
504
|
return {
|
|
@@ -525,7 +516,7 @@ export function applyPatch(input) {
|
|
|
525
516
|
const isNew = isReadError(originalContent);
|
|
526
517
|
const originalHash = isNew ? "none" : fileHash(originalContent);
|
|
527
518
|
const timestamp = Date.now();
|
|
528
|
-
const stagingDir = wsPath(STAGING_DIR, `${timestamp}-patch-${isNew ? "new" : originalHash.slice(0, 8)}`);
|
|
519
|
+
const stagingDir = wsPath(STAGING_DIR, `${timestamp}-patch-${isNew ? "new" : originalHash.slice(0, 8)}-${randomUUID()}`);
|
|
529
520
|
ensureDir(stagingDir);
|
|
530
521
|
const needsValidationWorkspace = Boolean(input.validation_command || input.test_command);
|
|
531
522
|
const stagedWorkspace = needsValidationWorkspace ? prepareStagedWorkspace(stagingDir) : undefined;
|
|
@@ -559,7 +550,7 @@ export function applyPatch(input) {
|
|
|
559
550
|
}
|
|
560
551
|
const patchedContent = readFileSync(stagedTarget, "utf-8");
|
|
561
552
|
const newHash = fileHash(patchedContent);
|
|
562
|
-
const check = runValidation(input.validation_command, input.test_command, stagedWorkspace ?? stagingDir);
|
|
553
|
+
const check = await runValidation(input.validation_command, input.test_command, stagedWorkspace ?? stagingDir, input.command_policy);
|
|
563
554
|
if (!check.passed) {
|
|
564
555
|
return {
|
|
565
556
|
ok: false,
|
|
@@ -571,24 +562,26 @@ export function applyPatch(input) {
|
|
|
571
562
|
validation_output: check.validation_output,
|
|
572
563
|
test_passed: check.test_passed,
|
|
573
564
|
test_output: check.test_output,
|
|
574
|
-
error:
|
|
565
|
+
error: `Validation/test failed — staged patch not promoted${check.reason_code ? ` (${check.reason_code})` : ""}`,
|
|
566
|
+
reason_code: check.reason_code,
|
|
575
567
|
restored: true,
|
|
576
568
|
};
|
|
577
569
|
}
|
|
578
570
|
try {
|
|
579
571
|
return withSourceMutationCoordinatorSync(relPath, (lease) => {
|
|
580
572
|
let backupPath;
|
|
581
|
-
|
|
573
|
+
{
|
|
574
|
+
const expectedBaseHash = input.expected_base_hash ?? (isNew ? "none" : fullFileHash(originalContent));
|
|
582
575
|
const currentContent = safeRead(input.path);
|
|
583
576
|
const observedHash = isReadError(currentContent) ? "none" : fullFileHash(currentContent);
|
|
584
|
-
if (isReadError(currentContent) || !hashMatchesExpected(
|
|
577
|
+
if (observedHash !== expectedBaseHash && (isReadError(currentContent) || !hashMatchesExpected(expectedBaseHash, currentContent))) {
|
|
585
578
|
appendSourceEditEvent({
|
|
586
579
|
event: "SOURCE_EDIT_CONFLICT",
|
|
587
580
|
target_path: relPath,
|
|
588
581
|
lock_path: lease.lockPath,
|
|
589
582
|
waited_ms: lease.waitedMs,
|
|
590
583
|
base_hash: originalHash,
|
|
591
|
-
expected_hash:
|
|
584
|
+
expected_hash: expectedBaseHash,
|
|
592
585
|
observed_hash: observedHash,
|
|
593
586
|
reason_code: "source_edit_conflict",
|
|
594
587
|
});
|
|
@@ -597,7 +590,7 @@ export function applyPatch(input) {
|
|
|
597
590
|
stagingDir,
|
|
598
591
|
originalHash,
|
|
599
592
|
newHash,
|
|
600
|
-
expectedHash:
|
|
593
|
+
expectedHash: expectedBaseHash,
|
|
601
594
|
observedHash,
|
|
602
595
|
validationCheck: check,
|
|
603
596
|
leaseLockPath: lease.lockPath,
|
package/dist/schemas.js
CHANGED
|
@@ -19,6 +19,8 @@ const REQUIRED_PROVENANCE_FIELDS = [
|
|
|
19
19
|
];
|
|
20
20
|
const swarmHandoffSchema = z
|
|
21
21
|
.object({
|
|
22
|
+
step_ref: z.object({ goal_id: z.string().min(1), step_id: z.string().min(1) }).strict().optional(),
|
|
23
|
+
recipient_agent: z.string().min(1).optional(),
|
|
22
24
|
handoff_id: NON_EMPTY,
|
|
23
25
|
timestamp_utc: z.string().datetime({ offset: true }),
|
|
24
26
|
router: z
|
|
@@ -405,8 +407,8 @@ const runtimeExecutorSessionRecordSchema = z
|
|
|
405
407
|
objective_id: z.string().optional(),
|
|
406
408
|
tracker_item_id: z.string().optional(),
|
|
407
409
|
command: NON_EMPTY,
|
|
408
|
-
max_turns: z.number().int().
|
|
409
|
-
turn_timeout_ms: z.number().int().
|
|
410
|
+
max_turns: z.number().int().nonnegative(),
|
|
411
|
+
turn_timeout_ms: z.number().int().nonnegative(),
|
|
410
412
|
turn_count: z.number().int().min(0),
|
|
411
413
|
created_at: z.string().datetime({ offset: true }),
|
|
412
414
|
updated_at: z.string().datetime({ offset: true }),
|
package/dist/server.d.ts
CHANGED
|
@@ -9,22 +9,8 @@ export interface CreateAceServerOptions {
|
|
|
9
9
|
}
|
|
10
10
|
export declare function createAceServer(options?: CreateAceServerOptions): McpServer;
|
|
11
11
|
export type AceHostProfile = "host-min" | "full";
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* full tool surface, cutting the per-session schema tax. `run_orchestrator` only reaches an
|
|
15
|
-
* omitted tool if the model-bridge child it spawns happens to have that tool in its own MCP
|
|
16
|
-
* session — it is a role/plan dispatcher, not a generic proxy for any omitted tool by name.
|
|
17
|
-
* The reliable way to reach anything below is to start with `--profile full`.
|
|
18
|
-
*
|
|
19
|
-
* Whole domains are intentionally absent, not just individual tools: goal-engine
|
|
20
|
-
* (start_goal/get_goal_status/resume_goal/stop_goal — until WS-F ships), git
|
|
21
|
-
* (git_status/git_diff/git_log/git_commit), scheduler (enqueue_job/dispatch_jobs/
|
|
22
|
-
* heartbeat_job/reap_stale_jobs/get_lock_table/…), drift/change-intelligence
|
|
23
|
-
* (snapshot_state/semantic_diff/drift_report/rewrite_targets/list_snapshots), discovery
|
|
24
|
-
* (scan_workspace_delta/web_research_packet/codemunch_snapshot/codemunch_index/
|
|
25
|
-
* mcp_ingestion_probe/refresh_astgrep_index), skills (skill_catalog_summary/skill_audit/
|
|
26
|
-
* problem_triage), and todo (get_todo/update_todo/get_run_ledger) all have zero
|
|
27
|
-
* representation below.
|
|
12
|
+
/** Compact host surface, including work state and optional execution aids.
|
|
13
|
+
* Optional git, scheduler, discovery and skill-administration tools remain in full.
|
|
28
14
|
*/
|
|
29
15
|
export declare const HOST_MIN_TOOL_PROFILE: readonly string[];
|
|
30
16
|
export declare function resolveHostProfileAllowlist(profile: AceHostProfile): readonly string[] | undefined;
|
package/dist/server.js
CHANGED
|
@@ -92,35 +92,30 @@ async function verifyMcpStoreStartup(workspaceRoot, logStartup) {
|
|
|
92
92
|
console.error(message);
|
|
93
93
|
return false;
|
|
94
94
|
}
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
* full tool surface, cutting the per-session schema tax. `run_orchestrator` only reaches an
|
|
98
|
-
* omitted tool if the model-bridge child it spawns happens to have that tool in its own MCP
|
|
99
|
-
* session — it is a role/plan dispatcher, not a generic proxy for any omitted tool by name.
|
|
100
|
-
* The reliable way to reach anything below is to start with `--profile full`.
|
|
101
|
-
*
|
|
102
|
-
* Whole domains are intentionally absent, not just individual tools: goal-engine
|
|
103
|
-
* (start_goal/get_goal_status/resume_goal/stop_goal — until WS-F ships), git
|
|
104
|
-
* (git_status/git_diff/git_log/git_commit), scheduler (enqueue_job/dispatch_jobs/
|
|
105
|
-
* heartbeat_job/reap_stale_jobs/get_lock_table/…), drift/change-intelligence
|
|
106
|
-
* (snapshot_state/semantic_diff/drift_report/rewrite_targets/list_snapshots), discovery
|
|
107
|
-
* (scan_workspace_delta/web_research_packet/codemunch_snapshot/codemunch_index/
|
|
108
|
-
* mcp_ingestion_probe/refresh_astgrep_index), skills (skill_catalog_summary/skill_audit/
|
|
109
|
-
* problem_triage), and todo (get_todo/update_todo/get_run_ledger) all have zero
|
|
110
|
-
* representation below.
|
|
95
|
+
/** Compact host surface, including work state and optional execution aids.
|
|
96
|
+
* Optional git, scheduler, discovery and skill-administration tools remain in full.
|
|
111
97
|
*/
|
|
112
98
|
export const HOST_MIN_TOOL_PROFILE = [
|
|
99
|
+
"get_todo",
|
|
100
|
+
"get_todo_state",
|
|
101
|
+
"update_todo",
|
|
102
|
+
"read_workspace_file",
|
|
103
|
+
"list_workspace",
|
|
104
|
+
"start_goal",
|
|
105
|
+
"next_step",
|
|
106
|
+
"complete_step",
|
|
107
|
+
"resume_goal",
|
|
108
|
+
"stop_goal",
|
|
109
|
+
"get_goal_status",
|
|
113
110
|
"recall_context",
|
|
111
|
+
"get_skill_instructions",
|
|
114
112
|
"route_task",
|
|
115
|
-
"run_orchestrator",
|
|
116
113
|
"propose_plan",
|
|
117
114
|
"validate_plan",
|
|
118
115
|
"create_handoff",
|
|
119
116
|
"ack_handoff",
|
|
120
117
|
"get_handoff_status",
|
|
121
118
|
"execute_gates",
|
|
122
|
-
"emit_status_event",
|
|
123
|
-
"append_vericify_process_post",
|
|
124
119
|
"get_vericify_context",
|
|
125
120
|
"get_vericify_delta",
|
|
126
121
|
"astgrep_locate",
|
|
@@ -129,7 +124,6 @@ export const HOST_MIN_TOOL_PROFILE = [
|
|
|
129
124
|
"astgrep_rewrite",
|
|
130
125
|
"safe_edit_file",
|
|
131
126
|
"run_tests",
|
|
132
|
-
"run_local_model",
|
|
133
127
|
"context_snapshot",
|
|
134
128
|
"build_continuity_packet",
|
|
135
129
|
];
|
package/dist/shared.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export declare const TODO_STATUS_ENUM: z.ZodEnum<{
|
|
|
60
60
|
blocked: "blocked";
|
|
61
61
|
gated: "gated";
|
|
62
62
|
done: "done";
|
|
63
|
+
cancelled: "cancelled";
|
|
63
64
|
}>;
|
|
64
65
|
export declare const HANDOFF_LIFECYCLE_STATUS_ENUM: z.ZodEnum<{
|
|
65
66
|
blocked: "blocked";
|
|
@@ -101,6 +102,7 @@ export declare const EVENT_TYPE: {
|
|
|
101
102
|
readonly RUNTIME_EXECUTOR_SESSION_FAILED: "RUNTIME_EXECUTOR_SESSION_FAILED";
|
|
102
103
|
readonly RUNTIME_EXECUTOR_SESSION_STARTED: "RUNTIME_EXECUTOR_SESSION_STARTED";
|
|
103
104
|
readonly RUNTIME_EXECUTOR_SESSION_STOPPED: "RUNTIME_EXECUTOR_SESSION_STOPPED";
|
|
105
|
+
readonly RUNTIME_EXECUTOR_SUSPECTED_STALL: "RUNTIME_EXECUTOR_SUSPECTED_STALL";
|
|
104
106
|
readonly RUNTIME_EXECUTOR_TURN_COMPLETED: "RUNTIME_EXECUTOR_TURN_COMPLETED";
|
|
105
107
|
readonly RUNTIME_EXECUTOR_TURN_FAILED: "RUNTIME_EXECUTOR_TURN_FAILED";
|
|
106
108
|
readonly RUNTIME_EXECUTOR_TURN_STARTED: "RUNTIME_EXECUTOR_TURN_STARTED";
|
package/dist/shared.js
CHANGED
|
@@ -102,6 +102,7 @@ export const TODO_STATUS_ENUM = z.enum([
|
|
|
102
102
|
"blocked",
|
|
103
103
|
"gated",
|
|
104
104
|
"done",
|
|
105
|
+
"cancelled",
|
|
105
106
|
]);
|
|
106
107
|
export const HANDOFF_LIFECYCLE_STATUS_ENUM = z.enum([
|
|
107
108
|
"open",
|
|
@@ -146,6 +147,7 @@ export const EVENT_TYPE = {
|
|
|
146
147
|
RUNTIME_EXECUTOR_SESSION_FAILED: "RUNTIME_EXECUTOR_SESSION_FAILED",
|
|
147
148
|
RUNTIME_EXECUTOR_SESSION_STARTED: "RUNTIME_EXECUTOR_SESSION_STARTED",
|
|
148
149
|
RUNTIME_EXECUTOR_SESSION_STOPPED: "RUNTIME_EXECUTOR_SESSION_STOPPED",
|
|
150
|
+
RUNTIME_EXECUTOR_SUSPECTED_STALL: "RUNTIME_EXECUTOR_SUSPECTED_STALL",
|
|
149
151
|
RUNTIME_EXECUTOR_TURN_COMPLETED: "RUNTIME_EXECUTOR_TURN_COMPLETED",
|
|
150
152
|
RUNTIME_EXECUTOR_TURN_FAILED: "RUNTIME_EXECUTOR_TURN_FAILED",
|
|
151
153
|
RUNTIME_EXECUTOR_TURN_STARTED: "RUNTIME_EXECUTOR_TURN_STARTED",
|
package/dist/skill-auditor.d.ts
CHANGED
|
@@ -20,7 +20,26 @@ export interface SkillAuditResult {
|
|
|
20
20
|
rollout_backlog: string[];
|
|
21
21
|
summary: SkillCatalogSummary;
|
|
22
22
|
}
|
|
23
|
+
export declare function auditSkillEntry(entry: SkillCatalogEntry): SkillAuditEntry;
|
|
23
24
|
export declare function auditSkillCatalog(entries?: SkillCatalogEntry[]): SkillAuditResult;
|
|
24
25
|
export declare function renderSkillAuditReport(result: SkillAuditResult): string;
|
|
25
26
|
export declare function writeSkillAuditReport(result: SkillAuditResult, targetPath?: string): string;
|
|
27
|
+
/** Mechanical qualification of host-written learned skills. Commands execute
|
|
28
|
+
* through the same goal verifier and its progress policy, outside the goal lease. */
|
|
29
|
+
export declare function proveLearnedSkill(content: string, root: string, run: (command: string, root: string, options?: {
|
|
30
|
+
timeout_ms?: number;
|
|
31
|
+
}) => Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
output: string;
|
|
34
|
+
}>, previous?: string): Promise<{
|
|
35
|
+
ok: boolean;
|
|
36
|
+
errors: string[];
|
|
37
|
+
checks: {
|
|
38
|
+
command: string;
|
|
39
|
+
cwd: string;
|
|
40
|
+
ok: boolean;
|
|
41
|
+
output: string;
|
|
42
|
+
}[];
|
|
43
|
+
description: string;
|
|
44
|
+
}>;
|
|
26
45
|
//# sourceMappingURL=skill-auditor.d.ts.map
|
package/dist/skill-auditor.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { safeWrite } from "./helpers.js";
|
|
2
|
-
import { listSkillCatalogEntries, summarizeSkillCatalog, } from "./skill-catalog.js";
|
|
2
|
+
import { listSkillCatalogEntries, readSkillCatalogEntry, summarizeSkillCatalog, } from "./skill-catalog.js";
|
|
3
3
|
function pushFinding(findings, severity, code, message) {
|
|
4
4
|
findings.push({ severity, code, message });
|
|
5
5
|
}
|
|
6
6
|
function clampScore(value) {
|
|
7
7
|
return Math.max(0, Math.min(100, value));
|
|
8
8
|
}
|
|
9
|
-
function auditSkillEntry(entry) {
|
|
9
|
+
export function auditSkillEntry(entry) {
|
|
10
10
|
const findings = [];
|
|
11
11
|
const strengths = [];
|
|
12
12
|
let score = 100;
|
|
@@ -181,4 +181,34 @@ export function renderSkillAuditReport(result) {
|
|
|
181
181
|
export function writeSkillAuditReport(result, targetPath = "agent-state/SKILL_AUDIT_REPORT.md") {
|
|
182
182
|
return safeWrite(targetPath, renderSkillAuditReport(result));
|
|
183
183
|
}
|
|
184
|
+
/** Mechanical qualification of host-written learned skills. Commands execute
|
|
185
|
+
* through the same goal verifier and its progress policy, outside the goal lease. */
|
|
186
|
+
export async function proveLearnedSkill(content, root, run, previous) {
|
|
187
|
+
const entry = readSkillCatalogEntry({ name: 'learned/proposal', path: '', source: 'store' }, content);
|
|
188
|
+
const errors = auditSkillEntry(entry).findings.filter(f => f.severity === 'fail').map(f => f.message);
|
|
189
|
+
if (content.split(/\r?\n/).length > 500)
|
|
190
|
+
errors.push('Skill exceeds 500 lines.');
|
|
191
|
+
if (!/^scope:/m.test(content))
|
|
192
|
+
errors.push('Skill frontmatter must declare scope.');
|
|
193
|
+
if (previous && content.length < previous.length * 0.6)
|
|
194
|
+
errors.push('Edit retains less than 60% of prior skill.');
|
|
195
|
+
const validation = content.match(/^## Validation[^\n]*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m)?.[1] ?? '';
|
|
196
|
+
const commands = [...validation.matchAll(/```bash\s*\n([\s\S]*?)```/g)].map(m => m[1].replace(/\r\n/g, '\n').replace(/\n$/, '')).filter(Boolean);
|
|
197
|
+
if (!commands.length)
|
|
198
|
+
errors.push('Validation needs at least one fenced bash command.');
|
|
199
|
+
const checks = [];
|
|
200
|
+
if (!errors.length)
|
|
201
|
+
for (const command of commands) {
|
|
202
|
+
try {
|
|
203
|
+
const result = await run(command, root);
|
|
204
|
+
checks.push({ command, cwd: root, ...result, output: result.output.slice(-12000) });
|
|
205
|
+
if (!result.ok)
|
|
206
|
+
errors.push(`Validation failed: ${command}: ${result.output.slice(-1000)}`);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
errors.push(`Validation failed: ${command}: ${String(error)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { ok: !errors.length, errors, checks, description: entry.description };
|
|
213
|
+
}
|
|
184
214
|
//# sourceMappingURL=skill-auditor.js.map
|
package/dist/skill-catalog.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export interface SkillAdapterOverlay {
|
|
|
7
7
|
}
|
|
8
8
|
export interface SkillCatalogEntry extends SkillReference {
|
|
9
9
|
description: string;
|
|
10
|
+
origin?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
learning_status?: string;
|
|
10
13
|
headings: string[];
|
|
11
14
|
normalized_headings: string[];
|
|
12
15
|
line_count: number;
|
|
@@ -52,7 +55,7 @@ interface ParsedFrontmatter {
|
|
|
52
55
|
has_frontmatter: boolean;
|
|
53
56
|
}
|
|
54
57
|
export declare function parseSkillFrontmatter(raw: string): ParsedFrontmatter;
|
|
55
|
-
export declare function readSkillCatalogEntry(skill: SkillReference): SkillCatalogEntry;
|
|
58
|
+
export declare function readSkillCatalogEntry(skill: SkillReference, content?: string): SkillCatalogEntry;
|
|
56
59
|
export declare function listSkillCatalogEntries(): SkillCatalogEntry[];
|
|
57
60
|
export declare function summarizeSkillCatalog(entries?: SkillCatalogEntry[]): SkillCatalogSummary;
|
|
58
61
|
export declare function findSkillCatalogEntry(name: string, entries?: SkillCatalogEntry[]): SkillCatalogEntry | undefined;
|
package/dist/skill-catalog.js
CHANGED
|
@@ -134,7 +134,7 @@ function detectAdapterClient(relativePath) {
|
|
|
134
134
|
function listSkillAdapterOverlays(skillPath) {
|
|
135
135
|
const parsed = parseVirtualStorePath(skillPath);
|
|
136
136
|
if (parsed) {
|
|
137
|
-
const skillName = parsed.key.
|
|
137
|
+
const skillName = parsed.key.replace(/^knowledge\/skills\//, "").replace(/\/SKILL\.md$/, "");
|
|
138
138
|
const prefix = `knowledge/skills/${skillName}/agents/`;
|
|
139
139
|
return listStoreKeysByPathSync(parsed.storePath, prefix)
|
|
140
140
|
.filter((key) => key.toLowerCase() !== `${prefix}readme.md`)
|
|
@@ -181,10 +181,10 @@ function listSkillAdapterOverlays(skillPath) {
|
|
|
181
181
|
walk(agentsRoot);
|
|
182
182
|
return overlays.sort((a, b) => a.relative_path.localeCompare(b.relative_path));
|
|
183
183
|
}
|
|
184
|
-
export function readSkillCatalogEntry(skill) {
|
|
185
|
-
const raw = skill.source === "store" || !existsSync(skill.path)
|
|
184
|
+
export function readSkillCatalogEntry(skill, content) {
|
|
185
|
+
const raw = content ?? (skill.source === "store" || !existsSync(skill.path)
|
|
186
186
|
? readVirtualStorePathSync(skill.path) ?? readSkillInstructions(skill.name)
|
|
187
|
-
: readFileSync(skill.path, "utf-8");
|
|
187
|
+
: readFileSync(skill.path, "utf-8"));
|
|
188
188
|
const parsed = parseSkillFrontmatter(raw);
|
|
189
189
|
const headings = extractHeadings(parsed.body);
|
|
190
190
|
const normalizedHeadings = headings.map(normalizeHeading);
|
|
@@ -203,12 +203,13 @@ export function readSkillCatalogEntry(skill) {
|
|
|
203
203
|
const parsed = parseVirtualStorePath(skill.path);
|
|
204
204
|
if (!parsed)
|
|
205
205
|
return false;
|
|
206
|
-
const skillName = parsed.key.
|
|
206
|
+
const skillName = parsed.key.replace(/^knowledge\/skills\//, "").replace(/\/SKILL\.md$/, "");
|
|
207
207
|
return listStoreKeysByPathSync(parsed.storePath, `knowledge/skills/${skillName}/references/`).length > 0;
|
|
208
208
|
})();
|
|
209
209
|
return {
|
|
210
210
|
...skill,
|
|
211
211
|
description: parsed.attributes.description?.trim() ?? "",
|
|
212
|
+
origin: parsed.attributes.origin, label: parsed.attributes.label, learning_status: parsed.attributes.status,
|
|
212
213
|
headings,
|
|
213
214
|
normalized_headings: normalizedHeadings,
|
|
214
215
|
line_count: raw.replace(/\r\n/g, "\n").split("\n").length,
|
|
@@ -67,6 +67,7 @@ export interface ArchiveColdEventsResult {
|
|
|
67
67
|
bytes_written: number;
|
|
68
68
|
}
|
|
69
69
|
export declare class AcePackedStore implements IAcePackedStore {
|
|
70
|
+
private assertWriteBoundary;
|
|
70
71
|
private storePath;
|
|
71
72
|
private readOnly;
|
|
72
73
|
private fh;
|
|
@@ -91,7 +92,10 @@ export declare class AcePackedStore implements IAcePackedStore {
|
|
|
91
92
|
* first when old hot events should move into BGZF archive batches.
|
|
92
93
|
*/
|
|
93
94
|
compact(): Promise<void>;
|
|
94
|
-
|
|
95
|
+
discardPending(): void;
|
|
96
|
+
close(options?: {
|
|
97
|
+
commit?: boolean;
|
|
98
|
+
}): Promise<void>;
|
|
95
99
|
get(key: string): Promise<Uint8Array | undefined>;
|
|
96
100
|
/** Events dropped as malformed since this instance was opened (v1 migration only). */
|
|
97
101
|
get skippedMalformedEventCount(): number;
|
|
@@ -59,6 +59,7 @@ import { MAGIC, STORE_VERSION, HEADER_SIZE, HEADER_SIZE_V1, ContentSource, Entit
|
|
|
59
59
|
import { invalidateStoreSnapshotCache, validateChunkBounds } from "./store-snapshot.js";
|
|
60
60
|
import { crc32 } from "./crc32.js";
|
|
61
61
|
import { bgzfCompress, bgzfDecompress } from "./bgzf.js";
|
|
62
|
+
import { assertTestWriteAllowed, captureTestWriteBoundary } from "./test-write-boundary.js";
|
|
62
63
|
// ── Corruption error ─────────────────────────────────────────────────────────
|
|
63
64
|
export class AcePackedStoreCorruptionError extends Error {
|
|
64
65
|
constructor(message) {
|
|
@@ -317,6 +318,7 @@ function loadCommittedStateFromBytes(file) {
|
|
|
317
318
|
}
|
|
318
319
|
function quarantineCorruptSource(storePath, file, operation, error) {
|
|
319
320
|
const quarantinePath = `${storePath}.corrupt-${Date.now()}`;
|
|
321
|
+
assertTestWriteAllowed(quarantinePath);
|
|
320
322
|
writeFileSync(quarantinePath, file);
|
|
321
323
|
const message = error instanceof Error ? error.message : String(error);
|
|
322
324
|
throw new AcePackedStoreCorruptionError(`AcePackedStore ${operation} refused: corrupt source store quarantined at ${quarantinePath}; original left in place. Run 'ace repair' or 'ace fsck --repair'. Cause: ${message}`);
|
|
@@ -374,6 +376,7 @@ async function readExact(fh, buf, position, context = {}) {
|
|
|
374
376
|
}
|
|
375
377
|
// ── AcePackedStore ────────────────────────────────────────────────────────────
|
|
376
378
|
export class AcePackedStore {
|
|
379
|
+
assertWriteBoundary = () => { };
|
|
377
380
|
storePath = "";
|
|
378
381
|
readOnly = false;
|
|
379
382
|
fh = null;
|
|
@@ -392,6 +395,8 @@ export class AcePackedStore {
|
|
|
392
395
|
async open(path, opts = {}) {
|
|
393
396
|
this.storePath = path;
|
|
394
397
|
this.readOnly = opts.readOnly ?? false;
|
|
398
|
+
if (!this.readOnly)
|
|
399
|
+
this.assertWriteBoundary = captureTestWriteBoundary(path);
|
|
395
400
|
if (!existsSync(path)) {
|
|
396
401
|
if (this.readOnly)
|
|
397
402
|
throw new Error(`AcePackedStore: file not found: ${path}`);
|
|
@@ -400,9 +405,15 @@ export class AcePackedStore {
|
|
|
400
405
|
else {
|
|
401
406
|
await this._loadExisting(path);
|
|
402
407
|
}
|
|
408
|
+
if (!this.readOnly) {
|
|
409
|
+
this.assertWriteBoundary();
|
|
410
|
+
// Pin the actual store directory after a permitted first creation.
|
|
411
|
+
this.assertWriteBoundary = captureTestWriteBoundary(path);
|
|
412
|
+
}
|
|
403
413
|
this.fh = await fsOpen(path, this.readOnly ? "r" : "r+");
|
|
404
414
|
}
|
|
405
415
|
async _initNew(path) {
|
|
416
|
+
this.assertWriteBoundary();
|
|
406
417
|
mkdirSync(dirname(path), { recursive: true });
|
|
407
418
|
const evtBytes = serializeEventSection([]);
|
|
408
419
|
const idxBytes = TEXT.encode("{}");
|
|
@@ -423,6 +434,7 @@ export class AcePackedStore {
|
|
|
423
434
|
out.set(header, 0);
|
|
424
435
|
out.set(evtBytes, evtOff);
|
|
425
436
|
out.set(idxBytes, kvIdxOff);
|
|
437
|
+
this.assertWriteBoundary();
|
|
426
438
|
writeFileSync(path, out);
|
|
427
439
|
this.kvChunkEnd = HEADER_SIZE;
|
|
428
440
|
this.kvIndex = new Map();
|
|
@@ -449,6 +461,7 @@ export class AcePackedStore {
|
|
|
449
461
|
async commit() {
|
|
450
462
|
if (this.readOnly || !this.fh || !this.storePath || !this.dirty)
|
|
451
463
|
return;
|
|
464
|
+
this.assertWriteBoundary();
|
|
452
465
|
const currentFile = readFileSync(this.storePath);
|
|
453
466
|
const latest = loadCommittedStateOrQuarantine(this.storePath, new Uint8Array(currentFile.buffer, currentFile.byteOffset, currentFile.byteLength), "commit");
|
|
454
467
|
this.skippedMalformedCount += latest.skippedMalformed;
|
|
@@ -506,10 +519,13 @@ export class AcePackedStore {
|
|
|
506
519
|
out.set(evtBytes, evtOff);
|
|
507
520
|
out.set(idxBytes, kvIdxOff);
|
|
508
521
|
const tmpPath = `${this.storePath}.${process.pid}.${Date.now()}.commit.tmp`;
|
|
522
|
+
this.assertWriteBoundary();
|
|
523
|
+
assertTestWriteAllowed(tmpPath);
|
|
509
524
|
writeFileSync(tmpPath, out);
|
|
510
525
|
readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE));
|
|
511
526
|
await this.fh.close();
|
|
512
527
|
this.fh = null;
|
|
528
|
+
this.assertWriteBoundary();
|
|
513
529
|
renameSync(tmpPath, this.storePath);
|
|
514
530
|
invalidateStoreSnapshotCache(this.storePath);
|
|
515
531
|
this.fh = await fsOpen(this.storePath, "r+");
|
|
@@ -530,6 +546,7 @@ export class AcePackedStore {
|
|
|
530
546
|
async compact() {
|
|
531
547
|
if (!this.storePath || this.readOnly)
|
|
532
548
|
return;
|
|
549
|
+
this.assertWriteBoundary();
|
|
533
550
|
if (this.dirty) {
|
|
534
551
|
await this.commit();
|
|
535
552
|
}
|
|
@@ -585,12 +602,15 @@ export class AcePackedStore {
|
|
|
585
602
|
out.set(evtBytes, evtOff);
|
|
586
603
|
out.set(idxBytes, kvIdxOff);
|
|
587
604
|
const tmpPath = this.storePath + ".tmp";
|
|
605
|
+
this.assertWriteBoundary();
|
|
606
|
+
assertTestWriteAllowed(tmpPath);
|
|
588
607
|
writeFileSync(tmpPath, out);
|
|
589
608
|
readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE)); // validate
|
|
590
609
|
if (this.fh) {
|
|
591
610
|
await this.fh.close();
|
|
592
611
|
this.fh = null;
|
|
593
612
|
}
|
|
613
|
+
this.assertWriteBoundary();
|
|
594
614
|
renameSync(tmpPath, this.storePath);
|
|
595
615
|
invalidateStoreSnapshotCache(this.storePath);
|
|
596
616
|
this.fh = await fsOpen(this.storePath, "r+");
|
|
@@ -599,7 +619,15 @@ export class AcePackedStore {
|
|
|
599
619
|
this.pendingDeletes.clear();
|
|
600
620
|
this.dirty = false;
|
|
601
621
|
}
|
|
602
|
-
|
|
622
|
+
discardPending() {
|
|
623
|
+
this.pending = [];
|
|
624
|
+
this.pendingKv.clear();
|
|
625
|
+
this.pendingDeletes.clear();
|
|
626
|
+
this.dirty = false;
|
|
627
|
+
}
|
|
628
|
+
async close(options = {}) {
|
|
629
|
+
if (options.commit === false)
|
|
630
|
+
this.discardPending();
|
|
603
631
|
let commitError;
|
|
604
632
|
try {
|
|
605
633
|
if (this.dirty) {
|
|
@@ -746,6 +774,7 @@ export class AcePackedStore {
|
|
|
746
774
|
if (!this.storePath || this.readOnly) {
|
|
747
775
|
return { batches_written: 0, events_archived: 0, bytes_written: 0 };
|
|
748
776
|
}
|
|
777
|
+
this.assertWriteBoundary();
|
|
749
778
|
if (this.dirty) {
|
|
750
779
|
await this.commit();
|
|
751
780
|
}
|
|
@@ -846,12 +875,15 @@ export class AcePackedStore {
|
|
|
846
875
|
out.set(evtBytes, evtOff);
|
|
847
876
|
out.set(idxBytes, kvIdxOff);
|
|
848
877
|
const tmpPath = `${this.storePath}.${process.pid}.${Date.now()}.archive.tmp`;
|
|
878
|
+
this.assertWriteBoundary();
|
|
879
|
+
assertTestWriteAllowed(tmpPath);
|
|
849
880
|
writeFileSync(tmpPath, out);
|
|
850
881
|
readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE));
|
|
851
882
|
if (this.fh) {
|
|
852
883
|
await this.fh.close();
|
|
853
884
|
this.fh = null;
|
|
854
885
|
}
|
|
886
|
+
this.assertWriteBoundary();
|
|
855
887
|
renameSync(tmpPath, this.storePath);
|
|
856
888
|
invalidateStoreSnapshotCache(this.storePath);
|
|
857
889
|
this.fh = await fsOpen(this.storePath, "r+");
|