diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..9e80924 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"a109bdc4-2fd1-49a3-803f-c3c3b4858c99","pid":3141099,"procStart":"375768368","acquiredAt":1784001797642} \ No newline at end of file diff --git a/.git.broken/objects/01/2ec3a338210b9c0f9821b4a92ac7408e7429f4 b/.git.broken/objects/01/2ec3a338210b9c0f9821b4a92ac7408e7429f4 new file mode 100644 index 0000000..538991c Binary files /dev/null and b/.git.broken/objects/01/2ec3a338210b9c0f9821b4a92ac7408e7429f4 differ diff --git a/.git.broken/objects/01/9a0efbbf3d65f5d14f4bedc700e3c0f23caf42 b/.git.broken/objects/01/9a0efbbf3d65f5d14f4bedc700e3c0f23caf42 new file mode 100644 index 0000000..7c7ba45 Binary files /dev/null and b/.git.broken/objects/01/9a0efbbf3d65f5d14f4bedc700e3c0f23caf42 differ diff --git a/.git.broken/objects/02/0c04a9e48af011ffc2b666b3847249b3dc2bb4 b/.git.broken/objects/02/0c04a9e48af011ffc2b666b3847249b3dc2bb4 new file mode 100644 index 0000000..7f283da Binary files /dev/null and b/.git.broken/objects/02/0c04a9e48af011ffc2b666b3847249b3dc2bb4 differ diff --git a/.git.broken/objects/09/10d5c707b4e6df57ee64e0c0b196d80d5897f6 b/.git.broken/objects/09/10d5c707b4e6df57ee64e0c0b196d80d5897f6 new file mode 100644 index 0000000..8d83818 Binary files /dev/null and b/.git.broken/objects/09/10d5c707b4e6df57ee64e0c0b196d80d5897f6 differ diff --git a/.git.broken/objects/0a/44e1115de1e814bebd347945fea58432105e62 b/.git.broken/objects/0a/44e1115de1e814bebd347945fea58432105e62 new file mode 100644 index 0000000..76753be Binary files /dev/null and b/.git.broken/objects/0a/44e1115de1e814bebd347945fea58432105e62 differ diff --git a/.git.broken/objects/0a/f4b7dfb342d37ef343df4187ec0122e69f2b30 b/.git.broken/objects/0a/f4b7dfb342d37ef343df4187ec0122e69f2b30 new file mode 100644 index 0000000..8b7a94d Binary files /dev/null and b/.git.broken/objects/0a/f4b7dfb342d37ef343df4187ec0122e69f2b30 differ diff --git a/.git.broken/objects/0c/00b8af95f28a732cdf6661876cdac1b0f1f15e b/.git.broken/objects/0c/00b8af95f28a732cdf6661876cdac1b0f1f15e new file mode 100644 index 0000000..fc8763b Binary files /dev/null and b/.git.broken/objects/0c/00b8af95f28a732cdf6661876cdac1b0f1f15e differ diff --git a/.git.broken/objects/0c/a43a069947b7a49bbda02126ddbcebc6912621 b/.git.broken/objects/0c/a43a069947b7a49bbda02126ddbcebc6912621 new file mode 100644 index 0000000..5721023 Binary files /dev/null and b/.git.broken/objects/0c/a43a069947b7a49bbda02126ddbcebc6912621 differ diff --git a/.git.broken/objects/0f/1de08a90e298592139c133e97ccc75e5ba65bc b/.git.broken/objects/0f/1de08a90e298592139c133e97ccc75e5ba65bc new file mode 100644 index 0000000..f16de43 Binary files /dev/null and b/.git.broken/objects/0f/1de08a90e298592139c133e97ccc75e5ba65bc differ diff --git a/.git.broken/objects/12/bf39149d873ae28282f752566ecea3548a0688 b/.git.broken/objects/12/bf39149d873ae28282f752566ecea3548a0688 new file mode 100644 index 0000000..ccb7f87 Binary files /dev/null and b/.git.broken/objects/12/bf39149d873ae28282f752566ecea3548a0688 differ diff --git a/.git.broken/objects/12/df3742e511be6b336d26f91bf1d57c9800d1f7 b/.git.broken/objects/12/df3742e511be6b336d26f91bf1d57c9800d1f7 new file mode 100644 index 0000000..467488d Binary files /dev/null and b/.git.broken/objects/12/df3742e511be6b336d26f91bf1d57c9800d1f7 differ diff --git a/.git.broken/objects/15/1b1cc0c37a3e859197d2f847313ed49a46b352 b/.git.broken/objects/15/1b1cc0c37a3e859197d2f847313ed49a46b352 new file mode 100644 index 0000000..271015a Binary files /dev/null and b/.git.broken/objects/15/1b1cc0c37a3e859197d2f847313ed49a46b352 differ diff --git a/.git.broken/objects/18/1469046ef7bf5c42a3fc5a064270de4cf7077e b/.git.broken/objects/18/1469046ef7bf5c42a3fc5a064270de4cf7077e new file mode 100644 index 0000000..a71b68a Binary files /dev/null and b/.git.broken/objects/18/1469046ef7bf5c42a3fc5a064270de4cf7077e differ diff --git a/.git.broken/objects/18/46d8eebf920176f59131888dafc64db0e7ba27 b/.git.broken/objects/18/46d8eebf920176f59131888dafc64db0e7ba27 new file mode 100644 index 0000000..09c5747 Binary files /dev/null and b/.git.broken/objects/18/46d8eebf920176f59131888dafc64db0e7ba27 differ diff --git a/.git.broken/objects/18/921f1995899bdc75a77f48413a0208acf0f3f3 b/.git.broken/objects/18/921f1995899bdc75a77f48413a0208acf0f3f3 new file mode 100644 index 0000000..e9ddf72 Binary files /dev/null and b/.git.broken/objects/18/921f1995899bdc75a77f48413a0208acf0f3f3 differ diff --git a/.git.broken/objects/22/f49677d5f03ee0324b6fbaf46980dc35958470 b/.git.broken/objects/22/f49677d5f03ee0324b6fbaf46980dc35958470 new file mode 100644 index 0000000..8c556f6 Binary files /dev/null and b/.git.broken/objects/22/f49677d5f03ee0324b6fbaf46980dc35958470 differ diff --git a/.git.broken/objects/26/ac0d652bd11899d733f4b793053ac20e117084 b/.git.broken/objects/26/ac0d652bd11899d733f4b793053ac20e117084 new file mode 100644 index 0000000..7d779cf Binary files /dev/null and b/.git.broken/objects/26/ac0d652bd11899d733f4b793053ac20e117084 differ diff --git a/.git.broken/objects/27/a40c6c9aa42d5fbdc745e5b68a8704770edab8 b/.git.broken/objects/27/a40c6c9aa42d5fbdc745e5b68a8704770edab8 new file mode 100644 index 0000000..ed2fa23 Binary files /dev/null and b/.git.broken/objects/27/a40c6c9aa42d5fbdc745e5b68a8704770edab8 differ diff --git a/.git.broken/objects/28/4489fc69b50a0b0dd4a7ae59b62b077c301b8b b/.git.broken/objects/28/4489fc69b50a0b0dd4a7ae59b62b077c301b8b new file mode 100644 index 0000000..574d618 --- /dev/null +++ b/.git.broken/objects/28/4489fc69b50a0b0dd4a7ae59b62b077c301b8b @@ -0,0 +1,2 @@ +x_O0Oq($)h R26 BV.V8 ivKK?=S.EMnt@'Yt0[MhvskVUR%_E^Ig MA0gU`Φۇ ĽU- +&oww:gӘB+r/Vt47iAZF 9oy.?VL+!$kڵN]%c:NXV5jZ,te[Vy>CcrΝt[EF}b ɽ>B!PA-@OUzv'G;5:ժ[E/ ( fz3Nj;r,7ʎV5ڴ>WpV <7SVxDg2>{r#2)yCx6Y)Lț^ߦ|] \ No newline at end of file diff --git a/.git.broken/objects/28/db42def2c512213ce5e36bd7082433d12af373 b/.git.broken/objects/28/db42def2c512213ce5e36bd7082433d12af373 new file mode 100644 index 0000000..3dbfff3 Binary files /dev/null and b/.git.broken/objects/28/db42def2c512213ce5e36bd7082433d12af373 differ diff --git a/.git.broken/objects/29/e643e79d218f7b86947cbabcd9d32822199dd2 b/.git.broken/objects/29/e643e79d218f7b86947cbabcd9d32822199dd2 new file mode 100644 index 0000000..f57d083 Binary files /dev/null and b/.git.broken/objects/29/e643e79d218f7b86947cbabcd9d32822199dd2 differ diff --git a/.git.broken/objects/2a/16d8cdb8720baab8fd35ce6fe19b8d35773d93 b/.git.broken/objects/2a/16d8cdb8720baab8fd35ce6fe19b8d35773d93 new file mode 100644 index 0000000..b77ef65 Binary files /dev/null and b/.git.broken/objects/2a/16d8cdb8720baab8fd35ce6fe19b8d35773d93 differ diff --git a/.git.broken/objects/2a/b9910be29e68dee9e8296dcc7d4bcd1d202f82 b/.git.broken/objects/2a/b9910be29e68dee9e8296dcc7d4bcd1d202f82 new file mode 100644 index 0000000..271cba6 Binary files /dev/null and b/.git.broken/objects/2a/b9910be29e68dee9e8296dcc7d4bcd1d202f82 differ diff --git a/.git.broken/objects/31/1a9ddfebdb0601e3065ff25ff5dce778888b71 b/.git.broken/objects/31/1a9ddfebdb0601e3065ff25ff5dce778888b71 new file mode 100644 index 0000000..de8f17b Binary files /dev/null and b/.git.broken/objects/31/1a9ddfebdb0601e3065ff25ff5dce778888b71 differ diff --git a/.git.broken/objects/31/2a44c65b5a7a5034b60105abedf25f8cc6a8e5 b/.git.broken/objects/31/2a44c65b5a7a5034b60105abedf25f8cc6a8e5 new file mode 100644 index 0000000..63e66ec Binary files /dev/null and b/.git.broken/objects/31/2a44c65b5a7a5034b60105abedf25f8cc6a8e5 differ diff --git a/.git.broken/objects/35/9c68ce0bb39351a943a1ee4c19c6539bf83752 b/.git.broken/objects/35/9c68ce0bb39351a943a1ee4c19c6539bf83752 new file mode 100644 index 0000000..d0f6ca4 Binary files /dev/null and b/.git.broken/objects/35/9c68ce0bb39351a943a1ee4c19c6539bf83752 differ diff --git a/.git.broken/objects/36/1caf262507f450f4124ba8ccaf275896c63a93 b/.git.broken/objects/36/1caf262507f450f4124ba8ccaf275896c63a93 new file mode 100644 index 0000000..de3a33b Binary files /dev/null and b/.git.broken/objects/36/1caf262507f450f4124ba8ccaf275896c63a93 differ diff --git a/.git.broken/objects/37/1c13ff593cb3f39e691760b20f4f64f026297c b/.git.broken/objects/37/1c13ff593cb3f39e691760b20f4f64f026297c new file mode 100644 index 0000000..0b5f4d2 --- /dev/null +++ b/.git.broken/objects/37/1c13ff593cb3f39e691760b20f4f64f026297c @@ -0,0 +1,3 @@ +xn@ EWE6kd9mQ7)Q "Ҍ!w M!)0tY}p$㉊8I Ȃ1 RomrɃOwDF0SJƔܲY hDXgך 'R>\3dNt;}J+|iISzFGl'3{'d(V1ᨧtXs-44K9>iVyuӽb~KyS qGh&;h[M?vՕCu +ʼh 䩾G~fHcyQ+oQf;\~sd` W ;[cp~y AZ=FDBKuB\ +Eco/s9\ \ No newline at end of file diff --git a/.git.broken/objects/37/57fa20eff714a07283b9a8f6380ef4df9d5cd2 b/.git.broken/objects/37/57fa20eff714a07283b9a8f6380ef4df9d5cd2 new file mode 100644 index 0000000..9e1d110 Binary files /dev/null and b/.git.broken/objects/37/57fa20eff714a07283b9a8f6380ef4df9d5cd2 differ diff --git a/.git.broken/objects/37/9c84759978f77c1a1f637b93d3d0a9ca1da49d b/.git.broken/objects/37/9c84759978f77c1a1f637b93d3d0a9ca1da49d new file mode 100644 index 0000000..98bde96 Binary files /dev/null and b/.git.broken/objects/37/9c84759978f77c1a1f637b93d3d0a9ca1da49d differ diff --git a/.git.broken/objects/37/b06a680a7c24597e1a85b22097cd170edfc4df b/.git.broken/objects/37/b06a680a7c24597e1a85b22097cd170edfc4df new file mode 100644 index 0000000..5be125d Binary files /dev/null and b/.git.broken/objects/37/b06a680a7c24597e1a85b22097cd170edfc4df differ diff --git a/.git.broken/objects/38/7699b7a3ef0ac022ac8eb0e0cc182d473bbe02 b/.git.broken/objects/38/7699b7a3ef0ac022ac8eb0e0cc182d473bbe02 new file mode 100644 index 0000000..ae9f9f7 Binary files /dev/null and b/.git.broken/objects/38/7699b7a3ef0ac022ac8eb0e0cc182d473bbe02 differ diff --git a/.git.broken/objects/3a/4940dd575103222ca14eac988c5ad3d881aeb6 b/.git.broken/objects/3a/4940dd575103222ca14eac988c5ad3d881aeb6 new file mode 100644 index 0000000..23de3e3 Binary files /dev/null and b/.git.broken/objects/3a/4940dd575103222ca14eac988c5ad3d881aeb6 differ diff --git a/.git.broken/objects/3b/e3a4b17843aed3a5898a93ac49139116566700 b/.git.broken/objects/3b/e3a4b17843aed3a5898a93ac49139116566700 new file mode 100644 index 0000000..6a23b58 Binary files /dev/null and b/.git.broken/objects/3b/e3a4b17843aed3a5898a93ac49139116566700 differ diff --git a/.git.broken/objects/3e/310cf397e122f811bb55d6aea4237024dc81da b/.git.broken/objects/3e/310cf397e122f811bb55d6aea4237024dc81da new file mode 100644 index 0000000..e143d29 Binary files /dev/null and b/.git.broken/objects/3e/310cf397e122f811bb55d6aea4237024dc81da differ diff --git a/.git.broken/objects/3e/75200b890de9d3f7945855f613e1392dfbce50 b/.git.broken/objects/3e/75200b890de9d3f7945855f613e1392dfbce50 new file mode 100644 index 0000000..d44dbc6 Binary files /dev/null and b/.git.broken/objects/3e/75200b890de9d3f7945855f613e1392dfbce50 differ diff --git a/.git.broken/objects/42/d54a78ab8b0660892e1730ff494cd7f7dfe4a4 b/.git.broken/objects/42/d54a78ab8b0660892e1730ff494cd7f7dfe4a4 new file mode 100644 index 0000000..f18efb5 Binary files /dev/null and b/.git.broken/objects/42/d54a78ab8b0660892e1730ff494cd7f7dfe4a4 differ diff --git a/.git.broken/objects/45/cb92e26861bd837e714994c9fad39aa6725374 b/.git.broken/objects/45/cb92e26861bd837e714994c9fad39aa6725374 new file mode 100644 index 0000000..44fb096 Binary files /dev/null and b/.git.broken/objects/45/cb92e26861bd837e714994c9fad39aa6725374 differ diff --git a/.git.broken/objects/48/73ce46be69e8fdc0ccd0df3575d2706088e80d b/.git.broken/objects/48/73ce46be69e8fdc0ccd0df3575d2706088e80d new file mode 100644 index 0000000..c4a5e82 Binary files /dev/null and b/.git.broken/objects/48/73ce46be69e8fdc0ccd0df3575d2706088e80d differ diff --git a/.git.broken/objects/48/76f07e16bec89ff725aa828de6a011c7a47bec b/.git.broken/objects/48/76f07e16bec89ff725aa828de6a011c7a47bec new file mode 100644 index 0000000..3fa30c2 Binary files /dev/null and b/.git.broken/objects/48/76f07e16bec89ff725aa828de6a011c7a47bec differ diff --git a/.git.broken/objects/49/8be941bcb29556be16092823cbcab72d10d210 b/.git.broken/objects/49/8be941bcb29556be16092823cbcab72d10d210 new file mode 100644 index 0000000..0e50667 Binary files /dev/null and b/.git.broken/objects/49/8be941bcb29556be16092823cbcab72d10d210 differ diff --git a/.git.broken/objects/4a/2de4f2ca58e519296143bd6a98dfd5522de64f b/.git.broken/objects/4a/2de4f2ca58e519296143bd6a98dfd5522de64f new file mode 100644 index 0000000..d83e21f Binary files /dev/null and b/.git.broken/objects/4a/2de4f2ca58e519296143bd6a98dfd5522de64f differ diff --git a/.git.broken/objects/4a/b98d049201eb963b5672da3506e16198d31d36 b/.git.broken/objects/4a/b98d049201eb963b5672da3506e16198d31d36 new file mode 100644 index 0000000..f0f8af6 Binary files /dev/null and b/.git.broken/objects/4a/b98d049201eb963b5672da3506e16198d31d36 differ diff --git a/.git.broken/objects/4b/246597b28449ac13b3c7de46beff894144ee53 b/.git.broken/objects/4b/246597b28449ac13b3c7de46beff894144ee53 new file mode 100644 index 0000000..c01617e Binary files /dev/null and b/.git.broken/objects/4b/246597b28449ac13b3c7de46beff894144ee53 differ diff --git a/.git.broken/objects/52/24707bd998e29174c685a8b070e616571b2164 b/.git.broken/objects/52/24707bd998e29174c685a8b070e616571b2164 new file mode 100644 index 0000000..794bad7 Binary files /dev/null and b/.git.broken/objects/52/24707bd998e29174c685a8b070e616571b2164 differ diff --git a/.git.broken/objects/54/37428e90918fa738f062634cdee3238b9f3a12 b/.git.broken/objects/54/37428e90918fa738f062634cdee3238b9f3a12 new file mode 100644 index 0000000..c30b8e6 Binary files /dev/null and b/.git.broken/objects/54/37428e90918fa738f062634cdee3238b9f3a12 differ diff --git a/.git.broken/objects/54/51f75a6d6211923f51c3d5ff0cdf7cd9a7005f b/.git.broken/objects/54/51f75a6d6211923f51c3d5ff0cdf7cd9a7005f new file mode 100644 index 0000000..cc6dcb1 Binary files /dev/null and b/.git.broken/objects/54/51f75a6d6211923f51c3d5ff0cdf7cd9a7005f differ diff --git a/.git.broken/objects/54/835a5e1b806eaad43ee067d49a1c07bab02a38 b/.git.broken/objects/54/835a5e1b806eaad43ee067d49a1c07bab02a38 new file mode 100644 index 0000000..57e179b Binary files /dev/null and b/.git.broken/objects/54/835a5e1b806eaad43ee067d49a1c07bab02a38 differ diff --git a/.git.broken/objects/54/b3eda36a152dd1d013316deb83092d5e4b1313 b/.git.broken/objects/54/b3eda36a152dd1d013316deb83092d5e4b1313 new file mode 100644 index 0000000..4347d19 Binary files /dev/null and b/.git.broken/objects/54/b3eda36a152dd1d013316deb83092d5e4b1313 differ diff --git a/.git.broken/objects/54/f0bbe5235362ca33c07f1df8c5f3db4ab1bff6 b/.git.broken/objects/54/f0bbe5235362ca33c07f1df8c5f3db4ab1bff6 new file mode 100644 index 0000000..2f20b40 Binary files /dev/null and b/.git.broken/objects/54/f0bbe5235362ca33c07f1df8c5f3db4ab1bff6 differ diff --git a/.git.broken/objects/55/b7816fffab77cebfc3a02be077a729ccfb4f03 b/.git.broken/objects/55/b7816fffab77cebfc3a02be077a729ccfb4f03 new file mode 100644 index 0000000..3bce528 Binary files /dev/null and b/.git.broken/objects/55/b7816fffab77cebfc3a02be077a729ccfb4f03 differ diff --git a/.git.broken/objects/57/b5b33f7af9327ef8172505b939f79fd3636b25 b/.git.broken/objects/57/b5b33f7af9327ef8172505b939f79fd3636b25 new file mode 100644 index 0000000..49718c8 Binary files /dev/null and b/.git.broken/objects/57/b5b33f7af9327ef8172505b939f79fd3636b25 differ diff --git a/.git.broken/objects/57/bed49f32b4b9a2ca642b4165810384f8af7c3a b/.git.broken/objects/57/bed49f32b4b9a2ca642b4165810384f8af7c3a new file mode 100644 index 0000000..52b7519 Binary files /dev/null and b/.git.broken/objects/57/bed49f32b4b9a2ca642b4165810384f8af7c3a differ diff --git a/.git.broken/objects/58/08a8ecf07ba2fd6f867e6e95aa938e7bce3621 b/.git.broken/objects/58/08a8ecf07ba2fd6f867e6e95aa938e7bce3621 new file mode 100644 index 0000000..c620553 Binary files /dev/null and b/.git.broken/objects/58/08a8ecf07ba2fd6f867e6e95aa938e7bce3621 differ diff --git a/.git.broken/objects/59/2b0e91e8424a2baf425c8bad52dad7aec6348b b/.git.broken/objects/59/2b0e91e8424a2baf425c8bad52dad7aec6348b new file mode 100644 index 0000000..108d91d Binary files /dev/null and b/.git.broken/objects/59/2b0e91e8424a2baf425c8bad52dad7aec6348b differ diff --git a/.git.broken/objects/59/6184f3f8aa307edaabb68f5176e64b53040184 b/.git.broken/objects/59/6184f3f8aa307edaabb68f5176e64b53040184 new file mode 100644 index 0000000..b3a2587 Binary files /dev/null and b/.git.broken/objects/59/6184f3f8aa307edaabb68f5176e64b53040184 differ diff --git a/.git.broken/objects/59/7c0d246335e3c8eb42a8132beb287a3cec1894 b/.git.broken/objects/59/7c0d246335e3c8eb42a8132beb287a3cec1894 new file mode 100644 index 0000000..84c6e70 Binary files /dev/null and b/.git.broken/objects/59/7c0d246335e3c8eb42a8132beb287a3cec1894 differ diff --git a/.git.broken/objects/5c/9c5c4f6076e0c1b32984ab4f23d6a485935cec b/.git.broken/objects/5c/9c5c4f6076e0c1b32984ab4f23d6a485935cec new file mode 100644 index 0000000..7595f25 Binary files /dev/null and b/.git.broken/objects/5c/9c5c4f6076e0c1b32984ab4f23d6a485935cec differ diff --git a/.git.broken/objects/5f/7d5c42eedd47e6f6b21396ce2f5325339ef2a4 b/.git.broken/objects/5f/7d5c42eedd47e6f6b21396ce2f5325339ef2a4 new file mode 100644 index 0000000..0e90dea Binary files /dev/null and b/.git.broken/objects/5f/7d5c42eedd47e6f6b21396ce2f5325339ef2a4 differ diff --git a/.git.broken/objects/65/17851f53d51d63e1630a8c6f33cd9146ea06f9 b/.git.broken/objects/65/17851f53d51d63e1630a8c6f33cd9146ea06f9 new file mode 100644 index 0000000..e30f903 Binary files /dev/null and b/.git.broken/objects/65/17851f53d51d63e1630a8c6f33cd9146ea06f9 differ diff --git a/.git.broken/objects/65/eeeccc0bec89628f60b447c1adf1b2ef60cc9d b/.git.broken/objects/65/eeeccc0bec89628f60b447c1adf1b2ef60cc9d new file mode 100644 index 0000000..6aebab2 Binary files /dev/null and b/.git.broken/objects/65/eeeccc0bec89628f60b447c1adf1b2ef60cc9d differ diff --git a/.git.broken/objects/67/feeb644c93b050697d799c53501f809e734331 b/.git.broken/objects/67/feeb644c93b050697d799c53501f809e734331 new file mode 100644 index 0000000..563b02a Binary files /dev/null and b/.git.broken/objects/67/feeb644c93b050697d799c53501f809e734331 differ diff --git a/.git.broken/objects/69/9bf817fc38eaaa2843acd9fee4c43f9f922b59 b/.git.broken/objects/69/9bf817fc38eaaa2843acd9fee4c43f9f922b59 new file mode 100644 index 0000000..ba8efb0 --- /dev/null +++ b/.git.broken/objects/69/9bf817fc38eaaa2843acd9fee4c43f9f922b59 @@ -0,0 +1,2 @@ +x1n0 E;"-TQ)[/@K4bؖWz:WS +v3)0& G/(,f{n$Gd8.1JR)p$,VᖯwOLu9o' Ч@pa^c3| |F([5I \ No newline at end of file diff --git a/.git.broken/objects/6a/02bfb3d16c190e8091f9eb3f3230f641a6a30a b/.git.broken/objects/6a/02bfb3d16c190e8091f9eb3f3230f641a6a30a new file mode 100644 index 0000000..3c919f2 --- /dev/null +++ b/.git.broken/objects/6a/02bfb3d16c190e8091f9eb3f3230f641a6a30a @@ -0,0 +1,2 @@ +x;n0D]0(r3RV%,H)|{3Wtuj`p8CuEb)S9C]A!A.\<"ztK3 +fFLzP۞"InS~Ip=j_^ac/䭊z#L \ No newline at end of file diff --git a/.git.broken/objects/6a/fe8eee847ebc5ef80f9e6d427b95c39abcb0c7 b/.git.broken/objects/6a/fe8eee847ebc5ef80f9e6d427b95c39abcb0c7 new file mode 100644 index 0000000..b117811 --- /dev/null +++ b/.git.broken/objects/6a/fe8eee847ebc5ef80f9e6d427b95c39abcb0c7 @@ -0,0 +1,3 @@ +x};01$nJ`7 +4b۴aCӜhު1xaj>ch@.{R2lēS_|!'dMg +hh]_c \ No newline at end of file diff --git a/.git.broken/objects/6e/aa7cd492b486d6555344cc6f3ae37ec0a4de2e b/.git.broken/objects/6e/aa7cd492b486d6555344cc6f3ae37ec0a4de2e new file mode 100644 index 0000000..74e8d69 Binary files /dev/null and b/.git.broken/objects/6e/aa7cd492b486d6555344cc6f3ae37ec0a4de2e differ diff --git a/.git.broken/objects/74/02e701aff547a78372e7f0212eb017e9fe42aa b/.git.broken/objects/74/02e701aff547a78372e7f0212eb017e9fe42aa new file mode 100644 index 0000000..b983c58 Binary files /dev/null and b/.git.broken/objects/74/02e701aff547a78372e7f0212eb017e9fe42aa differ diff --git a/.git.broken/objects/7d/2bbc6211ccc4b23b1ce8e84eb4481a8ebe181b b/.git.broken/objects/7d/2bbc6211ccc4b23b1ce8e84eb4481a8ebe181b new file mode 100644 index 0000000..bc0d6ee Binary files /dev/null and b/.git.broken/objects/7d/2bbc6211ccc4b23b1ce8e84eb4481a8ebe181b differ diff --git a/.git.broken/objects/7d/8540c089dc603cdda9cf3d9edf5196db24273e b/.git.broken/objects/7d/8540c089dc603cdda9cf3d9edf5196db24273e new file mode 100644 index 0000000..533b6dc Binary files /dev/null and b/.git.broken/objects/7d/8540c089dc603cdda9cf3d9edf5196db24273e differ diff --git a/.git.broken/objects/7d/d98cd6951950d475e738459dd292acdf86a0aa b/.git.broken/objects/7d/d98cd6951950d475e738459dd292acdf86a0aa new file mode 100644 index 0000000..b1ae8ab Binary files /dev/null and b/.git.broken/objects/7d/d98cd6951950d475e738459dd292acdf86a0aa differ diff --git a/.git.broken/objects/81/bcfaabd53c221cbb04534625bc8e48fe2af68a b/.git.broken/objects/81/bcfaabd53c221cbb04534625bc8e48fe2af68a new file mode 100644 index 0000000..99939ac Binary files /dev/null and b/.git.broken/objects/81/bcfaabd53c221cbb04534625bc8e48fe2af68a differ diff --git a/.git.broken/objects/84/f995a14d0cc70b115247b6c33633d969d4a30f b/.git.broken/objects/84/f995a14d0cc70b115247b6c33633d969d4a30f new file mode 100644 index 0000000..358bb20 Binary files /dev/null and b/.git.broken/objects/84/f995a14d0cc70b115247b6c33633d969d4a30f differ diff --git a/.git.broken/objects/86/34a00f2cf52d6f96d283e7cfc97de94724ccbc b/.git.broken/objects/86/34a00f2cf52d6f96d283e7cfc97de94724ccbc new file mode 100644 index 0000000..a453ec4 --- /dev/null +++ b/.git.broken/objects/86/34a00f2cf52d6f96d283e7cfc97de94724ccbc @@ -0,0 +1,3 @@ +xN0 ܧOh2i +i/\FUFw'x|o?e]BW?R$!>F]R'{ӴPa{MOP*RH{aʁAZ7mmںao*ӴDyO͟Iܨwu{wҥYC3~%R-tbB"O3x Ɠ +ye8D;GRd[eHޕ*%- a1Toqn \ No newline at end of file diff --git a/.git.broken/objects/86/b441b20c44ede8901546c2150cb99c8d9ec897 b/.git.broken/objects/86/b441b20c44ede8901546c2150cb99c8d9ec897 new file mode 100644 index 0000000..54b50c1 Binary files /dev/null and b/.git.broken/objects/86/b441b20c44ede8901546c2150cb99c8d9ec897 differ diff --git a/.git.broken/objects/88/625d151011228d719a4265081878c7707baeef b/.git.broken/objects/88/625d151011228d719a4265081878c7707baeef new file mode 100644 index 0000000..e55d815 Binary files /dev/null and b/.git.broken/objects/88/625d151011228d719a4265081878c7707baeef differ diff --git a/.git.broken/objects/88/7fef30440bb0feb896f7697fcf6495dd6b89fd b/.git.broken/objects/88/7fef30440bb0feb896f7697fcf6495dd6b89fd new file mode 100644 index 0000000..4833e86 Binary files /dev/null and b/.git.broken/objects/88/7fef30440bb0feb896f7697fcf6495dd6b89fd differ diff --git a/.git.broken/objects/8b/ef2102bb9084403f3100e86b0194f1f113c244 b/.git.broken/objects/8b/ef2102bb9084403f3100e86b0194f1f113c244 new file mode 100644 index 0000000..8b27d0a Binary files /dev/null and b/.git.broken/objects/8b/ef2102bb9084403f3100e86b0194f1f113c244 differ diff --git a/.git.broken/objects/8c/a64ad0d934976678e4e6054d3961bee49bf126 b/.git.broken/objects/8c/a64ad0d934976678e4e6054d3961bee49bf126 new file mode 100644 index 0000000..811bd1e Binary files /dev/null and b/.git.broken/objects/8c/a64ad0d934976678e4e6054d3961bee49bf126 differ diff --git a/.git.broken/objects/8d/beace1d4d631af6d9f5e32a594a8c318664e98 b/.git.broken/objects/8d/beace1d4d631af6d9f5e32a594a8c318664e98 new file mode 100644 index 0000000..4c3b27f Binary files /dev/null and b/.git.broken/objects/8d/beace1d4d631af6d9f5e32a594a8c318664e98 differ diff --git a/.git.broken/objects/91/7accc77a9ac57dbcd765bcc442d17cc75e914b b/.git.broken/objects/91/7accc77a9ac57dbcd765bcc442d17cc75e914b new file mode 100644 index 0000000..5b01cc0 Binary files /dev/null and b/.git.broken/objects/91/7accc77a9ac57dbcd765bcc442d17cc75e914b differ diff --git a/.git.broken/objects/93/574ee8969daf3e6c772a545868daf24c80efe4 b/.git.broken/objects/93/574ee8969daf3e6c772a545868daf24c80efe4 new file mode 100644 index 0000000..9e7b51f Binary files /dev/null and b/.git.broken/objects/93/574ee8969daf3e6c772a545868daf24c80efe4 differ diff --git a/.git.broken/objects/94.old/26a812d6b6e95b0ae8f32c37acf6c5b40980d0 b/.git.broken/objects/94.old/26a812d6b6e95b0ae8f32c37acf6c5b40980d0 new file mode 100644 index 0000000..c2aa41f Binary files /dev/null and b/.git.broken/objects/94.old/26a812d6b6e95b0ae8f32c37acf6c5b40980d0 differ diff --git a/.git.broken/objects/94.old/c11fb8f23d554e536c2401a58d06bbc6e01939 b/.git.broken/objects/94.old/c11fb8f23d554e536c2401a58d06bbc6e01939 new file mode 100644 index 0000000..656222b Binary files /dev/null and b/.git.broken/objects/94.old/c11fb8f23d554e536c2401a58d06bbc6e01939 differ diff --git a/.git.broken/objects/97/65514896af385a1abb3e0fe92af6bf40bf1695 b/.git.broken/objects/97/65514896af385a1abb3e0fe92af6bf40bf1695 new file mode 100644 index 0000000..a6d5dd0 Binary files /dev/null and b/.git.broken/objects/97/65514896af385a1abb3e0fe92af6bf40bf1695 differ diff --git a/.git.broken/objects/98/185d4f7cbcca540db6b9d94bc4df21c866a598 b/.git.broken/objects/98/185d4f7cbcca540db6b9d94bc4df21c866a598 new file mode 100644 index 0000000..18b6285 --- /dev/null +++ b/.git.broken/objects/98/185d4f7cbcca540db6b9d94bc4df21c866a598 @@ -0,0 +1 @@ +xAj0E)f0HRBƣllQ}+o>ÃۺN0؏%C:8EQ]VQض%Ys]JsUd ;M]Id$3lYmHe2y oca=bX/T>a}_zM Ty_TN \ No newline at end of file diff --git a/.git.broken/objects/98/48e364f34d5896bafb7e2ff97a83ac263ef7dc b/.git.broken/objects/98/48e364f34d5896bafb7e2ff97a83ac263ef7dc new file mode 100644 index 0000000..79cb056 Binary files /dev/null and b/.git.broken/objects/98/48e364f34d5896bafb7e2ff97a83ac263ef7dc differ diff --git a/.git.broken/objects/98/910386d8b98f03eece4f2c1ef7d1d9abeb57bf b/.git.broken/objects/98/910386d8b98f03eece4f2c1ef7d1d9abeb57bf new file mode 100644 index 0000000..e25ab33 Binary files /dev/null and b/.git.broken/objects/98/910386d8b98f03eece4f2c1ef7d1d9abeb57bf differ diff --git a/.git.broken/objects/9b/075f061c958b82596566de287ee8858cfa6bd8 b/.git.broken/objects/9b/075f061c958b82596566de287ee8858cfa6bd8 new file mode 100644 index 0000000..712bd21 Binary files /dev/null and b/.git.broken/objects/9b/075f061c958b82596566de287ee8858cfa6bd8 differ diff --git a/.git.broken/objects/9c/13ffc09bdb43e3e9a3c6fc878f6a4a434cf2b7 b/.git.broken/objects/9c/13ffc09bdb43e3e9a3c6fc878f6a4a434cf2b7 new file mode 100644 index 0000000..21d7b7c Binary files /dev/null and b/.git.broken/objects/9c/13ffc09bdb43e3e9a3c6fc878f6a4a434cf2b7 differ diff --git a/.git.broken/objects/9c/63562825a417f0f5040d6dc67b84cf3be770ac b/.git.broken/objects/9c/63562825a417f0f5040d6dc67b84cf3be770ac new file mode 100644 index 0000000..3068e78 Binary files /dev/null and b/.git.broken/objects/9c/63562825a417f0f5040d6dc67b84cf3be770ac differ diff --git a/.git.broken/objects/9f/f9b4889d1bcccaa389ec3b899eedf3f50b78d7 b/.git.broken/objects/9f/f9b4889d1bcccaa389ec3b899eedf3f50b78d7 new file mode 100644 index 0000000..3781403 Binary files /dev/null and b/.git.broken/objects/9f/f9b4889d1bcccaa389ec3b899eedf3f50b78d7 differ diff --git a/.git.broken/objects/a1/bfc00593834f869f1117cf37b985fd1dbea77b b/.git.broken/objects/a1/bfc00593834f869f1117cf37b985fd1dbea77b new file mode 100644 index 0000000..52c341a Binary files /dev/null and b/.git.broken/objects/a1/bfc00593834f869f1117cf37b985fd1dbea77b differ diff --git a/.git.broken/objects/a2/74571742218cf06741fd37136f9309cb56e2c3 b/.git.broken/objects/a2/74571742218cf06741fd37136f9309cb56e2c3 new file mode 100644 index 0000000..c0ac013 Binary files /dev/null and b/.git.broken/objects/a2/74571742218cf06741fd37136f9309cb56e2c3 differ diff --git a/.git.broken/objects/a2/a6f44e4f663d595aa2ae64d066ff8191f24b7e b/.git.broken/objects/a2/a6f44e4f663d595aa2ae64d066ff8191f24b7e new file mode 100644 index 0000000..b188d1e Binary files /dev/null and b/.git.broken/objects/a2/a6f44e4f663d595aa2ae64d066ff8191f24b7e differ diff --git a/.git.broken/objects/a5/813167b33e5489ef1958edbe88649d39fe00e7 b/.git.broken/objects/a5/813167b33e5489ef1958edbe88649d39fe00e7 new file mode 100644 index 0000000..67d3d51 Binary files /dev/null and b/.git.broken/objects/a5/813167b33e5489ef1958edbe88649d39fe00e7 differ diff --git a/.git.broken/objects/a8/9ca3dca965bf15f0db04e9149881efc0410fa3 b/.git.broken/objects/a8/9ca3dca965bf15f0db04e9149881efc0410fa3 new file mode 100644 index 0000000..e5277a0 --- /dev/null +++ b/.git.broken/objects/a8/9ca3dca965bf15f0db04e9149881efc0410fa3 @@ -0,0 +1,4 @@ +xAj0E)fXH! +]e FcblQ}+o>Ã/벌 0؏BJdGe| V +yڀ<0ΫƂ=e$0u`ny +Wq3פy؎.(b' {\O6/?eoﵪVNa \ No newline at end of file diff --git a/.git.broken/objects/a9/60909e404cac67b49e956b5a97ab43444be753 b/.git.broken/objects/a9/60909e404cac67b49e956b5a97ab43444be753 new file mode 100644 index 0000000..5b9e3b1 Binary files /dev/null and b/.git.broken/objects/a9/60909e404cac67b49e956b5a97ab43444be753 differ diff --git a/.git.broken/objects/a9/6b0d56030c12fafe3309d53f858b4955c07b13 b/.git.broken/objects/a9/6b0d56030c12fafe3309d53f858b4955c07b13 new file mode 100644 index 0000000..36c9dfd Binary files /dev/null and b/.git.broken/objects/a9/6b0d56030c12fafe3309d53f858b4955c07b13 differ diff --git a/.git.broken/objects/aa/b1a25f658bf17e3f285146b733daf63a90a72c b/.git.broken/objects/aa/b1a25f658bf17e3f285146b733daf63a90a72c new file mode 100644 index 0000000..a17a8ac Binary files /dev/null and b/.git.broken/objects/aa/b1a25f658bf17e3f285146b733daf63a90a72c differ diff --git a/.git.broken/objects/ab/41ad1b9c5789ddcf907c8ca9fc0985288ccc0d b/.git.broken/objects/ab/41ad1b9c5789ddcf907c8ca9fc0985288ccc0d new file mode 100644 index 0000000..a719505 Binary files /dev/null and b/.git.broken/objects/ab/41ad1b9c5789ddcf907c8ca9fc0985288ccc0d differ diff --git a/.git.broken/objects/ac/4dcfd3103c8aec872ed0508162ad6cea4cc963 b/.git.broken/objects/ac/4dcfd3103c8aec872ed0508162ad6cea4cc963 new file mode 100644 index 0000000..b4984ca Binary files /dev/null and b/.git.broken/objects/ac/4dcfd3103c8aec872ed0508162ad6cea4cc963 differ diff --git a/.git.broken/objects/ad/d06a8777a63026c2f8091cbd08c849ae63b35d b/.git.broken/objects/ad/d06a8777a63026c2f8091cbd08c849ae63b35d new file mode 100644 index 0000000..711a4fd Binary files /dev/null and b/.git.broken/objects/ad/d06a8777a63026c2f8091cbd08c849ae63b35d differ diff --git a/.git.broken/objects/af/6c8db0b779ae9bcde84e5cf33fbb77973d72c4 b/.git.broken/objects/af/6c8db0b779ae9bcde84e5cf33fbb77973d72c4 new file mode 100644 index 0000000..1265464 Binary files /dev/null and b/.git.broken/objects/af/6c8db0b779ae9bcde84e5cf33fbb77973d72c4 differ diff --git a/.git.broken/objects/af/ba9e750c21520a954bd96bed8866b1a1b7bebf b/.git.broken/objects/af/ba9e750c21520a954bd96bed8866b1a1b7bebf new file mode 100644 index 0000000..f3330e2 --- /dev/null +++ b/.git.broken/objects/af/ba9e750c21520a954bd96bed8866b1a1b7bebf @@ -0,0 +1,2 @@ +xM +0F]JL~FDW$iiz{K4we&I$d]L#DQ pj{ƥ4):L4U.ݼe%=:p}Ry:ACChC@mj"j9`upU 9iK5 \ No newline at end of file diff --git a/.git.broken/objects/b1/43672b78a0be97e73d38196da114197ba52a6c b/.git.broken/objects/b1/43672b78a0be97e73d38196da114197ba52a6c new file mode 100644 index 0000000..554aa26 Binary files /dev/null and b/.git.broken/objects/b1/43672b78a0be97e73d38196da114197ba52a6c differ diff --git a/.git.broken/objects/b1/b91431e1691556c670273aba47943efe27e065 b/.git.broken/objects/b1/b91431e1691556c670273aba47943efe27e065 new file mode 100644 index 0000000..c03c675 Binary files /dev/null and b/.git.broken/objects/b1/b91431e1691556c670273aba47943efe27e065 differ diff --git a/.git.broken/objects/b7/e1e1532ef3654aa6e314a78a6f8ce460bbe336 b/.git.broken/objects/b7/e1e1532ef3654aa6e314a78a6f8ce460bbe336 new file mode 100644 index 0000000..98bbb85 Binary files /dev/null and b/.git.broken/objects/b7/e1e1532ef3654aa6e314a78a6f8ce460bbe336 differ diff --git a/.git.broken/objects/b8/d98c91d2440c70b7b87f016480e6fd01491c5a b/.git.broken/objects/b8/d98c91d2440c70b7b87f016480e6fd01491c5a new file mode 100644 index 0000000..7fbc53a Binary files /dev/null and b/.git.broken/objects/b8/d98c91d2440c70b7b87f016480e6fd01491c5a differ diff --git a/.git.broken/objects/ba/0ff4dcfbdc1cf736a2eed216464fee5006ba16 b/.git.broken/objects/ba/0ff4dcfbdc1cf736a2eed216464fee5006ba16 new file mode 100644 index 0000000..d7f14c3 Binary files /dev/null and b/.git.broken/objects/ba/0ff4dcfbdc1cf736a2eed216464fee5006ba16 differ diff --git a/.git.broken/objects/bd/80805573fdd49b56711993377a87d04bf5f8a0 b/.git.broken/objects/bd/80805573fdd49b56711993377a87d04bf5f8a0 new file mode 100644 index 0000000..32f8ebc Binary files /dev/null and b/.git.broken/objects/bd/80805573fdd49b56711993377a87d04bf5f8a0 differ diff --git a/.git.broken/objects/bd/b623b99c13a025a639918a459da0cbdb4caf96 b/.git.broken/objects/bd/b623b99c13a025a639918a459da0cbdb4caf96 new file mode 100644 index 0000000..04ea768 Binary files /dev/null and b/.git.broken/objects/bd/b623b99c13a025a639918a459da0cbdb4caf96 differ diff --git a/.git.broken/objects/c0/0c764b640d95c73bbc414370f28e82a11774c5 b/.git.broken/objects/c0/0c764b640d95c73bbc414370f28e82a11774c5 new file mode 100644 index 0000000..9bd9a22 --- /dev/null +++ b/.git.broken/objects/c0/0c764b640d95c73bbc414370f28e82a11774c5 @@ -0,0 +1 @@ +x+)JMU0`040031QMNMIeЉ?>뎣WlU'ywE(,`]B@cڜW>'!) \ No newline at end of file diff --git a/.git.broken/objects/c1/625b23ef80d821ddce42e603229d1bba45a80f b/.git.broken/objects/c1/625b23ef80d821ddce42e603229d1bba45a80f new file mode 100644 index 0000000..efe4ccf Binary files /dev/null and b/.git.broken/objects/c1/625b23ef80d821ddce42e603229d1bba45a80f differ diff --git a/.git.broken/objects/c1/67101d497a806b573de0be9d2b4f46911b76f4 b/.git.broken/objects/c1/67101d497a806b573de0be9d2b4f46911b76f4 new file mode 100644 index 0000000..ed56ec2 Binary files /dev/null and b/.git.broken/objects/c1/67101d497a806b573de0be9d2b4f46911b76f4 differ diff --git a/.git.broken/objects/c1/bcff3230e787c045e52b6044960bf008a627dc b/.git.broken/objects/c1/bcff3230e787c045e52b6044960bf008a627dc new file mode 100644 index 0000000..4566d78 Binary files /dev/null and b/.git.broken/objects/c1/bcff3230e787c045e52b6044960bf008a627dc differ diff --git a/.git.broken/objects/c5/2ffb5bf94e33810d3c8a12a3390090adb43d99 b/.git.broken/objects/c5/2ffb5bf94e33810d3c8a12a3390090adb43d99 new file mode 100644 index 0000000..321e055 Binary files /dev/null and b/.git.broken/objects/c5/2ffb5bf94e33810d3c8a12a3390090adb43d99 differ diff --git a/.git.broken/objects/c6/a3d232349bfc966e79bd4ec16a3bc507ba9ff7 b/.git.broken/objects/c6/a3d232349bfc966e79bd4ec16a3bc507ba9ff7 new file mode 100644 index 0000000..b86f0bf Binary files /dev/null and b/.git.broken/objects/c6/a3d232349bfc966e79bd4ec16a3bc507ba9ff7 differ diff --git a/.git.broken/objects/c7/5536f0e9089215b7bd4c40ed7f222ccd76d847 b/.git.broken/objects/c7/5536f0e9089215b7bd4c40ed7f222ccd76d847 new file mode 100644 index 0000000..06f20dd Binary files /dev/null and b/.git.broken/objects/c7/5536f0e9089215b7bd4c40ed7f222ccd76d847 differ diff --git a/.git.broken/objects/c9/d57de7cd1d73e0975658f5d45a9770d327df65 b/.git.broken/objects/c9/d57de7cd1d73e0975658f5d45a9770d327df65 new file mode 100644 index 0000000..6ea7ec1 Binary files /dev/null and b/.git.broken/objects/c9/d57de7cd1d73e0975658f5d45a9770d327df65 differ diff --git a/.git.broken/objects/cd/2206874129f08f05dcae26ab661a3c10797a8e b/.git.broken/objects/cd/2206874129f08f05dcae26ab661a3c10797a8e new file mode 100644 index 0000000..9e8b684 Binary files /dev/null and b/.git.broken/objects/cd/2206874129f08f05dcae26ab661a3c10797a8e differ diff --git a/.git.broken/objects/ce/40a95762e52cf1b4c7514cc5b30cf6b0f3148a b/.git.broken/objects/ce/40a95762e52cf1b4c7514cc5b30cf6b0f3148a new file mode 100644 index 0000000..0eab803 Binary files /dev/null and b/.git.broken/objects/ce/40a95762e52cf1b4c7514cc5b30cf6b0f3148a differ diff --git a/.git.broken/objects/cf/0f6fbdd546be44677bc28a81127d1369b38cde b/.git.broken/objects/cf/0f6fbdd546be44677bc28a81127d1369b38cde new file mode 100644 index 0000000..74db1ee Binary files /dev/null and b/.git.broken/objects/cf/0f6fbdd546be44677bc28a81127d1369b38cde differ diff --git a/.git.broken/objects/d0/467b1257842659d986028be2dc0d03ce40f275 b/.git.broken/objects/d0/467b1257842659d986028be2dc0d03ce40f275 new file mode 100644 index 0000000..60a48d2 Binary files /dev/null and b/.git.broken/objects/d0/467b1257842659d986028be2dc0d03ce40f275 differ diff --git a/.git.broken/objects/d0/9d018427e14a3aaa570be2a4118cddf19f6c4b b/.git.broken/objects/d0/9d018427e14a3aaa570be2a4118cddf19f6c4b new file mode 100644 index 0000000..5f67ae1 Binary files /dev/null and b/.git.broken/objects/d0/9d018427e14a3aaa570be2a4118cddf19f6c4b differ diff --git a/.git.broken/objects/d2/53beed7f514effd2718dac33e4672f6354d752 b/.git.broken/objects/d2/53beed7f514effd2718dac33e4672f6354d752 new file mode 100644 index 0000000..5ea8410 Binary files /dev/null and b/.git.broken/objects/d2/53beed7f514effd2718dac33e4672f6354d752 differ diff --git a/.git.broken/objects/d2/f99c2ce8ba5f13a291cea0f598373302d4f392 b/.git.broken/objects/d2/f99c2ce8ba5f13a291cea0f598373302d4f392 new file mode 100644 index 0000000..ab6f4d0 Binary files /dev/null and b/.git.broken/objects/d2/f99c2ce8ba5f13a291cea0f598373302d4f392 differ diff --git a/.git.broken/objects/d4/6bfcddb06173de12b59e92419aec994864036c b/.git.broken/objects/d4/6bfcddb06173de12b59e92419aec994864036c new file mode 100644 index 0000000..f19e5a2 Binary files /dev/null and b/.git.broken/objects/d4/6bfcddb06173de12b59e92419aec994864036c differ diff --git a/.git.broken/objects/d4/80f057dcba4ab3b14b4ef6bcf856ab7ecc452c b/.git.broken/objects/d4/80f057dcba4ab3b14b4ef6bcf856ab7ecc452c new file mode 100644 index 0000000..e4b7a95 Binary files /dev/null and b/.git.broken/objects/d4/80f057dcba4ab3b14b4ef6bcf856ab7ecc452c differ diff --git a/.git.broken/objects/d4/a49b5f60fea575d5368b40318b34a81aaaa284 b/.git.broken/objects/d4/a49b5f60fea575d5368b40318b34a81aaaa284 new file mode 100644 index 0000000..fe81bbe Binary files /dev/null and b/.git.broken/objects/d4/a49b5f60fea575d5368b40318b34a81aaaa284 differ diff --git a/.git.broken/objects/df/49593726dbdfe0b53cf545d4e4dcec320bca01 b/.git.broken/objects/df/49593726dbdfe0b53cf545d4e4dcec320bca01 new file mode 100644 index 0000000..c208b84 Binary files /dev/null and b/.git.broken/objects/df/49593726dbdfe0b53cf545d4e4dcec320bca01 differ diff --git a/.git.broken/objects/e2/98ff3e5e58f246e7970a253eee02c6aad578ec b/.git.broken/objects/e2/98ff3e5e58f246e7970a253eee02c6aad578ec new file mode 100644 index 0000000..5eaa5ab Binary files /dev/null and b/.git.broken/objects/e2/98ff3e5e58f246e7970a253eee02c6aad578ec differ diff --git a/.git.broken/objects/e2/bb9294c5658ac86b98c01d5644ee949fbe237f b/.git.broken/objects/e2/bb9294c5658ac86b98c01d5644ee949fbe237f new file mode 100644 index 0000000..70d3fda Binary files /dev/null and b/.git.broken/objects/e2/bb9294c5658ac86b98c01d5644ee949fbe237f differ diff --git a/.git.broken/objects/e3/17b7599440d44a01a12f58daa37d45a3d18d37 b/.git.broken/objects/e3/17b7599440d44a01a12f58daa37d45a3d18d37 new file mode 100644 index 0000000..ccee8ed Binary files /dev/null and b/.git.broken/objects/e3/17b7599440d44a01a12f58daa37d45a3d18d37 differ diff --git a/.git.broken/objects/e3/271d3627d8214d76ec5d24bfc7e1277a8e388d b/.git.broken/objects/e3/271d3627d8214d76ec5d24bfc7e1277a8e388d new file mode 100644 index 0000000..e055c96 Binary files /dev/null and b/.git.broken/objects/e3/271d3627d8214d76ec5d24bfc7e1277a8e388d differ diff --git a/.git.broken/objects/e4/321d8908f9bd5913d1cbf9bd36ea828e02b1ed b/.git.broken/objects/e4/321d8908f9bd5913d1cbf9bd36ea828e02b1ed new file mode 100644 index 0000000..6dbf5f3 Binary files /dev/null and b/.git.broken/objects/e4/321d8908f9bd5913d1cbf9bd36ea828e02b1ed differ diff --git a/.git.broken/objects/e4/e2e456a16be615ca50bd738353517b9e804f64 b/.git.broken/objects/e4/e2e456a16be615ca50bd738353517b9e804f64 new file mode 100644 index 0000000..c29a1af Binary files /dev/null and b/.git.broken/objects/e4/e2e456a16be615ca50bd738353517b9e804f64 differ diff --git a/.git.broken/objects/e8/0650ef433c17a4faf20ec7e6cbf58ae035725d b/.git.broken/objects/e8/0650ef433c17a4faf20ec7e6cbf58ae035725d new file mode 100644 index 0000000..956cb33 Binary files /dev/null and b/.git.broken/objects/e8/0650ef433c17a4faf20ec7e6cbf58ae035725d differ diff --git a/.git.broken/objects/eb/c8c39b72529ef10d1c18219f51557a4d25be71 b/.git.broken/objects/eb/c8c39b72529ef10d1c18219f51557a4d25be71 new file mode 100644 index 0000000..146a86f Binary files /dev/null and b/.git.broken/objects/eb/c8c39b72529ef10d1c18219f51557a4d25be71 differ diff --git a/.git.broken/objects/ec.old/8777fde92ea34fa789ad32d438abe56845fd22 b/.git.broken/objects/ec.old/8777fde92ea34fa789ad32d438abe56845fd22 new file mode 100644 index 0000000..a8fc5cc --- /dev/null +++ b/.git.broken/objects/ec.old/8777fde92ea34fa789ad32d438abe56845fd22 @@ -0,0 +1,3 @@ +xR0圯c*%N$m 8ZJܨ&$!ﴫ"0yl2y'"(̕F,l*UUIs)16[cF1XVXWuRIMIY46צlJPy3w~'kD޺Pens)9tE#w`M"/R0iAxq5 +~~X0 ](=dqO0Z^s/dev/null || echo /usr/local/bin/tailscale)}" - : "${TAILSCALED_BIN:=$(command -v tailscaled 2>/dev/null || echo /usr/local/bin/tailscaled)}" + : "${TAILSCALE_BIN:=$(which tailscale 2>/dev/null || echo /usr/local/bin/tailscale)}" + : "${TAILSCALED_BIN:=$(which tailscaled 2>/dev/null || echo /usr/local/bin/tailscaled)}" # watchdog interval (seconds between forward health checks) : "${WATCHDOG_INTERVAL:=60}" diff --git a/kit-connect/kit-connect-0.1-0001.spk b/kit-connect/kit-connect-0.1-0001.spk index f2a7e53..0ee3bd0 100644 Binary files a/kit-connect/kit-connect-0.1-0001.spk and b/kit-connect/kit-connect-0.1-0001.spk differ diff --git a/kit-connect/wizard.sh b/kit-connect/wizard.sh index 60c189a..6dcc863 100755 --- a/kit-connect/wizard.sh +++ b/kit-connect/wizard.sh @@ -44,7 +44,7 @@ ok "Device ID: ${DEVICE_ID}" # ── Step 2: Check prerequisites ──────────────────────────────── step "Checking prerequisites..." for cmd in curl ssh ssh-keygen; do - command -v "$cmd" >/dev/null 2>&1 || { echo " ✗ Missing: $cmd"; exit 1; } + which "$cmd" >/dev/null 2>&1 || { echo " ✗ Missing: $cmd"; exit 1; } done ok "All prerequisites found (curl, ssh)" diff --git a/tests/bats/composite.bats b/tests/bats/composite.bats new file mode 100644 index 0000000..d9b686f --- /dev/null +++ b/tests/bats/composite.bats @@ -0,0 +1,23 @@ +setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh'; } + +@test "composite weights sum to a 0-100 score" { + # latency35 dl25 up10 jit10 loss10 sig10 ; all metrics perfect -> 100 + run score_composite 5 25 12.5 1 0 -65 + [ "$output" -eq 100 ] +} +@test "composite all-worst -> 0" { + run score_composite 200 0.5 0.5 50 40 -95 + [ "$output" -eq 0 ] +} +@test "rolling average of 3 samples" { + run rolling_avg 90 60 30 # -> 60 + [ "$output" -eq 60 ] +} +@test "rolling average rounds half-up" { + run rolling_avg 100 100 99 # 299/3 = 99.67 -> 100 + [ "$output" -eq 100 ] +} +@test "signal weight redistributes when no modem (signal='')" { + run score_composite 5 25 12.5 1 0 "" # still 100, sig weight moved to latency/throughput + [ "$output" -eq 100 ] +} diff --git a/tests/bats/daemon.bats b/tests/bats/daemon.bats new file mode 100644 index 0000000..020c04a --- /dev/null +++ b/tests/bats/daemon.bats @@ -0,0 +1,71 @@ +LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)" + +setup() { + export LIB_DIR="$LIBDIR" + export STATE_DIR="$BATS_TMPDIR/daemon_state" + rm -rf "$STATE_DIR" + mkdir -p "$STATE_DIR" + export SCORE_SAMPLES=3 + export CELL_IFACE=rmnet_mhi0 CELL_MBR=modem_0001 + export ETH_IFACE=eth0 ETH_MBR=wan + export INTERVAL=30 + # Load all libs (daemon.sh's while loop is guarded by case "${0##*/}" so safe to load) + load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/metrics-net.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/speedtest.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/modem-sim.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/starlink.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/wan-mwan.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/daemon.sh' +} + +@test "daemon.sh loads without error and does not start loop" { + run true + [ "$status" -eq 0 ] + # If we reach here, the load in setup() did not hang -> loop guard works +} + +@test "_score_history_update: primes from default 50 50 50 and appends new score" { + run _score_history_update "modem_0001" 70 + [ "$status" -eq 0 ] + echo "$output" | grep -qE '^50 50 70$' +} + +@test "_score_history_update: keeps last SCORE_SAMPLES scores only" { + export SCORE_SAMPLES=3 + _score_history_update "modem_0001" 60 >/dev/null + _score_history_update "modem_0001" 70 >/dev/null + _score_history_update "modem_0001" 80 >/dev/null + run _score_history_update "modem_0001" 90 + [ "$status" -eq 0 ] + # Should be "70 80 90" (last 3 of: 50 50 50 60 70 80 90) + echo "$output" | grep -qE '^70 80 90$' +} + +@test "_collect_wan: writes metric and score files" { + net_probe_iface() { echo "20 5 0"; } + speed_test_due() { return 0; } + speed_test_iface() { echo "50.0 10.0"; } + score_composite() { echo "75"; } + rolling_avg() { echo "72"; } + + run _collect_wan "eth0" "wan" + [ "$status" -eq 0 ] + [ -f "${STATE_DIR}/metric_wan" ] + [ -f "${STATE_DIR}/score_wan" ] + score=$(cat "${STATE_DIR}/score_wan") + [ "$score" -eq 72 ] +} + +@test "_collect_wan: fail-open when net_probe_iface returns error" { + net_probe_iface() { return 1; } + speed_test_due() { return 1; } + score_composite() { echo "0"; } + rolling_avg() { echo "0"; } + + run _collect_wan "rmnet_mhi0" "modem_0001" "-99" + [ "$status" -eq 0 ] + [ -f "${STATE_DIR}/metric_modem_0001" ] +} diff --git a/tests/bats/decide.bats b/tests/bats/decide.bats new file mode 100644 index 0000000..0548cf0 --- /dev/null +++ b/tests/bats/decide.bats @@ -0,0 +1,21 @@ +setup(){ load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh'; } + +@test "equal scores -> 50/50" { run wan_weight 70 70; [ "$output" -eq 50 ]; } +# 50 + 2*(80-60) = 90 -> clamp to 80 (max). So 80 is the true formula result. +@test "weight = 50 + 2*diff, rounded to 10" { run wan_weight 80 60; [ "$output" -eq 80 ]; } +@test "clamped to 20..80 (high)" { run wan_weight 100 0; [ "$output" -eq 80 ]; } +@test "clamped to 20..80 (low)" { run wan_weight 0 100; [ "$output" -eq 20 ]; } +# 50 + 2*(58-55) = 56 -> round to nearest 10 -> 60 +@test "rounds to nearest 10" { run wan_weight 58 55; [ "$output" -eq 60 ]; } +@test "no change if within hysteresis band" { run weight_changed 50 54; [ "$status" -eq 1 ]; } +@test "change if crosses a 10% step" { run weight_changed 50 65; [ "$status" -eq 0 ]; } +@test "change is symmetric (decrease)" { run weight_changed 60 50; [ "$status" -eq 0 ]; } +@test "sim switch only after N bad cycles" { run sim_should_switch 3 3; [ "$status" -eq 0 ]; } +@test "sim no switch before threshold" { run sim_should_switch 2 3; [ "$status" -eq 1 ]; } + +@test "gw_verdict: 3/3 -> alive" { run gw_verdict 1 1 1; [ "$status" -eq 0 ]; } +@test "gw_verdict: 2/3 no http -> alive" { run gw_verdict 1 1 0; [ "$status" -eq 0 ]; } +@test "gw_verdict: 2/3 no ping -> alive" { run gw_verdict 1 0 1; [ "$status" -eq 0 ]; } +@test "gw_verdict: 2/3 no dns -> alive" { run gw_verdict 0 1 1; [ "$status" -eq 0 ]; } +@test "gw_verdict: 1/3 -> dead" { run gw_verdict 1 0 0; [ "$status" -eq 1 ]; } +@test "gw_verdict: 0/3 -> dead" { run gw_verdict 0 0 0; [ "$status" -eq 1 ]; } diff --git a/tests/bats/modem.bats b/tests/bats/modem.bats new file mode 100644 index 0000000..40eacc0 --- /dev/null +++ b/tests/bats/modem.bats @@ -0,0 +1,125 @@ +FIXTURES="$BATS_TEST_DIRNAME/../fixtures" + +LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)" + +setup() { + export LIB_DIR="$LIBDIR" + load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh' + load '../../package/kit-busrouter/files/usr/lib/busrouter/modem-sim.sh' +} + +@test "modem_signal ubus parse: rsrp=-96 rsrq=-10 sinr=13" { + run _modem_parse_ubus < "$FIXTURES/modem-signal-ubus.json" + [ "$status" -eq 0 ] + rsrp=$(echo "$output" | awk '{print $1}') + rsrq=$(echo "$output" | awk '{print $2}') + sinr=$(echo "$output" | awk '{print $3}') + [ "$rsrp" -eq -96 ] + [ "$rsrq" -eq -10 ] + [ "$sinr" -eq 13 ] +} + +@test "modem_signal AT parse: rsrp=-97 rsrq=-10 sinr=14 (rsrp rsrq sinr order)" { + run _modem_parse_at < "$FIXTURES/modem-signal-at.txt" + [ "$status" -eq 0 ] + rsrp=$(echo "$output" | awk '{print $1}') + rsrq=$(echo "$output" | awk '{print $2}') + sinr=$(echo "$output" | awk '{print $3}') + [ "$rsrp" -eq -97 ] + [ "$rsrq" -eq -10 ] + [ "$sinr" -eq 14 ] +} + +@test "modem_signal ubus parse: empty input exits 1" { + run _modem_parse_ubus < /dev/null + [ "$status" -ne 0 ] + [ -z "$output" ] +} + +@test "modem_signal AT parse: empty input exits 1" { + run _modem_parse_at < /dev/null + [ "$status" -ne 0 ] + [ -z "$output" ] +} + +@test "modem_sim_switch: rejects invalid slot" { + export MODEM_AT_CMD=/bin/true MODEM_BUS=1-1.2 + run modem_sim_switch 3 + [ "$status" -eq 1 ] +} + +@test "modem_sim_switch: accepts slot 1" { + # Stub gl_modem to emit 'OK' and stub ifdown/ifup + mkdir -p "$BATS_TMPDIR/stub" + printf '#!/bin/sh +echo OK +' > "$BATS_TMPDIR/stub/gl_modem" + printf '#!/bin/sh +exit 0 +' > "$BATS_TMPDIR/stub/ifdown" + printf '#!/bin/sh +exit 0 +' > "$BATS_TMPDIR/stub/ifup" + chmod +x "$BATS_TMPDIR/stub/gl_modem" "$BATS_TMPDIR/stub/ifdown" "$BATS_TMPDIR/stub/ifup" + export PATH="$BATS_TMPDIR/stub:$PATH" + export MODEM_AT_CMD=gl_modem MODEM_BUS=1-1.2 + run modem_sim_switch 1 + [ "$status" -eq 0 ] +} + +# --- Task 4.2: SIM auto-switch --- + +_sim_make_stub() { + mkdir -p "$BATS_TMPDIR/sim_stub" + printf '#!/bin/sh +case "$*" in *QUIMSLOT\?*) echo "+QUIMSLOT: 1"; echo "OK" ;; *QUIMSLOT=*) echo "OK" ;; *) echo "OK" ;; esac +' > "$BATS_TMPDIR/sim_stub/gl_modem" + printf '#!/bin/sh +exit 0 +' > "$BATS_TMPDIR/sim_stub/ifdown" + printf '#!/bin/sh +exit 0 +' > "$BATS_TMPDIR/sim_stub/ifup" + chmod +x "$BATS_TMPDIR/sim_stub/gl_modem" "$BATS_TMPDIR/sim_stub/ifdown" "$BATS_TMPDIR/sim_stub/ifup" + export PATH="$BATS_TMPDIR/sim_stub:$PATH" + export MODEM_AT_CMD=gl_modem MODEM_BUS=1-1.2 + export SIM_STATE_DIR="$BATS_TMPDIR/simstate" + export SIM_SWITCH_RSRP=-80 # threshold above actual -99 bench value -> easy to trigger + export SIM_SWITCH_CYCLES=3 + export SIM_SWITCH_COOLDOWN=3600 + rm -rf "$BATS_TMPDIR/simstate" + mkdir -p "$BATS_TMPDIR/simstate" +} + +@test "sim_check: good signal resets counter and does not switch" { + _sim_make_stub + # First call with bad signal to set counter to 1 + sim_check_and_switch -90 || true + bad=$(cat "$SIM_STATE_DIR/sim_bad_cycles" 2>/dev/null || echo 0) + [ "$bad" -eq 1 ] + # Now good signal resets + sim_check_and_switch -70 || true + bad=$(cat "$SIM_STATE_DIR/sim_bad_cycles" 2>/dev/null || echo 0) + [ "$bad" -eq 0 ] +} + +@test "sim_check: triggers switch after SIM_SWITCH_CYCLES bad cycles" { + _sim_make_stub + # 2 bad cycles -> no switch yet + sim_check_and_switch -90 || true + sim_check_and_switch -90 || true + [ ! -f "$SIM_STATE_DIR/sim_last_switch" ] + # 3rd bad cycle -> switch triggered + run sim_check_and_switch -90 + [ "$status" -eq 0 ] + [ -f "$SIM_STATE_DIR/sim_last_switch" ] +} + +@test "sim_check: cooldown prevents switch within SIM_SWITCH_COOLDOWN" { + _sim_make_stub + export SIM_SWITCH_COOLDOWN=3600 + # Pre-seed a recent switch time + date +%s > "$SIM_STATE_DIR/sim_last_switch" + run sim_check_and_switch -90 + [ "$status" -eq 1 ] # blocked by cooldown +} diff --git a/tests/bats/net.bats b/tests/bats/net.bats new file mode 100644 index 0000000..311a9dd --- /dev/null +++ b/tests/bats/net.bats @@ -0,0 +1,20 @@ +FIXTURES="$BATS_TEST_DIRNAME/../../tests/fixtures" +setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/metrics-net.sh'; } + +@test "net: zero-jitter no-loss" { + run _net_parse_ping < "$FIXTURES/ping-ok.txt" + [ "$status" -eq 0 ] + [ "$output" = "15.0 0.0 0" ] +} +@test "net: jitter and packet loss" { + run _net_parse_ping < "$FIXTURES/ping-loss.txt" + [ "$output" = "15.0 5.0 20" ] +} +@test "net: 100pct loss gives 0 0 100" { + run _net_parse_ping < "$FIXTURES/ping-timeout.txt" + [ "$output" = "0 0 100" ] +} +@test "net: partial output defaults loss to 100" { + run _net_parse_ping < "$FIXTURES/ping-partial.txt" + [ "$output" = "20.0 0.0 100" ] +} diff --git a/tests/bats/score.bats b/tests/bats/score.bats new file mode 100644 index 0000000..dc8e45e --- /dev/null +++ b/tests/bats/score.bats @@ -0,0 +1,16 @@ +setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh'; } + +@test "latency: 5ms scores 100" { run score_latency 5; [ "$output" -eq 100 ]; } +@test "latency: 200ms scores 0" { run score_latency 200; [ "$output" -eq 0 ]; } +@test "latency clamps below 0" { run score_latency 500; [ "$output" -eq 0 ]; } +@test "download: 25MB/s scores 100" { run score_download 25; [ "$output" -eq 100 ]; } +@test "download: 0.5MB/s scores 0" { run score_download 0.5; [ "$output" -eq 0 ]; } +@test "upload: 12.5MB/s scores 100" { run score_upload 12.5; [ "$output" -eq 100 ]; } +@test "upload: 0.5MB/s scores 0" { run score_upload 0.5; [ "$output" -eq 0 ]; } +@test "jitter: 1ms scores 100" { run score_jitter 1; [ "$output" -eq 100 ]; } +@test "jitter: 50ms scores 0" { run score_jitter 50; [ "$output" -eq 0 ]; } +@test "loss: 0% scores 100" { run score_loss 0; [ "$output" -eq 100 ]; } +@test "loss: 40% scores 0" { run score_loss 40; [ "$output" -eq 0 ]; } +@test "signal: -65dBm scores 100" { run score_signal -65; [ "$output" -eq 100 ]; } +@test "signal: -95dBm scores 0" { run score_signal -95; [ "$output" -eq 0 ]; } +@test "signal clamps above 100" { run score_signal -50; [ "$output" -eq 100 ]; } diff --git a/tests/bats/smoke.bats b/tests/bats/smoke.bats new file mode 100644 index 0000000..f5eb0d9 --- /dev/null +++ b/tests/bats/smoke.bats @@ -0,0 +1 @@ +@test "bats runs" { run true; [ "$status" -eq 0 ]; } diff --git a/tests/bats/speed.bats b/tests/bats/speed.bats new file mode 100644 index 0000000..284489f --- /dev/null +++ b/tests/bats/speed.bats @@ -0,0 +1,39 @@ +SPEED_STATE_DIR="$BATS_TMPDIR/busrouter" +setup() { + load '../../package/kit-busrouter/files/usr/lib/busrouter/speedtest.sh' + mkdir -p "$SPEED_STATE_DIR" + export SPEED_STATE_DIR +} +teardown() { rm -rf "$SPEED_STATE_DIR"; } + +@test "speed: CELL_METERED=1 skips cellular, emits 0 0" { + export CELL_METERED=1 CELLULAR_IFACE=rmnet_mhi0 + run speed_test_iface rmnet_mhi0 + [ "$status" -eq 0 ] + [ "$output" = "0 0" ] +} + +@test "speed: CELL_METERED=0 does not skip cellular (returns non 0 0)" { + export CELL_METERED=0 CELLULAR_IFACE=rmnet_mhi0 + run speed_test_iface rmnet_mhi0 + [ "$output" != "0 0" ] || [ "$status" -ne 0 ] +} + +@test "speed_test_due: no state file -> due (exit 0)" { + run speed_test_due eth0 + [ "$status" -eq 0 ] +} + +@test "speed_test_due: fresh state file -> not due (exit 1)" { + date +%s > "$SPEED_STATE_DIR/speed_last_eth0" + export SPEED_INTERVAL=3600 + run speed_test_due eth0 + [ "$status" -eq 1 ] +} + +@test "speed_test_due: old state file -> due (exit 0)" { + echo "0" > "$SPEED_STATE_DIR/speed_last_eth0" + export SPEED_INTERVAL=300 + run speed_test_due eth0 + [ "$status" -eq 0 ] +} diff --git a/tests/bats/starlink.bats b/tests/bats/starlink.bats new file mode 100644 index 0000000..3ca0943 --- /dev/null +++ b/tests/bats/starlink.bats @@ -0,0 +1,59 @@ +FIXTURES="$BATS_TEST_DIRNAME/../fixtures" + +setup() { + load '../../package/kit-busrouter/files/usr/lib/busrouter/starlink.sh' +} + +# Helper: run _starlink_parse on a fixture file +_parse_fixture() { + _starlink_parse < "$FIXTURES/$1" +} + +@test "starlink: online dish: lat=28 dl=200000000 obstructed=0 outage=0" { + run _parse_fixture starlink-online.json + [ "$status" -eq 0 ] + lat=$(echo "$output" | awk '{print $1}') + dl=$(echo "$output" | awk '{print $2}') + obs=$(echo "$output" | awk '{print $3}') + outage=$(echo "$output" | awk '{print $4}') + [ "$lat" -eq 28 ] + [ "$dl" -gt 100000000 ] + [ "$obs" -eq 0 ] + [ "$outage" -eq 0 ] +} + +@test "starlink: obstructed dish: obstructed=1" { + run _parse_fixture starlink-obstructed.json + [ "$status" -eq 0 ] + obs=$(echo "$output" | awk '{print $3}') + [ "$obs" -eq 1 ] +} + +@test "starlink: obstructed dish: fractionObstructed>0.01 sets obstructed=1" { + run _parse_fixture starlink-obstructed.json + [ "$status" -eq 0 ] + obs=$(echo "$output" | awk '{print $3}') + [ "$obs" -eq 1 ] +} + +@test "starlink: outage dish: outage=1" { + run _parse_fixture starlink-outage.json + [ "$status" -eq 0 ] + outage=$(echo "$output" | awk '{print $4}') + [ "$outage" -eq 1 ] +} + +@test "starlink: outage dish: dl=0 lat=0" { + run _parse_fixture starlink-outage.json + [ "$status" -eq 0 ] + lat=$(echo "$output" | awk '{print $1}') + dl=$(echo "$output" | awk '{print $2}') + [ "$lat" -eq 0 ] + [ "$dl" -eq 0 ] +} + +@test "starlink: grpcurl absent returns exit 1" { + export GRPCURL=/nonexistent/grpcurl + run starlink_status + [ "$status" -eq 1 ] +} diff --git a/tests/bats/telemetry-synology.bats b/tests/bats/telemetry-synology.bats new file mode 100644 index 0000000..9c2d92f --- /dev/null +++ b/tests/bats/telemetry-synology.bats @@ -0,0 +1,225 @@ +# telemetry-synology.bats — Tests for Synology telemetry agent + +LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)" + +setup() { + export LIB_DIR="$LIBDIR" + export AIWANBAL_STATE_DIR="$BATS_TMPDIR/aiwanbal" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/telemetry-buf" + export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry" + rm -rf "$AIWANBAL_STATE_DIR" "$TELEMETRY_BUFFER_DIR" + mkdir -p "$AIWANBAL_STATE_DIR" + + # Seed minimal aiwanbal state for collection tests + printf '85\n' > "$AIWANBAL_STATE_DIR/wan1_score_avg" + printf '15\n' > "$AIWANBAL_STATE_DIR/wan1_latency" + printf '50000000\n' > "$AIWANBAL_STATE_DIR/wan1_throughput" # 50 Mbps + printf '10000000\n' > "$AIWANBAL_STATE_DIR/wan1_upload" + printf '2\n' > "$AIWANBAL_STATE_DIR/wan1_jitter" + printf '0\n' > "$AIWANBAL_STATE_DIR/wan1_packet_loss" + printf -- '-80\n' > "$AIWANBAL_STATE_DIR/wan1_rsrp" + printf -- '-12\n' > "$AIWANBAL_STATE_DIR/wan1_rsrq" + printf '8\n' > "$AIWANBAL_STATE_DIR/wan1_sinr" + printf '5G-NR\n' > "$AIWANBAL_STATE_DIR/wan1_technology" + printf 'eyeride\n' > "$AIWANBAL_STATE_DIR/wan1_modem_type" + + printf '70\n' > "$AIWANBAL_STATE_DIR/wan2_score_avg" + printf '25\n' > "$AIWANBAL_STATE_DIR/wan2_latency" + printf '30000000\n' > "$AIWANBAL_STATE_DIR/wan2_throughput" + printf '5000000\n' > "$AIWANBAL_STATE_DIR/wan2_upload" + printf '5\n' > "$AIWANBAL_STATE_DIR/wan2_jitter" + printf '1\n' > "$AIWANBAL_STATE_DIR/wan2_packet_loss" + printf -- '-90\n' > "$AIWANBAL_STATE_DIR/wan2_rsrp" + printf 'generic\n' > "$AIWANBAL_STATE_DIR/wan2_modem_type" + + printf 'active\n' > "$AIWANBAL_STATE_DIR/wan0_mode" + + # Fake device identity + mkdir -p /etc/busrouter + printf 'B0042\n' > /etc/busrouter/device-id + printf '1.0\n' > /etc/busrouter/version + + # Load the library under test + load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry-synology.sh' +} + +teardown() { + rm -rf "$AIWANBAL_STATE_DIR" "$TELEMETRY_BUFFER_DIR" + rm -f /etc/busrouter/device-id /etc/busrouter/version +} + +# ── Data collection ────────────────────────────────────────────────────────────── + +@test "telemetry_synology_collect: output contains required JSON keys" { + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"device_id"' + echo "$output" | grep -q '"timestamp"' + echo "$output" | grep -q '"uptime"' + echo "$output" | grep -q '"version"' + echo "$output" | grep -q '"platform":"synology"' + echo "$output" | grep -q '"gps"' + echo "$output" | grep -q '"wan"' + echo "$output" | grep -q '"wan1"' + echo "$output" | grep -q '"wan2"' + echo "$output" | grep -q '"starlink"' + echo "$output" | grep -q '"sel_primary"' +} + +@test "telemetry_synology_collect: device_id from identity file" { + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"device_id":"B0042"' +} + +@test "telemetry_synology_collect: falls back to hostname when identity file missing" { + rm -f /etc/busrouter/device-id + run telemetry_synology_collect + [ "$status" -eq 0 ] + # Should contain a device_id (whatever hostname returns) + echo "$output" | grep -q '"device_id"' +} + +@test "telemetry_synology_collect: handles missing state files gracefully" { + rm -rf "$AIWANBAL_STATE_DIR" + mkdir -p "$AIWANBAL_STATE_DIR" + # No state files at all — should still produce valid JSON + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"wan1"' + echo "$output" | grep -q '"wan2"' +} + +@test "telemetry_synology_collect: sel_primary is wan1 in active mode" { + printf 'active\n' > "$AIWANBAL_STATE_DIR/wan0_mode" + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"sel_primary":"wan1"' +} + +@test "telemetry_synology_collect: sel_primary is wan2 in failover when wan2 up" { + printf 'failover\n' > "$AIWANBAL_STATE_DIR/wan0_mode" + printf 'up\n' > "$AIWANBAL_STATE_DIR/wan2_state" + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"sel_primary":"wan2"' +} + +@test "telemetry_synology_collect: wan JSON includes cellular metadata" { + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"rsrp_dbm"' + echo "$output" | grep -q '"technology"' + echo "$output" | grep -q '"carrier"' + echo "$output" | grep -q '"5G-NR"' +} + +@test "telemetry_synology_collect: throughput converted from B/s to Mbps" { + run telemetry_synology_collect + [ "$status" -eq 0 ] + # 50000000 B/s = ~400 Mbps → should appear as "dl_mbps":400.0 or similar + echo "$output" | grep -q '"dl_mbps"' +} + +@test "telemetry_synology_collect: gps is null when Eyeride unavailable" { + # Without EYERIDE_PASSWORD, eyeride_gps returns 1 → gps fields are null + unset EYERIDE_PASSWORD + run telemetry_synology_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"lat":null' + echo "$output" | grep -q '"lon":null' + echo "$output" | grep -q '"fix":null' +} + +# ── Buffering + POST ───────────────────────────────────────────────────────────── + +@test "telemetry_synology_send: buffers payload when curl fails" { + mkdir -p "$BATS_TMPDIR/tel_stub" + printf '#!/bin/sh\nexit 7\n' > "$BATS_TMPDIR/tel_stub/curl" + chmod +x "$BATS_TMPDIR/tel_stub/curl" + export PATH="$BATS_TMPDIR/tel_stub:$PATH" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/buf_test" + rm -rf "$TELEMETRY_BUFFER_DIR" + + run telemetry_synology_send + [ -d "$TELEMETRY_BUFFER_DIR" ] + buf_count=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | wc -l) + [ "$buf_count" -ge 1 ] +} + +@test "telemetry_synology_flush: posts and removes buffered file on success" { + mkdir -p "$BATS_TMPDIR/tel_stub2" + printf '#!/bin/sh\necho 200\n' > "$BATS_TMPDIR/tel_stub2/curl" + chmod +x "$BATS_TMPDIR/tel_stub2/curl" + export PATH="$BATS_TMPDIR/tel_stub2:$PATH" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/flush_test" + mkdir -p "$TELEMETRY_BUFFER_DIR" + echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1234567890_test.json" + + run telemetry_synology_flush + [ "$status" -eq 0 ] + [ ! -f "$TELEMETRY_BUFFER_DIR/1234567890_test.json" ] +} + +@test "telemetry_synology_flush: stops on first failure" { + mkdir -p "$BATS_TMPDIR/tel_stub3" + # curl that fails on second call + printf '#!/bin/sh\nif [ -f /tmp/first_done ]; then exit 7; fi\ntouch /tmp/first_done; echo 200\n' > "$BATS_TMPDIR/tel_stub3/curl" + chmod +x "$BATS_TMPDIR/tel_stub3/curl" + export PATH="$BATS_TMPDIR/tel_stub3:$PATH" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/flush_fail" + rm -f /tmp/first_done + mkdir -p "$TELEMETRY_BUFFER_DIR" + echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1111111111_a.json" + echo '{"test":2}' > "$TELEMETRY_BUFFER_DIR/2222222222_b.json" + + run telemetry_synology_flush + [ "$status" -ne 0 ] + # First file should be deleted, second should remain + [ ! -f "$TELEMETRY_BUFFER_DIR/1111111111_a.json" ] + [ -f "$TELEMETRY_BUFFER_DIR/2222222222_b.json" ] + rm -f /tmp/first_done +} + +# ── WAN JSON fragment ──────────────────────────────────────────────────────────── + +@test "_synology_wan_json: produces valid JSON fragment" { + run _synology_wan_json 1 + [ "$status" -eq 0 ] + # Should be a valid JSON object (starts with {, ends with }) + echo "$output" | grep -q '^{' + echo "$output" | grep -q '}$' + echo "$output" | grep -q '"score"' + echo "$output" | grep -q '"latency_ms"' + echo "$output" | grep -q '"carrier"' +} + +@test "_synology_wan_json: defaults score to 0 when file missing" { + rm -f "$AIWANBAL_STATE_DIR/wan1_score_avg" + run _synology_wan_json 1 + [ "$status" -eq 0 ] + echo "$output" | grep -q '"score":0' +} + +@test "_synology_wan_json: modem type maps to carrier name" { + printf 'peplink\n' > "$AIWANBAL_STATE_DIR/wan1_modem_type" + run _synology_wan_json 1 + [ "$status" -eq 0 ] + echo "$output" | grep -q '"carrier":"Peplink"' +} + +# ── eyeride_gps ────────────────────────────────────────────────────────────────── + +@test "eyeride_gps: returns 1 when no password set" { + unset EYERIDE_PASSWORD + run eyeride_gps + [ "$status" -ne 0 ] +} + +@test "eyeride_gps: returns 1 when eyeride unreachable" { + export EYERIDE_PASSWORD="test" + export EYERIDE_IP="192.0.2.1" # TEST-NET-1 — guaranteed unreachable + export EYERIDE_PORT="9999" + run eyeride_gps + [ "$status" -ne 0 ] +} diff --git a/tests/bats/telemetry.bats b/tests/bats/telemetry.bats new file mode 100644 index 0000000..3075780 --- /dev/null +++ b/tests/bats/telemetry.bats @@ -0,0 +1,73 @@ +FIXTURES="$BATS_TEST_DIRNAME/../fixtures" + +LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)" + +setup() { + export LIB_DIR="$LIBDIR" + load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry.sh' +} + +@test "gps_fix parse: 3D fix returns lat lon fix_quality=3" { + run _gps_parse < "$FIXTURES/gps-fix.txt" + [ "$status" -eq 0 ] + lat=$(echo "$output" | awk '{print $1}') + lon=$(echo "$output" | awk '{print $2}') + fix=$(echo "$output" | awk '{print $3}') + [ "$lat" = "40.712776" ] + [ "$lon" = "-74.005974" ] + [ "$fix" -eq 3 ] +} + +@test "gps_fix parse: no-fix response exits 1" { + run _gps_parse < "$FIXTURES/gps-nofix.txt" + [ "$status" -ne 0 ] + [ -z "$output" ] +} + +@test "gps_fix parse: empty input exits 1" { + run _gps_parse < /dev/null + [ "$status" -ne 0 ] + [ -z "$output" ] +} + +# --- Task 5.2: telemetry collect + POST --- + +@test "telemetry_collect: output contains required JSON keys" { + run telemetry_collect + [ "$status" -eq 0 ] + echo "$output" | grep -q '"device_id"' + echo "$output" | grep -q '"timestamp"' + echo "$output" | grep -q '"gps"' + echo "$output" | grep -q '"wan"' + echo "$output" | grep -q '"starlink"' + echo "$output" | grep -q '"sim_slot"' + echo "$output" | grep -q '"uptime"' +} + +@test "telemetry_send: buffers payload when curl fails" { + mkdir -p "$BATS_TMPDIR/tel_stub" + printf '#!/bin/sh\nexit 7\n' > "$BATS_TMPDIR/tel_stub/curl" + chmod +x "$BATS_TMPDIR/tel_stub/curl" + export PATH="$BATS_TMPDIR/tel_stub:$PATH" + export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/tel_buf" + rm -rf "$TELEMETRY_BUFFER_DIR" + run telemetry_send + [ -d "$TELEMETRY_BUFFER_DIR" ] + buf_count=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | wc -l) + [ "$buf_count" -ge 1 ] +} + +@test "telemetry_flush: posts and removes buffered file on success" { + mkdir -p "$BATS_TMPDIR/tel_stub2" + printf '#!/bin/sh\necho 200\n' > "$BATS_TMPDIR/tel_stub2/curl" + chmod +x "$BATS_TMPDIR/tel_stub2/curl" + export PATH="$BATS_TMPDIR/tel_stub2:$PATH" + export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry" + export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/tel_flush_buf" + mkdir -p "$TELEMETRY_BUFFER_DIR" + echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1234567890_test.json" + run telemetry_flush + [ "$status" -eq 0 ] + [ ! -f "$TELEMETRY_BUFFER_DIR/1234567890_test.json" ] +} diff --git a/tests/fixtures/gps-fix.txt b/tests/fixtures/gps-fix.txt new file mode 100644 index 0000000..7dd98cd --- /dev/null +++ b/tests/fixtures/gps-fix.txt @@ -0,0 +1,2 @@ ++QGPSLOC: 154321.000,40.712776,-74.005974,1.1,10.2,3,0.00,0.0,0.0,010124,08 +OK diff --git a/tests/fixtures/gps-nofix.txt b/tests/fixtures/gps-nofix.txt new file mode 100644 index 0000000..e3271d3 --- /dev/null +++ b/tests/fixtures/gps-nofix.txt @@ -0,0 +1 @@ ++CME ERROR: 516 diff --git a/tests/fixtures/modem-signal-at.txt b/tests/fixtures/modem-signal-at.txt new file mode 100644 index 0000000..12df374 --- /dev/null +++ b/tests/fixtures/modem-signal-at.txt @@ -0,0 +1,2 @@ ++QCSQ: "NR5G",-97,14,-10 +OK diff --git a/tests/fixtures/modem-signal-ubus.json b/tests/fixtures/modem-signal-ubus.json new file mode 100644 index 0000000..9765514 --- /dev/null +++ b/tests/fixtures/modem-signal-ubus.json @@ -0,0 +1,7 @@ +{ + "signals": [{ + "sinr": 13, "slot": 1, "network_type": "NR5G-SA", + "timestamp": 1782917019, "strength": 4, + "rsrp": -96, "rsrq": -10, "mode": 5 + }] +} diff --git a/tests/fixtures/ping-loss.txt b/tests/fixtures/ping-loss.txt new file mode 100644 index 0000000..f69af7f --- /dev/null +++ b/tests/fixtures/ping-loss.txt @@ -0,0 +1,13 @@ +PING 1.1.1.1 (1.1.1.1): 56 data bytes +64 bytes from 1.1.1.1: seq=0 ttl=55 time=10.0 ms +64 bytes from 1.1.1.1: seq=1 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=2 ttl=55 time=10.0 ms +64 bytes from 1.1.1.1: seq=3 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=4 ttl=55 time=10.0 ms +64 bytes from 1.1.1.1: seq=5 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=6 ttl=55 time=10.0 ms +64 bytes from 1.1.1.1: seq=7 ttl=55 time=20.0 ms + +--- 1.1.1.1 ping statistics --- +10 packets transmitted, 8 packets received, 20% packet loss +round-trip min/avg/max = 10.0/15.0/20.0 ms diff --git a/tests/fixtures/ping-ok.txt b/tests/fixtures/ping-ok.txt new file mode 100644 index 0000000..215f59e --- /dev/null +++ b/tests/fixtures/ping-ok.txt @@ -0,0 +1,15 @@ +PING 1.1.1.1 (1.1.1.1): 56 data bytes +64 bytes from 1.1.1.1: seq=0 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=1 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=2 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=3 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=4 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=5 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=6 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=7 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=8 ttl=55 time=15.0 ms +64 bytes from 1.1.1.1: seq=9 ttl=55 time=15.0 ms + +--- 1.1.1.1 ping statistics --- +10 packets transmitted, 10 packets received, 0% packet loss +round-trip min/avg/max = 15.0/15.0/15.0 ms diff --git a/tests/fixtures/ping-partial.txt b/tests/fixtures/ping-partial.txt new file mode 100644 index 0000000..0803325 --- /dev/null +++ b/tests/fixtures/ping-partial.txt @@ -0,0 +1,6 @@ +PING 1.1.1.1 (1.1.1.1): 56 data bytes +64 bytes from 1.1.1.1: seq=0 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=1 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=2 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=3 ttl=55 time=20.0 ms +64 bytes from 1.1.1.1: seq=4 ttl=55 time=20.0 ms diff --git a/tests/fixtures/ping-timeout.txt b/tests/fixtures/ping-timeout.txt new file mode 100644 index 0000000..d777b59 --- /dev/null +++ b/tests/fixtures/ping-timeout.txt @@ -0,0 +1,4 @@ +PING 1.1.1.1 (1.1.1.1): 56 data bytes + +--- 1.1.1.1 ping statistics --- +10 packets transmitted, 0 packets received, 100% packet loss diff --git a/tests/fixtures/starlink-obstructed.json b/tests/fixtures/starlink-obstructed.json new file mode 100644 index 0000000..43bb801 --- /dev/null +++ b/tests/fixtures/starlink-obstructed.json @@ -0,0 +1,13 @@ +{ + "dishGetStatus": { + "obstructionStats": { + "fractionObstructed": 0.35, + "currentlyObstructed": true, + "last24hObstructedS": 1200 + }, + "downlinkThroughputBps": 5000000.0, + "uplinkThroughputBps": 1000000.0, + "popPingLatencyMs": 95.0, + "popPingDropRate": 0.12 + } +} diff --git a/tests/fixtures/starlink-online.json b/tests/fixtures/starlink-online.json new file mode 100644 index 0000000..fd6ecef --- /dev/null +++ b/tests/fixtures/starlink-online.json @@ -0,0 +1,21 @@ +{ + "dishGetStatus": { + "deviceInfo": { + "id": "ut01000000-00000000-00000000", + "hardwareVersion": "rev3_proto3", + "softwareVersion": "2024.01.01.0" + }, + "deviceState": { + "uptimeS": "86400" + }, + "obstructionStats": { + "fractionObstructed": 0.002, + "currentlyObstructed": false, + "last24hObstructedS": 5 + }, + "downlinkThroughputBps": 200000000.0, + "uplinkThroughputBps": 10000000.0, + "popPingLatencyMs": 28.5, + "popPingDropRate": 0.0 + } +} diff --git a/tests/fixtures/starlink-outage.json b/tests/fixtures/starlink-outage.json new file mode 100644 index 0000000..6afe8ee --- /dev/null +++ b/tests/fixtures/starlink-outage.json @@ -0,0 +1,16 @@ +{ + "dishGetStatus": { + "obstructionStats": { + "fractionObstructed": 0.0, + "currentlyObstructed": false + }, + "downlinkThroughputBps": 0.0, + "uplinkThroughputBps": 0.0, + "popPingLatencyMs": 0.0, + "outage": { + "cause": "NO_SATS", + "startTimestampNs": "1751337600000000000", + "duration": 30 + } + } +}