@wlfi-agent/cli 1.4.17 → 1.4.19

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 (93) hide show
  1. package/Cargo.lock +5 -0
  2. package/README.md +61 -28
  3. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  4. package/crates/vault-cli-admin/src/main.rs +639 -16
  5. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  6. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  7. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  8. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  9. package/crates/vault-cli-agent/Cargo.toml +1 -0
  10. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  11. package/crates/vault-cli-agent/src/main.rs +648 -32
  12. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  13. package/crates/vault-cli-daemon/src/main.rs +617 -67
  14. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  15. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  16. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  17. package/crates/vault-daemon/src/persistence.rs +637 -100
  18. package/crates/vault-daemon/src/tests.rs +1013 -3
  19. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  20. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  21. package/crates/vault-domain/src/nonce.rs +4 -0
  22. package/crates/vault-domain/src/tests.rs +616 -0
  23. package/crates/vault-policy/src/engine.rs +55 -32
  24. package/crates/vault-policy/src/tests.rs +195 -0
  25. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  26. package/crates/vault-signer/Cargo.toml +3 -0
  27. package/crates/vault-signer/src/lib.rs +266 -40
  28. package/crates/vault-transport-unix/src/lib.rs +653 -5
  29. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  30. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  31. package/dist/cli.cjs +756 -194
  32. package/dist/cli.cjs.map +1 -1
  33. package/package.json +5 -2
  34. package/packages/cache/.turbo/turbo-build.log +20 -20
  35. package/packages/cache/coverage/clover.xml +529 -394
  36. package/packages/cache/coverage/coverage-final.json +2 -2
  37. package/packages/cache/coverage/index.html +21 -21
  38. package/packages/cache/coverage/src/client/index.html +1 -1
  39. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  40. package/packages/cache/coverage/src/errors/index.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  42. package/packages/cache/coverage/src/index.html +1 -1
  43. package/packages/cache/coverage/src/index.ts.html +1 -1
  44. package/packages/cache/coverage/src/service/index.html +21 -21
  45. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  46. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  47. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  48. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  49. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  50. package/packages/cache/dist/index.cjs +2 -2
  51. package/packages/cache/dist/index.js +1 -1
  52. package/packages/cache/dist/service/index.cjs +2 -2
  53. package/packages/cache/dist/service/index.js +1 -1
  54. package/packages/cache/node_modules/.bin/tsc +2 -2
  55. package/packages/cache/node_modules/.bin/tsserver +2 -2
  56. package/packages/cache/node_modules/.bin/tsup +2 -2
  57. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  58. package/packages/cache/node_modules/.bin/vitest +4 -4
  59. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  60. package/packages/cache/src/service/index.test.ts +165 -19
  61. package/packages/cache/src/service/index.ts +38 -1
  62. package/packages/config/.turbo/turbo-build.log +4 -4
  63. package/packages/config/dist/index.cjs +0 -17
  64. package/packages/config/dist/index.cjs.map +1 -1
  65. package/packages/config/src/index.ts +0 -17
  66. package/packages/rpc/.turbo/turbo-build.log +11 -11
  67. package/packages/rpc/dist/index.cjs +0 -17
  68. package/packages/rpc/dist/index.cjs.map +1 -1
  69. package/packages/rpc/src/index.js +1 -0
  70. package/packages/ui/node_modules/.bin/tsc +2 -2
  71. package/packages/ui/node_modules/.bin/tsserver +2 -2
  72. package/packages/ui/node_modules/.bin/tsup +2 -2
  73. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  74. package/scripts/install-cli-launcher.mjs +37 -0
  75. package/scripts/install-rust-binaries.mjs +47 -0
  76. package/scripts/run-tests-isolated.mjs +210 -0
  77. package/src/cli.ts +310 -50
  78. package/src/lib/admin-reset.ts +101 -33
  79. package/src/lib/admin-setup.ts +285 -55
  80. package/src/lib/agent-auth-migrate.ts +5 -1
  81. package/src/lib/asset-broadcast.ts +15 -4
  82. package/src/lib/config-amounts.ts +6 -4
  83. package/src/lib/hidden-tty-prompt.js +1 -0
  84. package/src/lib/hidden-tty-prompt.ts +105 -0
  85. package/src/lib/keychain.ts +1 -0
  86. package/src/lib/local-admin-access.ts +4 -29
  87. package/src/lib/rust.ts +129 -33
  88. package/src/lib/signed-tx.ts +1 -0
  89. package/src/lib/sudo.ts +15 -5
  90. package/src/lib/wallet-profile.ts +3 -0
  91. package/src/lib/wallet-setup.ts +52 -0
  92. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  93. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">45.99% </span>
26
+ <span class="strong">68.63% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>218/474</span>
28
+ <span class='fraction'>418/609</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">69.11% </span>
33
+ <span class="strong">67.47% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>47/68</span>
35
+ <span class='fraction'>83/123</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">48.78% </span>
40
+ <span class="strong">72.72% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>20/41</span>
42
+ <span class='fraction'>32/44</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">45.99% </span>
47
+ <span class="strong">68.63% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>218/474</span>
49
+ <span class='fraction'>418/609</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line low'></div>
64
+ <div class='status-line medium'></div>
65
65
  <pre><table class="coverage">
66
66
  <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
67
  <a name='L2'></a><a href='#L2'>2</a>
@@ -772,7 +772,159 @@
772
772
  <a name='L707'></a><a href='#L707'>707</a>
773
773
  <a name='L708'></a><a href='#L708'>708</a>
774
774
  <a name='L709'></a><a href='#L709'>709</a>
775
- <a name='L710'></a><a href='#L710'>710</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
775
+ <a name='L710'></a><a href='#L710'>710</a>
776
+ <a name='L711'></a><a href='#L711'>711</a>
777
+ <a name='L712'></a><a href='#L712'>712</a>
778
+ <a name='L713'></a><a href='#L713'>713</a>
779
+ <a name='L714'></a><a href='#L714'>714</a>
780
+ <a name='L715'></a><a href='#L715'>715</a>
781
+ <a name='L716'></a><a href='#L716'>716</a>
782
+ <a name='L717'></a><a href='#L717'>717</a>
783
+ <a name='L718'></a><a href='#L718'>718</a>
784
+ <a name='L719'></a><a href='#L719'>719</a>
785
+ <a name='L720'></a><a href='#L720'>720</a>
786
+ <a name='L721'></a><a href='#L721'>721</a>
787
+ <a name='L722'></a><a href='#L722'>722</a>
788
+ <a name='L723'></a><a href='#L723'>723</a>
789
+ <a name='L724'></a><a href='#L724'>724</a>
790
+ <a name='L725'></a><a href='#L725'>725</a>
791
+ <a name='L726'></a><a href='#L726'>726</a>
792
+ <a name='L727'></a><a href='#L727'>727</a>
793
+ <a name='L728'></a><a href='#L728'>728</a>
794
+ <a name='L729'></a><a href='#L729'>729</a>
795
+ <a name='L730'></a><a href='#L730'>730</a>
796
+ <a name='L731'></a><a href='#L731'>731</a>
797
+ <a name='L732'></a><a href='#L732'>732</a>
798
+ <a name='L733'></a><a href='#L733'>733</a>
799
+ <a name='L734'></a><a href='#L734'>734</a>
800
+ <a name='L735'></a><a href='#L735'>735</a>
801
+ <a name='L736'></a><a href='#L736'>736</a>
802
+ <a name='L737'></a><a href='#L737'>737</a>
803
+ <a name='L738'></a><a href='#L738'>738</a>
804
+ <a name='L739'></a><a href='#L739'>739</a>
805
+ <a name='L740'></a><a href='#L740'>740</a>
806
+ <a name='L741'></a><a href='#L741'>741</a>
807
+ <a name='L742'></a><a href='#L742'>742</a>
808
+ <a name='L743'></a><a href='#L743'>743</a>
809
+ <a name='L744'></a><a href='#L744'>744</a>
810
+ <a name='L745'></a><a href='#L745'>745</a>
811
+ <a name='L746'></a><a href='#L746'>746</a>
812
+ <a name='L747'></a><a href='#L747'>747</a>
813
+ <a name='L748'></a><a href='#L748'>748</a>
814
+ <a name='L749'></a><a href='#L749'>749</a>
815
+ <a name='L750'></a><a href='#L750'>750</a>
816
+ <a name='L751'></a><a href='#L751'>751</a>
817
+ <a name='L752'></a><a href='#L752'>752</a>
818
+ <a name='L753'></a><a href='#L753'>753</a>
819
+ <a name='L754'></a><a href='#L754'>754</a>
820
+ <a name='L755'></a><a href='#L755'>755</a>
821
+ <a name='L756'></a><a href='#L756'>756</a>
822
+ <a name='L757'></a><a href='#L757'>757</a>
823
+ <a name='L758'></a><a href='#L758'>758</a>
824
+ <a name='L759'></a><a href='#L759'>759</a>
825
+ <a name='L760'></a><a href='#L760'>760</a>
826
+ <a name='L761'></a><a href='#L761'>761</a>
827
+ <a name='L762'></a><a href='#L762'>762</a>
828
+ <a name='L763'></a><a href='#L763'>763</a>
829
+ <a name='L764'></a><a href='#L764'>764</a>
830
+ <a name='L765'></a><a href='#L765'>765</a>
831
+ <a name='L766'></a><a href='#L766'>766</a>
832
+ <a name='L767'></a><a href='#L767'>767</a>
833
+ <a name='L768'></a><a href='#L768'>768</a>
834
+ <a name='L769'></a><a href='#L769'>769</a>
835
+ <a name='L770'></a><a href='#L770'>770</a>
836
+ <a name='L771'></a><a href='#L771'>771</a>
837
+ <a name='L772'></a><a href='#L772'>772</a>
838
+ <a name='L773'></a><a href='#L773'>773</a>
839
+ <a name='L774'></a><a href='#L774'>774</a>
840
+ <a name='L775'></a><a href='#L775'>775</a>
841
+ <a name='L776'></a><a href='#L776'>776</a>
842
+ <a name='L777'></a><a href='#L777'>777</a>
843
+ <a name='L778'></a><a href='#L778'>778</a>
844
+ <a name='L779'></a><a href='#L779'>779</a>
845
+ <a name='L780'></a><a href='#L780'>780</a>
846
+ <a name='L781'></a><a href='#L781'>781</a>
847
+ <a name='L782'></a><a href='#L782'>782</a>
848
+ <a name='L783'></a><a href='#L783'>783</a>
849
+ <a name='L784'></a><a href='#L784'>784</a>
850
+ <a name='L785'></a><a href='#L785'>785</a>
851
+ <a name='L786'></a><a href='#L786'>786</a>
852
+ <a name='L787'></a><a href='#L787'>787</a>
853
+ <a name='L788'></a><a href='#L788'>788</a>
854
+ <a name='L789'></a><a href='#L789'>789</a>
855
+ <a name='L790'></a><a href='#L790'>790</a>
856
+ <a name='L791'></a><a href='#L791'>791</a>
857
+ <a name='L792'></a><a href='#L792'>792</a>
858
+ <a name='L793'></a><a href='#L793'>793</a>
859
+ <a name='L794'></a><a href='#L794'>794</a>
860
+ <a name='L795'></a><a href='#L795'>795</a>
861
+ <a name='L796'></a><a href='#L796'>796</a>
862
+ <a name='L797'></a><a href='#L797'>797</a>
863
+ <a name='L798'></a><a href='#L798'>798</a>
864
+ <a name='L799'></a><a href='#L799'>799</a>
865
+ <a name='L800'></a><a href='#L800'>800</a>
866
+ <a name='L801'></a><a href='#L801'>801</a>
867
+ <a name='L802'></a><a href='#L802'>802</a>
868
+ <a name='L803'></a><a href='#L803'>803</a>
869
+ <a name='L804'></a><a href='#L804'>804</a>
870
+ <a name='L805'></a><a href='#L805'>805</a>
871
+ <a name='L806'></a><a href='#L806'>806</a>
872
+ <a name='L807'></a><a href='#L807'>807</a>
873
+ <a name='L808'></a><a href='#L808'>808</a>
874
+ <a name='L809'></a><a href='#L809'>809</a>
875
+ <a name='L810'></a><a href='#L810'>810</a>
876
+ <a name='L811'></a><a href='#L811'>811</a>
877
+ <a name='L812'></a><a href='#L812'>812</a>
878
+ <a name='L813'></a><a href='#L813'>813</a>
879
+ <a name='L814'></a><a href='#L814'>814</a>
880
+ <a name='L815'></a><a href='#L815'>815</a>
881
+ <a name='L816'></a><a href='#L816'>816</a>
882
+ <a name='L817'></a><a href='#L817'>817</a>
883
+ <a name='L818'></a><a href='#L818'>818</a>
884
+ <a name='L819'></a><a href='#L819'>819</a>
885
+ <a name='L820'></a><a href='#L820'>820</a>
886
+ <a name='L821'></a><a href='#L821'>821</a>
887
+ <a name='L822'></a><a href='#L822'>822</a>
888
+ <a name='L823'></a><a href='#L823'>823</a>
889
+ <a name='L824'></a><a href='#L824'>824</a>
890
+ <a name='L825'></a><a href='#L825'>825</a>
891
+ <a name='L826'></a><a href='#L826'>826</a>
892
+ <a name='L827'></a><a href='#L827'>827</a>
893
+ <a name='L828'></a><a href='#L828'>828</a>
894
+ <a name='L829'></a><a href='#L829'>829</a>
895
+ <a name='L830'></a><a href='#L830'>830</a>
896
+ <a name='L831'></a><a href='#L831'>831</a>
897
+ <a name='L832'></a><a href='#L832'>832</a>
898
+ <a name='L833'></a><a href='#L833'>833</a>
899
+ <a name='L834'></a><a href='#L834'>834</a>
900
+ <a name='L835'></a><a href='#L835'>835</a>
901
+ <a name='L836'></a><a href='#L836'>836</a>
902
+ <a name='L837'></a><a href='#L837'>837</a>
903
+ <a name='L838'></a><a href='#L838'>838</a>
904
+ <a name='L839'></a><a href='#L839'>839</a>
905
+ <a name='L840'></a><a href='#L840'>840</a>
906
+ <a name='L841'></a><a href='#L841'>841</a>
907
+ <a name='L842'></a><a href='#L842'>842</a>
908
+ <a name='L843'></a><a href='#L843'>843</a>
909
+ <a name='L844'></a><a href='#L844'>844</a>
910
+ <a name='L845'></a><a href='#L845'>845</a>
911
+ <a name='L846'></a><a href='#L846'>846</a>
912
+ <a name='L847'></a><a href='#L847'>847</a>
913
+ <a name='L848'></a><a href='#L848'>848</a>
914
+ <a name='L849'></a><a href='#L849'>849</a>
915
+ <a name='L850'></a><a href='#L850'>850</a>
916
+ <a name='L851'></a><a href='#L851'>851</a>
917
+ <a name='L852'></a><a href='#L852'>852</a>
918
+ <a name='L853'></a><a href='#L853'>853</a>
919
+ <a name='L854'></a><a href='#L854'>854</a>
920
+ <a name='L855'></a><a href='#L855'>855</a>
921
+ <a name='L856'></a><a href='#L856'>856</a>
922
+ <a name='L857'></a><a href='#L857'>857</a>
923
+ <a name='L858'></a><a href='#L858'>858</a>
924
+ <a name='L859'></a><a href='#L859'>859</a>
925
+ <a name='L860'></a><a href='#L860'>860</a>
926
+ <a name='L861'></a><a href='#L861'>861</a>
927
+ <a name='L862'></a><a href='#L862'>862</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
776
928
  <span class="cline-any cline-neutral">&nbsp;</span>
