Skip to content

Commit 796535d

Browse files
bjmeetsfosuperhaiouclaude
authored
Both languages honour the same spellings of TS_STORAGE_BACKEND (#1348)
resolve_backend says of itself that it "Mirrors StorageBackendConfig::resolve_decision", and nothing checked that it does. The engine accepts ten spellings across three backends and the plan carries its own copy of all ten. A spelling one side honours and the other does not is invisible from the outside: an unrecognised request does not fail, it falls through to auto-detection, which the docstring itself calls indistinguishable from having been honoured. The plan is what tells an operator which backend a deployment will get; the engine is what they actually get. The comparison is an EQUALITY, in both directions. Driving the plan with every rust spelling only shows that python honours everything rust does - it can never show a spelling python still advertises after rust dropped it, which is the direction that leaves the plan promising a forced backend the engine sends to auto. Both sets are parsed and compared, and the driven check is kept alongside so the parse cannot pass while the behaviour differs. Also pins the normalisation on both sides and the fall-through to Auto. Verified to fail on a spelling removed from the rust list, on a spelling removed from the python list, and on the trim-and-lowercase being dropped. Co-authored-by: Deeproute <haiou.jiang@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent bc88565 commit 796535d

1 file changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Copyright 2026 MatrixArkAI
4+
"""Both languages honour the same spellings of `TS_STORAGE_BACKEND`.
5+
6+
`matrixark_deployment_plan.resolve_backend` says of itself:
7+
8+
Mirrors `StorageBackendConfig::resolve_decision`: raft is forced; matrixobject is forced
9+
when compiled; shared is forced only when a directory is configured ...
10+
11+
Nothing checked that it does. The engine accepts **ten** spellings across three backends, and the
12+
plan has its own copy of all ten:
13+
14+
matrixobject | matrix_object | object
15+
shared | shared_path | shared_store | path
16+
raft | raft_replication | replication
17+
18+
A spelling one side honours and the other does not is not a cosmetic difference. The plan is what
19+
tells an operator which backend a deployment will get; the engine is what they actually get. They
20+
diverge silently, because a request that is not recognised does not fail — it falls through to
21+
auto-detection, which `resolve_backend`'s own docstring calls out as "from the outside ...
22+
indistinguishable from having been honoured".
23+
24+
The same shape already cost a production incident on the neighbouring rule: `TS_META_ADDR=local`
25+
was a sentinel to one implementation and a literal socket address to another, and every write on
26+
a one-box failed. See `test_both_languages_read_the_same_sentinels.py`.
27+
28+
Normalisation is part of the rule on both sides: rust matches on
29+
`value.trim().to_ascii_lowercase()`, python on `_clean(...).lower()`, so `" MatrixObject "` is
30+
honoured by both.
31+
"""
32+
from __future__ import annotations
33+
34+
import io
35+
import os
36+
import re
37+
import sys
38+
import unittest
39+
40+
TOOLS = os.path.dirname(os.path.abspath(__file__))
41+
REPO = os.path.dirname(TOOLS)
42+
RUST = os.path.join(REPO, "crates", "temporalstore-rust", "src", "storage_backend.rs")
43+
44+
sys.path.insert(0, TOOLS)
45+
46+
import matrixark_deployment_plan as plan # noqa: E402
47+
48+
#: One match arm of `BackendOverride::parse`: a run of `Some("...")` alternatives, then the
49+
#: variant it selects — either `Self::Name,` or a braced block holding `Self::Name`.
50+
_ARM = re.compile(
51+
r'(?P<spellings>Some\("[a-z_]+"\)(?:\s*\|\s*Some\("[a-z_]+"\))*)\s*=>\s*'
52+
r'(?:Self::(?P<direct>\w+)|\{\s*Self::(?P<braced>\w+)\s*\})')
53+
_SPELLING = re.compile(r'Some\("([a-z_]+)"\)')
54+
55+
#: The python side of the same rule: `if requested in ("a", "b"):` followed, within the branch,
56+
#: by the backend it returns. Parsed rather than probed, so the comparison below can be an
57+
#: EQUALITY -- probing only ever shows that python honours everything rust does, never that it
58+
#: honours something rust has since dropped.
59+
_PY_GROUP = re.compile(
60+
r'if\s+requested\s+in\s*\((?P<spellings>[^)]*)\)\s*:'
61+
r'(?P<body>(?:.|\n){0,400}?)"backend"\s*:\s*"(?P<backend>[a-z_]+)"')
62+
_PY_SPELLING = re.compile(r'"([a-z_]+)"')
63+
64+
#: The one correspondence that cannot be derived: the rust variant names and the strings the
65+
#: python plan reports are simply spelled differently. Exhaustiveness is asserted below, so a
66+
#: new variant fails this file rather than being skipped by it.
67+
VARIANT_TO_PLAN_BACKEND = {
68+
"MatrixObject": "matrixobject",
69+
"SharedPath": "shared_path",
70+
"Raft": "raft",
71+
}
72+
73+
#: What each backend needs before the plan will call the request honoured. Without these the
74+
#: engine forces the backend but the plan reports the auto fall-through, and the comparison
75+
#: would be measuring the precondition rather than the spelling.
76+
PRECONDITIONS = {
77+
"matrixobject": ({}, {"matrixobject_available": True}),
78+
"shared_path": ({"TS_SHARED_STORE_DIR": "/srv/shared"}, {}),
79+
"raft": ({}, {}),
80+
}
81+
82+
83+
def _rust_source():
84+
with io.open(RUST, encoding="utf-8") as handle:
85+
return handle.read()
86+
87+
88+
def plan_overrides():
89+
"""{backend: {spelling, ...}} read from resolve_backend."""
90+
with io.open(plan.__file__, encoding="utf-8") as handle:
91+
source = handle.read()
92+
found = {}
93+
for match in _PY_GROUP.finditer(source):
94+
found.setdefault(match.group("backend"), set()).update(
95+
_PY_SPELLING.findall(match.group("spellings")))
96+
return found
97+
98+
99+
def rust_overrides():
100+
"""{variant: {spelling, ...}} read from BackendOverride::parse."""
101+
found = {}
102+
for match in _ARM.finditer(_rust_source()):
103+
variant = match.group("direct") or match.group("braced")
104+
found[variant] = set(_SPELLING.findall(match.group("spellings")))
105+
return found
106+
107+
108+
class BothLanguagesHonourTheSameBackendSpellingsTest(unittest.TestCase):
109+
110+
def test_the_rust_arms_are_still_readable(self) -> None:
111+
"""Everything below iterates what this returns, so an empty scan would pass silently."""
112+
found = rust_overrides()
113+
self.assertGreaterEqual(
114+
len(found), 3,
115+
"read %d BackendOverride arms out of storage_backend.rs; if parse() changed shape, "
116+
"move this check with it rather than deleting it" % len(found))
117+
total = sum(len(v) for v in found.values())
118+
self.assertGreaterEqual(total, 8, "only %d spellings in total" % total)
119+
120+
def test_every_rust_variant_is_mapped(self) -> None:
121+
"""The mapping below is hand-written; this is what stops a new backend being skipped."""
122+
self.assertEqual(
123+
sorted(rust_overrides()), sorted(VARIANT_TO_PLAN_BACKEND),
124+
"BackendOverride variants and the plan mapping have diverged")
125+
126+
def test_the_two_spelling_sets_are_equal(self) -> None:
127+
"""Both directions. A spelling only RUST knows is one the plan will call auto while the
128+
engine forces it; a spelling only PYTHON knows is one the plan promises and the engine
129+
sends to auto-detection. The driven check below cannot see the second kind."""
130+
mine = plan_overrides()
131+
self.assertGreaterEqual(len(mine), 3,
132+
"read %d groups out of resolve_backend" % len(mine))
133+
for variant, spellings in sorted(rust_overrides().items()):
134+
backend = VARIANT_TO_PLAN_BACKEND[variant]
135+
with self.subTest(backend=backend):
136+
self.assertEqual(
137+
sorted(spellings), sorted(mine.get(backend, ())),
138+
"the engine and the plan accept different spellings for %s" % backend)
139+
140+
def test_the_plan_honours_every_spelling_the_engine_does(self) -> None:
141+
for variant, spellings in sorted(rust_overrides().items()):
142+
backend = VARIANT_TO_PLAN_BACKEND[variant]
143+
extra_env, kwargs = PRECONDITIONS[backend]
144+
for spelling in sorted(spellings):
145+
with self.subTest(variant=variant, spelling=spelling):
146+
env = dict(extra_env, TS_STORAGE_BACKEND=spelling)
147+
got = plan.resolve_backend(env, **kwargs)
148+
self.assertEqual(
149+
backend, got.get("backend"),
150+
"the engine reads %r as %s; the plan reports %r"
151+
% (spelling, variant, got.get("backend")))
152+
self.assertTrue(
153+
got.get("honoured"),
154+
"the plan does not recognise %r, so it reports the auto fall-through "
155+
"while the engine forces %s" % (spelling, variant))
156+
157+
def test_both_sides_normalise_the_request(self) -> None:
158+
normalises = re.search(
159+
r"raw\.map\(\|value\|\s*value\.trim\(\)\.to_ascii_lowercase\(\)\)", _rust_source())
160+
self.assertTrue(
161+
normalises,
162+
"BackendOverride::parse no longer trims and lowercases, so ` Raft ` stops being "
163+
"honoured by the engine while the plan still reports it as forced")
164+
for written in (" raft ", "RAFT", "\tRaft\n"):
165+
with self.subTest(written=written):
166+
got = plan.resolve_backend({"TS_STORAGE_BACKEND": written})
167+
self.assertEqual("raft", got.get("backend"))
168+
self.assertTrue(got.get("honoured"), "the plan did not honour %r" % written)
169+
170+
def test_an_unknown_request_falls_through_on_both_sides(self) -> None:
171+
"""The positive control, and the half that is easy to lose: an unrecognised value must
172+
reach auto-detection rather than being treated as a forced backend."""
173+
self.assertRegex(_rust_source(), r"_\s*=>\s*Self::Auto",
174+
"BackendOverride::parse no longer falls back to Auto")
175+
# `honoured` answers "did you get the backend you asked for", so an absent request is
176+
# trivially honoured -- it is not the signal for this. The signal is that an
177+
# unrecognised value lands where NO request lands: on auto-detection.
178+
auto = plan.resolve_backend({}).get("backend")
179+
for written in ("wat", "auto", ""):
180+
with self.subTest(written=written):
181+
self.assertEqual(
182+
auto, plan.resolve_backend({"TS_STORAGE_BACKEND": written}).get("backend"),
183+
"the plan treated %r as a forced backend; the engine sends it to Auto"
184+
% written)
185+
186+
187+
if __name__ == "__main__":
188+
unittest.main()

0 commit comments

Comments
 (0)