@smartmemory/stratum 0.3.3 → 0.3.4
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/dist/cli/query_gate.js +2 -1
- package/dist/cli/query_gate.js.map +1 -1
- package/dist/cli/stratum.js +2 -1
- package/dist/cli/stratum.js.map +1 -1
- package/dist/connectors/background.js +6 -2
- package/dist/connectors/background.js.map +1 -1
- package/dist/connectors/codex.js +72 -1
- package/dist/connectors/codex.js.map +1 -1
- package/dist/contracts/mcp-surface.json +13 -1
- package/dist/engine/engine.js +105 -0
- package/dist/engine/engine.js.map +1 -1
- package/dist/engine/evaluate.js +61 -0
- package/dist/engine/evaluate.js.map +1 -0
- package/dist/ir/schema.js +8 -1
- package/dist/ir/schema.js.map +1 -1
- package/dist/ir/validate.js +3 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/mcp/server.js +6 -3
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
package/dist/engine/engine.js
CHANGED
|
@@ -4,6 +4,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
+
import { z } from "zod";
|
|
7
8
|
import { runAgent } from "../connectors/runner.js";
|
|
8
9
|
import { extractReferences } from "../ir/refs.js";
|
|
9
10
|
import { validateSpec } from "../ir/validate.js";
|
|
@@ -11,6 +12,37 @@ import { BudgetLedger, validUsage } from "./ledger.js";
|
|
|
11
12
|
import { commitCheckpoint, revertCheckpoint } from "./checkpoint.js";
|
|
12
13
|
import { StateStore } from "./state.js";
|
|
13
14
|
const execFileAsync = promisify(execFile);
|
|
15
|
+
/**
|
|
16
|
+
* The fixed shape every S1 `evaluate:` step must return. Engine-owned and
|
|
17
|
+
* strict — an author's `out` contract governs what is *referenceable*, this
|
|
18
|
+
* schema governs what the data must *be*. It is the trust anchor: a transport
|
|
19
|
+
* failure can never be laundered into a `closed` verdict, and the cross-field
|
|
20
|
+
* invariants (`closed` ⇒ no children, `open` ⇒ ≥1 child) are enforced here.
|
|
21
|
+
*/
|
|
22
|
+
export const evaluatorResultSchema = z.object({
|
|
23
|
+
status: z.enum(["closed", "open", "failed"]),
|
|
24
|
+
children: z.array(z.unknown()),
|
|
25
|
+
reason: z.string(),
|
|
26
|
+
score: z.number().optional(),
|
|
27
|
+
route: z.enum(["claude", "codex"]).optional(),
|
|
28
|
+
}).strict().superRefine((result, ctx) => {
|
|
29
|
+
if (result.status === "closed" && result.children.length > 0) {
|
|
30
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["children"], message: "a closed verdict must carry no children" });
|
|
31
|
+
}
|
|
32
|
+
if (result.status === "open" && result.children.length === 0) {
|
|
33
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["children"], message: "an open verdict must carry at least one child" });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* The engine validates the runner's ENVELOPE at runtime, not just the verdict
|
|
38
|
+
* inside it — the `ok` discriminant is the runner's word for whether it even
|
|
39
|
+
* succeeded, and a malformed envelope must never let a `{status:"closed"}`
|
|
40
|
+
* payload reach the success path. Same trust posture as the judge verdict.
|
|
41
|
+
*/
|
|
42
|
+
const evaluateRunResultSchema = z.discriminatedUnion("ok", [
|
|
43
|
+
z.object({ ok: z.literal(true), result: z.unknown() }),
|
|
44
|
+
z.object({ ok: z.literal(false), kind: z.enum(["exit", "timeout", "parse"]), reason: z.string() }),
|
|
45
|
+
]);
|
|
14
46
|
export class CheckpointOperationError extends Error {
|
|
15
47
|
errorType;
|
|
16
48
|
available;
|
|
@@ -33,6 +65,7 @@ export class StratumEngine {
|
|
|
33
65
|
store;
|
|
34
66
|
evaluator;
|
|
35
67
|
judge;
|
|
68
|
+
evaluateRunner;
|
|
36
69
|
connector;
|
|
37
70
|
// Serializes load-modify-save per run: plan may hand out several ready steps, so
|
|
38
71
|
// stepDone/resume can race in-process. The state root is owned by one engine process in v1.
|
|
@@ -51,6 +84,8 @@ export class StratumEngine {
|
|
|
51
84
|
this.evaluator = options.evaluator;
|
|
52
85
|
if (options.judge)
|
|
53
86
|
this.judge = options.judge;
|
|
87
|
+
if (options.evaluateRunner)
|
|
88
|
+
this.evaluateRunner = options.evaluateRunner;
|
|
54
89
|
this.connector = options.connector ?? defaultConnector;
|
|
55
90
|
}
|
|
56
91
|
async loadRun(runId) {
|
|
@@ -895,6 +930,74 @@ export class StratumEngine {
|
|
|
895
930
|
break;
|
|
896
931
|
}
|
|
897
932
|
}
|
|
933
|
+
if (step.evaluate !== undefined) {
|
|
934
|
+
const attempt = state.attempts.length + 1;
|
|
935
|
+
const evaluate = step.evaluate;
|
|
936
|
+
// Deterministic, single-shot, and atomic: no intermediate `running`
|
|
937
|
+
// is persisted, so a crash mid-evaluate leaves the step `pending` and
|
|
938
|
+
// it re-runs on resume. `forceExhausted` terminalizes every failure —
|
|
939
|
+
// retrying a deterministic evaluator is pointless (backtrack is S3).
|
|
940
|
+
const evalFail = (reason) => this.failAttempt(run, spec, contracts, scope, step, state, attempt, reason, {}, undefined, undefined, true);
|
|
941
|
+
if (!this.evaluateRunner) {
|
|
942
|
+
await evalFail("evaluate: no evaluate runner configured");
|
|
943
|
+
changed = true;
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
let input;
|
|
947
|
+
try {
|
|
948
|
+
input = evaluate.in === undefined ? undefined : this.renderValue(evaluate.in, scope);
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
await evalFail(`evaluate: input render failed: ${message(error)}`);
|
|
952
|
+
changed = true;
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
// Sandboxing (workspaceRoot jail) is deferred — see design open question 3.
|
|
956
|
+
let rawOutcome;
|
|
957
|
+
try {
|
|
958
|
+
rawOutcome = await this.evaluateRunner({ command: evaluate.command, input, timeoutMs: evaluate.timeout_ms }, {});
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
await evalFail(`evaluate: runner threw: ${message(error)}`);
|
|
962
|
+
changed = true;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
const envelope = evaluateRunResultSchema.safeParse(rawOutcome);
|
|
966
|
+
if (!envelope.success) {
|
|
967
|
+
await evalFail(`evaluate: runner returned a malformed result envelope: ${envelope.error.message}`);
|
|
968
|
+
changed = true;
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
const outcome = envelope.data;
|
|
972
|
+
if (!outcome.ok) {
|
|
973
|
+
const detail = outcome.reason;
|
|
974
|
+
const reason = outcome.kind === "exit" ? `evaluate: command exited with a non-zero status (${detail})`
|
|
975
|
+
: outcome.kind === "timeout" ? `evaluate: command timed out (${detail})`
|
|
976
|
+
: `evaluate: output was not valid JSON (${detail})`;
|
|
977
|
+
await evalFail(reason);
|
|
978
|
+
changed = true;
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
const parsed = evaluatorResultSchema.safeParse(outcome.result);
|
|
982
|
+
if (!parsed.success) {
|
|
983
|
+
await evalFail(`evaluate: output failed the evaluator-result contract: ${parsed.error.message}`);
|
|
984
|
+
changed = true;
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
const outError = this.contractError(step, parsed.data, contracts);
|
|
988
|
+
if (outError) {
|
|
989
|
+
await evalFail(`evaluate: output failed the ${step.out} contract: ${outError}`);
|
|
990
|
+
changed = true;
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
state.status = "succeeded";
|
|
994
|
+
state.output = parsed.data;
|
|
995
|
+
state.attempts.push({ attempt, at: now(), result: parsed.data });
|
|
996
|
+
this.event(run, "result", this.scopedId(scope, step.id), { attempt, result: parsed.data });
|
|
997
|
+
await this.persist(run);
|
|
998
|
+
changed = true;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
898
1001
|
if (step.do === undefined) {
|
|
899
1002
|
await this.failScope(run, spec, contracts, scope, "construct is outside P1 engine scope");
|
|
900
1003
|
break;
|
|
@@ -2231,6 +2334,8 @@ function stringLeaves(step) {
|
|
|
2231
2334
|
// over "${prep.output.items}" must wait for prep, not fail at resolve time.
|
|
2232
2335
|
if (step.with !== undefined)
|
|
2233
2336
|
collect(step.with);
|
|
2337
|
+
if (step.evaluate?.in !== undefined)
|
|
2338
|
+
collect(step.evaluate.in);
|
|
2234
2339
|
if (step.fanout !== undefined) {
|
|
2235
2340
|
collect(step.fanout.over);
|
|
2236
2341
|
for (const stage of step.fanout.steps) {
|