777
929
  <span class="cline-any cline-yes">1x</span>
778
930
  <span class="cline-any cline-yes">1x</span>
@@ -955,7 +1107,6 @@
955
1107
  <span class="cline-any cline-yes">1x</span>
956
1108
  <span class="cline-any cline-yes">1x</span>
957
1109
  <span class="cline-any cline-yes">1x</span>
958
- <span class="cline-any cline-yes">1x</span>
959
1110
  <span class="cline-any cline-neutral">&nbsp;</span>
960
1111
  <span class="cline-any cline-yes">1x</span>
961
1112
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -973,12 +1124,12 @@
973
1124
  <span class="cline-any cline-no">&nbsp;</span>
974
1125
  <span class="cline-any cline-neutral">&nbsp;</span>
975
1126
  <span class="cline-any cline-yes">1x</span>
976
- <span class="cline-any cline-no">&nbsp;</span>
1127
+ <span class="cline-any cline-yes">8x</span>
977
1128
  <span class="cline-any cline-no">&nbsp;</span>
978
1129
  <span class="cline-any cline-no">&nbsp;</span>
979
1130
  <span class="cline-any cline-neutral">&nbsp;</span>
980
- <span class="cline-any cline-no">&nbsp;</span>
981
- <span class="cline-any cline-no">&nbsp;</span>
1131
+ <span class="cline-any cline-yes">8x</span>
1132
+ <span class="cline-any cline-yes">8x</span>
982
1133
  <span class="cline-any cline-neutral">&nbsp;</span>
983
1134
  <span class="cline-any cline-yes">1x</span>
984
1135
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -986,40 +1137,51 @@
986
1137
  <span class="cline-any cline-yes">3x</span>
987
1138
  <span class="cline-any cline-neutral">&nbsp;</span>
988
1139
  <span class="cline-any cline-yes">1x</span>
989
- <span class="cline-any cline-yes">5x</span>
990
- <span class="cline-any cline-yes">5x</span>
991
- <span class="cline-any cline-yes">5x</span>
992
- <span class="cline-any cline-yes">5x</span>
993
- <span class="cline-any cline-yes">5x</span>
994
- <span class="cline-any cline-yes">5x</span>
995
- <span class="cline-any cline-yes">5x</span>
1140
+ <span class="cline-any cline-yes">4x</span>
1141
+ <span class="cline-any cline-yes">4x</span>
996
1142
  <span class="cline-any cline-neutral">&nbsp;</span>
997
- <span class="cline-any cline-yes">5x</span>
998
1143
  <span class="cline-any cline-yes">4x</span>
999
1144
  <span class="cline-any cline-yes">4x</span>
1145
+ <span class="cline-any cline-yes">4x</span>
1146
+ <span class="cline-any cline-yes">4x</span>
1147
+ <span class="cline-any cline-yes">4x</span>
1148
+ <span class="cline-any cline-yes">4x</span>
1149
+ <span class="cline-any cline-neutral">&nbsp;</span>
1150
+ <span class="cline-any cline-yes">1x</span>
1151
+ <span class="cline-any cline-yes">11x</span>
1152
+ <span class="cline-any cline-yes">11x</span>
1153
+ <span class="cline-any cline-yes">11x</span>
1154
+ <span class="cline-any cline-yes">11x</span>
1155
+ <span class="cline-any cline-yes">11x</span>
1156
+ <span class="cline-any cline-yes">11x</span>
1157
+ <span class="cline-any cline-yes">11x</span>
1158
+ <span class="cline-any cline-neutral">&nbsp;</span>
1159
+ <span class="cline-any cline-yes">11x</span>
1160
+ <span class="cline-any cline-yes">10x</span>
1161
+ <span class="cline-any cline-yes">10x</span>
1000
1162
  <span class="cline-any cline-neutral">&nbsp;</span>
1001
1163
  <span class="cline-any cline-yes">1x</span>
1002
1164
  <span class="cline-any cline-yes">1x</span>
1003
1165
  <span class="cline-any cline-yes">1x</span>
1004
- <span class="cline-any cline-yes">5x</span>
1005
- <span class="cline-any cline-yes">5x</span>
1166
+ <span class="cline-any cline-yes">11x</span>
1167
+ <span class="cline-any cline-yes">11x</span>
1006
1168
  <span class="cline-any cline-yes">1x</span>
1007
1169
  <span class="cline-any cline-no">&nbsp;</span>
1008
- <span class="cline-any cline-yes">5x</span>
1009
- <span class="cline-any cline-yes">5x</span>
1010
- <span class="cline-any cline-yes">5x</span>
1011
- <span class="cline-any cline-yes">5x</span>
1170
+ <span class="cline-any cline-yes">11x</span>
1171
+ <span class="cline-any cline-yes">11x</span>
1172
+ <span class="cline-any cline-yes">11x</span>
1173
+ <span class="cline-any cline-yes">11x</span>
1012
1174
  <span class="cline-any cline-neutral">&nbsp;</span>
1013
1175
  <span class="cline-any cline-yes">1x</span>
1014
1176
  <span class="cline-any cline-yes">1x</span>
1015
- <span class="cline-any cline-yes">6x</span>
1177
+ <span class="cline-any cline-yes">14x</span>
1016
1178
  <span class="cline-any cline-neutral">&nbsp;</span>
1017
- <span class="cline-any cline-yes">6x</span>
1018
- <span class="cline-any cline-yes">6x</span>
1019
- <span class="cline-any cline-yes">6x</span>
1020
- <span class="cline-any cline-yes">6x</span>
1179
+ <span class="cline-any cline-yes">14x</span>
1180
+ <span class="cline-any cline-yes">14x</span>
1181
+ <span class="cline-any cline-yes">14x</span>
1182
+ <span class="cline-any cline-yes">14x</span>
1021
1183
  <span class="cline-any cline-neutral">&nbsp;</span>
1022
- <span class="cline-any cline-yes">6x</span>
1184
+ <span class="cline-any cline-yes">14x</span>
1023
1185
  <span class="cline-any cline-no">&nbsp;</span>
1024
1186
  <span class="cline-any cline-no">&nbsp;</span>
1025
1187
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1027,22 +1189,22 @@
1027
1189
  <span class="cline-any cline-no">&nbsp;</span>
1028
1190
  <span class="cline-any cline-no">&nbsp;</span>
1029
1191
  <span class="cline-any cline-neutral">&nbsp;</span>
1030
- <span class="cline-any cline-yes">6x</span>
1192
+ <span class="cline-any cline-yes">14x</span>
1031
1193
  <span class="cline-any cline-neutral">&nbsp;</span>
1032
1194
  <span class="cline-any cline-neutral">&nbsp;</span>
1033
1195
  <span class="cline-any cline-neutral">&nbsp;</span>
1034
- <span class="cline-any cline-yes">5x</span>
1035
- <span class="cline-any cline-yes">5x</span>
1036
- <span class="cline-any cline-yes">5x</span>
1037
- <span class="cline-any cline-yes">5x</span>
1038
- <span class="cline-any cline-yes">5x</span>
1039
- <span class="cline-any cline-yes">5x</span>
1196
+ <span class="cline-any cline-yes">11x</span>
1197
+ <span class="cline-any cline-yes">11x</span>
1198
+ <span class="cline-any cline-yes">11x</span>
1199
+ <span class="cline-any cline-yes">11x</span>
1200
+ <span class="cline-any cline-yes">11x</span>
1201
+ <span class="cline-any cline-yes">11x</span>
1040
1202
  <span class="cline-any cline-neutral">&nbsp;</span>
1041
- <span class="cline-any cline-yes">5x</span>
1042
- <span class="cline-any cline-yes">5x</span>
1043
- <span class="cline-any cline-yes">5x</span>
1203
+ <span class="cline-any cline-yes">11x</span>
1204
+ <span class="cline-any cline-yes">11x</span>
1205
+ <span class="cline-any cline-yes">11x</span>
1044
1206
  <span class="cline-any cline-neutral">&nbsp;</span>
1045
- <span class="cline-any cline-yes">5x</span>
1207
+ <span class="cline-any cline-yes">11x</span>
1046
1208
  <span class="cline-any cline-no">&nbsp;</span>
1047
1209
  <span class="cline-any cline-no">&nbsp;</span>
1048
1210
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1050,7 +1212,7 @@
1050
1212
  <span class="cline-any cline-no">&nbsp;</span>
1051
1213
  <span class="cline-any cline-no">&nbsp;</span>
1052
1214
  <span class="cline-any cline-neutral">&nbsp;</span>
1053
- <span class="cline-any cline-yes">5x</span>
1215
+ <span class="cline-any cline-yes">11x</span>
1054
1216
  <span class="cline-any cline-no">&nbsp;</span>
1055
1217
  <span class="cline-any cline-no">&nbsp;</span>
