@wlfi-agent/cli 1.4.13 → 1.4.14
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 +3968 -0
- package/Cargo.toml +50 -0
- package/README.md +426 -6
- package/crates/vault-cli-admin/Cargo.toml +26 -0
- package/crates/vault-cli-admin/src/io_utils.rs +500 -0
- package/crates/vault-cli-admin/src/main.rs +3990 -0
- package/crates/vault-cli-admin/src/shared_config.rs +624 -0
- package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
- package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
- package/crates/vault-cli-admin/src/tui.rs +3410 -0
- package/crates/vault-cli-agent/Cargo.toml +24 -0
- package/crates/vault-cli-agent/src/io_utils.rs +576 -0
- package/crates/vault-cli-agent/src/main.rs +833 -0
- package/crates/vault-cli-daemon/Cargo.toml +28 -0
- package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
- package/crates/vault-cli-daemon/src/main.rs +644 -0
- package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
- package/crates/vault-daemon/Cargo.toml +32 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
- package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
- package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
- package/crates/vault-daemon/src/lib.rs +54 -0
- package/crates/vault-daemon/src/persistence.rs +441 -0
- package/crates/vault-daemon/src/tests.rs +237 -0
- package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
- package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
- package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
- package/crates/vault-domain/Cargo.toml +20 -0
- package/crates/vault-domain/src/action.rs +849 -0
- package/crates/vault-domain/src/address.rs +51 -0
- package/crates/vault-domain/src/approval.rs +90 -0
- package/crates/vault-domain/src/constants.rs +4 -0
- package/crates/vault-domain/src/error.rs +54 -0
- package/crates/vault-domain/src/keys.rs +71 -0
- package/crates/vault-domain/src/lib.rs +42 -0
- package/crates/vault-domain/src/nonce.rs +102 -0
- package/crates/vault-domain/src/policy.rs +172 -0
- package/crates/vault-domain/src/request.rs +53 -0
- package/crates/vault-domain/src/scope.rs +24 -0
- package/crates/vault-domain/src/session.rs +50 -0
- package/crates/vault-domain/src/signature.rs +34 -0
- package/crates/vault-domain/src/tests.rs +651 -0
- package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
- package/crates/vault-policy/Cargo.toml +17 -0
- package/crates/vault-policy/src/engine.rs +301 -0
- package/crates/vault-policy/src/error.rs +81 -0
- package/crates/vault-policy/src/lib.rs +17 -0
- package/crates/vault-policy/src/report.rs +34 -0
- package/crates/vault-policy/src/tests.rs +891 -0
- package/crates/vault-policy/src/tests_explain.rs +78 -0
- package/crates/vault-sdk-agent/Cargo.toml +21 -0
- package/crates/vault-sdk-agent/src/lib.rs +711 -0
- package/crates/vault-signer/Cargo.toml +25 -0
- package/crates/vault-signer/src/lib.rs +731 -0
- package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
- package/crates/vault-transport-unix/Cargo.toml +24 -0
- package/crates/vault-transport-unix/src/lib.rs +1640 -0
- package/crates/vault-transport-xpc/Cargo.toml +25 -0
- package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
- package/crates/vault-transport-xpc/src/lib.rs +680 -0
- package/crates/vault-transport-xpc/src/tests.rs +818 -0
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
- package/dist/cli.cjs +35088 -0
- package/dist/cli.cjs.map +1 -0
- package/package.json +49 -43
- package/packages/cache/.turbo/turbo-build.log +52 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
- package/packages/cache/dist/client/index.cjs +11 -0
- package/packages/cache/dist/client/index.cjs.map +1 -0
- package/packages/cache/dist/client/index.d.cts +15 -0
- package/packages/cache/dist/client/index.d.ts +15 -0
- package/packages/cache/dist/client/index.js +11 -0
- package/packages/cache/dist/client/index.js.map +1 -0
- package/packages/cache/dist/errors/index.cjs +11 -0
- package/packages/cache/dist/errors/index.cjs.map +1 -0
- package/packages/cache/dist/errors/index.d.cts +26 -0
- package/packages/cache/dist/errors/index.d.ts +26 -0
- package/packages/cache/dist/errors/index.js +11 -0
- package/packages/cache/dist/errors/index.js.map +1 -0
- package/packages/cache/dist/index.cjs +29 -0
- package/packages/cache/dist/index.cjs.map +1 -0
- package/packages/cache/dist/index.d.cts +4 -0
- package/packages/cache/dist/index.d.ts +4 -0
- package/packages/cache/dist/index.js +29 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/service/index.cjs +15 -0
- package/packages/cache/dist/service/index.cjs.map +1 -0
- package/packages/cache/dist/service/index.d.cts +184 -0
- package/packages/cache/dist/service/index.d.ts +184 -0
- package/packages/cache/dist/service/index.js +15 -0
- package/packages/cache/dist/service/index.js.map +1 -0
- package/packages/cache/node_modules/.bin/jiti +17 -0
- package/packages/cache/node_modules/.bin/tsc +17 -0
- package/packages/cache/node_modules/.bin/tsserver +17 -0
- package/packages/cache/node_modules/.bin/tsup +17 -0
- package/packages/cache/node_modules/.bin/tsup-node +17 -0
- package/packages/cache/node_modules/.bin/tsx +17 -0
- package/packages/cache/node_modules/.bin/vitest +17 -0
- package/packages/cache/package.json +48 -0
- package/packages/cache/src/client/index.ts +56 -0
- package/packages/cache/src/errors/index.ts +53 -0
- package/packages/cache/src/index.ts +3 -0
- package/packages/cache/src/service/index.test.ts +263 -0
- package/packages/cache/src/service/index.ts +678 -0
- package/packages/cache/tsconfig.json +13 -0
- package/packages/cache/tsup.config.ts +13 -0
- package/packages/cache/vitest.config.ts +16 -0
- package/packages/config/.turbo/turbo-build.log +18 -0
- package/packages/config/dist/index.cjs +1037 -0
- package/packages/config/dist/index.cjs.map +1 -0
- package/packages/config/dist/index.d.ts +131 -0
- package/packages/config/node_modules/.bin/jiti +17 -0
- package/packages/config/node_modules/.bin/tsc +17 -0
- package/packages/config/node_modules/.bin/tsserver +17 -0
- package/packages/config/node_modules/.bin/tsup +17 -0
- package/packages/config/node_modules/.bin/tsup-node +17 -0
- package/packages/config/node_modules/.bin/tsx +17 -0
- package/packages/config/package.json +21 -0
- package/packages/config/src/index.js +1 -0
- package/packages/config/src/index.ts +1282 -0
- package/packages/config/tsconfig.json +4 -0
- package/packages/rpc/.turbo/turbo-build.log +32 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
- package/packages/rpc/dist/index.cjs +7342 -0
- package/packages/rpc/dist/index.cjs.map +1 -0
- package/packages/rpc/dist/index.d.ts +3857 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
- package/packages/rpc/node_modules/.bin/jiti +17 -0
- package/packages/rpc/node_modules/.bin/tsc +17 -0
- package/packages/rpc/node_modules/.bin/tsserver +17 -0
- package/packages/rpc/node_modules/.bin/tsup +17 -0
- package/packages/rpc/node_modules/.bin/tsup-node +17 -0
- package/packages/rpc/node_modules/.bin/tsx +17 -0
- package/packages/rpc/package.json +25 -0
- package/packages/rpc/src/index.ts +206 -0
- package/packages/rpc/tsconfig.json +4 -0
- package/packages/typescript/base.json +36 -0
- package/packages/typescript/nextjs.json +17 -0
- package/packages/typescript/package.json +10 -0
- package/packages/ui/.turbo/turbo-build.log +44 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
- package/packages/ui/dist/components/badge.d.ts +12 -0
- package/packages/ui/dist/components/badge.js +31 -0
- package/packages/ui/dist/components/badge.js.map +1 -0
- package/packages/ui/dist/components/button.d.ts +13 -0
- package/packages/ui/dist/components/button.js +40 -0
- package/packages/ui/dist/components/button.js.map +1 -0
- package/packages/ui/dist/components/card.d.ts +10 -0
- package/packages/ui/dist/components/card.js +39 -0
- package/packages/ui/dist/components/card.js.map +1 -0
- package/packages/ui/dist/components/input.d.ts +5 -0
- package/packages/ui/dist/components/input.js +28 -0
- package/packages/ui/dist/components/input.js.map +1 -0
- package/packages/ui/dist/components/label.d.ts +5 -0
- package/packages/ui/dist/components/label.js +13 -0
- package/packages/ui/dist/components/label.js.map +1 -0
- package/packages/ui/dist/components/separator.d.ts +5 -0
- package/packages/ui/dist/components/separator.js +13 -0
- package/packages/ui/dist/components/separator.js.map +1 -0
- package/packages/ui/dist/components/textarea.d.ts +5 -0
- package/packages/ui/dist/components/textarea.js +27 -0
- package/packages/ui/dist/components/textarea.js.map +1 -0
- package/packages/ui/dist/tailwind.d.ts +56 -0
- package/packages/ui/dist/tailwind.js +60 -0
- package/packages/ui/dist/tailwind.js.map +1 -0
- package/packages/ui/dist/utils/cn.d.ts +5 -0
- package/packages/ui/dist/utils/cn.js +7 -0
- package/packages/ui/dist/utils/cn.js.map +1 -0
- package/packages/ui/node_modules/.bin/jiti +17 -0
- package/packages/ui/node_modules/.bin/tsc +17 -0
- package/packages/ui/node_modules/.bin/tsserver +17 -0
- package/packages/ui/node_modules/.bin/tsup +17 -0
- package/packages/ui/node_modules/.bin/tsup-node +17 -0
- package/packages/ui/node_modules/.bin/tsx +17 -0
- package/packages/ui/package.json +69 -0
- package/packages/ui/src/components/badge.tsx +27 -0
- package/packages/ui/src/components/button.tsx +40 -0
- package/packages/ui/src/components/card.tsx +31 -0
- package/packages/ui/src/components/input.tsx +21 -0
- package/packages/ui/src/components/label.tsx +6 -0
- package/packages/ui/src/components/separator.tsx +6 -0
- package/packages/ui/src/components/textarea.tsx +20 -0
- package/packages/ui/src/globals.css +70 -0
- package/packages/ui/src/tailwind.ts +56 -0
- package/packages/ui/src/utils/cn.ts +6 -0
- package/packages/ui/tsconfig.json +20 -0
- package/packages/ui/tsup.config.ts +20 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/install-rust-binaries.mjs +84 -0
- package/scripts/launchd/install-user-daemon.sh +358 -0
- package/scripts/launchd/run-vault-daemon.sh +5 -0
- package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
- package/scripts/launchd/uninstall-user-daemon.sh +103 -0
- package/src/cli.ts +2121 -0
- package/src/lib/admin-guard.js +1 -0
- package/src/lib/admin-guard.ts +185 -0
- package/src/lib/admin-passthrough.ts +33 -0
- package/src/lib/admin-reset.ts +751 -0
- package/src/lib/admin-setup.ts +1612 -0
- package/src/lib/agent-auth-clear.js +1 -0
- package/src/lib/agent-auth-clear.ts +58 -0
- package/src/lib/agent-auth-forwarding.js +1 -0
- package/src/lib/agent-auth-forwarding.ts +149 -0
- package/src/lib/agent-auth-migrate.js +1 -0
- package/src/lib/agent-auth-migrate.ts +150 -0
- package/src/lib/agent-auth-revoke.ts +103 -0
- package/src/lib/agent-auth-rotate.ts +107 -0
- package/src/lib/agent-auth-token.js +1 -0
- package/src/lib/agent-auth-token.ts +25 -0
- package/src/lib/agent-auth.ts +89 -0
- package/src/lib/asset-broadcast.js +1 -0
- package/src/lib/asset-broadcast.ts +285 -0
- package/src/lib/bootstrap-artifacts.js +1 -0
- package/src/lib/bootstrap-artifacts.ts +205 -0
- package/src/lib/bootstrap-credentials.js +1 -0
- package/src/lib/bootstrap-credentials.ts +832 -0
- package/src/lib/config-amounts.js +1 -0
- package/src/lib/config-amounts.ts +189 -0
- package/src/lib/config-mutation.ts +27 -0
- package/src/lib/fs-trust.js +1 -0
- package/src/lib/fs-trust.ts +537 -0
- package/src/lib/keychain.js +1 -0
- package/src/lib/keychain.ts +225 -0
- package/src/lib/local-admin-access.ts +106 -0
- package/src/lib/network-selection.js +1 -0
- package/src/lib/network-selection.ts +71 -0
- package/src/lib/passthrough-security.js +1 -0
- package/src/lib/passthrough-security.ts +114 -0
- package/src/lib/rpc-guard.js +1 -0
- package/src/lib/rpc-guard.ts +7 -0
- package/src/lib/rust-spawn-options.js +1 -0
- package/src/lib/rust-spawn-options.ts +98 -0
- package/src/lib/rust.js +1 -0
- package/src/lib/rust.ts +143 -0
- package/src/lib/signed-tx.js +1 -0
- package/src/lib/signed-tx.ts +116 -0
- package/src/lib/status-repair-cli.ts +116 -0
- package/src/lib/sudo.js +1 -0
- package/src/lib/sudo.ts +172 -0
- package/src/lib/vault-password-forwarding.js +1 -0
- package/src/lib/vault-password-forwarding.ts +155 -0
- package/src/lib/wallet-profile.js +1 -0
- package/src/lib/wallet-profile.ts +332 -0
- package/src/lib/wallet-repair.js +1 -0
- package/src/lib/wallet-repair.ts +304 -0
- package/src/lib/wallet-setup.js +1 -0
- package/src/lib/wallet-setup.ts +1466 -0
- package/src/lib/wallet-status.js +1 -0
- package/src/lib/wallet-status.ts +640 -0
- package/tsconfig.base.json +17 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +25 -0
- package/turbo.json +41 -0
- package/LICENSE.md +0 -1
- package/dist/wlfa/index.cjs +0 -250
- package/dist/wlfa/index.d.cts +0 -1
- package/dist/wlfa/index.d.ts +0 -1
- package/dist/wlfa/index.js +0 -250
- package/dist/wlfc/index.cjs +0 -1839
- package/dist/wlfc/index.d.cts +0 -1
- package/dist/wlfc/index.d.ts +0 -1
- package/dist/wlfc/index.js +0 -1839
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import type Redis from 'ioredis';
|
|
3
|
+
import { getCacheClient } from '../client/index.js';
|
|
4
|
+
import { CacheError, cacheErrorCodes, toCacheError } from '../errors/index.js';
|
|
5
|
+
|
|
6
|
+
export const relayApprovalStatuses = [
|
|
7
|
+
'pending',
|
|
8
|
+
'approved',
|
|
9
|
+
'rejected',
|
|
10
|
+
'completed',
|
|
11
|
+
'expired',
|
|
12
|
+
] as const;
|
|
13
|
+
export type RelayApprovalStatus = (typeof relayApprovalStatuses)[number];
|
|
14
|
+
|
|
15
|
+
export const relayUpdateStatuses = [
|
|
16
|
+
'pending',
|
|
17
|
+
'inflight',
|
|
18
|
+
'applied',
|
|
19
|
+
'rejected',
|
|
20
|
+
'failed',
|
|
21
|
+
] as const;
|
|
22
|
+
export type RelayUpdateStatus = (typeof relayUpdateStatuses)[number];
|
|
23
|
+
|
|
24
|
+
export interface RelayDaemonProfile {
|
|
25
|
+
daemonId: string;
|
|
26
|
+
daemonPublicKey: string;
|
|
27
|
+
ethereumAddress: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
lastSeenAt: string;
|
|
30
|
+
registeredAt: string;
|
|
31
|
+
relayUrl?: string;
|
|
32
|
+
signerBackend?: string;
|
|
33
|
+
status: 'active' | 'paused';
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RelayPolicyRecord {
|
|
39
|
+
action: string;
|
|
40
|
+
amountMaxWei?: string;
|
|
41
|
+
amountMinWei?: string;
|
|
42
|
+
chainId?: number;
|
|
43
|
+
daemonId: string;
|
|
44
|
+
destination: string;
|
|
45
|
+
metadata?: Record<string, string>;
|
|
46
|
+
policyId: string;
|
|
47
|
+
requiresManualApproval: boolean;
|
|
48
|
+
scope: 'default' | 'override';
|
|
49
|
+
tokenAddress?: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RelayAgentKeyRecord {
|
|
54
|
+
agentKeyId: string;
|
|
55
|
+
createdAt?: string;
|
|
56
|
+
daemonId: string;
|
|
57
|
+
label?: string;
|
|
58
|
+
metadata?: Record<string, string>;
|
|
59
|
+
status: 'active' | 'revoked';
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RelayApprovalRequestRecord {
|
|
64
|
+
agentKeyId?: string;
|
|
65
|
+
amountWei?: string;
|
|
66
|
+
approvalRequestId: string;
|
|
67
|
+
chainId?: number;
|
|
68
|
+
daemonId: string;
|
|
69
|
+
destination: string;
|
|
70
|
+
metadata?: Record<string, string>;
|
|
71
|
+
network?: string;
|
|
72
|
+
reason?: string;
|
|
73
|
+
requestedAt: string;
|
|
74
|
+
status: RelayApprovalStatus;
|
|
75
|
+
tokenAddress?: string;
|
|
76
|
+
transactionType: string;
|
|
77
|
+
updatedAt: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RelayEncryptedPayload {
|
|
81
|
+
aadBase64?: string;
|
|
82
|
+
algorithm: string;
|
|
83
|
+
ciphertextBase64: string;
|
|
84
|
+
contentSha256Hex?: string;
|
|
85
|
+
encapsulatedKeyBase64: string;
|
|
86
|
+
nonceBase64: string;
|
|
87
|
+
schemaVersion: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface RelayUpdateFeedbackRecord {
|
|
91
|
+
daemonId: string;
|
|
92
|
+
details?: Record<string, string>;
|
|
93
|
+
feedbackAt: string;
|
|
94
|
+
message?: string;
|
|
95
|
+
status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;
|
|
96
|
+
updateId: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface RelayEncryptedUpdateRecord {
|
|
100
|
+
claimToken?: string;
|
|
101
|
+
claimUntil?: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
daemonId: string;
|
|
104
|
+
feedback?: RelayUpdateFeedbackRecord;
|
|
105
|
+
lastDeliveredAt?: string;
|
|
106
|
+
metadata?: Record<string, string>;
|
|
107
|
+
payload: RelayEncryptedPayload;
|
|
108
|
+
status: RelayUpdateStatus;
|
|
109
|
+
targetApprovalRequestId?: string;
|
|
110
|
+
type: string;
|
|
111
|
+
updateId: string;
|
|
112
|
+
updatedAt: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface SyncDaemonRegistrationInput {
|
|
116
|
+
agentKeys?: RelayAgentKeyRecord[];
|
|
117
|
+
approvalRequests?: RelayApprovalRequestRecord[];
|
|
118
|
+
daemon: RelayDaemonProfile;
|
|
119
|
+
policies?: RelayPolicyRecord[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ApprovalRequestFilters {
|
|
123
|
+
daemonId?: string;
|
|
124
|
+
destination?: string;
|
|
125
|
+
limit?: number;
|
|
126
|
+
status?: RelayApprovalStatus;
|
|
127
|
+
tokenAddress?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface CreateEncryptedUpdateInput {
|
|
131
|
+
daemonId: string;
|
|
132
|
+
metadata?: Record<string, string>;
|
|
133
|
+
payload: RelayEncryptedPayload;
|
|
134
|
+
targetApprovalRequestId?: string;
|
|
135
|
+
type: string;
|
|
136
|
+
updateId?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ClaimEncryptedUpdatesInput {
|
|
140
|
+
daemonId: string;
|
|
141
|
+
leaseSeconds?: number;
|
|
142
|
+
limit?: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface SubmitUpdateFeedbackInput {
|
|
146
|
+
claimToken: string;
|
|
147
|
+
daemonId: string;
|
|
148
|
+
details?: Record<string, string>;
|
|
149
|
+
message?: string;
|
|
150
|
+
status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;
|
|
151
|
+
updateId: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ApprovalCapabilityFailureRecord {
|
|
155
|
+
attempts: number;
|
|
156
|
+
blockedUntil?: string;
|
|
157
|
+
firstFailedAt: string;
|
|
158
|
+
lastFailedAt: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface RecordApprovalCapabilityFailureResult {
|
|
162
|
+
attempts: number;
|
|
163
|
+
blocked: boolean;
|
|
164
|
+
blockedUntil: string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface JsonCache {
|
|
168
|
+
del(key: string): Promise<number>;
|
|
169
|
+
get(key: string): Promise<string | null>;
|
|
170
|
+
ping(): Promise<string>;
|
|
171
|
+
quit(): Promise<string>;
|
|
172
|
+
sadd(key: string, ...members: string[]): Promise<number>;
|
|
173
|
+
set(key: string, value: string, mode?: 'NX' | 'XX'): Promise<'OK' | null>;
|
|
174
|
+
smembers(key: string): Promise<string[]>;
|
|
175
|
+
zadd(key: string, ...args: (string | number)[]): Promise<number>;
|
|
176
|
+
zrange(key: string, start: number, stop: number, ...args: string[]): Promise<string[]>;
|
|
177
|
+
zrem(key: string, ...members: string[]): Promise<number>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const defaultNamespace = 'wlfi:relay';
|
|
181
|
+
const activeApprovalUpdateScanLimit = 250;
|
|
182
|
+
const approvalCapabilityFailureWindowMs = 5 * 60 * 1000;
|
|
183
|
+
const approvalCapabilityMaxFailures = 5;
|
|
184
|
+
const approvalCapabilityBlockWindowMs = 10 * 60 * 1000;
|
|
185
|
+
|
|
186
|
+
const toIsoTimestamp = (value = new Date()): string => value.toISOString();
|
|
187
|
+
|
|
188
|
+
const dedupe = <T>(values: T[]): T[] => [...new Set(values)];
|
|
189
|
+
|
|
190
|
+
const matchesOptionalFilter = (
|
|
191
|
+
value: string | undefined,
|
|
192
|
+
expected: string | undefined,
|
|
193
|
+
): boolean => {
|
|
194
|
+
if (!expected) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return value?.toLowerCase() === expected.toLowerCase();
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const clampLimit = (limit: number | undefined, fallback: number, max: number): number => {
|
|
202
|
+
if (!limit || Number.isNaN(limit)) {
|
|
203
|
+
return fallback;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return Math.max(1, Math.min(limit, max));
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const createApprovalCapabilityToken = (): string => randomBytes(32).toString('hex');
|
|
210
|
+
|
|
211
|
+
const approvalCapabilityHash = (token: string): string =>
|
|
212
|
+
createHash('sha256').update(token, 'utf8').digest('hex');
|
|
213
|
+
|
|
214
|
+
export class RelayCacheService {
|
|
215
|
+
private readonly client: JsonCache;
|
|
216
|
+
private readonly namespace: string;
|
|
217
|
+
|
|
218
|
+
constructor(options: { client?: Redis; namespace?: string } = {}) {
|
|
219
|
+
this.client = (options.client ?? getCacheClient()) as unknown as JsonCache;
|
|
220
|
+
this.namespace = options.namespace ?? defaultNamespace;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async ping(): Promise<string> {
|
|
224
|
+
try {
|
|
225
|
+
return await this.client.ping();
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw toCacheError(error, { operation: 'ping' });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async syncDaemonRegistration(input: SyncDaemonRegistrationInput): Promise<{
|
|
232
|
+
agentKeyCount: number;
|
|
233
|
+
approvalRequestCount: number;
|
|
234
|
+
policyCount: number;
|
|
235
|
+
}> {
|
|
236
|
+
const profile = {
|
|
237
|
+
...input.daemon,
|
|
238
|
+
lastSeenAt: input.daemon.lastSeenAt || toIsoTimestamp(),
|
|
239
|
+
updatedAt: input.daemon.updatedAt || toIsoTimestamp(),
|
|
240
|
+
} satisfies RelayDaemonProfile;
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await this.writeJson(this.daemonProfileKey(profile.daemonId), profile);
|
|
244
|
+
await this.client.sadd(this.daemonIndexKey(), profile.daemonId);
|
|
245
|
+
|
|
246
|
+
if (input.policies) {
|
|
247
|
+
const policies = input.policies.map((policy) => ({
|
|
248
|
+
...policy,
|
|
249
|
+
daemonId: profile.daemonId,
|
|
250
|
+
}));
|
|
251
|
+
await this.writeJson(this.daemonPoliciesKey(profile.daemonId), policies);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (input.agentKeys) {
|
|
255
|
+
const agentKeys = input.agentKeys.map((agentKey) => ({
|
|
256
|
+
...agentKey,
|
|
257
|
+
daemonId: profile.daemonId,
|
|
258
|
+
}));
|
|
259
|
+
await this.writeJson(this.daemonAgentKeysKey(profile.daemonId), agentKeys);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (input.approvalRequests) {
|
|
263
|
+
for (const approvalRequest of input.approvalRequests) {
|
|
264
|
+
const normalized = { ...approvalRequest, daemonId: profile.daemonId };
|
|
265
|
+
await this.writeJson(this.approvalKey(normalized.approvalRequestId), normalized);
|
|
266
|
+
await this.client.zadd(
|
|
267
|
+
this.daemonApprovalsKey(profile.daemonId),
|
|
268
|
+
Date.parse(normalized.requestedAt),
|
|
269
|
+
normalized.approvalRequestId,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
agentKeyCount: input.agentKeys?.length ?? 0,
|
|
276
|
+
approvalRequestCount: input.approvalRequests?.length ?? 0,
|
|
277
|
+
policyCount: input.policies?.length ?? 0,
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw toCacheError(error, {
|
|
281
|
+
key: this.daemonProfileKey(profile.daemonId),
|
|
282
|
+
operation: 'syncDaemonRegistration',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async listDaemons(): Promise<RelayDaemonProfile[]> {
|
|
288
|
+
const daemonIds = await this.client.smembers(this.daemonIndexKey());
|
|
289
|
+
const profiles = await Promise.all(
|
|
290
|
+
daemonIds.map((daemonId) =>
|
|
291
|
+
this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId)),
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
return profiles.filter((profile): profile is RelayDaemonProfile => Boolean(profile));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async getDaemonProfile(daemonId: string): Promise<RelayDaemonProfile | null> {
|
|
299
|
+
return await this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async getDaemonPolicies(daemonId: string): Promise<RelayPolicyRecord[]> {
|
|
303
|
+
return (await this.readJson<RelayPolicyRecord[]>(this.daemonPoliciesKey(daemonId))) ?? [];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getDaemonAgentKeys(daemonId: string): Promise<RelayAgentKeyRecord[]> {
|
|
307
|
+
return (await this.readJson<RelayAgentKeyRecord[]>(this.daemonAgentKeysKey(daemonId))) ?? [];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async getApprovalRequest(approvalRequestId: string): Promise<RelayApprovalRequestRecord | null> {
|
|
311
|
+
return await this.readJson<RelayApprovalRequestRecord>(this.approvalKey(approvalRequestId));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async listApprovalRequests(
|
|
315
|
+
filters: ApprovalRequestFilters = {},
|
|
316
|
+
): Promise<RelayApprovalRequestRecord[]> {
|
|
317
|
+
const limit = clampLimit(filters.limit, 100, 500);
|
|
318
|
+
const daemonIds = filters.daemonId
|
|
319
|
+
? [filters.daemonId]
|
|
320
|
+
: await this.client.smembers(this.daemonIndexKey());
|
|
321
|
+
const requestIdsByDaemon = await Promise.all(
|
|
322
|
+
daemonIds.map((daemonId) =>
|
|
323
|
+
this.client.zrange(this.daemonApprovalsKey(daemonId), 0, limit * 2, 'REV'),
|
|
324
|
+
),
|
|
325
|
+
);
|
|
326
|
+
const requestIds = dedupe(requestIdsByDaemon.flat()).slice(0, limit * 3);
|
|
327
|
+
const requests = await Promise.all(
|
|
328
|
+
requestIds.map((requestId) =>
|
|
329
|
+
this.readJson<RelayApprovalRequestRecord>(this.approvalKey(requestId)),
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return requests
|
|
334
|
+
.filter((request): request is RelayApprovalRequestRecord => Boolean(request))
|
|
335
|
+
.filter((request) => (filters.daemonId ? request.daemonId === filters.daemonId : true))
|
|
336
|
+
.filter((request) => (filters.status ? request.status === filters.status : true))
|
|
337
|
+
.filter((request) => matchesOptionalFilter(request.destination, filters.destination))
|
|
338
|
+
.filter((request) => matchesOptionalFilter(request.tokenAddress, filters.tokenAddress))
|
|
339
|
+
.sort((left, right) => Date.parse(right.requestedAt) - Date.parse(left.requestedAt))
|
|
340
|
+
.slice(0, limit);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async createEncryptedUpdate(
|
|
344
|
+
input: CreateEncryptedUpdateInput,
|
|
345
|
+
): Promise<RelayEncryptedUpdateRecord> {
|
|
346
|
+
const updateId = input.updateId ?? randomUUID();
|
|
347
|
+
const now = toIsoTimestamp();
|
|
348
|
+
const record: RelayEncryptedUpdateRecord = {
|
|
349
|
+
createdAt: now,
|
|
350
|
+
daemonId: input.daemonId,
|
|
351
|
+
metadata: input.metadata,
|
|
352
|
+
payload: input.payload,
|
|
353
|
+
status: 'pending',
|
|
354
|
+
targetApprovalRequestId: input.targetApprovalRequestId,
|
|
355
|
+
type: input.type,
|
|
356
|
+
updateId,
|
|
357
|
+
updatedAt: now,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
await this.writeJson(this.updateKey(updateId), record);
|
|
361
|
+
await this.client.zadd(this.daemonUpdatesKey(input.daemonId), Date.now(), updateId);
|
|
362
|
+
|
|
363
|
+
return record;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async hasActiveApprovalUpdate(daemonId: string, approvalRequestId: string): Promise<boolean> {
|
|
367
|
+
const updateIds = await this.client.zrange(
|
|
368
|
+
this.daemonUpdatesKey(daemonId),
|
|
369
|
+
0,
|
|
370
|
+
activeApprovalUpdateScanLimit,
|
|
371
|
+
'REV',
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
for (const updateId of updateIds) {
|
|
375
|
+
const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));
|
|
376
|
+
if (!record) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
record.type !== 'manual_approval_decision' ||
|
|
382
|
+
record.targetApprovalRequestId !== approvalRequestId
|
|
383
|
+
) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (record.status === 'pending' || record.status === 'inflight') {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async consumeApprovalCapability(
|
|
396
|
+
approvalRequestId: string,
|
|
397
|
+
capabilityHash: string,
|
|
398
|
+
): Promise<boolean> {
|
|
399
|
+
try {
|
|
400
|
+
const result = await this.client.set(
|
|
401
|
+
this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),
|
|
402
|
+
toIsoTimestamp(),
|
|
403
|
+
'NX',
|
|
404
|
+
);
|
|
405
|
+
return result === 'OK';
|
|
406
|
+
} catch (error) {
|
|
407
|
+
throw toCacheError(error, {
|
|
408
|
+
key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),
|
|
409
|
+
operation: 'consumeApprovalCapability',
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async releaseApprovalCapabilityConsumption(
|
|
415
|
+
approvalRequestId: string,
|
|
416
|
+
capabilityHash: string,
|
|
417
|
+
): Promise<void> {
|
|
418
|
+
try {
|
|
419
|
+
await this.client.del(this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash));
|
|
420
|
+
} catch (error) {
|
|
421
|
+
throw toCacheError(error, {
|
|
422
|
+
key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),
|
|
423
|
+
operation: 'releaseApprovalCapabilityConsumption',
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async clearApprovalCapabilityFailures(approvalRequestId: string): Promise<void> {
|
|
429
|
+
try {
|
|
430
|
+
await this.client.del(this.approvalCapabilityFailuresKey(approvalRequestId));
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw toCacheError(error, {
|
|
433
|
+
key: this.approvalCapabilityFailuresKey(approvalRequestId),
|
|
434
|
+
operation: 'clearApprovalCapabilityFailures',
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async recordApprovalCapabilityFailure(
|
|
440
|
+
approvalRequestId: string,
|
|
441
|
+
): Promise<RecordApprovalCapabilityFailureResult> {
|
|
442
|
+
const key = this.approvalCapabilityFailuresKey(approvalRequestId);
|
|
443
|
+
const now = new Date();
|
|
444
|
+
const nowMs = now.getTime();
|
|
445
|
+
const existing = await this.readJson<ApprovalCapabilityFailureRecord>(key);
|
|
446
|
+
|
|
447
|
+
if (existing?.blockedUntil && Date.parse(existing.blockedUntil) > nowMs) {
|
|
448
|
+
return {
|
|
449
|
+
attempts: existing.attempts,
|
|
450
|
+
blocked: true,
|
|
451
|
+
blockedUntil: existing.blockedUntil,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const firstFailedAtMs = existing?.firstFailedAt
|
|
456
|
+
? Date.parse(existing.firstFailedAt)
|
|
457
|
+
: Number.NaN;
|
|
458
|
+
const withinWindow =
|
|
459
|
+
Number.isFinite(firstFailedAtMs) &&
|
|
460
|
+
nowMs - firstFailedAtMs <= approvalCapabilityFailureWindowMs;
|
|
461
|
+
const attempts = withinWindow && existing ? existing.attempts + 1 : 1;
|
|
462
|
+
const firstFailedAt = withinWindow && existing ? existing.firstFailedAt : now.toISOString();
|
|
463
|
+
const blockedUntil =
|
|
464
|
+
attempts >= approvalCapabilityMaxFailures
|
|
465
|
+
? new Date(nowMs + approvalCapabilityBlockWindowMs).toISOString()
|
|
466
|
+
: undefined;
|
|
467
|
+
|
|
468
|
+
await this.writeJson(key, {
|
|
469
|
+
attempts,
|
|
470
|
+
blockedUntil,
|
|
471
|
+
firstFailedAt,
|
|
472
|
+
lastFailedAt: now.toISOString(),
|
|
473
|
+
} satisfies ApprovalCapabilityFailureRecord);
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
attempts,
|
|
477
|
+
blocked: blockedUntil !== undefined,
|
|
478
|
+
blockedUntil: blockedUntil ?? null,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async rotateApprovalCapability(approvalRequestId: string): Promise<RelayApprovalRequestRecord> {
|
|
483
|
+
const key = this.approvalKey(approvalRequestId);
|
|
484
|
+
const approval = await this.readJson<RelayApprovalRequestRecord>(key);
|
|
485
|
+
|
|
486
|
+
if (!approval) {
|
|
487
|
+
throw new CacheError({
|
|
488
|
+
code: cacheErrorCodes.notFound,
|
|
489
|
+
key,
|
|
490
|
+
message: `Unknown approval '${approvalRequestId}'`,
|
|
491
|
+
operation: 'rotateApprovalCapability',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (approval.status !== 'pending') {
|
|
496
|
+
throw new CacheError({
|
|
497
|
+
code: cacheErrorCodes.invalidPayload,
|
|
498
|
+
key,
|
|
499
|
+
message: `Approval '${approvalRequestId}' is '${approval.status}' and cannot accept a new secure approval link`,
|
|
500
|
+
operation: 'rotateApprovalCapability',
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const capabilityToken = createApprovalCapabilityToken();
|
|
505
|
+
const nextRecord: RelayApprovalRequestRecord = {
|
|
506
|
+
...approval,
|
|
507
|
+
metadata: {
|
|
508
|
+
...(approval.metadata ?? {}),
|
|
509
|
+
approvalCapabilityHash: approvalCapabilityHash(capabilityToken),
|
|
510
|
+
approvalCapabilityToken: capabilityToken,
|
|
511
|
+
},
|
|
512
|
+
updatedAt: toIsoTimestamp(),
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
await this.writeJson(key, nextRecord);
|
|
516
|
+
await this.clearApprovalCapabilityFailures(approvalRequestId);
|
|
517
|
+
|
|
518
|
+
return nextRecord;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async claimEncryptedUpdates(
|
|
522
|
+
input: ClaimEncryptedUpdatesInput,
|
|
523
|
+
): Promise<RelayEncryptedUpdateRecord[]> {
|
|
524
|
+
const limit = clampLimit(input.limit, 25, 100);
|
|
525
|
+
const leaseSeconds = clampLimit(input.leaseSeconds, 30, 300);
|
|
526
|
+
const now = new Date();
|
|
527
|
+
const nowMs = now.getTime();
|
|
528
|
+
const claimUntil = new Date(nowMs + leaseSeconds * 1000).toISOString();
|
|
529
|
+
const updateIds = await this.client.zrange(
|
|
530
|
+
this.daemonUpdatesKey(input.daemonId),
|
|
531
|
+
0,
|
|
532
|
+
limit * 4,
|
|
533
|
+
'REV',
|
|
534
|
+
);
|
|
535
|
+
const claimed: RelayEncryptedUpdateRecord[] = [];
|
|
536
|
+
|
|
537
|
+
for (const updateId of updateIds) {
|
|
538
|
+
if (claimed.length >= limit) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));
|
|
543
|
+
if (!record) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (
|
|
548
|
+
record.status === 'applied' ||
|
|
549
|
+
record.status === 'failed' ||
|
|
550
|
+
record.status === 'rejected'
|
|
551
|
+
) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (
|
|
556
|
+
record.status === 'inflight' &&
|
|
557
|
+
record.claimUntil &&
|
|
558
|
+
Date.parse(record.claimUntil) > nowMs
|
|
559
|
+
) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const nextRecord: RelayEncryptedUpdateRecord = {
|
|
564
|
+
...record,
|
|
565
|
+
claimToken: randomUUID(),
|
|
566
|
+
claimUntil,
|
|
567
|
+
lastDeliveredAt: now.toISOString(),
|
|
568
|
+
status: 'inflight',
|
|
569
|
+
updatedAt: now.toISOString(),
|
|
570
|
+
};
|
|
571
|
+
await this.writeJson(this.updateKey(updateId), nextRecord);
|
|
572
|
+
claimed.push(nextRecord);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return claimed;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async submitUpdateFeedback(
|
|
579
|
+
input: SubmitUpdateFeedbackInput,
|
|
580
|
+
): Promise<RelayEncryptedUpdateRecord> {
|
|
581
|
+
const key = this.updateKey(input.updateId);
|
|
582
|
+
const record = await this.readJson<RelayEncryptedUpdateRecord>(key);
|
|
583
|
+
|
|
584
|
+
if (!record || record.daemonId !== input.daemonId) {
|
|
585
|
+
throw new CacheError({
|
|
586
|
+
code: cacheErrorCodes.notFound,
|
|
587
|
+
key,
|
|
588
|
+
message: `Unknown update '${input.updateId}' for daemon '${input.daemonId}'`,
|
|
589
|
+
operation: 'submitUpdateFeedback',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!record.claimToken || record.claimToken !== input.claimToken) {
|
|
594
|
+
throw new CacheError({
|
|
595
|
+
code: cacheErrorCodes.invalidPayload,
|
|
596
|
+
key,
|
|
597
|
+
message: `Claim token mismatch for update '${input.updateId}'`,
|
|
598
|
+
operation: 'submitUpdateFeedback',
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const feedback: RelayUpdateFeedbackRecord = {
|
|
603
|
+
daemonId: input.daemonId,
|
|
604
|
+
details: input.details,
|
|
605
|
+
feedbackAt: toIsoTimestamp(),
|
|
606
|
+
message: input.message,
|
|
607
|
+
status: input.status,
|
|
608
|
+
updateId: input.updateId,
|
|
609
|
+
};
|
|
610
|
+
const nextRecord: RelayEncryptedUpdateRecord = {
|
|
611
|
+
...record,
|
|
612
|
+
claimToken: undefined,
|
|
613
|
+
claimUntil: undefined,
|
|
614
|
+
feedback,
|
|
615
|
+
status: input.status,
|
|
616
|
+
updatedAt: toIsoTimestamp(),
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
await this.writeJson(key, nextRecord);
|
|
620
|
+
return nextRecord;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async getEncryptedUpdate(updateId: string): Promise<RelayEncryptedUpdateRecord | null> {
|
|
624
|
+
return await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async removeEncryptedUpdate(daemonId: string, updateId: string): Promise<void> {
|
|
628
|
+
await this.client.zrem(this.daemonUpdatesKey(daemonId), updateId);
|
|
629
|
+
await this.client.del(this.updateKey(updateId));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private readonly daemonIndexKey = (): string => `${this.namespace}:daemons`;
|
|
633
|
+
private readonly daemonProfileKey = (daemonId: string): string =>
|
|
634
|
+
`${this.namespace}:daemon:${daemonId}:profile`;
|
|
635
|
+
private readonly daemonPoliciesKey = (daemonId: string): string =>
|
|
636
|
+
`${this.namespace}:daemon:${daemonId}:policies`;
|
|
637
|
+
private readonly daemonAgentKeysKey = (daemonId: string): string =>
|
|
638
|
+
`${this.namespace}:daemon:${daemonId}:agent-keys`;
|
|
639
|
+
private readonly daemonApprovalsKey = (daemonId: string): string =>
|
|
640
|
+
`${this.namespace}:daemon:${daemonId}:approvals`;
|
|
641
|
+
private readonly daemonUpdatesKey = (daemonId: string): string =>
|
|
642
|
+
`${this.namespace}:daemon:${daemonId}:updates`;
|
|
643
|
+
private readonly approvalKey = (approvalRequestId: string): string =>
|
|
644
|
+
`${this.namespace}:approval:${approvalRequestId}`;
|
|
645
|
+
private readonly approvalCapabilityConsumedKey = (
|
|
646
|
+
approvalRequestId: string,
|
|
647
|
+
capabilityHash: string,
|
|
648
|
+
): string =>
|
|
649
|
+
`${this.namespace}:approval:${approvalRequestId}:capability:${capabilityHash}:consumed`;
|
|
650
|
+
private readonly approvalCapabilityFailuresKey = (approvalRequestId: string): string =>
|
|
651
|
+
`${this.namespace}:approval:${approvalRequestId}:capability-failures`;
|
|
652
|
+
private readonly updateKey = (updateId: string): string => `${this.namespace}:update:${updateId}`;
|
|
653
|
+
|
|
654
|
+
private async readJson<T>(key: string): Promise<T | null> {
|
|
655
|
+
try {
|
|
656
|
+
const payload = await this.client.get(key);
|
|
657
|
+
if (payload === null) {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return JSON.parse(payload) as T;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
throw toCacheError(error, { key, operation: 'readJson' });
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private async writeJson(key: string, value: unknown): Promise<void> {
|
|
668
|
+
try {
|
|
669
|
+
await this.client.set(key, JSON.stringify(value));
|
|
670
|
+
} catch (error) {
|
|
671
|
+
throw toCacheError(error, { key, operation: 'writeJson' });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export const createRelayCacheService = (options: { client?: Redis; namespace?: string } = {}) => {
|
|
677
|
+
return new RelayCacheService(options);
|
|
678
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
bundle: true,
|
|
5
|
+
clean: true,
|
|
6
|
+
dts: true,
|
|
7
|
+
entry: ['src/index.ts', 'src/client/index.ts', 'src/errors/index.ts', 'src/service/index.ts'],
|
|
8
|
+
format: ['esm', 'cjs'],
|
|
9
|
+
outDir: 'dist',
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
splitting: true,
|
|
12
|
+
target: 'node20',
|
|
13
|
+
});
|