@verax-ai/body 0.1.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.
Files changed (63) hide show
  1. package/dist/approve-cli.d.ts +12 -0
  2. package/dist/approve-cli.js +93 -0
  3. package/dist/atomic-write.d.ts +1 -0
  4. package/dist/atomic-write.js +65 -0
  5. package/dist/auth.d.ts +10 -0
  6. package/dist/auth.js +59 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +112 -0
  9. package/dist/config.d.ts +27 -0
  10. package/dist/config.js +71 -0
  11. package/dist/desktop.d.ts +29 -0
  12. package/dist/desktop.js +341 -0
  13. package/dist/doctor.d.ts +8 -0
  14. package/dist/doctor.js +342 -0
  15. package/dist/halt.d.ts +1 -0
  16. package/dist/halt.js +12 -0
  17. package/dist/health-extras.d.ts +11 -0
  18. package/dist/health-extras.js +47 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.js +4 -0
  21. package/dist/inputs-read.d.ts +12 -0
  22. package/dist/inputs-read.js +64 -0
  23. package/dist/inventory-file.d.ts +18 -0
  24. package/dist/inventory-file.js +41 -0
  25. package/dist/keys.d.ts +12 -0
  26. package/dist/keys.js +55 -0
  27. package/dist/main.d.ts +1 -0
  28. package/dist/main.js +34 -0
  29. package/dist/metrics.d.ts +2 -0
  30. package/dist/metrics.js +23 -0
  31. package/dist/no-bypass-scan.d.ts +10 -0
  32. package/dist/no-bypass-scan.js +95 -0
  33. package/dist/operator-cli.d.ts +1 -0
  34. package/dist/operator-cli.js +25 -0
  35. package/dist/operator-credentials.d.ts +23 -0
  36. package/dist/operator-credentials.js +56 -0
  37. package/dist/operator-pairing.d.ts +25 -0
  38. package/dist/operator-pairing.js +67 -0
  39. package/dist/policy-store.d.ts +3 -0
  40. package/dist/policy-store.js +44 -0
  41. package/dist/reconcile-cli.d.ts +24 -0
  42. package/dist/reconcile-cli.js +258 -0
  43. package/dist/revoke.d.ts +1 -0
  44. package/dist/revoke.js +16 -0
  45. package/dist/rp-config.d.ts +15 -0
  46. package/dist/rp-config.js +30 -0
  47. package/dist/server.d.ts +5 -0
  48. package/dist/server.js +484 -0
  49. package/dist/tools/audit.d.ts +2 -0
  50. package/dist/tools/audit.js +48 -0
  51. package/dist/tools/memory.d.ts +10 -0
  52. package/dist/tools/memory.js +137 -0
  53. package/dist/tools/message.d.ts +4 -0
  54. package/dist/tools/message.js +44 -0
  55. package/dist/tools/spend.d.ts +3 -0
  56. package/dist/tools/spend.js +19 -0
  57. package/dist/unlock.d.ts +9 -0
  58. package/dist/unlock.js +109 -0
  59. package/dist/wiring.d.ts +23 -0
  60. package/dist/wiring.js +101 -0
  61. package/dist/witness.d.ts +33 -0
  62. package/dist/witness.js +321 -0
  63. package/package.json +40 -0