1056
1218
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1058,38 +1220,38 @@
1058
1220
  <span class="cline-any cline-no">&nbsp;</span>
1059
1221
  <span class="cline-any cline-no">&nbsp;</span>
1060
1222
  <span class="cline-any cline-neutral">&nbsp;</span>
1061
- <span class="cline-any cline-yes">5x</span>
1062
- <span class="cline-any cline-yes">5x</span>
1063
- <span class="cline-any cline-yes">5x</span>
1064
- <span class="cline-any cline-yes">5x</span>
1065
- <span class="cline-any cline-yes">5x</span>
1066
- <span class="cline-any cline-yes">5x</span>
1067
- <span class="cline-any cline-yes">5x</span>
1068
- <span class="cline-any cline-yes">5x</span>
1069
- <span class="cline-any cline-yes">5x</span>
1070
- <span class="cline-any cline-yes">5x</span>
1071
- <span class="cline-any cline-yes">5x</span>
1072
- <span class="cline-any cline-yes">5x</span>
1073
- <span class="cline-any cline-yes">5x</span>
1074
- <span class="cline-any cline-yes">5x</span>
1075
- <span class="cline-any cline-yes">5x</span>
1076
- <span class="cline-any cline-yes">5x</span>
1077
- <span class="cline-any cline-yes">5x</span>
1223
+ <span class="cline-any cline-yes">11x</span>
1224
+ <span class="cline-any cline-yes">11x</span>
1225
+ <span class="cline-any cline-yes">11x</span>
1226
+ <span class="cline-any cline-yes">11x</span>
1227
+ <span class="cline-any cline-yes">11x</span>
1228
+ <span class="cline-any cline-yes">11x</span>
1229
+ <span class="cline-any cline-yes">11x</span>
1230
+ <span class="cline-any cline-yes">11x</span>
1231
+ <span class="cline-any cline-yes">11x</span>
1232
+ <span class="cline-any cline-yes">11x</span>
1233
+ <span class="cline-any cline-yes">11x</span>
1234
+ <span class="cline-any cline-yes">11x</span>
1235
+ <span class="cline-any cline-yes">11x</span>
1236
+ <span class="cline-any cline-yes">11x</span>
1237
+ <span class="cline-any cline-yes">11x</span>
1238
+ <span class="cline-any cline-yes">11x</span>
1239
+ <span class="cline-any cline-yes">11x</span>
1078
1240
  <span class="cline-any cline-neutral">&nbsp;</span>
1079
- <span class="cline-any cline-yes">5x</span>
1080
- <span class="cline-any cline-yes">5x</span>
1081
- <span class="cline-any cline-yes">5x</span>
1082
- <span class="cline-any cline-yes">5x</span>
1083
- <span class="cline-any cline-yes">5x</span>
1084
- <span class="cline-any cline-yes">5x</span>
1241
+ <span class="cline-any cline-yes">11x</span>
1242
+ <span class="cline-any cline-yes">11x</span>
1243
+ <span class="cline-any cline-yes">11x</span>
1244
+ <span class="cline-any cline-yes">11x</span>
1245
+ <span class="cline-any cline-yes">11x</span>
1246
+ <span class="cline-any cline-yes">11x</span>
1085
1247
  <span class="cline-any cline-no">&nbsp;</span>
1086
1248
  <span class="cline-any cline-no">&nbsp;</span>
1087
1249
  <span class="cline-any cline-no">&nbsp;</span>
1088
1250
  <span class="cline-any cline-no">&nbsp;</span>
1089
1251
  <span class="cline-any cline-no">&nbsp;</span>
1090
- <span class="cline-any cline-yes">5x</span>
1252
+ <span class="cline-any cline-yes">11x</span>
1091
1253
  <span class="cline-any cline-neutral">&nbsp;</span>
1092
- <span class="cline-any cline-yes">6x</span>
1254
+ <span class="cline-any cline-yes">14x</span>
1093
1255
  <span class="cline-any cline-no">&nbsp;</span>
1094
1256
  <span class="cline-any cline-no">&nbsp;</span>
1095
1257
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1100,23 +1262,23 @@
1100
1262
  <span class="cline-any cline-no">&nbsp;</span>
1101
1263
  <span class="cline-any cline-no">&nbsp;</span>
1102
1264
  <span class="cline-any cline-neutral">&nbsp;</span>
1103
- <span class="cline-any cline-yes">6x</span>
1265
+ <span class="cline-any cline-yes">14x</span>
1104
1266
  <span class="cline-any cline-no">&nbsp;</span>
1105
1267
  <span class="cline-any cline-no">&nbsp;</span>
1106
1268
  <span class="cline-any cline-neutral">&nbsp;</span>
1107
- <span class="cline-any cline-yes">6x</span>
1269
+ <span class="cline-any cline-yes">14x</span>
1108
1270
  <span class="cline-any cline-no">&nbsp;</span>
1109
1271
  <span class="cline-any cline-no">&nbsp;</span>
1110
1272
  <span class="cline-any cline-neutral">&nbsp;</span>
1111
- <span class="cline-any cline-yes">6x</span>
1273
+ <span class="cline-any cline-yes">14x</span>
1112
1274
  <span class="cline-any cline-no">&nbsp;</span>
1113
1275
  <span class="cline-any cline-no">&nbsp;</span>
1114
1276
  <span class="cline-any cline-neutral">&nbsp;</span>
1115
- <span class="cline-any cline-yes">6x</span>
1277
+ <span class="cline-any cline-yes">14x</span>
1116
1278
  <span class="cline-any cline-yes">1x</span>
1117
1279
  <span class="cline-any cline-yes">1x</span>
1118
1280
  <span class="cline-any cline-neutral">&nbsp;</span>
1119
- <span class="cline-any cline-yes">6x</span>
1281
+ <span class="cline-any cline-yes">14x</span>
1120
1282
  <span class="cline-any cline-no">&nbsp;</span>
1121
1283
  <span class="cline-any cline-no">&nbsp;</span>
1122
1284
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1145,15 +1307,21 @@
1145
1307
  <span class="cline-any cline-no">&nbsp;</span>
1146
1308
  <span class="cline-any cline-no">&nbsp;</span>
1147
1309
  <span class="cline-any cline-neutral">&nbsp;</span>
1148
- <span class="cline-any cline-yes">6x</span>
1149
- <span class="cline-any cline-no">&nbsp;</span>
1150
- <span class="cline-any cline-no">&nbsp;</span>
1310
+ <span class="cline-any cline-yes">14x</span>
1311
+ <span class="cline-any cline-yes">311x</span>
1312
+ <span class="cline-any cline-yes">311x</span>
1313
+ <span class="cline-any cline-yes">311x</span>
1314
+ <span class="cline-any cline-yes">9x</span>
1151
1315
  <span class="cline-any cline-no">&nbsp;</span>
1152
1316
  <span class="cline-any cline-no">&nbsp;</span>
1153
1317
  <span class="cline-any cline-no">&nbsp;</span>
1154
1318
  <span class="cline-any cline-no">&nbsp;</span>
1155
1319
  <span class="cline-any cline-no">&nbsp;</span>
1156
1320
  <span class="cline-any cline-no">&nbsp;</span>
1321
+ <span class="cline-any cline-neutral">&nbsp;</span>
1322
+ <span class="cline-any cline-yes">9x</span>
1323
+ <span class="cline-any cline-yes">9x</span>
1324
+ <span class="cline-any cline-yes">9x</span>
1157
1325
  <span class="cline-any cline-no">&nbsp;</span>
1158
1326
  <span class="cline-any cline-no">&nbsp;</span>
1159
1327
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1162,42 +1330,120 @@
1162
1330
  <span class="cline-any cline-no">&nbsp;</span>
1163
1331
  <span class="cline-any cline-no">&nbsp;</span>
1164
1332
  <span class="cline-any cline-neutral">&nbsp;</span>
1165
- <span class="cline-any cline-no">&nbsp;</span>
1166
- <span class="cline-any cline-no">&nbsp;</span>
1333
+ <span class="cline-any cline-yes">9x</span>
1334
+ <span class="cline-any cline-yes">1x</span>
1335
+ <span class="cline-any cline-yes">1x</span>
1336
+ <span class="cline-any cline-yes">1x</span>
1337
+ <span class="cline-any cline-yes">1x</span>
1338
+ <span class="cline-any cline-yes">1x</span>
1339
+ <span class="cline-any cline-yes">1x</span>
1340
+ <span class="cline-any cline-yes">1x</span>
1167
1341
  <span class="cline-any cline-neutral">&nbsp;</span>
1168
- <span class="cline-any cline-no">&nbsp;</span>
1342
+ <span class="cline-any cline-yes">9x</span>
1343
+ <span class="cline-any cline-yes">1x</span>
1344
+ <span class="cline-any cline-yes">1x</span>
1345
+ <span class="cline-any cline-yes">1x</span>
1346
+ <span class="cline-any cline-yes">1x</span>
1347
+ <span class="cline-any cline-yes">1x</span>
1348
+ <span class="cline-any cline-yes">1x</span>
1349
+ <span class="cline-any cline-yes">1x</span>
1350
+ <span class="cline-any cline-yes">9x</span>
1351
+ <span class="cline-any cline-neutral">&nbsp;</span>
1352
+ <span class="cline-any cline-yes">311x</span>
1353
+ <span class="cline-any cline-yes">311x</span>
1354
+ <span class="cline-any cline-yes">311x</span>
1355
+ <span class="cline-any cline-yes">311x</span>
1356
+ <span class="cline-any cline-yes">311x</span>
1357
+ <span class="cline-any cline-yes">311x</span>
1358
+ <span class="cline-any cline-yes">311x</span>
1359
+ <span class="cline-any cline-yes">311x</span>
1360
+ <span class="cline-any cline-yes">311x</span>
1361
+ <span class="cline-any cline-yes">311x</span>
1362
+ <span class="cline-any cline-yes">311x</span>
1363
+ <span class="cline-any cline-yes">311x</span>
1364
+ <span class="cline-any cline-yes">311x</span>
1365
+ <span class="cline-any cline-neutral">&nbsp;</span>
1366
+ <span class="cline-any cline-yes">311x</span>
1367
+ <span class="cline-any cline-yes">311x</span>
1368
+ <span class="cline-any cline-yes">7x</span>
1369
+ <span class="cline-any cline-yes">302x</span>
1370
+ <span class="cline-any cline-yes">311x</span>
1371
+ <span class="cline-any cline-yes">311x</span>
1372
+ <span class="cline-any cline-neutral">&nbsp;</span>
1373
+ <span class="cline-any cline-yes">311x</span>
1374
+ <span class="cline-any cline-neutral">&nbsp;</span>
1375
+ <span class="cline-any cline-yes">309x</span>
1376
+ <span class="cline-any cline-yes">311x</span>
1377
+ <span class="cline-any cline-yes">7x</span>
1378
+ <span class="cline-any cline-yes">7x</span>
1379
+ <span class="cline-any cline-yes">2x</span>
1380
+ <span class="cline-any cline-yes">2x</span>
1381
+ <span class="cline-any cline-yes">2x</span>
1169
1382
  <span class="cline-any cline-no">&nbsp;</span>
1170
1383
  <span class="cline-any cline-neutral">&nbsp;</span>
1171
- <span class="cline-any cline-yes">6x</span>
1384
+ <span class="cline-any cline-yes">2x</span>
1385
+ <span class="cline-any cline-yes">2x</span>
1386
+ <span class="cline-any cline-yes">2x</span>
1387
+ <span class="cline-any cline-yes">2x</span>
1388
+ <span class="cline-any cline-yes">2x</span>
1389
+ <span class="cline-any cline-yes">2x</span>
1390
+ <span class="cline-any cline-yes">2x</span>
1391
+ <span class="cline-any cline-yes">2x</span>
1392
+ <span class="cline-any cline-neutral">&nbsp;</span>
1172
1393
  <span class="cline-any cline-no">&nbsp;</span>
1173
1394
  <span class="cline-any cline-no">&nbsp;</span>
1174
1395
  <span class="cline-any cline-no">&nbsp;</span>
1175
1396
  <span class="cline-any cline-no">&nbsp;</span>
1176
1397
  <span class="cline-any cline-no">&nbsp;</span>
