Commit 9bcb71f
committed
store: tasks are durable on Valkey, and the plugin path is what proves it
`busbar_api::Store` defaults `put_task` to `Ok(())`, `get_task` to `Ok(None)`
and `list_tasks` to `Ok(vec![])`. This backend overrode none of them, so every
task write a Valkey deployment made was DISCARDED and REPORTED AS SUCCESS.
store-sqlite and store-mysql already persist tasks; Valkey and Postgres did
not, so half the fleet silently fell back to the in-memory default and an
operator would have found "task state survives a restart" false on their own
deployment, which is the worst place to discover it.
Nothing in the existing gate could see it. The conformance suite boots the
in-process RAM store, where those defaults ARE the honest answer and nothing
looks wrong; and in production this backend is only ever reached AS A PLUGIN,
which was the one path with zero coverage of these methods. So the proof added
here is over the real path, not the crate: a real dlopen of the built cdylib,
the real C ABI, the real DynStore, an unload/reload between the write and the
read, and a third leg that reads the same rows back through a plain ValkeyStore
that never touches the cdylib at all.
RED FIRST, on that test, before a line of the implementation existed:
test task_store_survives_an_unload_and_reload_over_the_real_plugin_abi ... FAILED
panicked at store-valkey-plugin/tests/e2e.rs:1053:58:
an in-flight task must survive the unload/reload over the plugin ABI; got
None back, which is exactly the accept-and-keep-nothing shape of the trait
default an unimplemented backend (or an unrelayed seam) substitutes
This is a key-value store, so the shape is NOT the SQL siblings' shape and that
is the substrate answering, not a shortcut:
- The row is ONE JSON STRING at `busbar:task:row:<id>`, not a column per field.
Valkey indexes nothing by itself, so columns would buy nothing and cost a
hash-field encoding for every field. It also means the FULL u64 range
round-trips with no clamp and no refusal: the SQL siblings need either an
unsigned column (mysql) or an outright refusal (sqlite, postgres) because a
clamped artifact_cursor reads back as a different number and then replays
delivered artifacts or skips undelivered ones with no error reported. Here
there is no ceiling to hit. `the_task_store_round_trips_the_full_u64_range`.
- The two questions JSON cannot answer get index structures, exactly as the MCP
call log does: `busbar:tasks` (SET) is the enumeration list_tasks walks, and
`busbar:tasks:byupdated` (ZSET by updated_at) is the retention sweep's age
index. The ZSET is an INDEX, never the truth -- purge_tasks_before re-reads
each candidate ROW and re-checks its state and updated_at under WATCH before
deleting, so a put_task that resumes an interrupted task between the
candidate read and the delete aborts the transaction instead of losing the
resumed task. That is also what makes an IEEE-double score safe as a coarse
filter, and it is why the count returned is one actually performed rather
than the size of the candidate list.
- Per-task provenance is one ZSET per task scored by seq, so the read comes
back in chain order for free. The upsert on (task_id, seq) is
ZREMRANGEBYSCORE-then-ZADD, atomically, and that pairing is load-bearing: a
corrected event is a DIFFERENT member string at the SAME score, which a bare
ZADD would add ALONGSIDE the old one -- two events at one seq, the exact
duplication the contract rules out. The call log's fork check is deliberately
NOT copied here; it would be wrong in a way that looks right, because the
task-event write-through is specified to be idempotent on replay.
- Key construction is collision-free STRUCTURALLY, not by convention. `row:`
and `events:` are fixed distinct segments and there is exactly ONE variable
component per key, so no task id can make its row key render as another
task's events key. This file already carries a recorded hazard where
credential keys join caller-supplied components on an unescaped ':' and two
distinct tuples render to one key; a task id is protocol-supplied and opaque,
colons very much included, so that question had to be answered rather than
assumed. `a_task_id_containing_the_key_separator_cannot_alias_another_tasks_chain`
is the adversarial spelling of it.
SCHEMA_VERSION deliberately does NOT move, and the constant now says why. This
keyspace is purely additive, so there is nothing for a migration to do -- and a
bump here does not mean "migrate", it means WIPE: migrate() handles any
version < SCHEMA_VERSION by SCANning `busbar:*` and deleting everything, whose
own justification ("1.5.0 is unreleased") expired three releases ago. The
durable MCP call log landed on the same reasoning. The marker moves again when
a migrate-in-place path exists to move it for.
On the collation class of bug store-mysql fixed (`vk_alice` reading
`vk_Alice`'s chain): it has no way in here. A Valkey key is compared as bytes
and the terminal-state check is a Rust `==` on &str, so there is no collation
to get wrong. `task_ids_differing_only_in_case_are_distinct_tasks` is added to
keep it that way, because the consequence would be the siblings' consequence --
two ids collide on one row key and one task is silently lost.
Nine live-instance unit tests cover the same surface from the crate side. Gate
is green: fmt clean, clippy -D warnings clean, 54 lib + 5 e2e passing.1 parent ccf08a0 commit 9bcb71f
3 files changed
Lines changed: 1065 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
936 | 936 | | |
937 | 937 | | |
938 | 938 | | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
0 commit comments