@rulvar/cli 1.46.0 → 1.47.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as processIo } from "./io-BXU6hEnw.js";
2
+ import { r as runCli, t as processIo } from "./io-BK1hP3Od.js";
3
3
  import { sanitizeTerminalText } from "@rulvar/core";
4
4
  import { inspect } from "node:util";
5
5
  //#region src/cli.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as looksLikeFile, a as resumeCommand, c as driveRun, d as attachProgress, f as renderEventLine, g as loadWorkflowModule, h as loadCliConfig, i as inspectCommand, l as reportOutcome, m as assembleEngine, n as HELP, o as runCommand, p as DEFAULT_STORE_DIR, r as runCli, s as runsLsCommand, t as processIo, u as strictExitCode } from "./io-BXU6hEnw.js";
1
+ import { _ as looksLikeFile, a as resumeCommand, c as driveRun, d as attachProgress, f as renderEventLine, g as loadWorkflowModule, h as loadCliConfig, i as inspectCommand, l as reportOutcome, m as assembleEngine, n as HELP, o as runCommand, p as DEFAULT_STORE_DIR, r as runCli, s as runsLsCommand, t as processIo, u as strictExitCode } from "./io-BK1hP3Od.js";
2
2
  import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, costReportFromJournal, maskSecrets, normalizeEntry, readRunMeta, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
3
3
  //#region src/server.ts
4
4
  /**
@@ -381,14 +381,16 @@ function createServer(options) {
381
381
  }
382
382
  const outcome = await run.handle.resolveExternal(key, value);
383
383
  let resumed = false;
384
- if (outcome.applied && settledSuspended) {
385
- const workflow = workflows[run.workflowName];
386
- if (workflow === void 0) return json(409, { error: {
387
- code: "config",
388
- message: `resolution applied, but workflow '${run.workflowName}' is no longer registered; resume it from a worker or a process with the registration`
389
- } });
390
- attach(run, engine.resume(run.runId, workflow, { args: run.args }));
391
- resumed = true;
384
+ if (outcome.applied && outcome.woke !== true) {
385
+ if ((settledSuspended ? run.outcome : await run.handle.result).status === "suspended") {
386
+ const workflow = workflows[run.workflowName];
387
+ if (workflow === void 0) return json(409, { error: {
388
+ code: "config",
389
+ message: `resolution applied, but workflow '${run.workflowName}' is no longer registered; resume it from a worker or a process with the registration`
390
+ } });
391
+ attach(run, engine.resume(run.runId, workflow, { args: run.args }));
392
+ resumed = true;
393
+ }
392
394
  }
393
395
  return json(200, {
394
396
  ...outcome,
@@ -1,4 +1,4 @@
1
- import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, parseModelRef, priceUsdOf, proposalStatement, readRunMeta, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
1
+ import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, LeaseHeldError, auditRuns, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, parseModelRef, priceUsdOf, proposalStatement, readRunMeta, reconcileRunMeta, 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";
@@ -443,6 +443,11 @@ const GRAMMAR = {
443
443
  flags: [STORE],
444
444
  note: "(no aliases in v1)"
445
445
  },
446
+ "runs audit": {
447
+ command: "runs audit",
448
+ positionals: [],
449
+ flags: [STORE, { name: "repair" }]
450
+ },
446
451
  inspect: {
447
452
  command: "inspect",
448
453
  positionals: ["<runId>"],
@@ -514,6 +519,8 @@ const GRAMMAR = {
514
519
  };
515
520
  /** The kb dispatch line: subcommands carry their own grammar entries. */
516
521
  const KB_FAMILY_USAGE = "usage: rulvar kb <list | inbox | gate | sweep> (no aliases in v1)";