1177
1398
  <span class="cline-any cline-no">&nbsp;</span>
1178
- <span class="cline-any cline-neutral">&nbsp;</span>
1179
1399
  <span class="cline-any cline-no">&nbsp;</span>
1180
1400
  <span class="cline-any cline-no">&nbsp;</span>
1181
1401
  <span class="cline-any cline-no">&nbsp;</span>
1182
1402
  <span class="cline-any cline-no">&nbsp;</span>
1403
+ <span class="cline-any cline-yes">2x</span>
1404
+ <span class="cline-any cline-neutral">&nbsp;</span>
1405
+ <span class="cline-any cline-yes">5x</span>
1406
+ <span class="cline-any cline-yes">5x</span>
1407
+ <span class="cline-any cline-neutral">&nbsp;</span>
1408
+ <span class="cline-any cline-yes">307x</span>
1409
+ <span class="cline-any cline-yes">311x</span>
1410
+ <span class="cline-any cline-yes">2x</span>
1411
+ <span class="cline-any cline-yes">2x</span>
1183
1412
  <span class="cline-any cline-no">&nbsp;</span>
1413
+ <span class="cline-any cline-no">&nbsp;</span>
1414
+ <span class="cline-any cline-yes">2x</span>
1415
+ <span class="cline-any cline-yes">2x</span>
1416
+ <span class="cline-any cline-neutral">&nbsp;</span>
1417
+ <span class="cline-any cline-yes">307x</span>
1418
+ <span class="cline-any cline-yes">311x</span>
1419
+ <span class="cline-any cline-neutral">&nbsp;</span>
1420
+ <span class="cline-any cline-yes">14x</span>
1421
+ <span class="cline-any cline-yes">2x</span>
1422
+ <span class="cline-any cline-yes">2x</span>
1423
+ <span class="cline-any cline-yes">2x</span>
1424
+ <span class="cline-any cline-yes">2x</span>
1425
+ <span class="cline-any cline-yes">2x</span>
1426
+ <span class="cline-any cline-yes">2x</span>
1427
+ <span class="cline-any cline-yes">2x</span>
1428
+ <span class="cline-any cline-yes">2x</span>
1184
1429
  <span class="cline-any cline-neutral">&nbsp;</span>
1185
1430
  <span class="cline-any cline-no">&nbsp;</span>
1186
1431
  <span class="cline-any cline-no">&nbsp;</span>
1432
+ <span class="cline-any cline-neutral">&nbsp;</span>
1187
1433
  <span class="cline-any cline-no">&nbsp;</span>
1434
+ <span class="cline-any cline-neutral">&nbsp;</span>
1188
1435
  <span class="cline-any cline-no">&nbsp;</span>
1189
1436
  <span class="cline-any cline-no">&nbsp;</span>
1190
1437
  <span class="cline-any cline-no">&nbsp;</span>
1191
- <span class="cline-any cline-neutral">&nbsp;</span>
1192
1438
  <span class="cline-any cline-no">&nbsp;</span>
1193
1439
  <span class="cline-any cline-no">&nbsp;</span>
1194
1440
  <span class="cline-any cline-no">&nbsp;</span>
1195
1441
  <span class="cline-any cline-no">&nbsp;</span>
1196
1442
  <span class="cline-any cline-neutral">&nbsp;</span>
1197
1443
  <span class="cline-any cline-no">&nbsp;</span>
1198
- <span class="cline-any cline-no">&nbsp;</span>
1444
+ <span class="cline-any cline-yes">2x</span>
1199
1445
  <span class="cline-any cline-neutral">&nbsp;</span>
1200
- <span class="cline-any cline-yes">6x</span>
1446
+ <span class="cline-any cline-yes">14x</span>
1201
1447
  <span class="cline-any cline-yes">2x</span>
1202
1448
  <span class="cline-any cline-yes">2x</span>
1203
1449
  <span class="cline-any cline-yes">2x</span>
@@ -1216,7 +1462,7 @@
1216
1462
  <span class="cline-any cline-no">&nbsp;</span>
1217
1463
  <span class="cline-any cline-yes">2x</span>
1218
1464
  <span class="cline-any cline-neutral">&nbsp;</span>
1219
- <span class="cline-any cline-yes">6x</span>
1465
+ <span class="cline-any cline-yes">14x</span>
1220
1466
  <span class="cline-any cline-no">&nbsp;</span>
1221
1467
  <span class="cline-any cline-no">&nbsp;</span>
1222
1468
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1230,7 +1476,7 @@
1230
1476
  <span class="cline-any cline-no">&nbsp;</span>
1231
1477
  <span class="cline-any cline-no">&nbsp;</span>
1232
1478
  <span class="cline-any cline-neutral">&nbsp;</span>
1233
- <span class="cline-any cline-yes">6x</span>
1479
+ <span class="cline-any cline-yes">14x</span>
1234
1480
  <span class="cline-any cline-yes">3x</span>
1235
1481
  <span class="cline-any cline-yes">3x</span>
1236
1482
  <span class="cline-any cline-yes">3x</span>
@@ -1241,7 +1487,7 @@
1241
1487
  <span class="cline-any cline-no">&nbsp;</span>
1242
1488
  <span class="cline-any cline-yes">3x</span>
1243
1489
  <span class="cline-any cline-neutral">&nbsp;</span>
1244
- <span class="cline-any cline-yes">6x</span>
1490
+ <span class="cline-any cline-yes">14x</span>
1245
1491
  <span class="cline-any cline-yes">8x</span>
1246
1492
  <span class="cline-any cline-yes">8x</span>
1247
1493
  <span class="cline-any cline-yes">8x</span>
@@ -1284,7 +1530,7 @@
1284
1530
  <span class="cline-any cline-yes">8x</span>
1285
1531
  <span class="cline-any cline-yes">8x</span>
1286
1532
  <span class="cline-any cline-neutral">&nbsp;</span>
1287
- <span class="cline-any cline-yes">6x</span>
1533
+ <span class="cline-any cline-yes">14x</span>
1288
1534
  <span class="cline-any cline-yes">4x</span>
1289
1535
  <span class="cline-any cline-yes">4x</span>
1290
1536
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1323,87 +1569,99 @@
1323
1569
  <span class="cline-any cline-yes">3x</span>
1324
1570
  <span class="cline-any cline-yes">4x</span>
1325
1571
  <span class="cline-any cline-neutral">&nbsp;</span>
1326
- <span class="cline-any cline-yes">6x</span>
1327
- <span class="cline-any cline-no">&nbsp;</span>
1328
- <span class="cline-any cline-no">&nbsp;</span>
1329
- <span class="cline-any cline-no">&nbsp;</span>
1330
- <span class="cline-any cline-no">&nbsp;</span>
1331
- <span class="cline-any cline-no">&nbsp;</span>
1332
- <span class="cline-any cline-no">&nbsp;</span>
1333
- <span class="cline-any cline-no">&nbsp;</span>
1334
- <span class="cline-any cline-no">&nbsp;</span>
1335
- <span class="cline-any cline-no">&nbsp;</span>
1336
- <span class="cline-any cline-no">&nbsp;</span>
1337
- <span class="cline-any cline-no">&nbsp;</span>
1338
- <span class="cline-any cline-no">&nbsp;</span>
1339
- <span class="cline-any cline-no">&nbsp;</span>
1340
- <span class="cline-any cline-no">&nbsp;</span>
1572
+ <span class="cline-any cline-yes">14x</span>
1573
+ <span class="cline-any cline-yes">4x</span>
1574
+ <span class="cline-any cline-yes">4x</span>
1575
+ <span class="cline-any cline-yes">4x</span>
1576
+ <span class="cline-any cline-yes">4x</span>
1577
+ <span class="cline-any cline-yes">4x</span>
1578
+ <span class="cline-any cline-yes">4x</span>
1579
+ <span class="cline-any cline-yes">4x</span>
1580
+ <span class="cline-any cline-yes">4x</span>
1581
+ <span class="cline-any cline-yes">4x</span>
1582
+ <span class="cline-any cline-yes">4x</span>
1583
+ <span class="cline-any cline-yes">4x</span>
1584
+ <span class="cline-any cline-yes">4x</span>
1585
+ <span class="cline-any cline-yes">4x</span>
1586
+ <span class="cline-any cline-yes">4x</span>
1341
1587
  <span class="cline-any cline-neutral">&nbsp;</span>
1342
- <span class="cline-any cline-no">&nbsp;</span>
1343
- <span class="cline-any cline-no">&nbsp;</span>
1588
+ <span class="cline-any cline-yes">4x</span>
1589
+ <span class="cline-any cline-yes">4x</span>
1344
1590
  <span class="cline-any cline-no">&nbsp;</span>
1345
1591
  <span class="cline-any cline-no">&nbsp;</span>
1346
1592
  <span class="cline-any cline-neutral">&nbsp;</span>
1347
- <span class="cline-any cline-no">&nbsp;</span>
1348
- <span class="cline-any cline-no">&nbsp;</span>
1349
- <span class="cline-any cline-no">&nbsp;</span>
1350
- <span class="cline-any cline-no">&nbsp;</span>
1593
+ <span class="cline-any cline-yes">4x</span>
1594
+ <span class="cline-any cline-yes">4x</span>
1351
1595
  <span class="cline-any cline-neutral">&nbsp;</span>
1352
- <span class="cline-any cline-no">&nbsp;</span>
1353
- <span class="cline-any cline-no">&nbsp;</span>
1354
- <span class="cline-any cline-no">&nbsp;</span>
1355
- <span class="cline-any cline-no">&nbsp;</span>
1356
- <span class="cline-any cline-no">&nbsp;</span>
1357
- <span class="cline-any cline-no">&nbsp;</span>
1358
- <span class="cline-any cline-no">&nbsp;</span>
1596
+ <span class="cline-any cline-yes">4x</span>
1597
+ <span class="cline-any cline-yes">4x</span>
1598
+ <span class="cline-any cline-yes">4x</span>
1599
+ <span class="cline-any cline-yes">1x</span>
1600
+ <span class="cline-any cline-yes">1x</span>
1601
+ <span class="cline-any cline-yes">1x</span>
1602
+ <span class="cline-any cline-yes">1x</span>
1603
+ <span class="cline-any cline-yes">1x</span>
1604
+ <span class="cline-any cline-yes">1x</span>
1605
+ <span class="cline-any cline-yes">1x</span>
1606
+ <span class="cline-any cline-yes">1x</span>
1359
1607
  <span class="cline-any cline-neutral">&nbsp;</span>
1360
1608
  <span class="cline-any cline-no">&nbsp;</span>
1361
1609
  <span class="cline-any cline-no">&nbsp;</span>
1362
1610
  <span class="cline-any cline-no">&nbsp;</span>
1363
1611
  <span class="cline-any cline-no">&nbsp;</span>
1364
1612
  <span class="cline-any cline-no">&nbsp;</span>
1365
- <span class="cline-any cline-no">&nbsp;</span>
1366
- <span class="cline-any cline-no">&nbsp;</span>
1613
+ <span class="cline-any cline-yes">1x</span>
1367
1614
  <span class="cline-any cline-neutral">&nbsp;</span>
1368
- <span class="cline-any cline-no">&nbsp;</span>
1369
- <span class="cline-any cline-no">&nbsp;</span>
1370
- <span class="cline-any cline-no">&nbsp;</span>
1371
- <span class="cline-any cline-no">&nbsp;</span>
1372
- <span class="cline-any cline-no">&nbsp;</span>
1373
- <span class="cline-any cline-no">&nbsp;</span>
1374
- <span class="cline-any cline-no">&nbsp;</span>
1375
- <span class="cline-any cline-no">&nbsp;</span>
1376
- <span class="cline-any cline-no">&nbsp;</span>
1377
- <span class="cline-any cline-no">&nbsp;</span>
1378
- <span class="cline-any cline-no">&nbsp;</span>
1615
+ <span class="cline-any cline-yes">3x</span>
1379
1616
  <span class="cline-any cline-neutral">&nbsp;</span>
1617
+ <span class="cline-any cline-yes">3x</span>
1618
+ <span class="cline-any cline-yes">4x</span>
1380
1619
  <span class="cline-any cline-no">&nbsp;</span>
1381
1620
  <span class="cline-any cline-no">&nbsp;</span>
