@rulvar/executor 1.111.0 → 1.113.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/index.d.ts CHANGED
@@ -273,8 +273,17 @@ interface CorruptLedgerLine {
273
273
  }
274
274
  /** A torn fragment the writer quarantined while repairing a tail (RV502). */
275
275
  interface TornLedgerArtifact {
276
- /** The raw torn bytes, preserved verbatim. */
276
+ /**
277
+ * The torn fragment as a LOSSY string: invalid UTF-8 bytes decode to
278
+ * U+FFFD, so two different byte tails can read identically here.
279
+ * Kept for compatibility with rows written before RV707; use
280
+ * `bytesBase64` for the exact bytes.
281
+ */
277
282
  bytes: string;
283
+ /** The exact torn bytes, base64 (RV707); absent on rows written before it. */
284
+ bytesBase64?: string;
285
+ /** sha256 (hex) of the exact torn bytes (RV707); absent on legacy rows. */
286
+ sha256?: string;
278
287
  /** Wall-clock ms when the writer quarantined the fragment. */
279
288
  recoveredAt: number;
280
289
  }
package/dist/index.js CHANGED
@@ -612,6 +612,9 @@ function containerExecutor(options) {
612
612
  * durability wraps the seam over its own fsync or database write.
613
613
  */
614
614
  const wallClock = Date.now.bind(globalThis);
615
+ /** Strict decode (RV607/RV707): invalid UTF-8 is damage, never a
616
+ * replacement character that forges a key or a parseable fragment. */
617
+ const decodeStrict = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes);
615
618
  /** How stale a repair lock's mtime must be before a writer may presume
616
619
  * its holder crashed and steal it. Repairs take milliseconds; ten
617
620
  * seconds is a crash verdict, not a performance budget. */
@@ -671,10 +674,10 @@ async function repairUnderLock(path, now) {
671
674
  }
672
675
  if (raw.length === 0 || raw[raw.length - 1] === 10) return;
673
676
  const boundary = raw.lastIndexOf(10) + 1;
674
- const fragment = raw.subarray(boundary).toString("utf8");
677
+ const fragment = raw.subarray(boundary);
675
678
  let parseable = true;
676
679
  try {
677
- JSON.parse(fragment);
680
+ JSON.parse(decodeStrict(fragment));
678
681
  } catch {
679
682
  parseable = false;
680
683
  }
@@ -686,7 +689,9 @@ async function repairUnderLock(path, now) {
686
689
  await truncate(path, boundary);
687
690
  await appendFile(path, `${JSON.stringify({
688
691
  phase: "torn",
689
- bytes: fragment,
692
+ bytes: fragment.toString("utf8"),
693
+ bytesBase64: fragment.toString("base64"),
694
+ sha256: createHash("sha256").update(fragment).digest("hex"),
690
695
  recoveredAt: now()
691
696
  })}\n`, "utf8");
692
697
  return;
@@ -775,9 +780,6 @@ var LedgerCorruptionError = class extends Error {
775
780
  this.lines = lines;
776
781
  }
777
782
  };
778
- /** Strict per-line decode (RV607): invalid UTF-8 is damage, never a
779
- * replacement character that forges a key. */
780
- const decodeStrict = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes);
781
783
  const isRecordObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
782
784
  const hasIntentShape = (value) => typeof value.idempotencyKey === "string" && typeof value.runId === "string" && typeof value.spanId === "string" && typeof value.tool === "string" && typeof value.argsHash === "string" && typeof value.executor === "string" && typeof value.workdir === "string" && typeof value.startedAt === "number" && (value.attemptId === void 0 || typeof value.attemptId === "string");
783
785
  const OUTCOME_CLASSES = /* @__PURE__ */ new Set([
@@ -798,7 +800,7 @@ function asLedgerLine(parsed) {
798
800
  if (!isRecordObject(parsed)) return void 0;
799
801
  if (parsed.phase === "intent") return hasIntentShape(parsed) ? parsed : void 0;
800
802
  if (parsed.phase === "outcome") return hasIntentShape(parsed) && typeof parsed.durationMs === "number" && OUTCOME_CLASSES.has(parsed.outcome) && (typeof parsed.exitCode === "number" || parsed.exitCode === null) && (typeof parsed.signal === "string" || parsed.signal === null) ? parsed : void 0;
801
- if (parsed.phase === "torn") return typeof parsed.bytes === "string" && typeof parsed.recoveredAt === "number" ? parsed : void 0;
803
+ if (parsed.phase === "torn") return typeof parsed.bytes === "string" && typeof parsed.recoveredAt === "number" && (parsed.bytesBase64 === void 0 || typeof parsed.bytesBase64 === "string") && (parsed.sha256 === void 0 || typeof parsed.sha256 === "string") ? parsed : void 0;
802
804
  }
803
805
  /**
804
806
  * Scans a JSONL ledger file into intents, outcomes, and the orphaned
@@ -867,6 +869,8 @@ async function loadEffectLedger(path, options) {
867
869
  outcomes.push(rest);
868
870
  } else tornArtifacts.push({
869
871
  bytes: entry.bytes,
872
+ ...entry.bytesBase64 === void 0 ? {} : { bytesBase64: entry.bytesBase64 },
873
+ ...entry.sha256 === void 0 ? {} : { sha256: entry.sha256 },
870
874
  recoveredAt: entry.recoveredAt
871
875
  });
872
876
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/executor",
3
- "version": "1.111.0",
3
+ "version": "1.113.0",
4
4
  "description": "Rulvar isolated tool executors: reference ToolExecutorProvider adapters that run tool work out of process (subprocess and container) so hostile or model-generated scripts cannot reach host capabilities.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,13 +22,13 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@rulvar/core": "1.111.0"
25
+ "@rulvar/core": "1.113.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.20.1",
29
29
  "tsdown": "^0.22.14",
30
30
  "typescript": "~6.0.3",
31
- "@rulvar/testing": "1.111.0"
31
+ "@rulvar/testing": "1.113.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",