@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.
Files changed (97) hide show
  1. package/Cargo.lock +26 -20
  2. package/Cargo.toml +1 -1
  3. package/README.md +61 -28
  4. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  5. package/crates/vault-cli-admin/src/main.rs +639 -16
  6. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  7. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  8. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  9. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  10. package/crates/vault-cli-agent/Cargo.toml +1 -0
  11. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  12. package/crates/vault-cli-agent/src/main.rs +648 -32
  13. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  14. package/crates/vault-cli-daemon/src/main.rs +617 -67
  15. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  16. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  17. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  18. package/crates/vault-daemon/src/persistence.rs +637 -100
  19. package/crates/vault-daemon/src/tests.rs +1013 -3
  20. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  21. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  22. package/crates/vault-domain/src/nonce.rs +4 -0
  23. package/crates/vault-domain/src/tests.rs +616 -0
  24. package/crates/vault-policy/src/engine.rs +55 -32
  25. package/crates/vault-policy/src/tests.rs +195 -0
  26. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  27. package/crates/vault-signer/Cargo.toml +3 -0
  28. package/crates/vault-signer/src/lib.rs +266 -40
  29. package/crates/vault-transport-unix/src/lib.rs +653 -5
  30. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  31. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  32. package/dist/cli.cjs +663 -190
  33. package/dist/cli.cjs.map +1 -1
  34. package/package.json +5 -2
  35. package/packages/cache/.turbo/turbo-build.log +53 -52
  36. package/packages/cache/coverage/clover.xml +529 -394
  37. package/packages/cache/coverage/coverage-final.json +2 -2
  38. package/packages/cache/coverage/index.html +21 -21
  39. package/packages/cache/coverage/src/client/index.html +1 -1
  40. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.html +1 -1
  42. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  43. package/packages/cache/coverage/src/index.html +1 -1
  44. package/packages/cache/coverage/src/index.ts.html +1 -1
  45. package/packages/cache/coverage/src/service/index.html +21 -21
  46. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  47. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  48. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  49. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  50. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  51. package/packages/cache/dist/index.cjs +2 -2
  52. package/packages/cache/dist/index.js +1 -1
  53. package/packages/cache/dist/service/index.cjs +2 -2
  54. package/packages/cache/dist/service/index.js +1 -1
  55. package/packages/cache/node_modules/.bin/tsc +2 -2
  56. package/packages/cache/node_modules/.bin/tsserver +2 -2
  57. package/packages/cache/node_modules/.bin/tsup +2 -2
  58. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  59. package/packages/cache/node_modules/.bin/vitest +4 -4
  60. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  61. package/packages/cache/src/service/index.test.ts +165 -19
  62. package/packages/cache/src/service/index.ts +38 -1
  63. package/packages/config/.turbo/turbo-build.log +18 -17
  64. package/packages/config/dist/index.cjs +0 -17
  65. package/packages/config/dist/index.cjs.map +1 -1
  66. package/packages/config/src/index.ts +0 -17
  67. package/packages/rpc/.turbo/turbo-build.log +32 -31
  68. package/packages/rpc/dist/index.cjs +0 -17
  69. package/packages/rpc/dist/index.cjs.map +1 -1
  70. package/packages/rpc/src/index.js +1 -0
  71. package/packages/ui/.turbo/turbo-build.log +44 -43
  72. package/packages/ui/dist/components/badge.d.ts +1 -1
  73. package/packages/ui/dist/components/button.d.ts +1 -1
  74. package/packages/ui/node_modules/.bin/tsc +2 -2
  75. package/packages/ui/node_modules/.bin/tsserver +2 -2
  76. package/packages/ui/node_modules/.bin/tsup +2 -2
  77. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  78. package/scripts/install-cli-launcher.mjs +37 -0
  79. package/scripts/install-rust-binaries.mjs +112 -0
  80. package/scripts/run-tests-isolated.mjs +210 -0
  81. package/src/cli.ts +310 -50
  82. package/src/lib/admin-reset.ts +15 -30
  83. package/src/lib/admin-setup.ts +246 -55
  84. package/src/lib/agent-auth-migrate.ts +5 -1
  85. package/src/lib/asset-broadcast.ts +15 -4
  86. package/src/lib/config-amounts.ts +6 -4
  87. package/src/lib/hidden-tty-prompt.js +1 -0
  88. package/src/lib/hidden-tty-prompt.ts +105 -0
  89. package/src/lib/keychain.ts +1 -0
  90. package/src/lib/local-admin-access.ts +4 -29
  91. package/src/lib/rust.ts +129 -33
  92. package/src/lib/signed-tx.ts +1 -0
  93. package/src/lib/sudo.ts +15 -5
  94. package/src/lib/wallet-profile.ts +3 -0
  95. package/src/lib/wallet-setup.ts +52 -0
  96. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  97. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { CacheError, cacheErrorCodes } from '../errors/index.js';