1382
1621
  <span class="cline-any cline-neutral">&nbsp;</span>
1383
- <span class="cline-any cline-yes">6x</span>
1384
- <span class="cline-any cline-no">&nbsp;</span>
1385
- <span class="cline-any cline-no">&nbsp;</span>
1622
+ <span class="cline-any cline-yes">3x</span>
1623
+ <span class="cline-any cline-yes">3x</span>
1624
+ <span class="cline-any cline-yes">3x</span>
1625
+ <span class="cline-any cline-yes">3x</span>
1626
+ <span class="cline-any cline-yes">4x</span>
1386
1627
  <span class="cline-any cline-no">&nbsp;</span>
1387
1628
  <span class="cline-any cline-no">&nbsp;</span>
1388
1629
  <span class="cline-any cline-neutral">&nbsp;</span>
1630
+ <span class="cline-any cline-yes">3x</span>
1631
+ <span class="cline-any cline-yes">3x</span>
1389
1632
  <span class="cline-any cline-no">&nbsp;</span>
1390
1633
  <span class="cline-any cline-no">&nbsp;</span>
1391
- <span class="cline-any cline-no">&nbsp;</span>
1392
- <span class="cline-any cline-no">&nbsp;</span>
1393
- <span class="cline-any cline-no">&nbsp;</span>
1394
- <span class="cline-any cline-no">&nbsp;</span>
1634
+ <span class="cline-any cline-yes">4x</span>
1395
1635
  <span class="cline-any cline-no">&nbsp;</span>
1396
1636
  <span class="cline-any cline-no">&nbsp;</span>
1397
1637
  <span class="cline-any cline-neutral">&nbsp;</span>
1398
- <span class="cline-any cline-no">&nbsp;</span>
1399
- <span class="cline-any cline-no">&nbsp;</span>
1400
- <span class="cline-any cline-no">&nbsp;</span>
1401
- <span class="cline-any cline-no">&nbsp;</span>
1402
- <span class="cline-any cline-no">&nbsp;</span>
1403
- <span class="cline-any cline-no">&nbsp;</span>
1404
- <span class="cline-any cline-no">&nbsp;</span>
1405
- <span class="cline-any cline-no">&nbsp;</span>
1638
+ <span class="cline-any cline-yes">3x</span>
1639
+ <span class="cline-any cline-yes">3x</span>
1640
+ <span class="cline-any cline-yes">3x</span>
1641
+ <span class="cline-any cline-yes">3x</span>
1642
+ <span class="cline-any cline-yes">3x</span>
1643
+ <span class="cline-any cline-yes">3x</span>
1644
+ <span class="cline-any cline-yes">3x</span>
1645
+ <span class="cline-any cline-yes">3x</span>
1646
+ <span class="cline-any cline-yes">3x</span>
1647
+ <span class="cline-any cline-yes">3x</span>
1648
+ <span class="cline-any cline-yes">4x</span>
1649
+ <span class="cline-any cline-yes">4x</span>
1650
+ <span class="cline-any cline-yes">3x</span>
1651
+ <span class="cline-any cline-yes">3x</span>
1652
+ <span class="cline-any cline-yes">4x</span>
1653
+ <span class="cline-any cline-yes">4x</span>
1406
1654
  <span class="cline-any cline-neutral">&nbsp;</span>
1655
+ <span class="cline-any cline-yes">4x</span>
1656
+ <span class="cline-any cline-yes">4x</span>
1657
+ <span class="cline-any cline-neutral">&nbsp;</span>
1658
+ <span class="cline-any cline-yes">14x</span>
1659
+ <span class="cline-any cline-yes">2x</span>
1660
+ <span class="cline-any cline-yes">2x</span>
1661
+ <span class="cline-any cline-yes">2x</span>
1662
+ <span class="cline-any cline-yes">2x</span>
1663
+ <span class="cline-any cline-neutral">&nbsp;</span>
1664
+ <span class="cline-any cline-yes">2x</span>
1407
1665
  <span class="cline-any cline-no">&nbsp;</span>
1408
1666
  <span class="cline-any cline-no">&nbsp;</span>
1409
1667
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1411,8 +1669,8 @@
1411
1669
  <span class="cline-any cline-no">&nbsp;</span>
1412
1670
  <span class="cline-any cline-no">&nbsp;</span>
1413
1671
  <span class="cline-any cline-no">&nbsp;</span>
1414
- <span class="cline-any cline-no">&nbsp;</span>
1415
- <span class="cline-any cline-no">&nbsp;</span>
1672
+ <span class="cline-any cline-neutral">&nbsp;</span>
1673
+ <span class="cline-any cline-yes">2x</span>
1416
1674
  <span class="cline-any cline-no">&nbsp;</span>
1417
1675
  <span class="cline-any cline-no">&nbsp;</span>
1418
1676
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1421,61 +1679,107 @@
1421
1679
  <span class="cline-any cline-no">&nbsp;</span>
1422
1680
  <span class="cline-any cline-no">&nbsp;</span>
1423
1681
  <span class="cline-any cline-neutral">&nbsp;</span>
1682
+ <span class="cline-any cline-yes">2x</span>
1683
+ <span class="cline-any cline-yes">2x</span>
1684
+ <span class="cline-any cline-yes">2x</span>
1685
+ <span class="cline-any cline-yes">2x</span>
1686
+ <span class="cline-any cline-yes">2x</span>
1687
+ <span class="cline-any cline-yes">2x</span>
1688
+ <span class="cline-any cline-yes">2x</span>
1689
+ <span class="cline-any cline-yes">2x</span>
1690
+ <span class="cline-any cline-yes">2x</span>
1691
+ <span class="cline-any cline-yes">2x</span>
1692
+ <span class="cline-any cline-yes">2x</span>
1693
+ <span class="cline-any cline-yes">2x</span>
1694
+ <span class="cline-any cline-yes">2x</span>
1695
+ <span class="cline-any cline-yes">2x</span>
1696
+ <span class="cline-any cline-yes">2x</span>
1697
+ <span class="cline-any cline-yes">2x</span>
1698
+ <span class="cline-any cline-neutral">&nbsp;</span>
1699
+ <span class="cline-any cline-yes">2x</span>
1700
+ <span class="cline-any cline-yes">2x</span>
1701
+ <span class="cline-any cline-yes">2x</span>
1702
+ <span class="cline-any cline-yes">2x</span>
1703
+ <span class="cline-any cline-yes">2x</span>
1704
+ <span class="cline-any cline-yes">1x</span>
1705
+ <span class="cline-any cline-yes">1x</span>
1706
+ <span class="cline-any cline-yes">2x</span>
1707
+ <span class="cline-any cline-yes">2x</span>
1708
+ <span class="cline-any cline-yes">2x</span>
1709
+ <span class="cline-any cline-neutral">&nbsp;</span>
1710
+ <span class="cline-any cline-yes">14x</span>
1711
+ <span class="cline-any cline-yes">3x</span>
1712
+ <span class="cline-any cline-yes">3x</span>
1713
+ <span class="cline-any cline-neutral">&nbsp;</span>
1714
+ <span class="cline-any cline-yes">14x</span>
1715
+ <span class="cline-any cline-yes">1x</span>
1716
+ <span class="cline-any cline-yes">1x</span>
1717
+ <span class="cline-any cline-yes">1x</span>
1718
+ <span class="cline-any cline-yes">1x</span>
1719
+ <span class="cline-any cline-yes">1x</span>
1720
+ <span class="cline-any cline-yes">1x</span>
1721
+ <span class="cline-any cline-yes">1x</span>
1722
+ <span class="cline-any cline-yes">1x</span>
1723
+ <span class="cline-any cline-yes">1x</span>
1724
+ <span class="cline-any cline-yes">1x</span>
1725
+ <span class="cline-any cline-neutral">&nbsp;</span>
1424
1726
  <span class="cline-any cline-no">&nbsp;</span>
1425
1727
  <span class="cline-any cline-no">&nbsp;</span>
1728
+ <span class="cline-any cline-yes">1x</span>
1426
1729
  <span class="cline-any cline-no">&nbsp;</span>
1427
- <span class="cline-any cline-neutral">&nbsp;</span>
1428
- <span class="cline-any cline-yes">6x</span>
1429
1730
  <span class="cline-any cline-no">&nbsp;</span>
1430
1731
  <span class="cline-any cline-no">&nbsp;</span>
1431
- <span class="cline-any cline-neutral">&nbsp;</span>
1432
- <span class="cline-any cline-yes">6x</span>
1433
1732
  <span class="cline-any cline-no">&nbsp;</span>
1434
1733
  <span class="cline-any cline-no">&nbsp;</span>
1435
1734
  <span class="cline-any cline-no">&nbsp;</span>
1735
+ <span class="cline-any cline-yes">1x</span>
1436
1736
  <span class="cline-any cline-neutral">&nbsp;</span>
1437
- <span class="cline-any cline-yes">6x</span>
1438
- <span class="cline-any cline-yes">6x</span>
1439
- <span class="cline-any cline-yes">5x</span>
1440
- <span class="cline-any cline-yes">6x</span>
1441
- <span class="cline-any cline-no">&nbsp;</span>
1442
- <span class="cline-any cline-yes">6x</span>
1737
+ <span class="cline-any cline-yes">14x</span>
1738
+ <span class="cline-any cline-yes">14x</span>
1739
+ <span class="cline-any cline-yes">11x</span>
1740
+ <span class="cline-any cline-yes">14x</span>
1443
1741
  <span class="cline-any cline-no">&nbsp;</span>
1444
- <span class="cline-any cline-yes">6x</span>
1445
- <span class="cline-any cline-yes">5x</span>
1446
- <span class="cline-any cline-yes">6x</span>
1742
+ <span class="cline-any cline-yes">14x</span>
1447
1743
  <span class="cline-any cline-no">&nbsp;</span>
1448
- <span class="cline-any cline-yes">6x</span>
1449
- <span class="cline-any cline-yes">15x</span>
1450
- <span class="cline-any cline-yes">6x</span>
1744
+ <span class="cline-any cline-yes">14x</span>
1745
+ <span class="cline-any cline-yes">11x</span>
1746
+ <span class="cline-any cline-yes">14x</span>
1747
+ <span class="cline-any cline-yes">311x</span>
1748
+ <span class="cline-any cline-yes">14x</span>
1749
+ <span class="cline-any cline-yes">36x</span>
1750
+ <span class="cline-any cline-yes">14x</span>
1451
1751
  <span class="cline-any cline-yes">2x</span>
1452
1752
  <span class="cline-any cline-yes">2x</span>
1453
1753
  <span class="cline-any cline-neutral">&nbsp;</span>
1454
1754
  <span class="cline-any cline-yes">2x</span>
1455
- <span class="cline-any cline-yes">6x</span>
1755
+ <span class="cline-any cline-yes">14x</span>
1756
+ <span class="cline-any cline-yes">11x</span>
1757
+ <span class="cline-any cline-yes">14x</span>
1456
1758
  <span class="cline-any cline-yes">11x</span>
1457
- <span class="cline-any cline-yes">6x</span>
1759
+ <span class="cline-any cline-yes">14x</span>
1760
+ <span class="cline-any cline-yes">4x</span>
1761
+ <span class="cline-any cline-yes">14x</span>
1458
1762
  <span class="cline-any cline-neutral">&nbsp;</span>
1459
1763
  <span class="cline-any cline-yes">1x</span>
1460
- <span class="cline-any cline-yes">18x</span>
1461
- <span class="cline-any cline-yes">18x</span>
1462
- <span class="cline-any cline-yes">18x</span>
1463
- <span class="cline-any cline-yes">7x</span>
1464
- <span class="cline-any cline-yes">7x</span>
1764
+ <span class="cline-any cline-yes">46x</span>
1765
+ <span class="cline-any cline-yes">46x</span>
1766
+ <span class="cline-any cline-yes">46x</span>
1767
+ <span class="cline-any cline-yes">13x</span>
1768
+ <span class="cline-any cline-yes">13x</span>
1465
1769
  <span class="cline-any cline-neutral">&nbsp;</span>
