@verax-ai/proxy 0.3.0 → 0.4.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.
@@ -67,7 +67,8 @@ export type CardCsvOpts = {
67
67
  };
68
68
  export declare function parseCardCsv(text: string, opts: CardCsvOpts): ParsedCardCsv;
69
69
  export declare function parseChannelJsonl(text: string): ChannelRow[];
70
- /** Read-only: does not take ledger.lock. */
70
+ /** Decision refs, read the same way `verifyLedger` lists decision files. */
71
+ export declare function loadDecisionRefsFromDir(stateDir: string): Set<string>;
71
72
  export declare function loadEffectsFromDir(stateDir: string): LedgerEffect[];
72
73
  export declare function reconcile(channelRows: readonly ChannelRow[], effects: readonly LedgerEffect[], opts?: {
73
74
  toleranceMs?: number;
@@ -78,4 +79,6 @@ export declare function reconcile(channelRows: readonly ChannelRow[], effects: r
78
79
  approvals?: readonly ApprovalRow[];
79
80
  skipped?: CardCsvSkip[];
80
81
  descriptorsByPayee?: Readonly<Record<string, readonly string[]>>;
82
+ /** Effect refs that have a decision. Absent means none: an effect is not a match. */
83
+ decisionRefs?: ReadonlySet<string>;
81
84
  }): ReconcileReport;
package/dist/reconcile.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
- import { readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { readLedgerManifest, requireLedgerPiecePath } from "./ledger-manifest.js";
4
5
  function asChannelRow(raw, line) {
5
6
  if (raw === null || typeof raw !== "object") {
6
7
  throw new Error(`channel-row-invalid:${line}`);
@@ -208,6 +209,39 @@ export function parseChannelJsonl(text) {
208
209
  return rows;
209
210
  }
210
211
  /** Read-only: does not take ledger.lock. */
212
+ function decisionPaths(dir) {
213
+ const manifest = readLedgerManifest(dir);
214
+ if (manifest && Array.isArray(manifest.pieces) && manifest.pieces.length > 0) {
215
+ return manifest.pieces
216
+ .map((p) => requireLedgerPiecePath(dir, p.decisions))
217
+ .filter((p) => existsSync(p));
218
+ }
219
+ const tek = join(dir, "decisions.jsonl");
220
+ return existsSync(tek) ? [tek] : [];
221
+ }
222
+ /** Decision refs, read the same way `verifyLedger` lists decision files. */
223
+ export function loadDecisionRefsFromDir(stateDir) {
224
+ const refs = new Set();
225
+ for (const path of decisionPaths(stateDir)) {
226
+ let text = "";
227
+ try {
228
+ text = readFileSync(path, "utf8");
229
+ }
230
+ catch (err) {
231
+ if (err.code === "ENOENT")
232
+ continue;
233
+ throw err;
234
+ }
235
+ for (const line of text.split("\n")) {
236
+ if (line === "")
237
+ continue;
238
+ const row = JSON.parse(line);
239
+ if (typeof row.claims?.ref === "string")
240
+ refs.add(row.claims.ref);
241
+ }
242
+ }
243
+ return refs;
244
+ }
211
245
  export function loadEffectsFromDir(stateDir) {
212
246
  try {
213
247
  const text = readFileSync(join(stateDir, "effects.jsonl"), "utf8");
@@ -291,6 +325,8 @@ function descriptorsFor(effect, approvals, byPayee) {
291
325
  }
292
326
  export function reconcile(channelRows, effects, opts) {
293
327
  const toleranceMs = opts?.toleranceMs ?? 60_000;
328
+ const decisionRefs = opts?.decisionRefs ?? new Set();
329
+ const decided = (effect) => decisionRefs.has(effect.row.ref);
294
330
  if (channelRows.length === 0) {
295
331
  return {
296
332
  scope: { channel: "", windowStartMs: 0, windowEndMs: 0, rowCount: 0 },
@@ -326,6 +362,7 @@ export function reconcile(channelRows, effects, opts) {
326
362
  const marked = { ...row, toleranceMs: rowTol };
327
363
  if (typeof row.ref === "string" && row.ref !== "") {
328
364
  const idx = effects.findIndex((e, i) => !used.has(i) &&
365
+ decided(e) &&
329
366
  (e.row.ref === row.ref ||
330
367
  opts?.approvals?.some((a) => a.ref === row.ref && a.allowRef === e.row.ref)));
331
368
  if (idx === -1) {
@@ -359,7 +396,7 @@ export function reconcile(channelRows, effects, opts) {
359
396
  let unknownNear = false;
360
397
  let descriptorMiss = false;
361
398
  const near = effects.findIndex((e, i) => {
362
- if (used.has(i) || e.row.effectClass !== row.subject)
399
+ if (used.has(i) || !decided(e) || e.row.effectClass !== row.subject)
363
400
  return false;
364
401
  if (Math.abs(row.occurredAtMs - e.row.timestampMs) > rowTol)
365
402
  return false;
package/dist/types.d.ts CHANGED
@@ -45,11 +45,14 @@ export type Policy = {
45
45
  /** Document-root posture. Absent keeps today's optional `_inputs`. */
46
46
  requireInputs?: boolean;
47
47
  evaluate(call: ToolCall, principal: Principal, ctx?: PolicyEvalCtx): PolicyDecision;
48
+ /** Spend-rule day bucket. Absent means UTC, the same as `dayOffsetMinutes: 0`. */
49
+ dayOffsetMinutes?: number;
48
50
  rule(id: string | null): {
49
51
  id: string;
50
52
  text: string;
51
53
  spend?: {
52
54
  dailyMaxMinor?: number;
55
+ dayOffsetMinutes?: number;
53
56
  };
54
57
  } | null;
55
58
  };
@@ -115,8 +118,8 @@ export type DecisionInputs = {
115
118
  resolves: string;
116
119
  };
117
120
  };
118
- /** How an operator's approval reached the ledger: `verax approve` on the machine, or the body's `/api/approve`. */
119
- export type ApprovalChannel = "cli" | "http";
121
+ /** How an operator's approval reached the ledger: a person at `verax approve`, a non-interactive `--from-script` run, or the body's `/api/approve`. */
122
+ export type ApprovalChannel = "cli" | "cli-script" | "http";
120
123
  export type InputsLog = {
121
124
  append(ref: string, inputs: DecisionInputs): Promise<void>;
122
125
  get(ref: string): Promise<DecisionInputs | null>;
@@ -16,10 +16,48 @@ export type VerifyResult = {
16
16
  effectsBound: number;
17
17
  effectsOrphaned: number;
18
18
  trust: VerifyTrust;
19
+ /** Which key answered for `self` effect rows. Same honesty rules as `trust`. */
20
+ effectTrust: VerifyTrust;
21
+ /** Which key answered for `same-org` effect rows. Agreement is not trust. */
22
+ witnessTrust: VerifyTrust;
23
+ /** Which key answered for checkpoint rows. Same honesty rules as `trust`. */
24
+ checkpointTrust: VerifyTrust;
25
+ /** What `index.jsonl` still names. Missing file is not a failure. */
26
+ index: VerifyIndex;
27
+ /**
28
+ * Absent index: effect completeness was not checked, and that absence is
29
+ * not itself a failure. Present index: every ref it marks was compared to
30
+ * a bound effect row.
31
+ */
32
+ effectCompleteness: string;
33
+ /** What the newest checkpoint covers, and the records after it. */
34
+ tail: VerifyTail;
19
35
  problems: string[];
20
36
  };
37
+ export type VerifyIndex = {
38
+ present: boolean;
39
+ /** Index refs that are not decisions in the ledger. */
40
+ missing: number;
41
+ line: string;
42
+ };
43
+ export type VerifyTail = {
44
+ line: string;
45
+ /** Newest signed checkpoint, or null when the file has none. */
46
+ checkpoint: {
47
+ receiptCount: number | null;
48
+ chainHeadHash: string | null;
49
+ /** True when some decision's hash is the checkpoint head. Null when the checkpoint carries no head. */
50
+ ledgerHoldsRecord: boolean | null;
51
+ } | null;
52
+ };
21
53
  export type VerifyOptions = {
22
- /** Verify against this key instead of the one the records carry. */
54
+ /** Verify decision records against this key instead of the one they carry. */
23
55
  publicKeyPem?: string;
56
+ /** Verify `self` effect rows against this key instead of the one those rows carry. */
57
+ effectPublicKeyPem?: string;
58
+ /** Verify `same-org` effect rows against this key instead of the one those rows carry. */
59
+ witnessPublicKeyPem?: string;
60
+ /** Verify every checkpoint row against this key instead of one key taken from the file. */
61
+ checkpointPublicKeyPem?: string;
24
62
  };
25
63
  export declare function verifyLedger(dir: string, opts?: VerifyOptions): Promise<VerifyResult>;