3
+ import { cacheErrorCodes } from '../errors/index.js';
4
+ import type { CacheError } from '../errors/index.js';
4
5
  import { RelayCacheService } from './index.js';
5
6
 
6
7
  class InMemoryCacheClient {
@@ -383,6 +384,87 @@ describe('RelayCacheService approval capability guards', () => {
383
384
  expect(synced?.metadata?.approvalCapabilityToken).not.toBe(originalToken);
384
385
  });
385
386
 
387
+ it('removes approvals omitted from a later daemon sync snapshot', async () => {
388
+ const client = new MutableInMemoryCacheClient();
389
+ const service = new RelayCacheService({
390
+ client: client as never,
391
+ namespace: 'test:relay',
392
+ });
393
+
394
+ const daemonId = '44'.repeat(32);
395
+ const retainedApprovalId = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd';
396
+ const removedApprovalId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee';
397
+ const activeApprovalKey = `test:relay:approval:${removedApprovalId}:active-update`;
398
+ const failureKey = `test:relay:approval:${removedApprovalId}:capability-failures`;
399
+
400
+ await service.syncDaemonRegistration({
401
+ daemon: {
402
+ daemonId,
403
+ daemonPublicKey: '12'.repeat(32),
404
+ ethereumAddress: '0x1212121212121212121212121212121212121212',
405
+ lastSeenAt: new Date().toISOString(),
406
+ registeredAt: new Date().toISOString(),
407
+ status: 'active',
408
+ updatedAt: new Date().toISOString(),
409
+ },
410
+ approvalRequests: [
411
+ {
412
+ approvalRequestId: retainedApprovalId,
413
+ daemonId,
414
+ destination: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
415
+ requestedAt: new Date().toISOString(),
416
+ status: 'pending',
417
+ transactionType: 'transfer_native',
418
+ updatedAt: new Date().toISOString(),
419
+ },
420
+ {
421
+ approvalRequestId: removedApprovalId,
422
+ daemonId,
423
+ destination: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
424
+ requestedAt: new Date().toISOString(),
425
+ status: 'pending',
426
+ transactionType: 'transfer_native',
427
+ updatedAt: new Date().toISOString(),
428
+ },
429
+ ],
430
+ });
431
+
432
+ await client.forceSet(activeApprovalKey, 'stale-update-id');
433
+ await service.recordApprovalCapabilityFailure(removedApprovalId);
434
+
435
+ await service.syncDaemonRegistration({
436
+ daemon: {
437
+ daemonId,
438
+ daemonPublicKey: '12'.repeat(32),
439
+ ethereumAddress: '0x1212121212121212121212121212121212121212',
440
+ lastSeenAt: new Date().toISOString(),
441
+ registeredAt: new Date().toISOString(),
442
+ status: 'active',
443
+ updatedAt: new Date().toISOString(),
444
+ },
445
+ approvalRequests: [
446
+ {
447
+ approvalRequestId: retainedApprovalId,
448
+ daemonId,
449
+ destination: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
450
+ requestedAt: new Date().toISOString(),
451
+ status: 'pending',
452
+ transactionType: 'transfer_native',
453
+ updatedAt: new Date().toISOString(),
454
+ },
455
+ ],
456
+ });
457
+
458
+ await expect(service.getApprovalRequest(removedApprovalId)).resolves.toBeNull();
459
+ await expect(service.listApprovalRequests({ daemonId })).resolves.toMatchObject([
460
+ {
461
+ approvalRequestId: retainedApprovalId,
462
+ },
463
+ ]);
464
+ await expect(client.forceGet(activeApprovalKey)).resolves.toBeNull();
465
+ await expect(client.forceGet(failureKey)).resolves.toBeNull();
466
+ });
467
+
386
468
  it('rejects secure approval capability rotation for non-pending approvals', async () => {
387
469
  const service = new RelayCacheService({
388
470
  client: new InMemoryCacheClient() as never,
@@ -508,11 +590,16 @@ describe('RelayCacheService approval capability guards', () => {
508
590
  }),
509
591
  ).rejects.toThrow(/already has a queued operator update/);
510
592
 
511
- const [claimed] = await service.claimEncryptedUpdates({
512
- daemonId,
513
- leaseSeconds: 30,
514
- limit: 1,
515
- });
593
+ const claimed = (
594
+ await service.claimEncryptedUpdates({
595
+ daemonId,
596
+ leaseSeconds: 30,
597
+ limit: 1,
598
+ })
599
+ )[0];
600
+ if (!claimed) {
601
+ throw new Error('expected a claimed update');
602
+ }
516
603
 
517
604
  await expect(
518
605
  service.submitUpdateFeedback({
@@ -597,10 +684,10 @@ describe('RelayCacheService approval capability guards', () => {
597
684
  targetApprovalRequestId: approvalRequestId,
598
685
  type: 'manual_approval_decision',
599
686
  }),
600
- ).rejects.toMatchObject<Partial<CacheError>>({
687
+ ).rejects.toMatchObject({
601
688
  code: cacheErrorCodes.invalidPayload,
602
689
  message: `Approval '${approvalRequestId}' is 'approved' and cannot accept new updates`,
603
- });
690
+ } satisfies Partial<CacheError>);
604
691
  });
605
692
 
606
693
  it('rejects manual approval updates that target another daemon approval', async () => {
@@ -652,10 +739,10 @@ describe('RelayCacheService approval capability guards', () => {
652
739
  targetApprovalRequestId: approvalRequestId,
653
740
  type: 'manual_approval_decision',
654
741
  }),
655
- ).rejects.toMatchObject<Partial<CacheError>>({
742
+ ).rejects.toMatchObject({
656
743
  code: cacheErrorCodes.invalidPayload,
657
744
  message: `Approval '${approvalRequestId}' belongs to daemon '${approvalDaemonId}', not '${updateDaemonId}'`,
658
- });
745
+ } satisfies Partial<CacheError>);
659
746
  });
660
747
 
661
748
  it('keeps the original active slot intact after rejecting a duplicate manual approval update', async () => {
@@ -774,12 +861,10 @@ describe('RelayCacheService approval capability guards', () => {
774
861
  type: 'daemon_status',
775
862
  });
776
863
 
777
- await expect(service.removeEncryptedUpdate('20'.repeat(32), update.updateId)).rejects.toMatchObject<
778
- Partial<CacheError>
779
- >({
864
+ await expect(service.removeEncryptedUpdate('20'.repeat(32), update.updateId)).rejects.toMatchObject({
780
865
  code: cacheErrorCodes.notFound,
781
866
  message: `Unknown update '${update.updateId}' for daemon '${'20'.repeat(32)}'`,
782
- });
867
+ } satisfies Partial<CacheError>);
783
868
  await expect(service.getEncryptedUpdate(update.updateId)).resolves.toMatchObject({
784
869
  daemonId: '10'.repeat(32),
785
870
  updateId: update.updateId,
@@ -813,11 +898,17 @@ describe('RelayCacheService approval capability guards', () => {
813
898
  type: 'manual_approval_decision',
814
899
  });
815
900
 
816
- const [claimed] = await service.claimEncryptedUpdates({
817
- daemonId,
818
- leaseSeconds: 30,
819
- limit: 1,
820
- });
901
+ const claimed = (
902
+ await service.claimEncryptedUpdates({
903
+ daemonId,
904
+ leaseSeconds: 30,
905
+ limit: 1,
906
+ })
907
+ )[0];
908
+ if (!claimed) {
909
+ throw new Error('expected a claimed update');
910
+ }
911
+
821
912
  const activeApprovalKey = `test:relay:approval:${approvalRequestId}:active-update`;
822
913
  await client.forceSet(activeApprovalKey, 'other-update-id');
823
914
 
@@ -835,4 +926,59 @@ describe('RelayCacheService approval capability guards', () => {
835
926
 
836
927
  await expect(client.forceGet(activeApprovalKey)).resolves.toBe('other-update-id');
837
928
  });
929
+
930
+ it('rejects feedback for an expired claim lease even when the claim token still matches', async () => {
931
+ const client = new MutableInMemoryCacheClient();
932
+ const service = new RelayCacheService({
933
+ client: client as never,
934
+ namespace: 'test:relay',
935
+ });
936
+
937
+ const daemonId = '66'.repeat(32);
938
+ const update = await service.createEncryptedUpdate({
939
+ daemonId,
940
+ metadata: {
941
+ source: 'test-seed',
942
+ },
943
+ payload: {
944
+ algorithm: 'x25519-xchacha20poly1305-v1',
945
+ ciphertextBase64: 'aa',
946
+ encapsulatedKeyBase64: 'bb',
947
+ nonceBase64: 'cc',
948
+ schemaVersion: 1,
949
+ },
950
+ type: 'daemon_status',
951
+ });
952
+
953
+ const claimed = (
954
+ await service.claimEncryptedUpdates({
955
+ daemonId,
956
+ leaseSeconds: 30,
957
+ limit: 1,
958
+ })
959
+ )[0];
960
+ if (!claimed) {
961
+ throw new Error('expected a claimed update');
962
+ }
963
+
964
+ await client.forceSet(
965
+ `test:relay:update:${update.updateId}`,
966
+ JSON.stringify({
967
+ ...claimed,
968
+ claimUntil: new Date(Date.now() - 1_000).toISOString(),
969
+ }),
970
+ );
971
+
972
+ await expect(
973
+ service.submitUpdateFeedback({
974
+ claimToken: claimed.claimToken ?? '',
975
+ daemonId,
976
+ status: 'applied',
977
+ updateId: update.updateId,
978
+ }),
979
+ ).rejects.toMatchObject({
980
+ code: cacheErrorCodes.invalidPayload,
981
+ message: `Claim for update '${update.updateId}' has expired`,
982
+ });
983
+ });
838
984
  });
@@ -295,6 +295,10 @@ export class RelayCacheService {
295
295
  }
296
296
 
297
297
  if (input.approvalRequests) {
298
+ const approvalIndexKey = this.daemonApprovalsKey(profile.daemonId);
299
+ const existingApprovalIds = new Set(await this.client.zrange(approvalIndexKey, 0, -1));
300
+ const nextApprovalIds = new Set<string>();
301
+
298
302
  for (const approvalRequest of input.approvalRequests) {
299
303
  const existing = await this.readJson<RelayApprovalRequestRecord>(
300
304
  this.approvalKey(approvalRequest.approvalRequestId),
@@ -303,13 +307,26 @@ export class RelayCacheService {
303
307
  { ...approvalRequest, daemonId: profile.daemonId },
304
308
  existing,
305
309
  );
310
+ nextApprovalIds.add(normalized.approvalRequestId);
306
311
  await this.writeJson(this.approvalKey(normalized.approvalRequestId), normalized);
307
312
  await this.client.zadd(
308
- this.daemonApprovalsKey(profile.daemonId),
313
+ approvalIndexKey,
309
314
  Date.parse(normalized.requestedAt),
310
315
  normalized.approvalRequestId,
311
316
  );
312
317
  }
318
+
319
+ const staleApprovalIds = [...existingApprovalIds].filter(
320
+ (approvalRequestId) => !nextApprovalIds.has(approvalRequestId),
321
+ );
322
+ if (staleApprovalIds.length > 0) {
323
+ await this.client.zrem(approvalIndexKey, ...staleApprovalIds);
324
+ for (const approvalRequestId of staleApprovalIds) {
325
+ await this.client.del(this.approvalKey(approvalRequestId));
326
+ await this.client.del(this.activeApprovalUpdateKey(approvalRequestId));
327
+ await this.client.del(this.approvalCapabilityFailuresKey(approvalRequestId));
328
+ }
329
+ }
313
330
  }
314
331
 
315
332
  return {
@@ -734,6 +751,7 @@ export class RelayCacheService {
734
751
  ): Promise<RelayEncryptedUpdateRecord> {
735
752
  const key = this.updateKey(input.updateId);
736
753
  const record = await this.readJson<RelayEncryptedUpdateRecord>(key);
754
+ const nowMs = Date.now();
737
755
 
738
756
  if (!record || record.daemonId !== input.daemonId) {
739
757
  throw new CacheError({
@@ -744,6 +762,25 @@ export class RelayCacheService {
744
762
  });
745
763
  }
746
764
 
765
+ if (record.status !== 'inflight') {
766
+ throw new CacheError({
767
+ code: cacheErrorCodes.invalidPayload,
768
+ key,
769
+ message: `Update '${input.updateId}' is '${record.status}' and is not currently claimed`,
770
+ operation: 'submitUpdateFeedback',
771
+ });
772
+ }
773
+
774
+ const claimUntilMs = record.claimUntil ? Date.parse(record.claimUntil) : Number.NaN;
775
+ if (!Number.isFinite(claimUntilMs) || claimUntilMs <= nowMs) {
776
+ throw new CacheError({
777
+ code: cacheErrorCodes.invalidPayload,
778
+ key,
779
+ message: `Claim for update '${input.updateId}' has expired`,
780
+ operation: 'submitUpdateFeedback',
781
+ });
782
+ }
783
+
747
784
  if (!record.claimToken || record.claimToken !== input.claimToken) {
748
785
  throw new CacheError({
749
786
  code: cacheErrorCodes.invalidPayload,
@@ -1,17 +1,18 @@
1
-
2
- > @wlfi-agent/config@0.1.0 build /Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/config
3
- > tsup src/index.ts --format esm --dts --clean
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/tsup.config.ts
9
- CLI Target: node20
10
- CLI Cleaning output folder
11
- ESM Build start
12
- ESM dist/index.cjs 36.27 KB
13
- ESM dist/index.cjs.map 66.36 KB
14
- ESM ⚡️ Build success in 10ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 437ms
17
- DTS dist/index.d.ts 5.23 KB
1
+
2
+ 
3
+ > @wlfi-agent/config@0.1.0 build /Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/config
4
+ > tsup src/index.ts --format esm --dts --clean
5
+
6
+ CLI Building entry: src/index.ts
7
+ CLI Using tsconfig: tsconfig.json
8
+ CLI tsup v8.5.1
9
+ CLI Using tsup config: /Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/tsup.config.ts
10
+ CLI Target: node20
11
+ CLI Cleaning output folder
12
+ ESM Build start
13
+ ESM dist/index.cjs 35.92 KB
14
+ ESM dist/index.cjs.map 65.75 KB
15
+ ESM ⚡️ Build success in 8ms
16
+ DTS Build start
17
+ DTS ⚡️ Build success in 377ms
18
+ DTS dist/index.d.ts 5.23 KB
@@ -52,23 +52,6 @@ var BUILTIN_TOKENS = {
52
52
  arbitrum: { chainId: 42161, isNative: true, decimals: 18 }
53
53
  }
54
54
  },
55
- usdc: {
56
- symbol: "USDC",
57
- chains: {
58
- ethereum: {
59
- chainId: 1,
60
- isNative: false,
61
- address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
62
- decimals: 6
63
- },
64
- base: {
65
- chainId: 8453,
66
- isNative: false,
67
- address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
68
- decimals: 6
69
- }
70
- }
71
- },
72
55
  usd1: {
73
56
  name: "USD1",
74
57
  symbol: "USD1",