@rulvar/cli 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{io-Dh-Fa5Nf.js → io-CL4wldEQ.js} +92 -4
- package/package.json +7 -7
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-
|
|
1
|
+
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-CL4wldEQ.js";
|
|
2
2
|
import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, costReportFromJournal, maskSecrets, normalizeEntry, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
|
|
3
3
|
//#region src/server.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, parseModelRef, priceUsdOf, proposalStatement, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
|
|
1
|
+
import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, parseModelRef, priceUsdOf, proposalStatement, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -303,6 +303,35 @@ async function driveRun(options) {
|
|
|
303
303
|
handle = options.engine.resume(handle.runId, options.workflow, { args: options.args });
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Renders the `resume --dry-run` preview (the v1.23.0 review): the
|
|
308
|
+
* replay accounting from `handle.preview`, then what a real resume
|
|
309
|
+
* would do. The engine's replay-strict mode guarantees zero journal or
|
|
310
|
+
* meta writes and zero adapter calls. A preview that stops at a
|
|
311
|
+
* would-be-live call is a SUCCESSFUL preview (the miss IS the answer),
|
|
312
|
+
* so the exit code is 0 either way; only structural failures (missing
|
|
313
|
+
* run, unregistered workflow, args refusal) exit nonzero via their
|
|
314
|
+
* typed errors.
|
|
315
|
+
*/
|
|
316
|
+
async function reportDryRun(handle, io) {
|
|
317
|
+
const outcome = await handle.result;
|
|
318
|
+
const preview = await handle.preview;
|
|
319
|
+
io.err("dry-run preview (zero journal or meta writes, zero adapter calls):");
|
|
320
|
+
io.err(` hits: ${preview.hits} misses: ${preview.misses} reruns: ${preview.reruns} skipped: ${preview.skipped}`);
|
|
321
|
+
io.err(preview.orphaned.length === 0 ? " orphaned effect roots: none" : ` orphaned effect roots (entryRefs): ${preview.orphaned.join(", ")}`);
|
|
322
|
+
if (preview.invalidResolutions.length === 0) io.err(" invalid resolutions: none");
|
|
323
|
+
else for (const invalid of preview.invalidResolutions) io.err(` invalid resolution at seq ${invalid.seq}: ${invalid.detail}`);
|
|
324
|
+
if (outcome.error?.code === "journal_miss") {
|
|
325
|
+
io.err(` stopped at the first would-be-live call: ${outcome.error.message}`);
|
|
326
|
+
io.err(" a real resume would perform new paid work from this point");
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
io.err(` would settle: ${outcome.status}`);
|
|
330
|
+
if (outcome.error !== void 0) io.err(` error: ${outcome.error.message}`);
|
|
331
|
+
for (const pending of outcome.pending) io.err(` pending: ${pending.key} (entry ${pending.entryRef})`);
|
|
332
|
+
if (outcome.value !== void 0) io.out(JSON.stringify(outcome.value, null, 2));
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
306
335
|
/** Renders the settled outcome; returns the process exit code. */
|
|
307
336
|
function reportOutcome(outcome, io) {
|
|
308
337
|
io.err(`status: ${outcome.status}`);
|
|
@@ -366,7 +395,12 @@ const GRAMMAR = {
|
|
|
366
395
|
resume: {
|
|
367
396
|
command: "resume",
|
|
368
397
|
positionals: ["<runId>"],
|
|
369
|
-
flags: [
|
|
398
|
+
flags: [
|
|
399
|
+
ARGS,
|
|
400
|
+
STORE,
|
|
401
|
+
{ name: "dry-run" },
|
|
402
|
+
{ name: "allow-args-change" }
|
|
403
|
+
]
|
|
370
404
|
},
|
|
371
405
|
"runs ls": {
|
|
372
406
|
command: "runs ls",
|
|
@@ -645,10 +679,52 @@ async function runCommand(argv, context) {
|
|
|
645
679
|
args
|
|
646
680
|
}), context.io);
|
|
647
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* The resume args safety gate (the v1.23.0 review): a run's logical
|
|
684
|
+
* identity includes its genesis args, so a resume that silently drops,
|
|
685
|
+
* adds, or changes them is refused BEFORE the engine starts (zero
|
|
686
|
+
* provider calls, zero journal writes). `--allow-args-change` is the
|
|
687
|
+
* explicit override for every divergence class; legacy runs recorded
|
|
688
|
+
* before the binding existed require it (or explicit `--args`) because
|
|
689
|
+
* nothing can be verified against them.
|
|
690
|
+
*/
|
|
691
|
+
function enforceArgsBinding(input) {
|
|
692
|
+
const { meta, argsGiven, args, allowChange, io } = input;
|
|
693
|
+
if (meta.argsProvided === void 0) {
|
|
694
|
+
if (!argsGiven && !allowChange) throw new ConfigError(`run '${meta.runId}' predates the args binding (rulvar < 1.24.0), so the CLI cannot tell whether it was started with --args, and resuming without them silently changes the logical run if any were used at start. Re-supply the original --args, or acknowledge explicitly with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
695
|
+
if (argsGiven) io.err(`warning: run '${meta.runId}' predates the args binding; the supplied --args cannot be verified against the original invocation`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (meta.argsProvided) {
|
|
699
|
+
if (!argsGiven) {
|
|
700
|
+
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITH args, but this resume supplies none; the workflow would see undefined and every args-dependent call would become new paid work instead of a replay. Re-supply the original --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
701
|
+
io.err(`warning: resuming '${meta.runId}' without its genesis args (--allow-args-change)`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (meta.argsHash === void 0) {
|
|
705
|
+
io.err(`warning: run '${meta.runId}' recorded args presence but no hash (the genesis args were not JCS-serializable); the supplied --args cannot be verified`);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const supplied = hashRunArgs(args);
|
|
709
|
+
if (supplied !== meta.argsHash) {
|
|
710
|
+
if (!allowChange) throw new ConfigError(`--args does not match the args run '${meta.runId}' was started with (recorded hash ${meta.argsHash.slice(0, 12)}, supplied ${supplied?.slice(0, 12) ?? "none"}); changed args silently change the logical run and re-pay every args-dependent call. Force deliberately with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
711
|
+
io.err(`warning: resuming '${meta.runId}' with changed args (--allow-args-change)`);
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (argsGiven) {
|
|
716
|
+
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITHOUT args, but this resume supplies some; added args silently change the logical run. Drop --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
717
|
+
io.err(`warning: resuming no-args run '${meta.runId}' with args (--allow-args-change)`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
648
720
|
async function resumeCommand(argv, context) {
|
|
649
721
|
const parsed = parseCommand(GRAMMAR.resume, argv);
|
|
650
722
|
const runId = parsed.positionals[0];
|
|
651
|
-
const
|
|
723
|
+
const rawArgs = parsed.values.args;
|
|
724
|
+
const args = parseArgsJson(rawArgs);
|
|
725
|
+
const argsGiven = rawArgs !== void 0;
|
|
726
|
+
const dryRun = parsed.values["dry-run"] === true;
|
|
727
|
+
const allowChange = parsed.values["allow-args-change"] === true;
|
|
652
728
|
const store = parsed.values.store;
|
|
653
729
|
const assembled = assembleEngine({
|
|
654
730
|
config: await loadCliConfig(context.cwd),
|
|
@@ -657,10 +733,21 @@ async function resumeCommand(argv, context) {
|
|
|
657
733
|
});
|
|
658
734
|
const meta = (await assembled.store.listRuns()).find((m) => m.runId === runId);
|
|
659
735
|
if (meta === void 0) throw new ConfigError(`run '${runId}' not found in the store`);
|
|
736
|
+
enforceArgsBinding({
|
|
737
|
+
meta,
|
|
738
|
+
argsGiven,
|
|
739
|
+
args,
|
|
740
|
+
allowChange,
|
|
741
|
+
io: context.io
|
|
742
|
+
});
|
|
660
743
|
const name = meta.workflowName;
|
|
661
744
|
const workflow = name === void 0 ? void 0 : assembled.workflows[name];
|
|
662
745
|
if (workflow === void 0) throw new ConfigError(`run '${runId}' was started from workflow '${name ?? "(unknown)"}'; register it under that name in rulvar.config.mjs workflows to resume (resume requires the in-process workflow value)`);
|
|
663
|
-
const first = assembled.engine.resume(runId, workflow, {
|
|
746
|
+
const first = assembled.engine.resume(runId, workflow, {
|
|
747
|
+
args,
|
|
748
|
+
...dryRun ? { dryRun: true } : {}
|
|
749
|
+
});
|
|
750
|
+
if (dryRun) return await reportDryRun(first, context.io);
|
|
664
751
|
return reportOutcome(await driveRun({
|
|
665
752
|
engine: assembled.engine,
|
|
666
753
|
workflow,
|
|
@@ -701,6 +788,7 @@ async function inspectCommand(argv, context) {
|
|
|
701
788
|
const entries = await assembled.store.load(runId);
|
|
702
789
|
context.io.out(`run ${meta.runId}: ${meta.status} (updated ${meta.updatedAt})`);
|
|
703
790
|
if (meta.workflowName !== void 0) context.io.out(`workflow: ${meta.workflowName}`);
|
|
791
|
+
if (meta.argsProvided !== void 0) context.io.out(meta.argsProvided ? `args at genesis: provided${meta.argsHash === void 0 ? " (no hash: not JCS-serializable)" : ` (hash ${meta.argsHash})`}` : "args at genesis: none");
|
|
704
792
|
const byKind = /* @__PURE__ */ new Map();
|
|
705
793
|
let openSuspensions = 0;
|
|
706
794
|
const resolvedRefs = /* @__PURE__ */ new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Rulvar shell: run/resume/runs/inspect/plan/kb commands, TUI progress, createServer, createWorker, OTel exporter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.24.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.0",
|
|
29
29
|
"tsdown": "^0.22.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/testing": "1.
|
|
32
|
-
"@rulvar/store-sqlite": "1.
|
|
33
|
-
"@rulvar/planner": "1.
|
|
34
|
-
"@rulvar/
|
|
35
|
-
"@rulvar/
|
|
31
|
+
"@rulvar/testing": "1.24.0",
|
|
32
|
+
"@rulvar/store-sqlite": "1.24.0",
|
|
33
|
+
"@rulvar/planner": "1.24.0",
|
|
34
|
+
"@rulvar/evals": "1.24.0",
|
|
35
|
+
"@rulvar/plan": "1.24.0"
|
|
36
36
|
},
|
|
37
37
|
"bin": {
|
|
38
38
|
"rulvar": "./dist/cli.js"
|