522
+ /** The runs dispatch line: subcommands carry their own grammar entries. */
523
+ const RUNS_FAMILY_USAGE = "usage: rulvar runs <ls | audit> (no aliases in v1)";
517
524
  function renderFlags(flags) {
518
525
  const tokens = [];
519
526
  const renderedGroups = /* @__PURE__ */ new Set();
@@ -554,6 +561,7 @@ function helpCommandLines() {
554
561
  GRAMMAR.run,
555
562
  GRAMMAR.resume,
556
563
  GRAMMAR["runs ls"],
564
+ GRAMMAR["runs audit"],
557
565
  GRAMMAR.inspect,
558
566
  GRAMMAR.plan
559
567
  ];
@@ -854,6 +862,60 @@ async function resumeCommand(argv, context) {
854
862
  const base = reportOutcome(outcome, context.io);
855
863
  return parsed.values.strict === true ? strictExitCode(outcome, base, context.io) : base;
856
864
  }
865
+ /**
866
+ * The stranded run probe and reconciler (fenced run state RFC, phase
867
+ * 3): audits every run the catalog lists against its journal, prints
868
+ * the divergences worker sweeps can never see, and with --repair
869
+ * rewrites the sound ones from the journal (under a brief lease when
870
+ * the store is leasable, so a live owner is never raced). Exit 0 when
871
+ * the catalog ends clean; exit 1 while any divergence remains.
872
+ */
873
+ async function runsAuditCommand(argv, context) {
874
+ const parsed = parseCommand(GRAMMAR["runs audit"], argv);
875
+ const store = parsed.values.store;
876
+ const repair = parsed.values.repair === true;
877
+ const assembled = assembleEngine({
878
+ config: await loadCliConfig(context.cwd),
879
+ ...store === void 0 ? {} : { storePath: store },
880
+ cwd: context.cwd
881
+ });
882
+ const audits = await auditRuns(assembled.store);
883
+ if (audits.length === 0) {
884
+ context.io.err("every run is consistent with its journal");
885
+ return 0;
886
+ }
887
+ let remaining = 0;
888
+ for (const audit of audits) {
889
+ const line = `${audit.runId} ${audit.verdict}: ${audit.reason}`;
890
+ if (!repair || audit.repairTo === void 0) {
891
+ context.io.out(sanitizeTerminalText(line));
892
+ remaining += 1;
893
+ continue;
894
+ }
895
+ const leasable = assembled.store;
896
+ let lease;
897
+ if (typeof leasable.acquire === "function") try {
898
+ lease = await leasable.acquire(audit.runId, `runs-audit-${String(process.pid)}`);
899
+ } catch (thrown) {
900
+ if (thrown instanceof LeaseHeldError) {
901
+ context.io.out(sanitizeTerminalText(`${line} (leased by a live owner, skipped)`));
902
+ remaining += 1;
903
+ continue;
904
+ }
905
+ throw thrown;
906
+ }
907
+ try {
908
+ const result = await reconcileRunMeta(assembled.store, audit.runId, { ...lease === void 0 ? {} : { lease } });
909
+ const wrote = result.audit.repairTo;
910
+ context.io.out(sanitizeTerminalText(result.repaired ? `${line} (repaired to '${String(wrote)}')` : `${line} (not repaired)`));
911
+ if (!result.repaired) remaining += 1;
912
+ } finally {
913
+ if (lease !== void 0 && typeof leasable.release === "function") await leasable.release(lease);
914
+ }
915
+ }
916
+ context.io.err(remaining === 0 ? "every divergence repaired" : `${String(remaining)} divergence(s) remain; suspect verdicts need an operator (https://docs.rulvar.com/contributing/rfc-fenced-run-state)`);
917
+ return remaining === 0 ? 0 : 1;
918
+ }
857
919
  async function runsLsCommand(argv, context) {
858
920
  const store = parseCommand(GRAMMAR["runs ls"], argv).values.store;
859
921
  const metas = await assembleEngine({
@@ -1364,6 +1426,10 @@ Engine assembly: adapters, defaults, and the workflow registry come from
1364
1426
  rulvar.config.mjs in the working directory (default export
1365
1427
  { engineOptions?, workflows? }) or from the workflow module's named
1366
1428
  exports. --store selects the JsonlFileStore directory (default .rulvar).
1429
+ runs audit compares every run's meta row against its journal and names
1430
+ the divergences worker sweeps cannot see (a stranded run's terminal
1431
+ meta over live journal work); --repair rewrites the sound ones from
1432
+ the journal under a brief lease, exit 1 while any divergence remains.
1367
1433
  plan asks the planner model (role plan) to write a workflow script,
1368
1434
  lints and self-repairs it, then runs it in the worker sandbox; --dry-run
1369
1435
  prints the accepted script without running. Both stages are paid runs
@@ -1394,8 +1460,9 @@ async function runCli(argv, options) {
1394
1460
  case "resume": return await resumeCommand(rest, context);
1395
1461
  case "runs": {
1396
1462
  const [sub, ...subRest] = rest;
1397
- if (sub !== "ls") throw new ConfigError(usageOf(GRAMMAR["runs ls"]));
1398
- return await runsLsCommand(subRest, context);
1463
+ if (sub === "ls") return await runsLsCommand(subRest, context);
1464
+ if (sub === "audit") return await runsAuditCommand(subRest, context);
1465
+ throw new ConfigError(RUNS_FAMILY_USAGE);
1399
1466
  }
1400
1467
  case "inspect": return await inspectCommand(rest, context);
1401
1468
  case "plan": return await planCommand(rest, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/cli",
3
- "version": "1.46.0",
3
+ "version": "1.47.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.46.0"
25
+ "@rulvar/core": "1.47.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.46.0",
32
- "@rulvar/store-sqlite": "1.46.0",
33
- "@rulvar/planner": "1.46.0",
34
- "@rulvar/evals": "1.46.0",
35
- "@rulvar/plan": "1.46.0"
31
+ "@rulvar/testing": "1.47.0",
32
+ "@rulvar/plan": "1.47.0",
33
+ "@rulvar/store-sqlite": "1.47.0",
34
+ "@rulvar/evals": "1.47.0",
35
+ "@rulvar/planner": "1.47.0"
36
36
  },
37
37
  "bin": {
38
38
  "rulvar": "./dist/cli.js"