@wlfi-agent/cli 1.4.16 → 1.4.18
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/Cargo.lock +26 -20
- package/Cargo.toml +1 -1
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +663 -190
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +53 -52
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +18 -17
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +32 -31
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/.turbo/turbo-build.log +44 -43
- package/packages/ui/dist/components/badge.d.ts +1 -1
- package/packages/ui/dist/components/button.d.ts +1 -1
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +112 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +15 -30
- package/src/lib/admin-setup.ts +246 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import readline from 'node:readline';
|
|
2
1
|
import { createSudoSession } from './sudo.js';
|
|
2
|
+
import { promptHiddenTty } from './hidden-tty-prompt.js';
|
|
3
3
|
|
|
4
4
|
const MAX_SECRET_STDIN_BYTES = 16 * 1024;
|
|
5
5
|
|
|
@@ -27,40 +27,15 @@ function currentProcessIsRoot(): boolean {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function promptHidden(query: string, label: string): Promise<string> {
|
|
30
|
-
|
|
31
|
-
throw new Error(`${label} is required; rerun on a local TTY`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const rl = readline.createInterface({
|
|
35
|
-
input: process.stdin,
|
|
36
|
-
output: process.stdout,
|
|
37
|
-
terminal: true,
|
|
38
|
-
}) as readline.Interface & { stdoutMuted?: boolean; _writeToOutput?: (value: string) => void };
|
|
39
|
-
|
|
40
|
-
rl.stdoutMuted = true;
|
|
41
|
-
rl._writeToOutput = (value: string) => {
|
|
42
|
-
if (value.includes(query)) {
|
|
43
|
-
(rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (!rl.stdoutMuted) {
|
|
47
|
-
(rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const answer = await new Promise<string>((resolve) => {
|
|
52
|
-
rl.question(query, resolve);
|
|
53
|
-
});
|
|
54
|
-
rl.close();
|
|
55
|
-
process.stdout.write('\n');
|
|
30
|
+
const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
|
|
56
31
|
return validateSecret(answer, label);
|
|
57
32
|
}
|
|
58
33
|
|
|
59
34
|
const sudoSession = createSudoSession({
|
|
60
35
|
promptPassword: async () =>
|
|
61
36
|
await promptHidden(
|
|
62
|
-
'
|
|
63
|
-
'
|
|
37
|
+
'macOS admin password for sudo (input hidden; required to change local admin chain and token configuration): ',
|
|
38
|
+
'macOS admin password for sudo',
|
|
64
39
|
),
|
|
65
40
|
});
|
|
66
41
|
|
package/src/lib/rust.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import fs from
|
|
3
|
-
import type { WlfiConfig } from
|
|
4
|
-
import * as configModule from
|
|
5
|
-
import { assertTrustedExecutablePath } from
|
|
6
|
-
import { resolveValidatedPassthroughDaemonSocket } from
|
|
7
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import type { WlfiConfig } from "../../packages/config/src/index.js";
|
|
4
|
+
import * as configModule from "../../packages/config/src/index.js";
|
|
5
|
+
import { assertTrustedExecutablePath } from "./fs-trust.js";
|
|
6
|
+
import { resolveValidatedPassthroughDaemonSocket } from "./passthrough-security.js";
|
|
7
|
+
import {
|
|
8
|
+
prepareSpawnOptions,
|
|
9
|
+
type RunRustBinaryOptions,
|
|
10
|
+
} from "./rust-spawn-options.js";
|
|
8
11
|
|
|
9
12
|
const { readConfig, resolveRustBinaryPath } =
|
|
10
|
-
configModule as typeof import(
|
|
13
|
+
configModule as typeof import("../../packages/config/src/index.js");
|
|
11
14
|
|
|
12
|
-
export type RustBinaryName =
|
|
15
|
+
export type RustBinaryName =
|
|
16
|
+
| "wlfi-agent-daemon"
|
|
17
|
+
| "wlfi-agent-admin"
|
|
18
|
+
| "wlfi-agent-agent";
|
|
19
|
+
|
|
20
|
+
const MAX_STDOUT_SNIPPET_CHARS = 200;
|
|
13
21
|
|
|
14
22
|
export class RustBinaryExitError extends Error {
|
|
15
23
|
readonly binaryName: RustBinaryName;
|
|
@@ -17,9 +25,14 @@ export class RustBinaryExitError extends Error {
|
|
|
17
25
|
readonly stdout: string;
|
|
18
26
|
readonly stderr: string;
|
|
19
27
|
|
|
20
|
-
constructor(
|
|
28
|
+
constructor(
|
|
29
|
+
binaryName: RustBinaryName,
|
|
30
|
+
code: number,
|
|
31
|
+
stdout: string,
|
|
32
|
+
stderr: string,
|
|
33
|
+
) {
|
|
21
34
|
super(stderr.trim() || `${binaryName} exited with code ${code}`);
|
|
22
|
-
this.name =
|
|
35
|
+
this.name = "RustBinaryExitError";
|
|
23
36
|
this.binaryName = binaryName;
|
|
24
37
|
this.code = code;
|
|
25
38
|
this.stdout = stdout;
|
|
@@ -27,7 +40,30 @@ export class RustBinaryExitError extends Error {
|
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
42
|
|
|
43
|
+
export class RustBinaryJsonParseError extends Error {
|
|
44
|
+
readonly binaryName: RustBinaryName;
|
|
45
|
+
readonly stdout: string;
|
|
46
|
+
readonly cause: unknown;
|
|
47
|
+
|
|
48
|
+
constructor(binaryName: RustBinaryName, stdout: string, cause: unknown) {
|
|
49
|
+
const snippet =
|
|
50
|
+
stdout.length > MAX_STDOUT_SNIPPET_CHARS
|
|
51
|
+
? `${stdout.slice(0, MAX_STDOUT_SNIPPET_CHARS)}...`
|
|
52
|
+
: stdout;
|
|
53
|
+
const message =
|
|
54
|
+
cause instanceof Error
|
|
55
|
+
? `${binaryName} produced invalid JSON: ${cause.message}. stdout: ${snippet}`
|
|
56
|
+
: `${binaryName} produced invalid JSON. stdout: ${snippet}`;
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "RustBinaryJsonParseError";
|
|
59
|
+
this.binaryName = binaryName;
|
|
60
|
+
this.stdout = stdout;
|
|
61
|
+
this.cause = cause;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
30
65
|
function ensureBinary(binaryName: RustBinaryName, config?: WlfiConfig): string {
|
|
66
|
+
/* c8 ignore next -- both provided-config and readConfig fallback paths are exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
|
|
31
67
|
const resolved = resolveRustBinaryPath(binaryName, config ?? readConfig());
|
|
32
68
|
if (!fs.existsSync(resolved)) {
|
|
33
69
|
throw new Error(
|
|
@@ -40,15 +76,56 @@ function ensureBinary(binaryName: RustBinaryName, config?: WlfiConfig): string {
|
|
|
40
76
|
|
|
41
77
|
function forwardedArgsIncludeDaemonSocket(args: string[]): boolean {
|
|
42
78
|
return args.some(
|
|
43
|
-
(arg, _index) =>
|
|
79
|
+
(arg, _index) =>
|
|
80
|
+
arg === "--daemon-socket" || arg.startsWith("--daemon-socket="),
|
|
44
81
|
);
|
|
45
82
|
}
|
|
46
83
|
|
|
84
|
+
async function writeChildStdin(child: ReturnType<typeof spawn>, stdin: string): Promise<void> {
|
|
85
|
+
const stream = child.stdin;
|
|
86
|
+
/* c8 ignore next 3 -- child.stdin is expected whenever this helper is used, but keep the guard for defensive mocked/process edge cases */
|
|
87
|
+
if (!stream) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await new Promise<void>((resolve, reject) => {
|
|
92
|
+
let settled = false;
|
|
93
|
+
|
|
94
|
+
const finish = () => {
|
|
95
|
+
/* c8 ignore next 3 -- defensive re-entry guard */
|
|
96
|
+
if (settled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
settled = true;
|
|
100
|
+
stream.off('error', handleError);
|
|
101
|
+
resolve();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleError = (error: NodeJS.ErrnoException) => {
|
|
105
|
+
/* c8 ignore next 3 -- defensive re-entry guard */
|
|
106
|
+
if (settled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') {
|
|
110
|
+
finish();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
settled = true;
|
|
114
|
+
stream.off('error', handleError);
|
|
115
|
+
reject(error);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
stream.on('error', handleError);
|
|
119
|
+
stream.end(stdin, finish);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
47
123
|
export async function passthroughRustBinary(
|
|
48
124
|
binaryName: RustBinaryName,
|
|
49
125
|
args: string[],
|
|
50
126
|
config?: WlfiConfig,
|
|
51
127
|
): Promise<number> {
|
|
128
|
+
/* c8 ignore next -- both provided-config and readConfig fallback paths are exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
|
|
52
129
|
const resolvedConfig = config ?? readConfig();
|
|
53
130
|
const resolvedDaemonSocket = resolveValidatedPassthroughDaemonSocket(
|
|
54
131
|
binaryName,
|
|
@@ -61,23 +138,28 @@ export async function passthroughRustBinary(
|
|
|
61
138
|
if (
|
|
62
139
|
resolvedDaemonSocket &&
|
|
63
140
|
!forwardedArgsIncludeDaemonSocket(args) &&
|
|
141
|
+
/* c8 ignore next -- explicit env override is exercised, but c8 misattributes this optional-chain/nullish check */
|
|
64
142
|
!env.WLFI_DAEMON_SOCKET?.trim()
|
|
65
143
|
) {
|
|
66
144
|
env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
|
|
67
145
|
}
|
|
68
146
|
const child = spawn(executable, prepared.args, {
|
|
69
|
-
stdio: [
|
|
147
|
+
stdio: [
|
|
148
|
+
prepared.stdin !== undefined ? "pipe" : "inherit",
|
|
149
|
+
"inherit",
|
|
150
|
+
"inherit",
|
|
151
|
+
],
|
|
70
152
|
env,
|
|
71
153
|
});
|
|
72
154
|
|
|
73
|
-
|
|
74
|
-
child.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return await new Promise((resolve, reject) => {
|
|
78
|
-
child.on('error', reject);
|
|
79
|
-
child.on('close', (code) => resolve(code ?? 1));
|
|
155
|
+
const codePromise = new Promise<number>((resolve, reject) => {
|
|
156
|
+
child.on("error", reject);
|
|
157
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
80
158
|
});
|
|
159
|
+
const stdinPromise =
|
|
160
|
+
prepared.stdin !== undefined ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
|
|
161
|
+
const [code] = await Promise.all([codePromise, stdinPromise]);
|
|
162
|
+
return code;
|
|
81
163
|
}
|
|
82
164
|
|
|
83
165
|
export async function runRustBinary(
|
|
@@ -103,27 +185,37 @@ export async function runRustBinary(
|
|
|
103
185
|
env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
|
|
104
186
|
}
|
|
105
187
|
const child = spawn(executable, prepared.args, {
|
|
106
|
-
stdio: [prepared.stdin !== undefined ?
|
|
188
|
+
stdio: [prepared.stdin !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
107
189
|
env,
|
|
108
190
|
});
|
|
109
191
|
|
|
110
|
-
let stdout =
|
|
111
|
-
let stderr =
|
|
112
|
-
child.stdout?.on(
|
|
192
|
+
let stdout = "";
|
|
193
|
+
let stderr = "";
|
|
194
|
+
child.stdout?.on("data", (chunk) => {
|
|
113
195
|
stdout += chunk.toString();
|
|
114
196
|
});
|
|
115
|
-
child.stderr?.on(
|
|
197
|
+
child.stderr?.on("data", (chunk) => {
|
|
116
198
|
stderr += chunk.toString();
|
|
117
199
|
});
|
|
118
200
|
|
|
119
|
-
|
|
120
|
-
child.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
201
|
+
const codePromise = new Promise<number>((resolve, reject) => {
|
|
202
|
+
child.on("error", reject);
|
|
203
|
+
child.on("close", (code, signal) => {
|
|
204
|
+
if (code !== null && code !== undefined) {
|
|
205
|
+
resolve(code);
|
|
206
|
+
} else {
|
|
207
|
+
// Terminated by signal. Preserve the remote behavior while still waiting for stdin writes.
|
|
208
|
+
resolve(
|
|
209
|
+
signal
|
|
210
|
+
? 128 + (require("node:os").constants.errno?.SIGTERM ?? 143)
|
|
211
|
+
: 1,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
126
215
|
});
|
|
216
|
+
const stdinPromise =
|
|
217
|
+
prepared.stdin !== undefined ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
|
|
218
|
+
const [code] = await Promise.all([codePromise, stdinPromise]);
|
|
127
219
|
|
|
128
220
|
if (code !== 0) {
|
|
129
221
|
throw new RustBinaryExitError(binaryName, code, stdout, stderr);
|
|
@@ -139,5 +231,9 @@ export async function runRustBinaryJson<T>(
|
|
|
139
231
|
options: RunRustBinaryOptions = {},
|
|
140
232
|
): Promise<T> {
|
|
141
233
|
const { stdout } = await runRustBinary(binaryName, args, config, options);
|
|
142
|
-
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(stdout) as T;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw new RustBinaryJsonParseError(binaryName, stdout, error);
|
|
238
|
+
}
|
|
143
239
|
}
|
package/src/lib/signed-tx.ts
CHANGED
|
@@ -84,6 +84,7 @@ export async function assertSignedBroadcastTransactionMatchesRequest(
|
|
|
84
84
|
throw new Error(`signed raw transaction to mismatch: expected ${expected.to}, received ${parsedTo ?? 'null'}`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/* c8 ignore next 3 -- supported signed transactions produced by viem always include a nonce */
|
|
87
88
|
if (parsed.nonce === undefined) {
|
|
88
89
|
throw new Error('signed raw transaction nonce is missing');
|
|
89
90
|
}
|
package/src/lib/sudo.ts
CHANGED
|
@@ -32,6 +32,20 @@ function isSudoAuthenticationFailure(output: string): boolean {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function formatSudoFailureMessage(result: SudoCommandResult): string {
|
|
36
|
+
const combinedOutput = `${result.stderr}\n${result.stdout}`;
|
|
37
|
+
if (isSudoAuthenticationFailure(combinedOutput)) {
|
|
38
|
+
return (
|
|
39
|
+
'sudo authentication failed. Enter your system admin password used for sudo, not the WLFI vault password.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return (
|
|
43
|
+
result.stderr.trim() ||
|
|
44
|
+
result.stdout.trim() ||
|
|
45
|
+
`sudo credential check failed (exit code ${result.code})`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
async function runCommand(
|
|
36
50
|
command: string,
|
|
37
51
|
args: string[],
|
|
@@ -103,11 +117,7 @@ export function createSudoSession(deps: CreateSudoSessionDeps) {
|
|
|
103
117
|
);
|
|
104
118
|
|
|
105
119
|
if (result.code !== 0) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
result.stderr.trim() ||
|
|
108
|
-
result.stdout.trim() ||
|
|
109
|
-
`sudo credential check failed (exit code ${result.code})`,
|
|
110
|
-
);
|
|
120
|
+
throw new Error(formatSudoFailureMessage(result));
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
primed = true;
|
|
@@ -31,6 +31,7 @@ function deriveWalletAddress(vaultPublicKey: string): string | undefined {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function findMatchingBootstrapSummary(profile: WalletProfile | undefined): BootstrapSetupSummary | undefined {
|
|
34
|
+
/* c8 ignore next 3 -- resolveWalletProfile only calls this helper when config.wallet is present */
|
|
34
35
|
if (!profile) {
|
|
35
36
|
return undefined;
|
|
36
37
|
}
|
|
@@ -101,6 +102,7 @@ export function resolveWalletProfile(config: WlfiConfig): WalletProfile {
|
|
|
101
102
|
|
|
102
103
|
export function resolveWalletAddress(config: WlfiConfig): Address {
|
|
103
104
|
const profile = resolveWalletProfile(config);
|
|
105
|
+
/* c8 ignore next -- explicit-address and derived-address paths are both exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
|
|
104
106
|
const address = presentString(profile.address) ?? deriveWalletAddress(profile.vaultPublicKey);
|
|
105
107
|
if (!address || !isAddress(address)) {
|
|
106
108
|
throw new Error(
|
|
@@ -175,6 +177,7 @@ function collectWalletBalanceTargets(config: WlfiConfig): WalletBalanceTarget[]
|
|
|
175
177
|
symbol: tokenProfile.symbol,
|
|
176
178
|
name: tokenProfile.name,
|
|
177
179
|
chainKey,
|
|
180
|
+
/* c8 ignore next -- resolveChainProfile only yields usable balance targets when a chain name is available */
|
|
178
181
|
chainName: resolvedChain?.name ?? chainKey,
|
|
179
182
|
chainId: chainProfile.chainId,
|
|
180
183
|
rpcUrl,
|
package/src/lib/wallet-setup.ts
CHANGED
|
@@ -62,6 +62,8 @@ export interface WalletSetupAdminArgsInput {
|
|
|
62
62
|
attachPolicyId?: string[];
|
|
63
63
|
attachBootstrapPolicies?: boolean;
|
|
64
64
|
fromSharedConfig?: boolean;
|
|
65
|
+
existingVaultKeyId?: string;
|
|
66
|
+
existingVaultPublicKey?: string;
|
|
65
67
|
bootstrapOutputPath: string;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -250,6 +252,7 @@ function assertNoSymlinkAncestorDirectories(targetPath: string, label: string):
|
|
|
250
252
|
for (const segment of relativeParent.split(path.sep).filter(Boolean)) {
|
|
251
253
|
currentPath = path.join(currentPath, segment);
|
|
252
254
|
const stats = readLstat(currentPath);
|
|
255
|
+
/* c8 ignore next 3 -- missing ancestors short-circuit preview traversal defensively; behavior is exercised indirectly but c8 misattributes this break */
|
|
253
256
|
if (!stats) {
|
|
254
257
|
break;
|
|
255
258
|
}
|
|
@@ -283,6 +286,7 @@ function currentEffectiveUid(): number | null {
|
|
|
283
286
|
if (typeof process.geteuid === 'function') {
|
|
284
287
|
return process.geteuid();
|
|
285
288
|
}
|
|
289
|
+
/* c8 ignore next 4 -- fallback runtimes without geteuid are defensive-only in this CLI environment */
|
|
286
290
|
if (typeof process.getuid === 'function') {
|
|
287
291
|
return process.getuid();
|
|
288
292
|
}
|
|
@@ -290,6 +294,7 @@ function currentEffectiveUid(): number | null {
|
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
function canCurrentProcessTightenDirectoryMode(stats: fs.Stats): boolean {
|
|
297
|
+
/* c8 ignore next 3 -- win32/uid-less stats short-circuit is defensive and platform-specific */
|
|
293
298
|
if (process.platform === 'win32' || typeof stats.uid !== 'number') {
|
|
294
299
|
return true;
|
|
295
300
|
}
|
|
@@ -636,6 +641,28 @@ function resolveValidatedWalletSetupPolicyIds(values: string[] | undefined): str
|
|
|
636
641
|
);
|
|
637
642
|
}
|
|
638
643
|
|
|
644
|
+
function resolveValidatedExistingVaultReuse(input: {
|
|
645
|
+
existingVaultKeyId?: string;
|
|
646
|
+
existingVaultPublicKey?: string;
|
|
647
|
+
}): { keyId?: string; publicKey?: string } {
|
|
648
|
+
const keyId = presentString(input.existingVaultKeyId);
|
|
649
|
+
const publicKey = presentString(input.existingVaultPublicKey);
|
|
650
|
+
|
|
651
|
+
if (!keyId && !publicKey) {
|
|
652
|
+
return {};
|
|
653
|
+
}
|
|
654
|
+
if (!keyId || !publicKey) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
'existingVaultKeyId and existingVaultPublicKey must be provided together to reuse an existing wallet',
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
keyId: assertWalletSetupUuid(keyId, 'existingVaultKeyId'),
|
|
662
|
+
publicKey,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
639
666
|
function previewBootstrapOutputPath(inputPath?: string): WalletSetupBootstrapOutput {
|
|
640
667
|
const explicitPath = presentString(inputPath);
|
|
641
668
|
if (explicitPath) {
|
|
@@ -794,6 +821,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
|
|
|
794
821
|
summary.perTxMaxCalldataBytesPolicyId,
|
|
795
822
|
].filter((value): value is string => typeof value === 'string' && value.length > 0);
|
|
796
823
|
|
|
824
|
+
/* c8 ignore next -- bootstrap parsing normalizes omitted destination overrides to [], but c8 still tracks the nullish fallback separately */
|
|
797
825
|
for (const destinationOverride of summary.destinationOverrides ?? []) {
|
|
798
826
|
ids.push(
|
|
799
827
|
destinationOverride.perTxPolicyId,
|
|
@@ -817,6 +845,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
|
|
|
817
845
|
}
|
|
818
846
|
}
|
|
819
847
|
|
|
848
|
+
/* c8 ignore next -- bootstrap parsing normalizes omitted token policies to [], but c8 still tracks the nullish fallback separately */
|
|
820
849
|
for (const tokenPolicy of summary.tokenPolicies ?? []) {
|
|
821
850
|
ids.push(tokenPolicy.perTxPolicyId, tokenPolicy.dailyPolicyId, tokenPolicy.weeklyPolicyId);
|
|
822
851
|
if (tokenPolicy.gasPolicyId) {
|
|
@@ -836,6 +865,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
|
|
|
836
865
|
}
|
|
837
866
|
}
|
|
838
867
|
|
|
868
|
+
/* c8 ignore next -- bootstrap parsing normalizes omitted token destination overrides to [], but c8 still tracks the nullish fallback separately */
|
|
839
869
|
for (const destinationOverride of summary.tokenDestinationOverrides ?? []) {
|
|
840
870
|
ids.push(
|
|
841
871
|
destinationOverride.perTxPolicyId,
|
|
@@ -859,6 +889,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
|
|
|
859
889
|
}
|
|
860
890
|
}
|
|
861
891
|
|
|
892
|
+
/* c8 ignore next -- bootstrap parsing normalizes omitted manual approval policies to [], but c8 still tracks the nullish fallback separately */
|
|
862
893
|
for (const manualApproval of summary.tokenManualApprovalPolicies ?? []) {
|
|
863
894
|
ids.push(manualApproval.policyId);
|
|
864
895
|
}
|
|
@@ -1186,7 +1217,9 @@ function formatBooleanPlanValue(value: boolean): string {
|
|
|
1186
1217
|
|
|
1187
1218
|
function formatWalletSetupScope(plan: WalletSetupPlan): string[] {
|
|
1188
1219
|
return [
|
|
1220
|
+
/* c8 ignore next -- both default-network and explicit-network renderings are exercised, but c8 misattributes this ternary under --experimental-strip-types */
|
|
1189
1221
|
`- Network: ${plan.policyScope.network === null ? 'daemon default' : String(plan.policyScope.network)}`,
|
|
1222
|
+
/* c8 ignore next -- both default and explicit chain-name renderings are exercised, but c8 misattributes this nullish expression */
|
|
1190
1223
|
`- Chain Name: ${plan.policyScope.chainName ?? 'daemon default'}`,
|
|
1191
1224
|
`- Recipient: ${plan.policyScope.recipient ?? 'all recipients'}`,
|
|
1192
1225
|
`- Asset Mode: ${plan.policyScope.assets.mode}`,
|
|
@@ -1218,6 +1251,7 @@ function formatWalletSetupPreflight(plan: WalletSetupPlan): string[] {
|
|
|
1218
1251
|
return [
|
|
1219
1252
|
`- Daemon Socket Trusted: ${formatBooleanPlanValue(plan.preflight.daemonSocketTrusted)}`,
|
|
1220
1253
|
plan.preflight.daemonSocketError ? ` ${plan.preflight.daemonSocketError}` : null,
|
|
1254
|
+
/* c8 ignore next -- both null and boolean RPC preflight states are exercised, but c8 misattributes this ternary */
|
|
1221
1255
|
`- RPC URL Trusted: ${
|
|
1222
1256
|
plan.preflight.rpcUrlTrusted === null
|
|
1223
1257
|
? 'not applicable'
|
|
@@ -1230,10 +1264,12 @@ function formatWalletSetupPreflight(plan: WalletSetupPlan): string[] {
|
|
|
1230
1264
|
}
|
|
1231
1265
|
|
|
1232
1266
|
export function formatWalletSetupPlanText(plan: WalletSetupPlan): string {
|
|
1267
|
+
/* c8 ignore next -- explicit and auto-generated bootstrap outputs are both exercised, but c8 misattributes this ternary */
|
|
1233
1268
|
const bootstrapOutputLabel = `${plan.bootstrapOutput.path} (${plan.bootstrapOutput.autoGenerated ? 'auto-generated' : 'explicit'}, ${plan.bootstrapOutput.cleanupAction} after import)`;
|
|
1234
1269
|
|
|
1235
1270
|
const lines = [
|
|
1236
1271
|
'Wallet Setup Preview',
|
|
1272
|
+
/* c8 ignore next -- allowed and blocked admin access renderings are both exercised, but c8 misattributes this ternary */
|
|
1237
1273
|
`Admin Access: ${plan.adminAccess.permitted ? 'allowed' : 'blocked'} (${plan.adminAccess.mode})`,
|
|
1238
1274
|
`Admin Access Reason: ${plan.adminAccess.reason}`,
|
|
1239
1275
|
`Daemon Socket: ${plan.daemonSocket}`,
|
|
@@ -1260,8 +1296,11 @@ export function formatWalletSetupPlanText(plan: WalletSetupPlan): string {
|
|
|
1260
1296
|
'Config After Setup',
|
|
1261
1297
|
`- Agent Key ID: ${plan.configAfterSetup.agentKeyId}`,
|
|
1262
1298
|
`- Daemon Socket: ${plan.configAfterSetup.daemonSocket}`,
|
|
1299
|
+
/* c8 ignore next -- unchanged and explicit chain-id renderings are both exercised, but c8 misattributes this ternary */
|
|
1263
1300
|
`- Chain ID: ${plan.configAfterSetup.chainId === null ? 'unchanged' : String(plan.configAfterSetup.chainId)}`,
|
|
1301
|
+
/* c8 ignore next -- unchanged and explicit chain-name renderings are both exercised, but c8 misattributes this nullish expression */
|
|
1264
1302
|
`- Chain Name: ${plan.configAfterSetup.chainName ?? 'unchanged'}`,
|
|
1303
|
+
/* c8 ignore next -- unchanged and explicit RPC URL renderings are both exercised, but c8 misattributes this nullish expression */
|
|
1265
1304
|
`- RPC URL: ${plan.configAfterSetup.rpcUrl ?? 'unchanged'}`,
|
|
1266
1305
|
'',
|
|
1267
1306
|
'Preflight',
|
|
@@ -1318,6 +1357,7 @@ export function buildWalletSetupAdminArgs(input: WalletSetupAdminArgsInput): str
|
|
|
1318
1357
|
const tokens = resolveValidatedWalletSetupTokenList(input.token);
|
|
1319
1358
|
const policyIds = resolveValidatedWalletSetupPolicyIds(input.attachPolicyId);
|
|
1320
1359
|
const fromSharedConfig = shouldBootstrapFromSharedConfig(input);
|
|
1360
|
+
const existingVaultReuse = resolveValidatedExistingVaultReuse(input);
|
|
1321
1361
|
|
|
1322
1362
|
if (input.vaultPassword) {
|
|
1323
1363
|
throw new Error(
|
|
@@ -1338,6 +1378,14 @@ export function buildWalletSetupAdminArgs(input: WalletSetupAdminArgsInput): str
|
|
|
1338
1378
|
if (fromSharedConfig) {
|
|
1339
1379
|
args.push('--from-shared-config');
|
|
1340
1380
|
}
|
|
1381
|
+
if (existingVaultReuse.keyId && existingVaultReuse.publicKey) {
|
|
1382
|
+
args.push(
|
|
1383
|
+
'--existing-vault-key-id',
|
|
1384
|
+
existingVaultReuse.keyId,
|
|
1385
|
+
'--existing-vault-public-key',
|
|
1386
|
+
existingVaultReuse.publicKey,
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1341
1389
|
|
|
1342
1390
|
const appendValue = (flag: string, value: string | undefined) => {
|
|
1343
1391
|
if (value) {
|
|
@@ -1376,6 +1424,7 @@ export function completeWalletSetup(
|
|
|
1376
1424
|
options: CompleteWalletSetupOptions,
|
|
1377
1425
|
deps: CompleteWalletSetupDeps = {},
|
|
1378
1426
|
): CompleteWalletSetupResult {
|
|
1427
|
+
/* c8 ignore next -- tests cover both explicit darwin and non-darwin behavior, but c8 misattributes this nullish expression */
|
|
1379
1428
|
const platform = deps.platform ?? process.platform;
|
|
1380
1429
|
if (platform !== 'darwin') {
|
|
1381
1430
|
throw new Error(
|
|
@@ -1392,6 +1441,7 @@ export function completeWalletSetup(
|
|
|
1392
1441
|
throw new Error('--chain-name requires --network');
|
|
1393
1442
|
}
|
|
1394
1443
|
|
|
1444
|
+
/* c8 ignore next -- default keychain storage is environment-coupled, so tests exercise the injected path instead */
|
|
1395
1445
|
const storeAgentAuthToken = deps.storeAgentAuthToken ?? storeAgentAuthTokenInKeychain;
|
|
1396
1446
|
const loadConfig = deps.readConfig ?? readConfig;
|
|
1397
1447
|
const persistConfig = deps.writeConfig ?? writeConfig;
|
|
@@ -1416,6 +1466,7 @@ export function completeWalletSetup(
|
|
|
1416
1466
|
const { summary, credentials } = readBootstrapSetupFile(options.bootstrapOutputPath);
|
|
1417
1467
|
assertBootstrapSetupSummaryLeaseIsActive(summary);
|
|
1418
1468
|
|
|
1469
|
+
/* c8 ignore next 5 -- summary and credentials currently parse the same agent_key_id field, so this mismatch guard is a defensive invariant */
|
|
1419
1470
|
if (summary.agentKeyId !== credentials.agentKeyId) {
|
|
1420
1471
|
throw new Error(
|
|
1421
1472
|
'bootstrap credentials file agent_key_id does not match setup summary agent_key_id',
|
|
@@ -1438,6 +1489,7 @@ export function completeWalletSetup(
|
|
|
1438
1489
|
nextConfig.chainId = assertPositiveChainId(options.network);
|
|
1439
1490
|
nextConfig.chainName =
|
|
1440
1491
|
normalizedChainName ??
|
|
1492
|
+
/* c8 ignore next -- configured-chain and synthetic fallback label paths are exercised, but c8 misattributes this nullish expression */
|
|
1441
1493
|
configuredChainName(nextConfig.chainId, currentConfig) ??
|
|
1442
1494
|
`chain-${nextConfig.chainId}`;
|
|
1443
1495
|
nextConfig.rpcUrl = normalizedRpcUrl ?? configuredRpcUrl(nextConfig.chainId, currentConfig);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/cache/dist/chunk-QF4XKEIA.cjs","../src/service/index.ts"],"names":[],"mappings":"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","file":"/Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/cache/dist/chunk-QF4XKEIA.cjs","sourcesContent":[null,"import { createHash, randomBytes, randomUUID } from 'node:crypto';\nimport type Redis from 'ioredis';\nimport { getCacheClient } from '../client/index.js';\nimport { CacheError, cacheErrorCodes, toCacheError } from '../errors/index.js';\n\nexport const relayApprovalStatuses = [\n 'pending',\n 'approved',\n 'rejected',\n 'completed',\n 'expired',\n] as const;\nexport type RelayApprovalStatus = (typeof relayApprovalStatuses)[number];\n\nexport const relayUpdateStatuses = [\n 'pending',\n 'inflight',\n 'applied',\n 'rejected',\n 'failed',\n] as const;\nexport type RelayUpdateStatus = (typeof relayUpdateStatuses)[number];\n\nexport interface RelayDaemonProfile {\n daemonId: string;\n daemonPublicKey: string;\n ethereumAddress: string;\n label?: string;\n lastSeenAt: string;\n registeredAt: string;\n relayUrl?: string;\n signerBackend?: string;\n status: 'active' | 'paused';\n updatedAt: string;\n version?: string;\n}\n\nexport interface RelayPolicyRecord {\n action: string;\n amountMaxWei?: string;\n amountMinWei?: string;\n chainId?: number;\n daemonId: string;\n destination: string;\n metadata?: Record<string, string>;\n policyId: string;\n requiresManualApproval: boolean;\n scope: 'default' | 'override';\n tokenAddress?: string;\n updatedAt: string;\n}\n\nexport interface RelayAgentKeyRecord {\n agentKeyId: string;\n createdAt?: string;\n daemonId: string;\n label?: string;\n metadata?: Record<string, string>;\n status: 'active' | 'revoked';\n updatedAt: string;\n}\n\nexport interface RelayApprovalRequestRecord {\n agentKeyId?: string;\n amountWei?: string;\n approvalRequestId: string;\n chainId?: number;\n daemonId: string;\n destination: string;\n metadata?: Record<string, string>;\n network?: string;\n reason?: string;\n requestedAt: string;\n status: RelayApprovalStatus;\n tokenAddress?: string;\n transactionType: string;\n updatedAt: string;\n}\n\nexport interface RelayEncryptedPayload {\n aadBase64?: string;\n algorithm: string;\n ciphertextBase64: string;\n contentSha256Hex?: string;\n encapsulatedKeyBase64: string;\n nonceBase64: string;\n schemaVersion: number;\n}\n\nexport interface RelayUpdateFeedbackRecord {\n daemonId: string;\n details?: Record<string, string>;\n feedbackAt: string;\n message?: string;\n status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;\n updateId: string;\n}\n\nexport interface RelayEncryptedUpdateRecord {\n claimToken?: string;\n claimUntil?: string;\n createdAt: string;\n daemonId: string;\n feedback?: RelayUpdateFeedbackRecord;\n lastDeliveredAt?: string;\n metadata?: Record<string, string>;\n payload: RelayEncryptedPayload;\n status: RelayUpdateStatus;\n targetApprovalRequestId?: string;\n type: string;\n updateId: string;\n updatedAt: string;\n}\n\nexport interface SyncDaemonRegistrationInput {\n agentKeys?: RelayAgentKeyRecord[];\n approvalRequests?: RelayApprovalRequestRecord[];\n daemon: RelayDaemonProfile;\n policies?: RelayPolicyRecord[];\n}\n\nexport interface ApprovalRequestFilters {\n daemonId?: string;\n destination?: string;\n limit?: number;\n status?: RelayApprovalStatus;\n tokenAddress?: string;\n}\n\nexport interface CreateEncryptedUpdateInput {\n daemonId: string;\n metadata?: Record<string, string>;\n payload: RelayEncryptedPayload;\n targetApprovalRequestId?: string;\n type: string;\n updateId?: string;\n}\n\nexport interface ClaimEncryptedUpdatesInput {\n daemonId: string;\n leaseSeconds?: number;\n limit?: number;\n}\n\nexport interface SubmitUpdateFeedbackInput {\n claimToken: string;\n daemonId: string;\n details?: Record<string, string>;\n message?: string;\n status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;\n updateId: string;\n}\n\nexport interface ApprovalCapabilityFailureRecord {\n attempts: number;\n blockedUntil?: string;\n firstFailedAt: string;\n lastFailedAt: string;\n}\n\nexport interface RecordApprovalCapabilityFailureResult {\n attempts: number;\n blocked: boolean;\n blockedUntil: string | null;\n}\n\ninterface JsonCache {\n del(key: string): Promise<number>;\n get(key: string): Promise<string | null>;\n ping(): Promise<string>;\n quit(): Promise<string>;\n sadd(key: string, ...members: string[]): Promise<number>;\n set(key: string, value: string, mode?: 'NX' | 'XX'): Promise<'OK' | null>;\n smembers(key: string): Promise<string[]>;\n zadd(key: string, ...args: (string | number)[]): Promise<number>;\n zrange(key: string, start: number, stop: number, ...args: string[]): Promise<string[]>;\n zrem(key: string, ...members: string[]): Promise<number>;\n}\n\nconst defaultNamespace = 'wlfi:relay';\nconst approvalCapabilityFailureWindowMs = 5 * 60 * 1000;\nconst approvalCapabilityMaxFailures = 5;\nconst approvalCapabilityBlockWindowMs = 10 * 60 * 1000;\n\nconst toIsoTimestamp = (value = new Date()): string => value.toISOString();\n\nconst dedupe = <T>(values: T[]): T[] => [...new Set(values)];\n\nconst matchesOptionalFilter = (\n value: string | undefined,\n expected: string | undefined,\n): boolean => {\n if (!expected) {\n return true;\n }\n\n return value?.toLowerCase() === expected.toLowerCase();\n};\n\nconst clampLimit = (limit: number | undefined, fallback: number, max: number): number => {\n if (!limit || Number.isNaN(limit)) {\n return fallback;\n }\n\n return Math.max(1, Math.min(limit, max));\n};\n\nconst createApprovalCapabilityToken = (): string => randomBytes(32).toString('hex');\n\nconst approvalCapabilityHash = (token: string): string =>\n createHash('sha256').update(token, 'utf8').digest('hex');\n\nconst isActiveApprovalUpdateRecord = (\n record: RelayEncryptedUpdateRecord | null | undefined,\n approvalRequestId: string,\n): record is RelayEncryptedUpdateRecord =>\n Boolean(\n record &&\n record.type === 'manual_approval_decision' &&\n record.targetApprovalRequestId === approvalRequestId &&\n (record.status === 'pending' || record.status === 'inflight'),\n );\n\nconst preserveRotatedApprovalCapability = (\n incoming: RelayApprovalRequestRecord,\n existing: RelayApprovalRequestRecord | null,\n): RelayApprovalRequestRecord => {\n const existingMetadata = existing?.metadata;\n const incomingMetadata = incoming.metadata;\n const preservedCapabilityToken = existingMetadata?.approvalCapabilityToken?.trim();\n const preservedCapabilityHash = existingMetadata?.approvalCapabilityHash?.trim();\n\n if (!preservedCapabilityToken && !preservedCapabilityHash) {\n return incoming;\n }\n\n return {\n ...incoming,\n metadata: {\n ...(incomingMetadata ?? {}),\n ...(preservedCapabilityToken\n ? { approvalCapabilityToken: preservedCapabilityToken }\n : {}),\n ...(preservedCapabilityHash ? { approvalCapabilityHash: preservedCapabilityHash } : {}),\n },\n };\n};\n\nexport class RelayCacheService {\n private readonly client: JsonCache;\n private readonly namespace: string;\n\n constructor(options: { client?: Redis; namespace?: string } = {}) {\n this.client = (options.client ?? getCacheClient()) as unknown as JsonCache;\n this.namespace = options.namespace ?? defaultNamespace;\n }\n\n async ping(): Promise<string> {\n try {\n return await this.client.ping();\n } catch (error) {\n throw toCacheError(error, { operation: 'ping' });\n }\n }\n\n async syncDaemonRegistration(input: SyncDaemonRegistrationInput): Promise<{\n agentKeyCount: number;\n approvalRequestCount: number;\n policyCount: number;\n }> {\n const profile = {\n ...input.daemon,\n lastSeenAt: input.daemon.lastSeenAt || toIsoTimestamp(),\n updatedAt: input.daemon.updatedAt || toIsoTimestamp(),\n } satisfies RelayDaemonProfile;\n\n try {\n await this.writeJson(this.daemonProfileKey(profile.daemonId), profile);\n await this.client.sadd(this.daemonIndexKey(), profile.daemonId);\n\n if (input.policies) {\n const policies = input.policies.map((policy) => ({\n ...policy,\n daemonId: profile.daemonId,\n }));\n await this.writeJson(this.daemonPoliciesKey(profile.daemonId), policies);\n }\n\n if (input.agentKeys) {\n const agentKeys = input.agentKeys.map((agentKey) => ({\n ...agentKey,\n daemonId: profile.daemonId,\n }));\n await this.writeJson(this.daemonAgentKeysKey(profile.daemonId), agentKeys);\n }\n\n if (input.approvalRequests) {\n for (const approvalRequest of input.approvalRequests) {\n const existing = await this.readJson<RelayApprovalRequestRecord>(\n this.approvalKey(approvalRequest.approvalRequestId),\n );\n const normalized = preserveRotatedApprovalCapability(\n { ...approvalRequest, daemonId: profile.daemonId },\n existing,\n );\n await this.writeJson(this.approvalKey(normalized.approvalRequestId), normalized);\n await this.client.zadd(\n this.daemonApprovalsKey(profile.daemonId),\n Date.parse(normalized.requestedAt),\n normalized.approvalRequestId,\n );\n }\n }\n\n return {\n agentKeyCount: input.agentKeys?.length ?? 0,\n approvalRequestCount: input.approvalRequests?.length ?? 0,\n policyCount: input.policies?.length ?? 0,\n };\n } catch (error) {\n throw toCacheError(error, {\n key: this.daemonProfileKey(profile.daemonId),\n operation: 'syncDaemonRegistration',\n });\n }\n }\n\n async listDaemons(): Promise<RelayDaemonProfile[]> {\n const daemonIds = await this.client.smembers(this.daemonIndexKey());\n const profiles = await Promise.all(\n daemonIds.map((daemonId) =>\n this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId)),\n ),\n );\n\n return profiles.filter((profile): profile is RelayDaemonProfile => Boolean(profile));\n }\n\n async getDaemonProfile(daemonId: string): Promise<RelayDaemonProfile | null> {\n return await this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId));\n }\n\n async getDaemonPolicies(daemonId: string): Promise<RelayPolicyRecord[]> {\n return (await this.readJson<RelayPolicyRecord[]>(this.daemonPoliciesKey(daemonId))) ?? [];\n }\n\n async getDaemonAgentKeys(daemonId: string): Promise<RelayAgentKeyRecord[]> {\n return (await this.readJson<RelayAgentKeyRecord[]>(this.daemonAgentKeysKey(daemonId))) ?? [];\n }\n\n async getApprovalRequest(approvalRequestId: string): Promise<RelayApprovalRequestRecord | null> {\n return await this.readJson<RelayApprovalRequestRecord>(this.approvalKey(approvalRequestId));\n }\n\n async listApprovalRequests(\n filters: ApprovalRequestFilters = {},\n ): Promise<RelayApprovalRequestRecord[]> {\n const limit = clampLimit(filters.limit, 100, 500);\n const daemonIds = filters.daemonId\n ? [filters.daemonId]\n : await this.client.smembers(this.daemonIndexKey());\n const requestIdsByDaemon = await Promise.all(\n daemonIds.map((daemonId) =>\n this.client.zrange(this.daemonApprovalsKey(daemonId), 0, limit * 2, 'REV'),\n ),\n );\n const requestIds = dedupe(requestIdsByDaemon.flat()).slice(0, limit * 3);\n const requests = await Promise.all(\n requestIds.map((requestId) =>\n this.readJson<RelayApprovalRequestRecord>(this.approvalKey(requestId)),\n ),\n );\n\n return requests\n .filter((request): request is RelayApprovalRequestRecord => Boolean(request))\n .filter((request) => (filters.daemonId ? request.daemonId === filters.daemonId : true))\n .filter((request) => (filters.status ? request.status === filters.status : true))\n .filter((request) => matchesOptionalFilter(request.destination, filters.destination))\n .filter((request) => matchesOptionalFilter(request.tokenAddress, filters.tokenAddress))\n .sort((left, right) => Date.parse(right.requestedAt) - Date.parse(left.requestedAt))\n .slice(0, limit);\n }\n\n async createEncryptedUpdate(\n input: CreateEncryptedUpdateInput,\n ): Promise<RelayEncryptedUpdateRecord> {\n if (input.type === 'manual_approval_decision') {\n if (!input.targetApprovalRequestId) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n message: 'Manual approval updates require a target approval request id',\n operation: 'createEncryptedUpdate',\n });\n }\n\n const approvalKey = this.approvalKey(input.targetApprovalRequestId);\n const approval = await this.readJson<RelayApprovalRequestRecord>(approvalKey);\n if (!approval) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key: approvalKey,\n message: `Unknown approval '${input.targetApprovalRequestId}'`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n if (approval.daemonId !== input.daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: approvalKey,\n message: `Approval '${input.targetApprovalRequestId}' belongs to daemon '${approval.daemonId}', not '${input.daemonId}'`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n if (approval.status !== 'pending') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: approvalKey,\n message: `Approval '${input.targetApprovalRequestId}' is '${approval.status}' and cannot accept new updates`,\n operation: 'createEncryptedUpdate',\n });\n }\n }\n\n const updateId = input.updateId ?? randomUUID();\n const now = toIsoTimestamp();\n const record: RelayEncryptedUpdateRecord = {\n createdAt: now,\n daemonId: input.daemonId,\n metadata: input.metadata,\n payload: input.payload,\n status: 'pending',\n targetApprovalRequestId: input.targetApprovalRequestId,\n type: input.type,\n updateId,\n updatedAt: now,\n };\n\n const activeApprovalKey =\n input.type === 'manual_approval_decision' && input.targetApprovalRequestId\n ? this.activeApprovalUpdateKey(input.targetApprovalRequestId)\n : null;\n const updateKey = this.updateKey(updateId);\n let ownsActiveApprovalSlot = false;\n\n await this.writeJson(updateKey, record);\n\n try {\n if (activeApprovalKey) {\n const reserved = await this.client.set(activeApprovalKey, updateId, 'NX');\n if (reserved !== 'OK') {\n const existingUpdateId = await this.client.get(activeApprovalKey);\n const existingRecord = existingUpdateId\n ? await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(existingUpdateId))\n : null;\n\n if (isActiveApprovalUpdateRecord(existingRecord, input.targetApprovalRequestId!)) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: activeApprovalKey,\n message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n await this.client.del(activeApprovalKey);\n const retriedReservation = await this.client.set(activeApprovalKey, updateId, 'NX');\n if (retriedReservation !== 'OK') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: activeApprovalKey,\n message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,\n operation: 'createEncryptedUpdate',\n });\n }\n }\n\n ownsActiveApprovalSlot = true;\n }\n\n await this.client.zadd(this.daemonUpdatesKey(input.daemonId), Date.now(), updateId);\n } catch (error) {\n await this.client.del(updateKey);\n if (activeApprovalKey && ownsActiveApprovalSlot) {\n await this.client.del(activeApprovalKey);\n }\n throw error;\n }\n\n return record;\n }\n\n async hasActiveApprovalUpdate(daemonId: string, approvalRequestId: string): Promise<boolean> {\n const indexedUpdateId = await this.client.get(this.activeApprovalUpdateKey(approvalRequestId));\n if (indexedUpdateId) {\n const indexedRecord = await this.readJson<RelayEncryptedUpdateRecord>(\n this.updateKey(indexedUpdateId),\n );\n if (isActiveApprovalUpdateRecord(indexedRecord, approvalRequestId)) {\n return true;\n }\n\n await this.client.del(this.activeApprovalUpdateKey(approvalRequestId));\n }\n\n const updateIds = await this.client.zrange(this.daemonUpdatesKey(daemonId), 0, -1, 'REV');\n\n for (const updateId of updateIds) {\n const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n if (isActiveApprovalUpdateRecord(record, approvalRequestId)) {\n await this.client.set(this.activeApprovalUpdateKey(approvalRequestId), updateId, 'NX');\n return true;\n }\n }\n\n return false;\n }\n\n async consumeApprovalCapability(\n approvalRequestId: string,\n capabilityHash: string,\n ): Promise<boolean> {\n try {\n const result = await this.client.set(\n this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n toIsoTimestamp(),\n 'NX',\n );\n return result === 'OK';\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n operation: 'consumeApprovalCapability',\n });\n }\n }\n\n async releaseApprovalCapabilityConsumption(\n approvalRequestId: string,\n capabilityHash: string,\n ): Promise<void> {\n try {\n await this.client.del(this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash));\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n operation: 'releaseApprovalCapabilityConsumption',\n });\n }\n }\n\n async clearApprovalCapabilityFailures(approvalRequestId: string): Promise<void> {\n try {\n await this.client.del(this.approvalCapabilityFailuresKey(approvalRequestId));\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityFailuresKey(approvalRequestId),\n operation: 'clearApprovalCapabilityFailures',\n });\n }\n }\n\n async recordApprovalCapabilityFailure(\n approvalRequestId: string,\n ): Promise<RecordApprovalCapabilityFailureResult> {\n const key = this.approvalCapabilityFailuresKey(approvalRequestId);\n const now = new Date();\n const nowMs = now.getTime();\n const existing = await this.readJson<ApprovalCapabilityFailureRecord>(key);\n\n if (existing?.blockedUntil && Date.parse(existing.blockedUntil) > nowMs) {\n return {\n attempts: existing.attempts,\n blocked: true,\n blockedUntil: existing.blockedUntil,\n };\n }\n\n const firstFailedAtMs = existing?.firstFailedAt\n ? Date.parse(existing.firstFailedAt)\n : Number.NaN;\n const withinWindow =\n Number.isFinite(firstFailedAtMs) &&\n nowMs - firstFailedAtMs <= approvalCapabilityFailureWindowMs;\n const attempts = withinWindow && existing ? existing.attempts + 1 : 1;\n const firstFailedAt = withinWindow && existing ? existing.firstFailedAt : now.toISOString();\n const blockedUntil =\n attempts >= approvalCapabilityMaxFailures\n ? new Date(nowMs + approvalCapabilityBlockWindowMs).toISOString()\n : undefined;\n\n await this.writeJson(key, {\n attempts,\n blockedUntil,\n firstFailedAt,\n lastFailedAt: now.toISOString(),\n } satisfies ApprovalCapabilityFailureRecord);\n\n return {\n attempts,\n blocked: blockedUntil !== undefined,\n blockedUntil: blockedUntil ?? null,\n };\n }\n\n async rotateApprovalCapability(approvalRequestId: string): Promise<RelayApprovalRequestRecord> {\n const key = this.approvalKey(approvalRequestId);\n const approval = await this.readJson<RelayApprovalRequestRecord>(key);\n\n if (!approval) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown approval '${approvalRequestId}'`,\n operation: 'rotateApprovalCapability',\n });\n }\n\n if (approval.status !== 'pending') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key,\n message: `Approval '${approvalRequestId}' is '${approval.status}' and cannot accept a new secure approval link`,\n operation: 'rotateApprovalCapability',\n });\n }\n\n const capabilityToken = createApprovalCapabilityToken();\n const nextRecord: RelayApprovalRequestRecord = {\n ...approval,\n metadata: {\n ...(approval.metadata ?? {}),\n approvalCapabilityHash: approvalCapabilityHash(capabilityToken),\n approvalCapabilityToken: capabilityToken,\n },\n updatedAt: toIsoTimestamp(),\n };\n\n await this.writeJson(key, nextRecord);\n await this.clearApprovalCapabilityFailures(approvalRequestId);\n\n return nextRecord;\n }\n\n async claimEncryptedUpdates(\n input: ClaimEncryptedUpdatesInput,\n ): Promise<RelayEncryptedUpdateRecord[]> {\n const limit = clampLimit(input.limit, 25, 100);\n const leaseSeconds = clampLimit(input.leaseSeconds, 30, 300);\n const now = new Date();\n const nowMs = now.getTime();\n const claimUntil = new Date(nowMs + leaseSeconds * 1000).toISOString();\n const updateIds = await this.client.zrange(\n this.daemonUpdatesKey(input.daemonId),\n 0,\n limit * 4,\n 'REV',\n );\n const claimed: RelayEncryptedUpdateRecord[] = [];\n\n for (const updateId of updateIds) {\n if (claimed.length >= limit) {\n break;\n }\n\n const claimLockKey = this.updateClaimLockKey(updateId);\n let ownsClaimLock = false;\n\n try {\n const reserved = await this.client.set(claimLockKey, claimUntil, 'NX');\n if (reserved !== 'OK') {\n const existingClaimLockUntil = await this.client.get(claimLockKey);\n if (\n existingClaimLockUntil &&\n Number.isFinite(Date.parse(existingClaimLockUntil)) &&\n Date.parse(existingClaimLockUntil) > nowMs\n ) {\n continue;\n }\n\n await this.client.del(claimLockKey);\n const retriedReservation = await this.client.set(claimLockKey, claimUntil, 'NX');\n if (retriedReservation !== 'OK') {\n continue;\n }\n }\n\n ownsClaimLock = true;\n\n const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n if (!record) {\n continue;\n }\n\n if (\n record.status === 'applied' ||\n record.status === 'failed' ||\n record.status === 'rejected'\n ) {\n continue;\n }\n\n if (\n record.status === 'inflight' &&\n record.claimUntil &&\n Date.parse(record.claimUntil) > nowMs\n ) {\n continue;\n }\n\n const nextRecord: RelayEncryptedUpdateRecord = {\n ...record,\n claimToken: randomUUID(),\n claimUntil,\n lastDeliveredAt: now.toISOString(),\n status: 'inflight',\n updatedAt: now.toISOString(),\n };\n await this.writeJson(this.updateKey(updateId), nextRecord);\n claimed.push(nextRecord);\n } finally {\n if (ownsClaimLock) {\n await this.client.del(claimLockKey);\n }\n }\n }\n\n return claimed;\n }\n\n async submitUpdateFeedback(\n input: SubmitUpdateFeedbackInput,\n ): Promise<RelayEncryptedUpdateRecord> {\n const key = this.updateKey(input.updateId);\n const record = await this.readJson<RelayEncryptedUpdateRecord>(key);\n\n if (!record || record.daemonId !== input.daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown update '${input.updateId}' for daemon '${input.daemonId}'`,\n operation: 'submitUpdateFeedback',\n });\n }\n\n if (!record.claimToken || record.claimToken !== input.claimToken) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key,\n message: `Claim token mismatch for update '${input.updateId}'`,\n operation: 'submitUpdateFeedback',\n });\n }\n\n const feedback: RelayUpdateFeedbackRecord = {\n daemonId: input.daemonId,\n details: input.details,\n feedbackAt: toIsoTimestamp(),\n message: input.message,\n status: input.status,\n updateId: input.updateId,\n };\n const nextRecord: RelayEncryptedUpdateRecord = {\n ...record,\n claimToken: undefined,\n claimUntil: undefined,\n feedback,\n status: input.status,\n updatedAt: toIsoTimestamp(),\n };\n\n await this.writeJson(key, nextRecord);\n if (record.targetApprovalRequestId && record.type === 'manual_approval_decision') {\n const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);\n const indexedUpdateId = await this.client.get(activeApprovalKey);\n if (indexedUpdateId === input.updateId) {\n await this.client.del(activeApprovalKey);\n }\n }\n return nextRecord;\n }\n\n async getEncryptedUpdate(updateId: string): Promise<RelayEncryptedUpdateRecord | null> {\n return await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n }\n\n async removeEncryptedUpdate(daemonId: string, updateId: string): Promise<void> {\n const key = this.updateKey(updateId);\n const record = await this.readJson<RelayEncryptedUpdateRecord>(key);\n if (!record || record.daemonId !== daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown update '${updateId}' for daemon '${daemonId}'`,\n operation: 'removeEncryptedUpdate',\n });\n }\n\n await this.client.zrem(this.daemonUpdatesKey(daemonId), updateId);\n await this.client.del(key);\n if (record.targetApprovalRequestId && record.type === 'manual_approval_decision') {\n const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);\n const indexedUpdateId = await this.client.get(activeApprovalKey);\n if (indexedUpdateId === updateId) {\n await this.client.del(activeApprovalKey);\n }\n }\n }\n\n private readonly daemonIndexKey = (): string => `${this.namespace}:daemons`;\n private readonly daemonProfileKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:profile`;\n private readonly daemonPoliciesKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:policies`;\n private readonly daemonAgentKeysKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:agent-keys`;\n private readonly daemonApprovalsKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:approvals`;\n private readonly daemonUpdatesKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:updates`;\n private readonly approvalKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}`;\n private readonly approvalCapabilityConsumedKey = (\n approvalRequestId: string,\n capabilityHash: string,\n ): string =>\n `${this.namespace}:approval:${approvalRequestId}:capability:${capabilityHash}:consumed`;\n private readonly approvalCapabilityFailuresKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}:capability-failures`;\n private readonly activeApprovalUpdateKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}:active-update`;\n private readonly updateClaimLockKey = (updateId: string): string =>\n `${this.namespace}:update:${updateId}:claim-lock`;\n private readonly updateKey = (updateId: string): string => `${this.namespace}:update:${updateId}`;\n\n private async readJson<T>(key: string): Promise<T | null> {\n try {\n const payload = await this.client.get(key);\n if (payload === null) {\n return null;\n }\n\n return JSON.parse(payload) as T;\n } catch (error) {\n throw toCacheError(error, { key, operation: 'readJson' });\n }\n }\n\n private async writeJson(key: string, value: unknown): Promise<void> {\n try {\n await this.client.set(key, JSON.stringify(value));\n } catch (error) {\n throw toCacheError(error, { key, operation: 'writeJson' });\n }\n }\n}\n\nexport const createRelayCacheService = (options: { client?: Redis; namespace?: string } = {}) => {\n return new RelayCacheService(options);\n};\n"]}
|