1466
- <span class="cline-any cline-yes">11x</span>
1467
- <span class="cline-any cline-yes">18x</span>
1770
+ <span class="cline-any cline-yes">33x</span>
1771
+ <span class="cline-any cline-yes">46x</span>
1468
1772
  <span class="cline-any cline-no">&nbsp;</span>
1469
1773
  <span class="cline-any cline-no">&nbsp;</span>
1470
- <span class="cline-any cline-yes">18x</span>
1774
+ <span class="cline-any cline-yes">46x</span>
1471
1775
  <span class="cline-any cline-neutral">&nbsp;</span>
1472
1776
  <span class="cline-any cline-yes">1x</span>
1473
- <span class="cline-any cline-yes">21x</span>
1474
- <span class="cline-any cline-yes">21x</span>
1475
- <span class="cline-any cline-yes">21x</span>
1777
+ <span class="cline-any cline-yes">347x</span>
1778
+ <span class="cline-any cline-yes">347x</span>
1779
+ <span class="cline-any cline-yes">347x</span>
1476
1780
  <span class="cline-any cline-no">&nbsp;</span>
1477
1781
  <span class="cline-any cline-no">&nbsp;</span>
1478
- <span class="cline-any cline-yes">21x</span>
1782
+ <span class="cline-any cline-yes">347x</span>
1479
1783
  <span class="cline-any cline-yes">1x</span>
1480
1784
  <span class="cline-any cline-neutral">&nbsp;</span>
1481
1785
  <span class="cline-any cline-yes">1x</span>
@@ -1661,7 +1965,6 @@ interface JsonCache {
1661
1965
  }
1662
1966
  &nbsp;
1663
1967
  const defaultNamespace = 'wlfi:relay';
1664
- const activeApprovalUpdateScanLimit = 250;
1665
1968
  const approvalCapabilityFailureWindowMs = 5 * 60 * 1000;
1666
1969
  const approvalCapabilityMaxFailures = 5;
1667
1970
  const approvalCapabilityBlockWindowMs = 10 * 60 * 1000;
@@ -1681,19 +1984,30 @@ const matchesOptionalFilter = <span class="fstat-no" title="function not covered
1681
1984
  <span class="cstat-no" title="statement not covered" > return value?.toLowerCase() === expected.toLowerCase();</span>
1682
1985
  <span class="cstat-no" title="statement not covered" >};</span>
1683
1986
  &nbsp;
1684
- const clampLimit = <span class="fstat-no" title="function not covered" >(limit: number | undefined, fallback: number, max: number): number =&gt; {</span>
1685
- <span class="cstat-no" title="statement not covered" > if (!limit || Number.isNaN(limit)) {</span>
1987
+ const clampLimit = (limit: number | undefined, fallback: number, max: number): number =&gt; {
1988
+ if (!limit || Number.isNaN(limit)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1686
1989
  <span class="cstat-no" title="statement not covered" > return fallback;</span>
1687
1990
  <span class="cstat-no" title="statement not covered" > }</span>
1688
1991
  &nbsp;
1689
- <span class="cstat-no" title="statement not covered" > return Math.max(1, Math.min(limit, max));</span>
1690
- <span class="cstat-no" title="statement not covered" >};</span>
1992
+ return Math.max(1, Math.min(limit, max));
1993
+ };
1691
1994
  &nbsp;
1692
1995
  const createApprovalCapabilityToken = (): string =&gt; randomBytes(32).toString('hex');
1693
1996
  &nbsp;
1694
1997
  const approvalCapabilityHash = (token: string): string =&gt;
1695
1998
  createHash('sha256').update(token, 'utf8').digest('hex');
1696
1999
  &nbsp;
2000
+ const isActiveApprovalUpdateRecord = (
2001
+ record: RelayEncryptedUpdateRecord | null | undefined,
2002
+ approvalRequestId: string,
2003
+ ): record is RelayEncryptedUpdateRecord =&gt;
2004
+ Boolean(
2005
+ record &amp;&amp;
2006
+ record.type === 'manual_approval_decision' &amp;&amp;
2007
+ record.targetApprovalRequestId === approvalRequestId &amp;&amp;
2008
+ (record.status === <span class="branch-0 cbranch-no" title="branch not covered" >'pending' || record.status === 'inflight'),</span>
2009
+ );
2010
+ &nbsp;
1697
2011
  const preserveRotatedApprovalCapability = (
1698
2012
  incoming: RelayApprovalRequestRecord,
1699
2013
  existing: RelayApprovalRequestRecord | null,
@@ -1854,57 +2168,141 @@ export class RelayCacheService {
1854
2168
  <span class="cstat-no" title="statement not covered" > .slice(0, limit);</span>
1855
2169
  <span class="cstat-no" title="statement not covered" > }</span>
1856
2170
  &nbsp;
1857
- <span class="fstat-no" title="function not covered" > async createEncryptedUpdate(</span>
1858
- <span class="cstat-no" title="statement not covered" > input: CreateEncryptedUpdateInput,</span>
1859
- <span class="cstat-no" title="statement not covered" > ): Promise&lt;RelayEncryptedUpdateRecord&gt; {</span>
1860
- <span class="cstat-no" title="statement not covered" > const updateId = input.updateId ?? randomUUID();</span>
1861
- <span class="cstat-no" title="statement not covered" > const now = toIsoTimestamp();</span>
1862
- <span class="cstat-no" title="statement not covered" > const record: RelayEncryptedUpdateRecord = {</span>
1863
- <span class="cstat-no" title="statement not covered" > createdAt: now,</span>
1864
- <span class="cstat-no" title="statement not covered" > daemonId: input.daemonId,</span>
1865
- <span class="cstat-no" title="statement not covered" > metadata: input.metadata,</span>
1866
- <span class="cstat-no" title="statement not covered" > payload: input.payload,</span>
1867
- <span class="cstat-no" title="statement not covered" > status: 'pending',</span>
1868
- <span class="cstat-no" title="statement not covered" > targetApprovalRequestId: input.targetApprovalRequestId,</span>
1869
- <span class="cstat-no" title="statement not covered" > type: input.type,</span>
1870
- <span class="cstat-no" title="statement not covered" > updateId,</span>
1871
- <span class="cstat-no" title="statement not covered" > updatedAt: now,</span>
1872
- <span class="cstat-no" title="statement not covered" > };</span>
1873
- &nbsp;
1874
- <span class="cstat-no" title="statement not covered" > await this.writeJson(this.updateKey(updateId), record);</span>
1875
- <span class="cstat-no" title="statement not covered" > await this.client.zadd(this.daemonUpdatesKey(input.daemonId), Date.now(), updateId);</span>
1876
- &nbsp;
1877
- <span class="cstat-no" title="statement not covered" > return record;</span>
1878
- <span class="cstat-no" title="statement not covered" > }</span>
1879
- &nbsp;
1880
- <span class="fstat-no" title="function not covered" > async hasActiveApprovalUpdate(daemonId: string, approvalRequestId: string): Promise&lt;boolean&gt; {</span>
1881
- <span class="cstat-no" title="statement not covered" > const updateIds = await this.client.zrange(</span>
1882
- <span class="cstat-no" title="statement not covered" > this.daemonUpdatesKey(daemonId),</span>
1883
- <span class="cstat-no" title="statement not covered" > 0,</span>
1884
- <span class="cstat-no" title="statement not covered" > activeApprovalUpdateScanLimit,</span>
1885
- <span class="cstat-no" title="statement not covered" > 'REV',</span>
1886
- <span class="cstat-no" title="statement not covered" > );</span>
2171
+ async createEncryptedUpdate(
2172
+ input: CreateEncryptedUpdateInput,
2173
+ ): Promise&lt;RelayEncryptedUpdateRecord&gt; {
2174
+ if (input.type === 'manual_approval_decision') {
2175
+ if (!input.targetApprovalRequestId) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2176
+ <span class="cstat-no" title="statement not covered" > throw new CacheError({</span>
2177
+ <span class="cstat-no" title="statement not covered" > code: cacheErrorCodes.invalidPayload,</span>
2178
+ <span class="cstat-no" title="statement not covered" > message: 'Manual approval updates require a target approval request id',</span>
2179
+ <span class="cstat-no" title="statement not covered" > operation: 'createEncryptedUpdate',</span>
2180
+ <span class="cstat-no" title="statement not covered" > });</span>
2181
+ <span class="cstat-no" title="statement not covered" > }</span>
1887
2182
  &nbsp;
1888
- <span class="cstat-no" title="statement not covered" > for (const updateId of updateIds) {</span>
1889
- <span class="cstat-no" title="statement not covered" > const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));</span>
1890
- <span class="cstat-no" title="statement not covered" > if (!record) {</span>
1891
- <span class="cstat-no" title="statement not covered" > continue;</span>
2183
+ const approvalKey = this.approvalKey(input.targetApprovalRequestId);
2184
+ const approval = await this.readJson&lt;RelayApprovalRequestRecord&gt;(approvalKey);
2185
+ if (!approval) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2186
+ <span class="cstat-no" title="statement not covered" > throw new CacheError({</span>
2187
+ <span class="cstat-no" title="statement not covered" > code: cacheErrorCodes.notFound,</span>
2188
+ <span class="cstat-no" title="statement not covered" > key: approvalKey,</span>
2189
+ <span class="cstat-no" title="statement not covered" > message: `Unknown approval '${input.targetApprovalRequestId}'`,</span>
2190
+ <span class="cstat-no" title="statement not covered" > operation: 'createEncryptedUpdate',</span>
2191
+ <span class="cstat-no" title="statement not covered" > });</span>
1892
2192
  <span class="cstat-no" title="statement not covered" > }</span>
1893
2193
  &nbsp;
1894
- <span class="cstat-no" title="statement not covered" > if (</span>
1895
- <span class="cstat-no" title="statement not covered" > record.type !== 'manual_approval_decision' ||</span>
1896
- <span class="cstat-no" title="statement not covered" > record.targetApprovalRequestId !== approvalRequestId</span>
1897
- <span class="cstat-no" title="statement not covered" > ) {</span>
1898
- <span class="cstat-no" title="statement not covered" > continue;</span>
2194
+ if (approval.daemonId !== input.daemonId) {
2195
+ throw new CacheError({
2196
+ code: cacheErrorCodes.invalidPayload,
2197
+ key: approvalKey,
2198
+ message: `Approval '${input.targetApprovalRequestId}' belongs to daemon '${approval.daemonId}', not '${input.daemonId}'`,
2199
+ operation: 'createEncryptedUpdate',
2200
+ });
2201
+ }
2202
+ &nbsp;
2203
+ if (approval.status !== 'pending') {
2204
+ throw new CacheError({
2205
+ code: cacheErrorCodes.invalidPayload,
2206
+ key: approvalKey,
2207
+ message: `Approval '${input.targetApprovalRequestId}' is '${approval.status}' and cannot accept new updates`,
2208
+ operation: 'createEncryptedUpdate',
2209
+ });
2210
+ }
2211
+ }
2212
+ &nbsp;
2213
+ const updateId = input.updateId ?? randomUUID();
2214
+ const now = toIsoTimestamp();
2215
+ const record: RelayEncryptedUpdateRecord = {
2216
+ createdAt: now,
2217
+ daemonId: input.daemonId,
2218
+ metadata: input.metadata,
2219
+ payload: input.payload,
2220
+ status: 'pending',
2221
+ targetApprovalRequestId: input.targetApprovalRequestId,
2222
+ type: input.type,
2223
+ updateId,
2224
+ updatedAt: now,
2225
+ };
2226
+ &nbsp;
2227
+ const activeApprovalKey =
2228
+ input.type === 'manual_approval_decision' &amp;&amp; input.targetApprovalRequestId
2229
+ ? this.activeApprovalUpdateKey(input.targetApprovalRequestId)
2230
+ : null;
2231
+ const updateKey = this.updateKey(updateId);
2232
+ let ownsActiveApprovalSlot = false;
2233
+ &nbsp;
2234
+ await this.writeJson(updateKey, record);
2235
+ &nbsp;
2236
+ try {
2237
+ if (activeApprovalKey) {
2238
+ const reserved = await this.client.set(activeApprovalKey, updateId, 'NX');
2239
+ if (reserved !== 'OK') {
2240
+ const existingUpdateId = await this.client.get(activeApprovalKey);
2241
+ const existingRecord = existingUpdateId
2242
+ ? await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(existingUpdateId)<span class="branch-0 cbranch-no" title="branch not covered" >)</span>
2243
+ <span class="cstat-no" title="statement not covered" > : null;</span>
2244
+ &nbsp;
2245
+ if (isActiveApprovalUpdateRecord(existingRecord, input.targetApprovalRequestId!)) {
2246
+ throw new CacheError({
2247
+ code: cacheErrorCodes.invalidPayload,
2248
+ key: activeApprovalKey,
2249
+ message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,
2250
+ operation: 'createEncryptedUpdate',
2251
+ });
2252
+ <span class="branch-0 cbranch-no" title="branch not covered" > }</span>
2253
+ &nbsp;
2254
+ <span class="cstat-no" title="statement not covered" > await this.client.del(activeApprovalKey);</span>
2255
+ <span class="cstat-no" title="statement not covered" > const retriedReservation = await this.client.set(activeApprovalKey, updateId, 'NX');</span>
2256
+ <span class="cstat-no" title="statement not covered" > if (retriedReservation !== 'OK') {</span>
2257
+ <span class="cstat-no" title="statement not covered" > throw new CacheError({</span>
2258
+ <span class="cstat-no" title="statement not covered" > code: cacheErrorCodes.invalidPayload,</span>
2259
+ <span class="cstat-no" title="statement not covered" > key: activeApprovalKey,</span>
2260
+ <span class="cstat-no" title="statement not covered" > message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,</span>
2261
+ <span class="cstat-no" title="statement not covered" > operation: 'createEncryptedUpdate',</span>
2262
+ <span class="cstat-no" title="statement not covered" > });</span>
2263
+ <span class="cstat-no" title="statement not covered" > }</span>
2264
+ }
2265
+ &nbsp;
2266
+ ownsActiveApprovalSlot = true;
2267
+ }
2268
+ &nbsp;
2269
+ await this.client.zadd(this.daemonUpdatesKey(input.daemonId), Date.now(), updateId);
2270
+ } catch (error) {
2271
+ await this.client.del(updateKey);
2272
+ if (activeApprovalKey &amp;&amp; ownsActiveApprovalSlot) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2273
+ <span class="cstat-no" title="statement not covered" > await this.client.del(activeApprovalKey);</span>
1899
2274
  <span class="cstat-no" title="statement not covered" > }</span>
2275
+ throw error;
2276
+ }
2277
+ &nbsp;
2278
+ return record;
2279
+ }
2280
+ &nbsp;
2281
+ async hasActiveApprovalUpdate(daemonId: string, approvalRequestId: string): Promise&lt;boolean&gt; {
2282
+ const indexedUpdateId = await this.client.get(this.activeApprovalUpdateKey(approvalRequestId));
2283
+ if (indexedUpdateId) {
2284
+ const indexedRecord = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(
2285
+ this.updateKey(indexedUpdateId),
2286
+ );
2287
+ if (isActiveApprovalUpdateRecord(indexedRecord, approvalRequestId)) {
2288
+ return true;
2289
+ <span class="branch-0 cbranch-no" title="branch not covered" > }</span>
1900
2290
  &nbsp;
1901
- <span class="cstat-no" title="statement not covered" > if (record.status === 'pending' || record.status === 'inflight') {</span>
2291
+ <span class="cstat-no" title="statement not covered" > await this.client.del(this.activeApprovalUpdateKey(approvalRequestId));</span>
2292
+ <span class="cstat-no" title="statement not covered" > }</span>
2293
+ &nbsp;
2294
+ <span class="cstat-no" title="statement not covered" > const updateIds = await this.client.zrange(this.daemonUpdatesKey(daemonId), 0, -1, 'REV');</span>
2295
+ &nbsp;
2296
+ <span class="cstat-no" title="statement not covered" > for (const updateId of updateIds) {</span>
2297
+ <span class="cstat-no" title="statement not covered" > const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));</span>
2298
+ <span class="cstat-no" title="statement not covered" > if (isActiveApprovalUpdateRecord(record, approvalRequestId)) {</span>
2299
+ <span class="cstat-no" title="statement not covered" > await this.client.set(this.activeApprovalUpdateKey(approvalRequestId), updateId, 'NX');</span>
1902
2300
  <span class="cstat-no" title="statement not covered" > return true;</span>
1903
2301
  <span class="cstat-no" title="statement not covered" > }</span>
1904
2302
  <span class="cstat-no" title="statement not covered" > }</span>
