Skip to content

Commit 1c1761a

Browse files
authored
Merge pull request #3 from soficis/copilot/sub-pr-1-again
[WIP] Address feedback on build system fixes and controller refactor
2 parents 414a0a6 + 679756d commit 1c1761a

2 files changed

Lines changed: 108 additions & 1 deletion

File tree

src/ai/FeatureSchema.cpp

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,53 @@ double vectorValueOrDefault(const std::vector<double>& values, const size_t inde
99
return index < values.size() ? values[index] : 0.0;
1010
}
1111

12+
struct SemanticVersion {
13+
int major = 0;
14+
int minor = 0;
15+
int patch = 0;
16+
bool valid = false;
17+
};
18+
19+
SemanticVersion parseVersion(const std::string& versionStr) {
20+
SemanticVersion version;
21+
22+
if (versionStr.empty()) {
23+
return version;
24+
}
25+
26+
size_t pos = 0;
27+
size_t dotPos = versionStr.find('.');
28+
29+
try {
30+
// Parse major version
31+
if (dotPos == std::string::npos) {
32+
// Partial version with only major - not valid for strict semver
33+
return version;
34+
}
35+
36+
version.major = std::stoi(versionStr.substr(pos, dotPos - pos));
37+
pos = dotPos + 1;
38+
39+
// Parse minor version
40+
dotPos = versionStr.find('.', pos);
41+
if (dotPos == std::string::npos) {
42+
// Partial version with major.minor only - not valid for strict semver
43+
return version;
44+
}
45+
46+
version.minor = std::stoi(versionStr.substr(pos, dotPos - pos));
47+
pos = dotPos + 1;
48+
49+
// Parse patch version
50+
version.patch = std::stoi(versionStr.substr(pos));
51+
version.valid = true;
52+
} catch (...) {
53+
version.valid = false;
54+
}
55+
56+
return version;
57+
}
58+
1259
} // namespace
1360

1461
const std::vector<std::string>& FeatureSchemaV1::names() {
@@ -83,7 +130,33 @@ const std::vector<std::string>& FeatureSchemaV1::names() {
83130
return kNames;
84131
}
85132

86-
bool FeatureSchemaV1::isCompatible(const std::string& version) { return version == kVersion; }
133+
bool FeatureSchemaV1::isCompatible(const std::string& version) {
134+
const auto current = parseVersion(kVersion);
135+
const auto provided = parseVersion(version);
136+
137+
// Both versions must be valid
138+
if (!current.valid || !provided.valid) {
139+
return false;
140+
}
141+
142+
// Major version must match exactly (breaking changes)
143+
if (provided.major != current.major) {
144+
return false;
145+
}
146+
147+
// Minor version must be >= current (backward compatible)
148+
if (provided.minor < current.minor) {
149+
return false;
150+
}
151+
152+
// If minor version is greater, patch doesn't matter (newer compatible version)
153+
if (provided.minor > current.minor) {
154+
return true;
155+
}
156+
157+
// Minor versions match, so patch must be >= current
158+
return provided.patch >= current.patch;
159+
}
87160

88161
size_t FeatureSchemaV1::featureCount() { return names().size(); }
89162

tests/unit/AiExtensionTests.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,40 @@ TEST_CASE("Feature schema exposes rich feature vector for AI plans", "[ai]") {
161161
REQUIRE(automix::ai::FeatureSchemaV1::featureCount() >= 20);
162162
}
163163

164+
TEST_CASE("Feature schema version compatibility uses semantic versioning", "[ai]") {
165+
// Exact version match should be compatible
166+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.0.0"));
167+
168+
// Patch version updates should be compatible (backward compatible)
169+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.0.1"));
170+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.0.2"));
171+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.0.99"));
172+
173+
// Minor version updates should be compatible (backward compatible)
174+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.1.0"));
175+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.2.0"));
176+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.99.0"));
177+
REQUIRE(automix::ai::FeatureSchemaV1::isCompatible("1.1.5"));
178+
179+
// Different major version should be incompatible (breaking changes)
180+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("0.9.0"));
181+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("2.0.0"));
182+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("2.1.0"));
183+
184+
// Invalid or malformed versions should be incompatible
185+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible(""));
186+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("invalid"));
187+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("1.x.0"));
188+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("a.b.c"));
189+
190+
// Partial versions (missing components) should be rejected per strict semver
191+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("1"));
192+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("1.0"));
193+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("1.1"));
194+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("0"));
195+
REQUIRE_FALSE(automix::ai::FeatureSchemaV1::isCompatible("2"));
196+
}
197+
164198
TEST_CASE("Model manager scans demo packs from assets roots", "[ai]") {
165199
automix::ai::ModelManager manager("missing_root_for_test");
166200
const auto packs = manager.scan();

0 commit comments

Comments
 (0)