From d628a43db2b76638bd7d8a07fa26f3c914c24de8 Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 13:00:09 -0400 Subject: [PATCH 1/6] ci(shell): syntax-check Bash client --- .github/workflows/nine-client-e2e.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/nine-client-e2e.yml b/.github/workflows/nine-client-e2e.yml index f82abcc..c0159e1 100644 --- a/.github/workflows/nine-client-e2e.yml +++ b/.github/workflows/nine-client-e2e.yml @@ -102,6 +102,10 @@ jobs: shell: bash run: cd clients/python && PYTHONPATH=src python3 -m live_mutex_client.smoke + - name: Shell syntax checks + shell: bash + run: bash -n ./clients/shell/live_mutex_client.sh ./clients/shell/smoke.sh + - name: Shell source contract shell: bash run: bash ./clients/shell/source-contract.sh From 9b7b36c69da5a8e93a6371d20a4a6d48993ec457 Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 13:00:44 -0400 Subject: [PATCH 2/6] ci: pin ores-lint actions to immutable SHAs --- .github/workflows/ores-lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ores-lint.yml b/.github/workflows/ores-lint.yml index 9ff8409..df12654 100644 --- a/.github/workflows/ores-lint.yml +++ b/.github/workflows/ores-lint.yml @@ -10,7 +10,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Detect project types id: detect @@ -26,7 +26,7 @@ jobs: # Node is the runner for ESLint and for the cross-language require-send # scanner, so it is installed whenever any supported language is present. - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 if: steps.detect.outputs.js == 'true' || steps.detect.outputs.rs == 'true' || steps.detect.outputs.dart == 'true' || steps.detect.outputs.gleam == 'true' with: node-version: '22' @@ -43,7 +43,7 @@ jobs: # Dart/Gleam SDKs are optional. When absent, dart.sh / gleam.sh skip # with an actionable message; require-send.mjs still runs via Node. - - uses: dart-lang/setup-dart@v1 + - uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1 if: steps.detect.outputs.dart == 'true' continue-on-error: true From 46290ec8032bde1ddf6e405083ee00dd155bb419 Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 13:07:25 -0400 Subject: [PATCH 3/6] style: enforce explicit blocks and one statement per line --- eslint.config.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index ee5719d..a2f76eb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,6 +6,9 @@ import oresConfig from './.ores-lint/eslint/base.mjs'; export default await oresConfig({ // requireSend: { loggerNames: ['myLogger'], terminalMethods: ['send', 'flush'] }, - // rules: { 'no-console': 'warn' }, + rules: { + curly: ['warn', 'all'], + 'max-statements-per-line': ['warn', {max: 1}], + }, // ignores: ['**/generated/**'], }); From a662de38eb258e8a31ac73b069542d7f1ec0ee31 Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 13:09:50 -0400 Subject: [PATCH 4/6] style: scope no-compression lint to hand-written sources --- eslint.config.mjs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index a2f76eb..0dd17b3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,11 +4,27 @@ // not overwrite this file once you have changed it. import oresConfig from './.ores-lint/eslint/base.mjs'; -export default await oresConfig({ - // requireSend: { loggerNames: ['myLogger'], terminalMethods: ['send', 'flush'] }, +const baseConfig = await oresConfig({ rules: { curly: ['warn', 'all'], - 'max-statements-per-line': ['warn', {max: 1}], }, - // ignores: ['**/generated/**'], }); + +export default [ + ...baseConfig, + { + files: [ + 'src/**/*.ts', + 'test/**/*.ts', + 'formal/**/*.mjs', + 'scripts/**/*.js', + 'clients/**/*.js', + 'clients/**/*.mjs', + 'clients/**/*.ts', + 'clients/**/*.tsx', + ], + rules: { + 'max-statements-per-line': ['warn', {max: 1}], + }, + }, +]; From 383f047e7ef1dc036594a6695b82d54734336df2 Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 14:14:26 -0400 Subject: [PATCH 5/6] style: expand C++ protocol control flow --- clients/cpp/include/live_mutex/protocol.hpp | 54 +++++++++++++++------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/clients/cpp/include/live_mutex/protocol.hpp b/clients/cpp/include/live_mutex/protocol.hpp index 8cead67..106bd4a 100644 --- a/clients/cpp/include/live_mutex/protocol.hpp +++ b/clients/cpp/include/live_mutex/protocol.hpp @@ -52,20 +52,34 @@ struct Response { r.contended_key = r.raw.str_or("contendedKey"); if (const json::Value* ks = r.raw.find("keys"); ks && ks->type() == json::Type::Array) { - for (const auto& v : ks->as_array()) - if (v.type() == json::Type::String) r.keys.push_back(v.as_string()); + for (const auto& v : ks->as_array()) { + if (v.type() == json::Type::String) { + r.keys.push_back(v.as_string()); + } + } } if (const json::Value* ft = r.raw.find("fencingTokens"); ft && ft->type() == json::Type::Object) { - for (const auto& [k, v] : ft->as_object()) - if (v.type() == json::Type::Number) r.fencing_tokens[k] = v.as_u64(); + for (const auto& [k, v] : ft->as_object()) { + if (v.type() == json::Type::Number) { + r.fencing_tokens[k] = v.as_u64(); + } + } } - if (r.raw_type == "lock") r.type = ResponseType::Lock; - else if (r.raw_type == "unlock" || r.raw.contains("unlocked")) r.type = ResponseType::Unlock; - else if (r.raw_type == "version-mismatch") r.type = ResponseType::VersionMismatch; - else if (r.raw.contains("released")) r.type = ResponseType::ReleaseMany; - else if (r.raw.contains("fencingTokens") || (r.raw.contains("lockUuid") && r.raw.contains("acquired"))) + if (r.raw_type == "lock") { + r.type = ResponseType::Lock; + } else if (r.raw_type == "unlock" || r.raw.contains("unlocked")) { + r.type = ResponseType::Unlock; + } else if (r.raw_type == "version-mismatch") { + r.type = ResponseType::VersionMismatch; + } else if (r.raw.contains("released")) { + r.type = ResponseType::ReleaseMany; + } else if ( + r.raw.contains("fencingTokens") || + (r.raw.contains("lockUuid") && r.raw.contains("acquired")) + ) { r.type = ResponseType::AcquireMany; + } return r; } }; @@ -85,9 +99,14 @@ inline std::string lock_request(const std::string& uuid, const std::string& key, o["key"] = key; o["pid"] = static_cast(pid); o["keepLocksAfterDeath"] = false; - if (ttl_ms > 0) o["ttl"] = static_cast(ttl_ms); - else o["ttl"] = nullptr; - if (max_holders) o["max"] = static_cast(*max_holders); + if (ttl_ms > 0) { + o["ttl"] = static_cast(ttl_ms); + } else { + o["ttl"] = nullptr; + } + if (max_holders) { + o["max"] = static_cast(*max_holders); + } return json::Value(std::move(o)).dump(); } @@ -105,13 +124,18 @@ inline std::string unlock_request(const std::string& uuid, const std::string& ke inline std::string acquire_many_request(const std::string& uuid, const std::vector& keys, uint64_t ttl_ms) { json::Array arr; - for (const auto& k : keys) arr.push_back(json::Value(k)); + for (const auto& k : keys) { + arr.push_back(json::Value(k)); + } json::Object o; o["type"] = std::string("acquire-many"); o["uuid"] = uuid; o["keys"] = json::Value(std::move(arr)); - if (ttl_ms > 0) o["ttl"] = static_cast(ttl_ms); - else o["ttl"] = nullptr; + if (ttl_ms > 0) { + o["ttl"] = static_cast(ttl_ms); + } else { + o["ttl"] = nullptr; + } return json::Value(std::move(o)).dump(); } From f328f958c7f04fdcba0484af4e44bbd361bd5e4a Mon Sep 17 00:00:00 2001 From: "Mr. Denman Mills" Date: Sat, 26 Sep 2026 14:18:35 -0400 Subject: [PATCH 6/6] style: expand C++ JSON switch branches --- clients/cpp/include/live_mutex/json.hpp | 90 ++++++++++++++++++------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/clients/cpp/include/live_mutex/json.hpp b/clients/cpp/include/live_mutex/json.hpp index 8b16a1e..9ff5315 100644 --- a/clients/cpp/include/live_mutex/json.hpp +++ b/clients/cpp/include/live_mutex/json.hpp @@ -88,10 +88,18 @@ class Value { private: void write(std::ostringstream& os) const { switch (type_) { - case Type::Null: os << "null"; break; - case Type::Bool: os << (bool_ ? "true" : "false"); break; - case Type::Number: os << str_; break; - case Type::String: write_string(os, str_); break; + case Type::Null: + os << "null"; + break; + case Type::Bool: + os << (bool_ ? "true" : "false"); + break; + case Type::Number: + os << str_; + break; + case Type::String: + write_string(os, str_); + break; case Type::Array: { os << '['; for (size_t i = 0; i < arr_.size(); ++i) { @@ -125,11 +133,21 @@ class Value { os << '"'; for (char c : s) { switch (c) { - case '"': os << "\\\""; break; - case '\\': os << "\\\\"; break; - case '\n': os << "\\n"; break; - case '\r': os << "\\r"; break; - case '\t': os << "\\t"; break; + case '"': + os << "\\\""; + break; + case '\\': + os << "\\\\"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + case '\t': + os << "\\t"; + break; default: if (static_cast(c) < 0x20) { char buf[8]; @@ -186,12 +204,19 @@ class Parser { Value parse_value() { char c = peek(); switch (c) { - case '{': return parse_object(); - case '[': return parse_array(); - case '"': return Value(parse_string()); - case 't': case 'f': return parse_bool(); - case 'n': return parse_null(); - default: return parse_number(); + case '{': + return parse_object(); + case '[': + return parse_array(); + case '"': + return Value(parse_string()); + case 't': + case 'f': + return parse_bool(); + case 'n': + return parse_null(); + default: + return parse_number(); } } @@ -271,14 +296,30 @@ class Parser { } char e = s_[i_++]; switch (e) { - case '"': out.push_back('"'); break; - case '\\': out.push_back('\\'); break; - case '/': out.push_back('/'); break; - case 'n': out.push_back('\n'); break; - case 'r': out.push_back('\r'); break; - case 't': out.push_back('\t'); break; - case 'b': out.push_back('\b'); break; - case 'f': out.push_back('\f'); break; + case '"': + out.push_back('"'); + break; + case '\\': + out.push_back('\\'); + break; + case '/': + out.push_back('/'); + break; + case 'n': + out.push_back('\n'); + break; + case 'r': + out.push_back('\r'); + break; + case 't': + out.push_back('\t'); + break; + case 'b': + out.push_back('\b'); + break; + case 'f': + out.push_back('\f'); + break; case 'u': { if (i_ + 4 > s_.size()) { throw ParseError("bad \\u escape"); @@ -297,7 +338,8 @@ class Parser { } break; } - default: throw ParseError("unknown escape"); + default: + throw ParseError("unknown escape"); } } else { out.push_back(c);