@rulvar/cli 1.23.0 → 1.24.1
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-CV_G44A2.js} +121 -6
- 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-CV_G44A2.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",
|
|
@@ -607,14 +641,35 @@ async function loadCompanion(loading, specifier, command, missingMessage) {
|
|
|
607
641
|
throw new Error(`${command}: ${specifier} is installed but failed to load; the cause below is a defect in the installed package or its dependencies, not a missing install`, { cause: error });
|
|
608
642
|
}
|
|
609
643
|
}
|
|
610
|
-
/**
|
|
644
|
+
/**
|
|
645
|
+
* Parses --args JSON into workflow arguments; undefined when absent.
|
|
646
|
+
*
|
|
647
|
+
* CLI args must be representable in canonical JCS, i.e. finite JSON. A
|
|
648
|
+
* numeric literal that overflows JavaScript's finite range parses to
|
|
649
|
+
* Infinity, which `hashRunArgs` cannot canonicalize, so genesis would
|
|
650
|
+
* record `argsProvided` WITHOUT a hash and the resume gate would soften
|
|
651
|
+
* to an unverifiable warning that lets changed args through (v1.24.0
|
|
652
|
+
* review P2-1). A CLI value always arrives as JSON text, so it can
|
|
653
|
+
* always be canonicalized; reject the non-finite case here, before any
|
|
654
|
+
* config, store, or adapter loads, instead of letting it defeat the gate
|
|
655
|
+
* later. In-process hosts keep the wider engine contract (functions,
|
|
656
|
+
* BigInt, cycles record presence without a hash); the CLI does not need
|
|
657
|
+
* it.
|
|
658
|
+
*/
|
|
611
659
|
function parseArgsJson(raw) {
|
|
612
660
|
if (raw === void 0) return;
|
|
661
|
+
let parsed;
|
|
613
662
|
try {
|
|
614
|
-
|
|
663
|
+
parsed = JSON.parse(raw);
|
|
615
664
|
} catch {
|
|
616
665
|
throw new ConfigError(`--args is not valid JSON: ${raw}`);
|
|
617
666
|
}
|
|
667
|
+
try {
|
|
668
|
+
hashRunArgs(parsed);
|
|
669
|
+
} catch {
|
|
670
|
+
throw new ConfigError(`--args is not representable as canonical JSON: a numeric value overflows JavaScript's finite range (e.g. 1e400 parses to Infinity). Supply finite JSON so the run's args binding can be hashed and later verified on resume: ${raw}`);
|
|
671
|
+
}
|
|
672
|
+
return parsed;
|
|
618
673
|
}
|
|
619
674
|
async function runCommand(argv, context) {
|
|
620
675
|
const parsed = parseCommand(GRAMMAR.run, argv);
|
|
@@ -645,10 +700,58 @@ async function runCommand(argv, context) {
|
|
|
645
700
|
args
|
|
646
701
|
}), context.io);
|
|
647
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* The resume args safety gate (the v1.23.0 review): a run's logical
|
|
705
|
+
* identity includes its genesis args, so a resume that silently drops,
|
|
706
|
+
* adds, or changes them is refused BEFORE the engine starts (zero
|
|
707
|
+
* provider calls, zero journal writes). `--allow-args-change` is the
|
|
708
|
+
* explicit override for every divergence class; legacy runs recorded
|
|
709
|
+
* before the binding existed require it (or explicit `--args`) because
|
|
710
|
+
* nothing can be verified against them.
|
|
711
|
+
*/
|
|
712
|
+
function enforceArgsBinding(input) {
|
|
713
|
+
const { meta, argsGiven, args, allowChange, io } = input;
|
|
714
|
+
if (meta.argsProvided === void 0) {
|
|
715
|
+
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)}`);
|
|
716
|
+
if (argsGiven) io.err(`warning: run '${meta.runId}' predates the args binding; the supplied --args cannot be verified against the original invocation`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (meta.argsProvided) {
|
|
720
|
+
if (!argsGiven) {
|
|
721
|
+
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)}`);
|
|
722
|
+
io.err(`warning: resuming '${meta.runId}' without its genesis args (--allow-args-change)`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (meta.argsHash === void 0) {
|
|
726
|
+
if (!allowChange) throw new ConfigError(`run '${meta.runId}' started WITH args but recorded no verifiable hash (the genesis args were not JCS-serializable), so the CLI cannot confirm the supplied --args match the original; resuming risks silently changing the logical run and re-paying every args-dependent call. Force deliberately with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
727
|
+
io.err(`warning: run '${meta.runId}' recorded args presence but no hash (genesis args not JCS-serializable); the supplied --args cannot be verified (--allow-args-change)`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
let supplied;
|
|
731
|
+
try {
|
|
732
|
+
supplied = hashRunArgs(args);
|
|
733
|
+
} catch {
|
|
734
|
+
throw new ConfigError(`--args cannot be canonicalized to compare against run '${meta.runId}' (a numeric value overflows the finite range, or the value is otherwise not canonical JSON); supply finite JSON, or force the resume with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
735
|
+
}
|
|
736
|
+
if (supplied !== meta.argsHash) {
|
|
737
|
+
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)}`);
|
|
738
|
+
io.err(`warning: resuming '${meta.runId}' with changed args (--allow-args-change)`);
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (argsGiven) {
|
|
743
|
+
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)}`);
|
|
744
|
+
io.err(`warning: resuming no-args run '${meta.runId}' with args (--allow-args-change)`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
648
747
|
async function resumeCommand(argv, context) {
|
|
649
748
|
const parsed = parseCommand(GRAMMAR.resume, argv);
|
|
650
749
|
const runId = parsed.positionals[0];
|
|
651
|
-
const
|
|
750
|
+
const rawArgs = parsed.values.args;
|
|
751
|
+
const args = parseArgsJson(rawArgs);
|
|
752
|
+
const argsGiven = rawArgs !== void 0;
|
|
753
|
+
const dryRun = parsed.values["dry-run"] === true;
|
|
754
|
+
const allowChange = parsed.values["allow-args-change"] === true;
|
|
652
755
|
const store = parsed.values.store;
|
|
653
756
|
const assembled = assembleEngine({
|
|
654
757
|
config: await loadCliConfig(context.cwd),
|
|
@@ -657,10 +760,21 @@ async function resumeCommand(argv, context) {
|
|
|
657
760
|
});
|
|
658
761
|
const meta = (await assembled.store.listRuns()).find((m) => m.runId === runId);
|
|
659
762
|
if (meta === void 0) throw new ConfigError(`run '${runId}' not found in the store`);
|
|
763
|
+
enforceArgsBinding({
|
|
764
|
+
meta,
|
|
765
|
+
argsGiven,
|
|
766
|
+
args,
|
|
767
|
+
allowChange,
|
|
768
|
+
io: context.io
|
|
769
|
+
});
|
|
660
770
|
const name = meta.workflowName;
|
|
661
771
|
const workflow = name === void 0 ? void 0 : assembled.workflows[name];
|
|
662
772
|
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, {
|
|
773
|
+
const first = assembled.engine.resume(runId, workflow, {
|
|
774
|
+
args,
|
|
775
|
+
...dryRun ? { dryRun: true } : {}
|
|
776
|
+
});
|
|
777
|
+
if (dryRun) return await reportDryRun(first, context.io);
|
|
664
778
|
return reportOutcome(await driveRun({
|
|
665
779
|
engine: assembled.engine,
|
|
666
780
|
workflow,
|
|
@@ -701,6 +815,7 @@ async function inspectCommand(argv, context) {
|
|
|
701
815
|
const entries = await assembled.store.load(runId);
|
|
702
816
|
context.io.out(`run ${meta.runId}: ${meta.status} (updated ${meta.updatedAt})`);
|
|
703
817
|
if (meta.workflowName !== void 0) context.io.out(`workflow: ${meta.workflowName}`);
|
|
818
|
+
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
819
|
const byKind = /* @__PURE__ */ new Map();
|
|
705
820
|
let openSuspensions = 0;
|
|
706
821
|
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.1",
|
|
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.1"
|
|
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/
|
|
32
|
-
"@rulvar/
|
|
33
|
-
"@rulvar/planner": "1.
|
|
34
|
-
"@rulvar/plan": "1.
|
|
35
|
-
"@rulvar/evals": "1.
|
|
31
|
+
"@rulvar/store-sqlite": "1.24.1",
|
|
32
|
+
"@rulvar/testing": "1.24.1",
|
|
33
|
+
"@rulvar/planner": "1.24.1",
|
|
34
|
+
"@rulvar/plan": "1.24.1",
|
|
35
|
+
"@rulvar/evals": "1.24.1"
|
|
36
36
|
},
|
|
37
37
|
"bin": {
|
|
38
38
|
"rulvar": "./dist/cli.js"
|