1905
2303
  &nbsp;
1906
2304
  <span class="cstat-no" title="statement not covered" > return false;</span>
1907
- <span class="cstat-no" title="statement not covered" > }</span>
2305
+ }
1908
2306
  &nbsp;
1909
2307
  async consumeApprovalCapability(
1910
2308
  approvalRequestId: string,
@@ -2032,70 +2430,99 @@ export class RelayCacheService {
2032
2430
  return nextRecord;
2033
2431
  }
2034
2432
  &nbsp;
2035
- <span class="fstat-no" title="function not covered" > async claimEncryptedUpdates(</span>
2036
- <span class="cstat-no" title="statement not covered" > input: ClaimEncryptedUpdatesInput,</span>
2037
- <span class="cstat-no" title="statement not covered" > ): Promise&lt;RelayEncryptedUpdateRecord[]&gt; {</span>
2038
- <span class="cstat-no" title="statement not covered" > const limit = clampLimit(input.limit, 25, 100);</span>
2039
- <span class="cstat-no" title="statement not covered" > const leaseSeconds = clampLimit(input.leaseSeconds, 30, 300);</span>
2040
- <span class="cstat-no" title="statement not covered" > const now = new Date();</span>
2041
- <span class="cstat-no" title="statement not covered" > const nowMs = now.getTime();</span>
2042
- <span class="cstat-no" title="statement not covered" > const claimUntil = new Date(nowMs + leaseSeconds * 1000).toISOString();</span>
2043
- <span class="cstat-no" title="statement not covered" > const updateIds = await this.client.zrange(</span>
2044
- <span class="cstat-no" title="statement not covered" > this.daemonUpdatesKey(input.daemonId),</span>
2045
- <span class="cstat-no" title="statement not covered" > 0,</span>
2046
- <span class="cstat-no" title="statement not covered" > limit * 4,</span>
2047
- <span class="cstat-no" title="statement not covered" > 'REV',</span>
2048
- <span class="cstat-no" title="statement not covered" > );</span>
2049
- <span class="cstat-no" title="statement not covered" > const claimed: RelayEncryptedUpdateRecord[] = [];</span>
2050
- &nbsp;
2051
- <span class="cstat-no" title="statement not covered" > for (const updateId of updateIds) {</span>
2052
- <span class="cstat-no" title="statement not covered" > if (claimed.length &gt;= limit) {</span>
2433
+ async claimEncryptedUpdates(
2434
+ input: ClaimEncryptedUpdatesInput,
2435
+ ): Promise&lt;RelayEncryptedUpdateRecord[]&gt; {
2436
+ const limit = clampLimit(input.limit, 25, 100);
2437
+ const leaseSeconds = clampLimit(input.leaseSeconds, 30, 300);
2438
+ const now = new Date();
2439
+ const nowMs = now.getTime();
2440
+ const claimUntil = new Date(nowMs + leaseSeconds * 1000).toISOString();
2441
+ const updateIds = await this.client.zrange(
2442
+ this.daemonUpdatesKey(input.daemonId),
2443
+ 0,
2444
+ limit * 4,
2445
+ 'REV',
2446
+ );
2447
+ const claimed: RelayEncryptedUpdateRecord[] = [];
2448
+ &nbsp;
2449
+ for (const updateId of updateIds) {
2450
+ if (claimed.length &gt;= limit) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2053
2451
  <span class="cstat-no" title="statement not covered" > break;</span>
2054
2452
  <span class="cstat-no" title="statement not covered" > }</span>
2055
2453
  &nbsp;
2056
- <span class="cstat-no" title="statement not covered" > const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));</span>
2057
- <span class="cstat-no" title="statement not covered" > if (!record) {</span>
2058
- <span class="cstat-no" title="statement not covered" > continue;</span>
2059
- <span class="cstat-no" title="statement not covered" > }</span>
2060
- &nbsp;
2061
- <span class="cstat-no" title="statement not covered" > if (</span>
2062
- <span class="cstat-no" title="statement not covered" > record.status === 'applied' ||</span>
2063
- <span class="cstat-no" title="statement not covered" > record.status === 'failed' ||</span>
2064
- <span class="cstat-no" title="statement not covered" > record.status === 'rejected'</span>
2065
- <span class="cstat-no" title="statement not covered" > ) {</span>
2066
- <span class="cstat-no" title="statement not covered" > continue;</span>
2067
- <span class="cstat-no" title="statement not covered" > }</span>
2068
- &nbsp;
2069
- <span class="cstat-no" title="statement not covered" > if (</span>
2070
- <span class="cstat-no" title="statement not covered" > record.status === 'inflight' &amp;&amp;</span>
2071
- <span class="cstat-no" title="statement not covered" > record.claimUntil &amp;&amp;</span>
2072
- <span class="cstat-no" title="statement not covered" > Date.parse(record.claimUntil) &gt; nowMs</span>
2073
- <span class="cstat-no" title="statement not covered" > ) {</span>
2074
- <span class="cstat-no" title="statement not covered" > continue;</span>
2075
- <span class="cstat-no" title="statement not covered" > }</span>
2454
+ const claimLockKey = this.updateClaimLockKey(updateId);
2455
+ let ownsClaimLock = false;
2456
+ &nbsp;
2457
+ try {
2458
+ const reserved = await this.client.set(claimLockKey, claimUntil, 'NX');
2459
+ if (reserved !== 'OK') {
2460
+ const existingClaimLockUntil = await this.client.get(claimLockKey);
2461
+ if (
2462
+ existingClaimLockUntil &amp;&amp;
2463
+ Number.isFinite(Date.parse(existingClaimLockUntil)) &amp;&amp;
2464
+ Date.parse(existingClaimLockUntil) &gt; nowMs
2465
+ ) {
2466
+ continue;
2467
+ <span class="branch-0 cbranch-no" title="branch not covered" > }</span>
2468
+ &nbsp;
2469
+ <span class="cstat-no" title="statement not covered" > await this.client.del(claimLockKey);</span>
2470
+ <span class="cstat-no" title="statement not covered" > const retriedReservation = await this.client.set(claimLockKey, claimUntil, 'NX');</span>
2471
+ <span class="cstat-no" title="statement not covered" > if (retriedReservation !== 'OK') {</span>
2472
+ <span class="cstat-no" title="statement not covered" > continue;</span>
2473
+ <span class="cstat-no" title="statement not covered" > }</span>
2474
+ }
2076
2475
  &nbsp;
2077
- <span class="cstat-no" title="statement not covered" > const nextRecord: RelayEncryptedUpdateRecord = {</span>
2078
- <span class="cstat-no" title="statement not covered" > ...record,</span>
2079
- <span class="cstat-no" title="statement not covered" > claimToken: randomUUID(),</span>
2080
- <span class="cstat-no" title="statement not covered" > claimUntil,</span>
2081
- <span class="cstat-no" title="statement not covered" > lastDeliveredAt: now.toISOString(),</span>
2082
- <span class="cstat-no" title="statement not covered" > status: 'inflight',</span>
2083
- <span class="cstat-no" title="statement not covered" > updatedAt: now.toISOString(),</span>
2084
- <span class="cstat-no" title="statement not covered" > };</span>
2085
- <span class="cstat-no" title="statement not covered" > await this.writeJson(this.updateKey(updateId), nextRecord);</span>
2086
- <span class="cstat-no" title="statement not covered" > claimed.push(nextRecord);</span>
2087
- <span class="cstat-no" title="statement not covered" > }</span>
2476
+ ownsClaimLock = true;
2477
+ &nbsp;
2478
+ const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));
2479
+ if (!record) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2480
+ <span class="cstat-no" title="statement not covered" > continue;</span>
2481
+ <span class="cstat-no" title="statement not covered" > }</span>
2482
+ &nbsp;
2483
+ if (
2484
+ record.status === 'applied' ||
2485
+ record.status === 'failed' ||
2486
+ record.status === 'rejected'
2487
+ ) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2488
+ <span class="cstat-no" title="statement not covered" > continue;</span>
2489
+ <span class="cstat-no" title="statement not covered" > }</span>
2490
+ &nbsp;
2491
+ if (
2492
+ record.status === <span class="branch-0 cbranch-no" title="branch not covered" >'inflight' &amp;&amp;</span>
2493
+ <span class="cstat-no" title="statement not covered" > record.<span class="branch-0 cbranch-no" title="branch not covered" >claimUntil &amp;&amp;</span></span>
2494
+ <span class="cstat-no" title="statement not covered" > Date.parse(record.claimUntil) &gt; nowMs</span>
2495
+ ) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2496
+ <span class="cstat-no" title="statement not covered" > continue;</span>
2497
+ <span class="cstat-no" title="statement not covered" > }</span>
2498
+ &nbsp;
2499
+ const nextRecord: RelayEncryptedUpdateRecord = {
2500
+ ...record,
2501
+ claimToken: randomUUID(),
2502
+ claimUntil,
2503
+ lastDeliveredAt: now.toISOString(),
2504
+ status: 'inflight',
2505
+ updatedAt: now.toISOString(),
2506
+ };
2507
+ await this.writeJson(this.updateKey(updateId), nextRecord);
2508
+ claimed.push(nextRecord);
2509
+ } finally {
2510
+ if (ownsClaimLock) {
2511
+ await this.client.del(claimLockKey);
2512
+ }
2513
+ }
2514
+ }
2088
2515
  &nbsp;