@@ -0,0 +1,3 @@
1
+ /** Write <stateDir>/policies/<policyHash>.json once; existing bytes are hash-checked. */
2
+ export declare function persistPolicySnapshot(stateDir: string, hash: string, document: unknown): void;
3
+ export declare function readPolicySnapshots(stateDir: string, hashes: Iterable<string>): Record<string, unknown>;
@@ -0,0 +1,44 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { loadPolicy } from "@verax-ai/proxy";
4
+ const HASH_RE = /^[0-9a-f]{64}$/;
5
+ function snapshotPath(stateDir, hash) {
6
+ return join(stateDir, "policies", `${hash}.json`);
7
+ }
8
+ /** Write <stateDir>/policies/<policyHash>.json once; existing bytes are hash-checked. */
9
+ export function persistPolicySnapshot(stateDir, hash, document) {
10
+ if (!HASH_RE.test(hash)) {
11
+ throw new Error(`policy-hash-invalid:${hash}`);
12
+ }
13
+ const dir = join(stateDir, "policies");
14
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
15
+ const path = snapshotPath(stateDir, hash);
16
+ if (existsSync(path)) {
17
+ const existing = JSON.parse(readFileSync(path, "utf8"));
18
+ if (loadPolicy(existing).hash !== hash) {
19
+ throw new Error(`policy-snapshot-mismatch:${hash}`);
20
+ }
21
+ return;
22
+ }
23
+ writeFileSync(path, `${JSON.stringify(document)}\n`, { encoding: "utf8", mode: 0o600 });
24
+ }
25
+ export function readPolicySnapshots(stateDir, hashes) {
26
+ const out = {};
27
+ for (const hash of hashes) {
28
+ if (!HASH_RE.test(hash))
29
+ continue;
30
+ const path = snapshotPath(stateDir, hash);
31
+ if (!existsSync(path))
32
+ continue;
33
+ try {
34
+ const document = JSON.parse(readFileSync(path, "utf8"));
35
+ if (loadPolicy(document).hash !== hash)
36
+ continue;
37
+ out[hash] = document;
38
+ }
39
+ catch {
40
+ // Omit a bad snapshot so the panel can say the historical policy is unavailable.
41
+ }
42
+ }
43
+ return out;
44
+ }
@@ -0,0 +1,24 @@
1
+ export declare function parseReconcileArgs(argv: string[]): {
2
+ stateDir: string;
3
+ channelPath: string;
4
+ outPath: string;
5
+ toleranceMs: number;
6
+ window?: {
7
+ startMs: number;
8
+ endMs: number;
9
+ };
10
+ channel?: "card";
11
+ currency?: string;
12
+ columns?: {
13
+ date: string;
14
+ amount: string;
15
+ description: string;
16
+ id?: string;
17
+ };
18
+ delimiter?: ";" | ",";
19
+ decimal?: "," | ".";
20
+ tzOffsetMinutes?: number;
21
+ } | {
22
+ error: string;
23
+ };
24
+ export declare function runReconcile(argv: string[], writeErr?: (s: string) => void): number;
@@ -0,0 +1,258 @@
1
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { loadApprovalsFromDir, loadEffectsFromDir, loadPolicy, parseCardCsv, parseChannelJsonl, reconcile } from "@verax-ai/proxy";
4
+ const SNAPSHOT_NAME = /^[0-9a-f]{64}\.json$/;
5
+ function descriptorsFromDoc(doc) {
6
+ const out = {};
7
+ if (!Array.isArray(doc.rules))
8
+ return out;
9
+ for (const rule of doc.rules) {
10
+ const spend = rule.spend;
11
+ if (!spend || !Array.isArray(spend.payees) || !Array.isArray(spend.descriptors))
12
+ continue;
13
+ const stamps = spend.descriptors.filter((d) => typeof d === "string" && d !== "");
14
+ if (stamps.length === 0)
15
+ continue;
16
+ for (const payee of spend.payees) {
17
+ if (typeof payee === "string" && payee !== "")
18
+ out[payee] = stamps;
19
+ }
20
+ }
21
+ return out;
22
+ }
23
+ function loadDescriptorsFromDir(stateDir) {
24
+ const dir = join(stateDir, "policies");
25
+ let names;
26
+ try {
27
+ names = readdirSync(dir);
28
+ }
29
+ catch {
30
+ return { descriptorsByPayee: {}, policyHash: null };
31
+ }
32
+ const candidates = [];
33
+ for (const name of names) {
34
+ if (!SNAPSHOT_NAME.test(name))
35
+ continue;
36
+ try {
37
+ const st = statSync(join(dir, name));
38
+ candidates.push({ name, mtimeMs: st.mtimeMs });
39
+ }
40
+ catch {
41
+ // A missing snapshot is skipped.
42
+ }
43
+ }
44
+ if (candidates.length === 0)
45
+ return { descriptorsByPayee: {}, policyHash: null };
46
+ candidates.sort((a, b) => a.mtimeMs - b.mtimeMs || a.name.localeCompare(b.name));
47
+ const chosen = candidates[candidates.length - 1];
48
+ const hash = chosen.name.slice(0, 64);
49
+ try {
50
+ const doc = JSON.parse(readFileSync(join(dir, chosen.name), "utf8"));
51
+ if (loadPolicy(doc).hash !== hash)
52
+ return { descriptorsByPayee: {}, policyHash: null };
53
+ return { descriptorsByPayee: descriptorsFromDoc(doc), policyHash: hash };
54
+ }
55
+ catch {
56
+ return { descriptorsByPayee: {}, policyHash: null };
57
+ }
58
+ }
59
+ const CARD_TOLERANCE_MS = 3 * 86_400_000;
60
+ /** UTC-12 .. UTC+14 in minutes. */
61
+ const MAX_TZ_OFFSET_MIN = 840;
62
+ export function parseReconcileArgs(argv) {
63
+ const rest = argv.slice(1);
64
+ let tzOffsetMinutes;
65
+ let toleranceMs;
66
+ let outPath;
67
+ let windowStart;
68
+ let windowEnd;
69
+ let channel;
70
+ let currency;
71
+ let columns;
72
+ let delimiter;
73
+ let decimal;
74
+ const positionals = [];
75
+ for (let i = 0; i < rest.length; i += 1) {
76
+ const a = rest[i];
77
+ if (a === "--tolerance") {
78
+ const raw = rest[i + 1];
79
+ const n = Number(raw);
80
+ if (!Number.isFinite(n) || n < 0)
81
+ return { error: "tolerance-invalid" };
82
+ toleranceMs = n;
83
+ i += 1;
84
+ continue;
85
+ }
86
+ if (a === "--window-start") {
87
+ const n = Number(rest[i + 1]);
88
+ if (!Number.isFinite(n))
89
+ return { error: "window-invalid" };
90
+ windowStart = n;
91
+ i += 1;
92
+ continue;
93
+ }
94
+ if (a === "--window-end") {
95
+ const n = Number(rest[i + 1]);
96
+ if (!Number.isFinite(n))
97
+ return { error: "window-invalid" };
98
+ windowEnd = n;
99
+ i += 1;
100
+ continue;
101
+ }
102
+ if (a === "--out") {
103
+ const p = rest[i + 1];
104
+ if (!p || p.startsWith("-"))
105
+ return { error: "out-missing" };
106
+ outPath = p;
107
+ i += 1;
108
+ continue;
109
+ }
110
+ if (a === "--channel") {
111
+ const p = rest[i + 1];
112
+ if (p !== "card")
113
+ return { error: "channel-invalid" };
114
+ channel = "card";
115
+ i += 1;
116
+ continue;
117
+ }
118
+ if (a === "--currency") {
119
+ const p = rest[i + 1];
120
+ if (!p || p.startsWith("-"))
121
+ return { error: "currency-missing" };
122
+ currency = p;
123
+ i += 1;
124
+ continue;
125
+ }
126
+ if (a === "--columns") {
127
+ const p = rest[i + 1];
128
+ if (!p || p.startsWith("-"))
129
+ return { error: "columns-invalid" };
130
+ const parsed = parseColumns(p);
131
+ if (!parsed)
132
+ return { error: "columns-invalid" };
133
+ columns = parsed;
134
+ i += 1;
135
+ continue;
136
+ }
137
+ if (a === "--delimiter") {
138
+ const p = rest[i + 1];
139
+ if (p !== ";" && p !== ",")
140
+ return { error: "delimiter-invalid" };
141
+ delimiter = p;
142
+ i += 1;
143
+ continue;
144
+ }
145
+ if (a === "--tz-offset") {
146
+ const n = Number(rest[i + 1]);
147
+ if (!Number.isInteger(n) || Math.abs(n) > MAX_TZ_OFFSET_MIN)
148
+ return { error: "tz-offset-invalid" };
149
+ tzOffsetMinutes = n;
150
+ i += 1;
151
+ continue;
152
+ }
153
+ if (a === "--decimal") {
154
+ const p = rest[i + 1];
155
+ if (p !== "," && p !== ".")
156
+ return { error: "decimal-invalid" };
157
+ decimal = p;
158
+ i += 1;
159
+ continue;
160
+ }
161
+ if (a.startsWith("-"))
162
+ return { error: `flag-unknown:${a}` };
163
+ positionals.push(a);
164
+ }
165
+ const stateDir = positionals[0];
166
+ const channelPath = positionals[1];
167
+ if (!stateDir || !channelPath || !outPath) {
168
+ return { error: "usage" };
169
+ }
170
+ if (channel === "card" && !currency)
171
+ return { error: "currency-missing" };
172
+ if ((windowStart === undefined) !== (windowEnd === undefined)) {
173
+ return { error: "window-invalid" };
174
+ }
175
+ const window = windowStart !== undefined && windowEnd !== undefined
176
+ ? { startMs: windowStart, endMs: windowEnd }
177
+ : undefined;
178
+ return {
179
+ stateDir,
180
+ channelPath,
181
+ outPath,
182
+ toleranceMs: toleranceMs ?? (channel === "card" ? CARD_TOLERANCE_MS : 60_000),
183
+ window,
184
+ channel,
185
+ currency,
186
+ columns,
187
+ delimiter,
188
+ decimal,
189
+ tzOffsetMinutes,
190
+ };
191
+ }
192
+ function parseColumns(raw) {
193
+ const map = {};
194
+ for (const part of raw.split(",")) {
195
+ const eq = part.indexOf("=");
196
+ if (eq <= 0)
197
+ return null;
198
+ map[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
199
+ }
200
+ if (!map.date || !map.amount || !map.desc)
201
+ return null;
202
+ return {
203
+ date: map.date,
204
+ amount: map.amount,
205
+ description: map.desc,
206
+ ...(map.id ? { id: map.id } : {}),
207
+ };
208
+ }
209
+ export function runReconcile(argv, writeErr = (s) => process.stderr.write(s)) {
210
+ const parsed = parseReconcileArgs(argv);
211
+ if ("error" in parsed) {
212
+ if (parsed.error === "usage") {
213
+ writeErr("verax reconcile <stateDir> <channel.jsonl> [--tolerance 60000] [--window-start ms --window-end ms] [--tz-offset 180] --out report.json\n");
214
+ return 78;
215
+ }
216
+ writeErr(`${parsed.error}\n`);
217
+ return 78;
218
+ }
219
+ try {
220
+ const raw = readFileSync(parsed.channelPath, "utf8");
221
+ const card = parsed.channel === "card" && parsed.currency
222
+ ? parseCardCsv(raw, {
223
+ currency: parsed.currency,
224
+ columns: parsed.columns,
225
+ delimiter: parsed.delimiter,
226
+ decimal: parsed.decimal,
227
+ tzOffsetMinutes: parsed.tzOffsetMinutes,
228
+ })
229
+ : undefined;
230
+ const channel = card ?? parseChannelJsonl(raw);
231
+ const effects = loadEffectsFromDir(parsed.stateDir);
232
+ const loaded = parsed.channel === "card" ? loadDescriptorsFromDir(parsed.stateDir) : undefined;
233
+ const descriptorsByPayee = loaded?.descriptorsByPayee;
234
+ const report = reconcile(channel, effects, {
235
+ toleranceMs: parsed.toleranceMs,
236
+ window: parsed.window,
237
+ approvals: parsed.channel === "card" ? loadApprovalsFromDir(parsed.stateDir) : undefined,
238
+ skipped: card?.skipped,
239
+ ...(descriptorsByPayee && Object.keys(descriptorsByPayee).length > 0
240
+ ? { descriptorsByPayee }
241
+ : {}),
242
+ });
243
+ if (loaded?.policyHash)
244
+ report.scope.policyHash = loaded.policyHash;
245
+ writeFileSync(parsed.outPath, `${JSON.stringify(report, null, 2)}\n`, { encoding: "utf8" });
246
+ if (parsed.channel === "card") {
247
+ const mismatch = report.ghost.filter((g) => g.reason === "amount-mismatch").length;
248
+ const unknown = report.ghost.filter((g) => g.reason === "amount-unknown").length;
249
+ writeErr(`matched ${report.matched.length} · ghost ${report.ghost.length} · authorizedUnpaid ${report.authorizedUnpaid.length} · mismatch ${mismatch} · unknown ${unknown}\n`);
250
+ }
251
+ return 0;
252
+ }
253
+ catch (err) {
254
+ const code = err.code ?? (err instanceof Error ? err.message : "unknown");
255
+ writeErr(`reconcile-failed:${code}\n`);
256
+ return 1;
257
+ }
258
+ }
@@ -0,0 +1 @@
1
+ export declare function isRevokedJti(stateDir: string, jti: string): boolean;
package/dist/revoke.js ADDED
@@ -0,0 +1,16 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export function isRevokedJti(stateDir, jti) {
4
+ const path = join(stateDir, "revoked-jti.jsonl");
5
+ if (!existsSync(path))
6
+ return false;
7
+ const text = readFileSync(path, "utf8");
8
+ for (const line of text.split("\n")) {
9
+ if (line === "")
10
+ continue;
11
+ const row = JSON.parse(line);
12
+ if (row.jti === jti)
13
+ return true;
14
+ }
15
+ return false;
16
+ }
@@ -0,0 +1,15 @@
1
+ export type RpConfig = {
2
+ rpID: string;
3
+ origins: string[];
4
+ };
5
+ /**
6
+ * WebAuthn will not take an IP address as an RP ID. The values come from the
7
+ * environment so a machine name never has to be written into the source.
8
+ */
9
+ export declare function readRpConfig(env?: NodeJS.ProcessEnv): {
10
+ ok: true;
11
+ config: RpConfig;
12
+ } | {
13
+ ok: false;
14
+ reason: string;
15
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * WebAuthn will not take an IP address as an RP ID. The values come from the
3
+ * environment so a machine name never has to be written into the source.
4
+ */
5
+ export function readRpConfig(env = process.env) {
6
+ const rpID = (env.VERAX_RP_ID ?? "").trim();
7
+ const rawOrigins = (env.VERAX_RP_ORIGINS ?? "").trim();
8
+ if (rpID === "") {
9
+ return { ok: false, reason: "VERAX_RP_ID is unset" };
10
+ }
11
+ if (rawOrigins === "") {
12
+ return { ok: false, reason: "VERAX_RP_ORIGINS is unset" };
13
+ }
14
+ const origins = rawOrigins
15
+ .split(",")
16
+ .map((s) => s.trim())
17
+ .filter((s) => s !== "");
18
+ if (origins.length === 0) {
19
+ return { ok: false, reason: "VERAX_RP_ORIGINS is empty" };
20
+ }
21
+ for (const origin of origins) {
22
+ try {
23
+ new URL(origin);
24
+ }
25
+ catch {
26
+ return { ok: false, reason: `VERAX_RP_ORIGINS has an unparsable origin` };
27
+ }
28
+ }
29
+ return { ok: true, config: { rpID, origins } };
30
+ }
@@ -0,0 +1,5 @@
1
+ import { type Server } from "node:http";
2
+ import type { BodyConfig } from "./config.ts";
3
+ import { TOOL_NAMES } from "./wiring.ts";
4
+ export declare function listen(config: BodyConfig): Promise<Server>;
5
+ export { TOOL_NAMES };