2089
- <span class="cstat-no" title="statement not covered" > return claimed;</span>
2090
- <span class="cstat-no" title="statement not covered" > }</span>
2516
+ return claimed;
2517
+ }
2091
2518
  &nbsp;
2092
- <span class="fstat-no" title="function not covered" > async submitUpdateFeedback(</span>
2093
- <span class="cstat-no" title="statement not covered" > input: SubmitUpdateFeedbackInput,</span>
2094
- <span class="cstat-no" title="statement not covered" > ): Promise&lt;RelayEncryptedUpdateRecord&gt; {</span>
2095
- <span class="cstat-no" title="statement not covered" > const key = this.updateKey(input.updateId);</span>
2096
- <span class="cstat-no" title="statement not covered" > const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(key);</span>
2519
+ async submitUpdateFeedback(
2520
+ input: SubmitUpdateFeedbackInput,
2521
+ ): Promise&lt;RelayEncryptedUpdateRecord&gt; {
2522
+ const key = this.updateKey(input.updateId);
2523
+ const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(key);
2097
2524
  &nbsp;
2098
- <span class="cstat-no" title="statement not covered" > if (!record || record.daemonId !== input.daemonId) {</span>
2525
+ if (!record || record.daemonId !== input.daemonId) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2099
2526
  <span class="cstat-no" title="statement not covered" > throw new CacheError({</span>
2100
2527
  <span class="cstat-no" title="statement not covered" > code: cacheErrorCodes.notFound,</span>
2101
2528
  <span class="cstat-no" title="statement not covered" > key,</span>
@@ -2104,7 +2531,7 @@ export class RelayCacheService {
2104
2531
  <span class="cstat-no" title="statement not covered" > });</span>
2105
2532
  <span class="cstat-no" title="statement not covered" > }</span>
2106
2533
  &nbsp;
2107
- <span class="cstat-no" title="statement not covered" > if (!record.claimToken || record.claimToken !== input.claimToken) {</span>
2534
+ if (!record.claimToken || record.claimToken !== input.claimToken) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2108
2535
  <span class="cstat-no" title="statement not covered" > throw new CacheError({</span>
2109
2536
  <span class="cstat-no" title="statement not covered" > code: cacheErrorCodes.invalidPayload,</span>
2110
2537
  <span class="cstat-no" title="statement not covered" > key,</span>
@@ -2113,35 +2540,60 @@ export class RelayCacheService {
2113
2540
  <span class="cstat-no" title="statement not covered" > });</span>
2114
2541
  <span class="cstat-no" title="statement not covered" > }</span>
2115
2542
  &nbsp;
2116
- <span class="cstat-no" title="statement not covered" > const feedback: RelayUpdateFeedbackRecord = {</span>
2117
- <span class="cstat-no" title="statement not covered" > daemonId: input.daemonId,</span>
2118
- <span class="cstat-no" title="statement not covered" > details: input.details,</span>
2119
- <span class="cstat-no" title="statement not covered" > feedbackAt: toIsoTimestamp(),</span>
2120
- <span class="cstat-no" title="statement not covered" > message: input.message,</span>
2121
- <span class="cstat-no" title="statement not covered" > status: input.status,</span>
2122
- <span class="cstat-no" title="statement not covered" > updateId: input.updateId,</span>
2123
- <span class="cstat-no" title="statement not covered" > };</span>
2124
- <span class="cstat-no" title="statement not covered" > const nextRecord: RelayEncryptedUpdateRecord = {</span>
2125
- <span class="cstat-no" title="statement not covered" > ...record,</span>
2126
- <span class="cstat-no" title="statement not covered" > claimToken: undefined,</span>
2127
- <span class="cstat-no" title="statement not covered" > claimUntil: undefined,</span>
2128
- <span class="cstat-no" title="statement not covered" > feedback,</span>
2129
- <span class="cstat-no" title="statement not covered" > status: input.status,</span>
2130
- <span class="cstat-no" title="statement not covered" > updatedAt: toIsoTimestamp(),</span>
2131
- <span class="cstat-no" title="statement not covered" > };</span>
2132
- &nbsp;
2133
- <span class="cstat-no" title="statement not covered" > await this.writeJson(key, nextRecord);</span>
2134
- <span class="cstat-no" title="statement not covered" > return nextRecord;</span>
2135
- <span class="cstat-no" title="statement not covered" > }</span>
2543
+ const feedback: RelayUpdateFeedbackRecord = {
2544
+ daemonId: input.daemonId,
2545
+ details: input.details,
2546
+ feedbackAt: toIsoTimestamp(),
2547
+ message: input.message,
2548
+ status: input.status,
2549
+ updateId: input.updateId,
2550
+ };
2551
+ const nextRecord: RelayEncryptedUpdateRecord = {
2552
+ ...record,
2553
+ claimToken: undefined,
2554
+ claimUntil: undefined,
2555
+ feedback,
2556
+ status: input.status,
2557
+ updatedAt: toIsoTimestamp(),
2558
+ };
2136
2559
  &nbsp;
2137
- <span class="fstat-no" title="function not covered" > async getEncryptedUpdate(updateId: string): Promise&lt;RelayEncryptedUpdateRecord | null&gt; {</span>
2138
- <span class="cstat-no" title="statement not covered" > return await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));</span>
2139
- <span class="cstat-no" title="statement not covered" > }</span>
2560
+ await this.writeJson(key, nextRecord);
2561
+ if (record.targetApprovalRequestId &amp;&amp; record.type === 'manual_approval_decision') {
2562
+ const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);
2563
+ const indexedUpdateId = await this.client.get(activeApprovalKey);
2564
+ if (indexedUpdateId === input.updateId) {
2565
+ await this.client.del(activeApprovalKey);
2566
+ }
2567
+ }
2568
+ return nextRecord;
2569
+ }
2570
+ &nbsp;
2571
+ async getEncryptedUpdate(updateId: string): Promise&lt;RelayEncryptedUpdateRecord | null&gt; {
2572
+ return await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(this.updateKey(updateId));
2573
+ }
2574
+ &nbsp;
2575
+ async removeEncryptedUpdate(daemonId: string, updateId: string): Promise&lt;void&gt; {
2576
+ const key = this.updateKey(updateId);
2577
+ const record = await this.readJson&lt;RelayEncryptedUpdateRecord&gt;(key);
2578
+ if (!record || record.daemonId !== daemonId) {
2579
+ throw new CacheError({
2580
+ code: cacheErrorCodes.notFound,
2581
+ key,
2582
+ message: `Unknown update '${updateId}' for daemon '${daemonId}'`,
2583
+ operation: 'removeEncryptedUpdate',
2584
+ });
2585
+ <span class="branch-0 cbranch-no" title="branch not covered" > }</span>
2140
2586
  &nbsp;
2141
- <span class="fstat-no" title="function not covered" > async removeEncryptedUpdate(daemonId: string, updateId: string): Promise&lt;void&gt; {</span>
2142
2587
  <span class="cstat-no" title="statement not covered" > await this.client.zrem(this.daemonUpdatesKey(daemonId), updateId);</span>
2143
- <span class="cstat-no" title="statement not covered" > await this.client.del(this.updateKey(updateId));</span>
2144
- <span class="cstat-no" title="statement not covered" > }</span>
2588
+ <span class="cstat-no" title="statement not covered" > await this.client.del(key);</span>
2589
+ if (record.targetApprovalRequestId &amp;&amp; record.type === 'manual_approval_decision') <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
2590
+ <span class="cstat-no" title="statement not covered" > const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);</span>
2591
+ <span class="cstat-no" title="statement not covered" > const indexedUpdateId = await this.client.get(activeApprovalKey);</span>
2592
+ <span class="cstat-no" title="statement not covered" > if (indexedUpdateId === updateId) {</span>
2593
+ <span class="cstat-no" title="statement not covered" > await this.client.del(activeApprovalKey);</span>
2594
+ <span class="cstat-no" title="statement not covered" > }</span>
2595
+ <span class="cstat-no" title="statement not covered" > }</span>
2596
+ }
2145
2597
  &nbsp;
2146
2598
  private readonly daemonIndexKey = (): string =&gt; `${this.namespace}:daemons`;
2147
2599
  private readonly daemonProfileKey = (daemonId: string): string =&gt;
@@ -2152,8 +2604,8 @@ export class RelayCacheService {
2152
2604
  <span class="cstat-no" title="statement not covered" > `${this.namespace}:daemon:${daemonId}:agent-keys`;</span>
2153
2605
  private readonly daemonApprovalsKey = (daemonId: string): string =&gt;
2154
2606
  `${this.namespace}:daemon:${daemonId}:approvals`;
2155
- private readonly daemonUpdatesKey = <span class="fstat-no" title="function not covered" >(daemonId: string): string =&gt;</span>
2156
- <span class="cstat-no" title="statement not covered" > `${this.namespace}:daemon:${daemonId}:updates`;</span>
2607
+ private readonly daemonUpdatesKey = (daemonId: string): string =&gt;
2608
+ `${this.namespace}:daemon:${daemonId}:updates`;
2157
2609
  private readonly approvalKey = (approvalRequestId: string): string =&gt;
2158
2610
  `${this.namespace}:approval:${approvalRequestId}`;
2159
2611
  private readonly approvalCapabilityConsumedKey = (
@@ -2163,7 +2615,11 @@ export class RelayCacheService {
2163
2615
  `${this.namespace}:approval:${approvalRequestId}:capability:${capabilityHash}:consumed`;
2164
2616
  private readonly approvalCapabilityFailuresKey = (approvalRequestId: string): string =&gt;
2165
2617
  `${this.namespace}:approval:${approvalRequestId}:capability-failures`;
2166
- private readonly updateKey = <span class="fstat-no" title="function not covered" >(updateId: string): string =&gt; `${this.namespace}:update:${updateId}`;</span>
2618
+ private readonly activeApprovalUpdateKey = (approvalRequestId: string): string =&gt;
2619
+ `${this.namespace}:approval:${approvalRequestId}:active-update`;
2620
+ private readonly updateClaimLockKey = (updateId: string): string =&gt;
2621
+ `${this.namespace}:update:${updateId}:claim-lock`;
2622
+ private readonly updateKey = (updateId: string): string =&gt; `${this.namespace}:update:${updateId}`;
2167
2623
  &nbsp;
2168
2624
  private async readJson&lt;T&gt;(key: string): Promise&lt;T | null&gt; {
2169
2625
  try {
@@ -2197,7 +2653,7 @@ export const createRelayCacheService = <span class="fstat-no" title="function no
2197
2653
  <div class='footer quiet pad2 space-top1 center small'>
2198
2654
  Code coverage generated by
2199
2655
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2200
- at 2026-03-11T05:44:58.399Z
2656
+ at 2026-03-11T13:14:36.205Z
2201
2657
  </div>
2202
2658
  <script src="../../prettify.js"></script>
2203
2659
  <script>