Sprint 287: PerTaskLibrarySelector (steps 1963-1967)

Deterministic library dispatch layer. Five new headers:
- LibrarySelection: result struct with avoidedLibraries + justification
- OperationClassifier: wraps taxonomy with ClassificationResult
- PerTaskLibrarySelector: sort by score, pick max, build avoided list
- SelectionJustificationLog: append-only auditable selection log
- Sprint287IntegrationSummary: 8-task spec, all selections verified

25/25 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-03-02 10:04:39 -07:00
parent b5c8b46117
commit 2a40cb1ba5
11 changed files with 728 additions and 0 deletions

View File

@@ -11099,3 +11099,18 @@ target_include_directories(step1961_test PRIVATE src)
add_executable(step1962_test tests/step1962_test.cpp)
target_include_directories(step1962_test PRIVATE src)
add_executable(step1963_test tests/step1963_test.cpp)
target_include_directories(step1963_test PRIVATE src)
add_executable(step1964_test tests/step1964_test.cpp)
target_include_directories(step1964_test PRIVATE src)
add_executable(step1965_test tests/step1965_test.cpp)
target_include_directories(step1965_test PRIVATE src)
add_executable(step1966_test tests/step1966_test.cpp)
target_include_directories(step1966_test PRIVATE src)
add_executable(step1967_test tests/step1967_test.cpp)
target_include_directories(step1967_test PRIVATE src)

View File

@@ -0,0 +1,28 @@
#pragma once
// Step 1963: LibrarySelection
// Result struct from PerTaskLibrarySelector — fully specifies which library
// to use for a given operation domain and why.
#include <string>
#include <vector>
namespace whetstone {
struct AvoidedLibrary {
std::string name;
std::string reason; // e.g. "score 0.60 < selected 0.92"
float score = 0.0f;
};
struct LibrarySelection {
std::string selectedLibrary; // empty if no library qualifies
std::string operationDomain;
float capabilityScore = 0.0f;
std::vector<std::string> preferredAPIs;
std::vector<AvoidedLibrary> avoidedLibraries; // sorted by score desc
std::string justification;
bool hasSelection() const { return !selectedLibrary.empty(); }
};
} // namespace whetstone

View File

@@ -0,0 +1,39 @@
#pragma once
// Step 1964: OperationClassifier
// Wraps OperationTaxonomy with a ClassificationResult that includes
// confidence and isKnown flags.
#include "OperationTaxonomy.h"
#include <string>
namespace whetstone {
struct ClassificationResult {
std::string domain; // e.g. "serialization.json" or "unknown"
float confidence; // 1.0 if known, 0.0 if "unknown"
bool isKnown;
};
class OperationClassifier {
public:
explicit OperationClassifier(OperationTaxonomy taxonomy)
: taxonomy_(std::move(taxonomy)) {}
ClassificationResult classify(const std::string& text) const {
auto domain = taxonomy_.classify(text);
bool known = (domain != "unknown");
return {domain, known ? 1.0f : 0.0f, known};
}
// Convenience: build with the default 40+ domain taxonomy
static OperationClassifier withDefaultTaxonomy() {
return OperationClassifier(OperationTaxonomy::buildDefault());
}
const OperationTaxonomy& taxonomy() const { return taxonomy_; }
private:
OperationTaxonomy taxonomy_;
};
} // namespace whetstone

View File

@@ -0,0 +1,90 @@
#pragma once
// Step 1965: PerTaskLibrarySelector
// Deterministic library dispatch: given (operationDomain, availableLibraries, ledger)
// rank by score, pick max, build avoidedLibraries + justification.
#include "LibraryCapabilityLedger.h"
#include "LibrarySelection.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
namespace whetstone {
class PerTaskLibrarySelector {
public:
// Core selection: deterministic, no inference.
LibrarySelection select(const std::string& operationDomain,
const std::vector<std::string>& availableLibraries,
const LibraryCapabilityLedger& ledger) const {
LibrarySelection result;
result.operationDomain = operationDomain;
if (availableLibraries.empty()) {
result.justification = "no library with score > 0";
return result;
}
// Score all candidates
struct Candidate { std::string lib; float score; };
std::vector<Candidate> candidates;
candidates.reserve(availableLibraries.size());
for (const auto& lib : availableLibraries) {
candidates.push_back({lib, ledger.score(lib, operationDomain)});
}
// Stable sort by score descending (stable for deterministic tie-breaking)
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b) {
return a.score > b.score;
});
if (candidates[0].score == 0.0f) {
result.justification = "no library with score > 0";
return result;
}
// Selected: highest score
result.selectedLibrary = candidates[0].lib;
result.capabilityScore = candidates[0].score;
// Preferred APIs from ledger record
auto rec = ledger.get(candidates[0].lib, operationDomain);
if (rec) result.preferredAPIs = rec->preferredAPIs;
// Avoided: remaining with score > 0, sorted desc (already sorted)
for (size_t i = 1; i < candidates.size(); ++i) {
if (candidates[i].score > 0.0f) {
result.avoidedLibraries.push_back({
candidates[i].lib,
"score " + fmt(candidates[i].score) + " < selected " + fmt(candidates[0].score),
candidates[i].score
});
}
}
// Justification string
if (result.avoidedLibraries.empty()) {
result.justification = result.selectedLibrary + " scores " +
fmt(result.capabilityScore) + " (only qualified library)";
} else {
result.justification = result.selectedLibrary + " scores " +
fmt(result.capabilityScore) + " vs best alternative " +
result.avoidedLibraries[0].name + " at " +
fmt(result.avoidedLibraries[0].score);
}
return result;
}
private:
static std::string fmt(float s) {
std::ostringstream ss;
ss << std::fixed << std::setprecision(2) << s;
return ss.str();
}
};
} // namespace whetstone

View File

@@ -0,0 +1,59 @@
#pragma once
// Step 1966: SelectionJustificationLog
// Append-only auditable log of every library selection decision.
#include <string>
#include <vector>
namespace whetstone {
struct SelectionLogEntry {
std::string taskId;
std::string operationDomain;
std::string selectedLibrary; // empty if no selection was made
float capabilityScore = 0.0f;
std::string justification;
std::string timestamp; // ISO date/time string
};
class SelectionJustificationLog {
public:
void append(SelectionLogEntry entry) {
entries_.push_back(std::move(entry));
}
const std::vector<SelectionLogEntry>& entries() const { return entries_; }
size_t size() const { return entries_.size(); }
std::vector<SelectionLogEntry> findByDomain(const std::string& domain) const {
std::vector<SelectionLogEntry> result;
for (const auto& e : entries_) {
if (e.operationDomain == domain) result.push_back(e);
}
return result;
}
std::vector<SelectionLogEntry> findByLibrary(const std::string& lib) const {
std::vector<SelectionLogEntry> result;
for (const auto& e : entries_) {
if (e.selectedLibrary == lib) result.push_back(e);
}
return result;
}
std::vector<SelectionLogEntry> findByTask(const std::string& taskId) const {
std::vector<SelectionLogEntry> result;
for (const auto& e : entries_) {
if (e.taskId == taskId) result.push_back(e);
}
return result;
}
void clear() { entries_.clear(); }
private:
std::vector<SelectionLogEntry> entries_;
};
} // namespace whetstone

View File

@@ -0,0 +1,60 @@
#pragma once
// Step 1967: Sprint 287 Integration Summary
// Steps 19631967: PerTaskLibrarySelector
//
// 1963: LibrarySelection — result struct with avoidedLibraries + justification
// 1964: OperationClassifier — wraps taxonomy with ClassificationResult
// 1965: PerTaskLibrarySelector — deterministic max-score dispatch
// 1966: SelectionJustificationLog — append-only auditable selection log
// 1967: Integration — 8-task spec, all selections verified
#include "LibraryCapabilityLedger.h"
#include "PerTaskLibrarySelector.h"
#include "Sprint286IntegrationSummary.h"
#include <string>
#include <vector>
namespace whetstone {
struct Sprint287IntegrationSummary {
int stepsCompleted = 5;
bool success = true;
std::string sprintName() const {
return "Sprint 287: PerTaskLibrarySelector";
}
struct Task {
std::string taskId;
std::string operationDomain;
std::vector<std::string> availableLibraries;
std::string expectedLibrary;
};
// 8 tasks covering all seed libraries; each task has a verifiable expected selection.
static std::vector<Task> buildTaskSpec() {
return {
{"t1", "serialization.json", {"nlohmann_json", "protobuf"}, "nlohmann_json"},
{"t2", "compute.matrix", {"cublas", "numpy"}, "cublas"},
{"t3", "serialization.binary", {"protobuf", "nlohmann_json"}, "protobuf"},
{"t4", "compute.statistics", {"numpy"}, "numpy"},
{"t5", "network.async", {"tokio", "boost.asio"}, "tokio"},
{"t6", "compute.parallel", {"thrust"}, "thrust"},
{"t7", "io.stream", {"tokio", "boost.asio"}, "tokio"},
{"t8", "network.http.client", {"boost.asio", "tokio"}, "boost.asio"},
};
}
// Run selector over all 8 tasks; return true if all selections match expected.
static bool verifyAllSelections() {
auto ledger = Sprint286IntegrationSummary::buildSeedLedger();
PerTaskLibrarySelector selector;
for (const auto& task : buildTaskSpec()) {
auto sel = selector.select(task.operationDomain,
task.availableLibraries, ledger);
if (sel.selectedLibrary != task.expectedLibrary) return false;
}
return true;
}
};
} // namespace whetstone

View File

@@ -0,0 +1,92 @@
// Step 1963: LibrarySelection
//
// t1: default LibrarySelection has no selection (hasSelection=false)
// t2: populated selection fields are correct
// t3: avoidedLibraries struct fields
// t4: multiple avoided libraries
// t5: selection with no avoidedLibraries (only one qualified)
#include "LibrarySelection.h"
#include <iostream>
namespace ws = whetstone;
static int p = 0, f = 0;
#define T(n) { std::cout << " " << #n << "... "; }
#define P() { std::cout << "PASS\n"; ++p; }
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
#define C(c, m) if (!(c)) { F(m); return; }
void t1() {
T(default_selection_has_no_selection);
ws::LibrarySelection s;
C(!s.hasSelection(), "hasSelection false by default");
C(s.selectedLibrary.empty(), "empty selectedLibrary");
C(s.capabilityScore == 0.0f, "score 0");
C(s.preferredAPIs.empty(), "no APIs");
C(s.avoidedLibraries.empty(), "no avoided");
P();
}
void t2() {
T(populated_selection_fields);
ws::LibrarySelection s;
s.selectedLibrary = "nlohmann_json";
s.operationDomain = "serialization.json";
s.capabilityScore = 0.92f;
s.preferredAPIs = {"nlohmann::json::dump()", "nlohmann::json::parse()"};
s.justification = "nlohmann_json scores 0.92 vs best alternative protobuf at 0.60";
C(s.hasSelection(), "hasSelection true");
C(s.selectedLibrary == "nlohmann_json", "selectedLibrary");
C(s.capabilityScore == 0.92f, "capabilityScore");
C(s.preferredAPIs.size() == 2, "2 preferredAPIs");
C(!s.justification.empty(), "justification set");
P();
}
void t3() {
T(avoided_library_struct_fields);
ws::AvoidedLibrary av;
av.name = "protobuf";
av.reason = "score 0.60 < selected 0.92";
av.score = 0.60f;
C(av.name == "protobuf", "name");
C(av.score == 0.60f, "score");
C(av.reason.find("0.60") != std::string::npos, "reason contains score");
C(av.reason.find("0.92") != std::string::npos, "reason contains selected score");
P();
}
void t4() {
T(multiple_avoided_libraries_sorted_by_score_desc);
ws::LibrarySelection s;
s.selectedLibrary = "serde_json";
s.capabilityScore = 0.98f;
s.avoidedLibraries = {
{"nlohmann_json", "score 0.92 < selected 0.98", 0.92f},
{"protobuf", "score 0.60 < selected 0.98", 0.60f},
};
C(s.avoidedLibraries.size() == 2, "2 avoided");
C(s.avoidedLibraries[0].name == "nlohmann_json", "best avoided first");
C(s.avoidedLibraries[0].score > s.avoidedLibraries[1].score, "sorted desc");
P();
}
void t5() {
T(selection_with_no_avoided_only_one_qualified);
ws::LibrarySelection s;
s.selectedLibrary = "thrust";
s.operationDomain = "compute.parallel";
s.capabilityScore = 0.97f;
s.justification = "thrust scores 0.97 (only qualified library)";
C(s.hasSelection(), "hasSelection");
C(s.avoidedLibraries.empty(), "no avoided when only one qualifies");
C(s.justification.find("only") != std::string::npos, "justification says only");
P();
}
int main() {
std::cout << "Step 1963: LibrarySelection\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
return f > 0 ? 1 : 0;
}

View File

@@ -0,0 +1,75 @@
// Step 1964: OperationClassifier
//
// t1: classify known text returns isKnown=true, confidence=1.0
// t2: classify unknown text returns isKnown=false, confidence=0.0
// t3: classify "serialize to JSON" -> serialization.json
// t4: classify "matrix multiply" -> compute.matrix
// t5: withDefaultTaxonomy() builds correctly; taxonomy() accessible
#include "OperationClassifier.h"
#include <iostream>
namespace ws = whetstone;
static int p = 0, f = 0;
#define T(n) { std::cout << " " << #n << "... "; }
#define P() { std::cout << "PASS\n"; ++p; }
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
#define C(c, m) if (!(c)) { F(m); return; }
void t1() {
T(known_text_returns_confidence_1);
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
auto r = cls.classify("serialize to JSON");
C(r.isKnown, "isKnown true");
C(r.confidence == 1.0f, "confidence 1.0");
C(!r.domain.empty(), "domain not empty");
C(r.domain != "unknown", "domain is not unknown");
P();
}
void t2() {
T(unknown_text_returns_confidence_0);
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
auto r = cls.classify("xyzzy frobble zogzog");
C(!r.isKnown, "isKnown false");
C(r.confidence == 0.0f, "confidence 0.0");
C(r.domain == "unknown", "domain is unknown");
P();
}
void t3() {
T(classify_json_text);
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
auto r = cls.classify("serialize to JSON");
C(r.domain == "serialization.json",
"expected serialization.json, got " + r.domain);
P();
}
void t4() {
T(classify_matrix_multiply_text);
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
auto r = cls.classify("matrix multiply on GPU");
C(r.domain == "compute.matrix",
"expected compute.matrix, got " + r.domain);
P();
}
void t5() {
T(withDefaultTaxonomy_and_taxonomy_accessor);
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
C(cls.taxonomy().domainCount() >= 40, ">=40 domains");
auto cats = cls.taxonomy().topLevelCategories();
C(cats.size() >= 6, ">=6 top-level categories");
// Classify a few more domains
C(cls.classify("sql select query").domain == "db.query.sql", "sql");
C(cls.classify("compute sha256 hash").domain == "crypto.hash", "hash");
P();
}
int main() {
std::cout << "Step 1964: OperationClassifier\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
return f > 0 ? 1 : 0;
}

View File

@@ -0,0 +1,88 @@
// Step 1965: PerTaskLibrarySelector
//
// t1: selects nlohmann_json (0.92) over protobuf (0.60) for serialization.json
// t2: selects cublas (0.99) over numpy (0.95) for compute.matrix
// t3: avoidedLibraries populated correctly (sorted desc)
// t4: returns empty selection when no library has score > 0
// t5: single qualified library: no avoided, justification says "only"
#include "PerTaskLibrarySelector.h"
#include "Sprint286IntegrationSummary.h"
#include <iostream>
namespace ws = whetstone;
static int p = 0, f = 0;
#define T(n) { std::cout << " " << #n << "... "; }
#define P() { std::cout << "PASS\n"; ++p; }
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
#define C(c, m) if (!(c)) { F(m); return; }
void t1() {
T(selects_nlohmann_over_protobuf_for_json);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
auto r = sel.select("serialization.json", {"nlohmann_json", "protobuf"}, ledger);
C(r.hasSelection(), "hasSelection");
C(r.selectedLibrary == "nlohmann_json", "nlohmann_json selected");
C(r.capabilityScore == 0.92f, "score 0.92");
C(!r.justification.empty(), "justification set");
C(r.justification.find("nlohmann_json") != std::string::npos, "justification names winner");
P();
}
void t2() {
T(selects_cublas_over_numpy_for_compute_matrix);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
auto r = sel.select("compute.matrix", {"cublas", "numpy"}, ledger);
C(r.selectedLibrary == "cublas", "cublas selected");
C(r.capabilityScore == 0.99f, "score 0.99");
C(!r.preferredAPIs.empty(), "preferredAPIs populated");
P();
}
void t3() {
T(avoided_libraries_sorted_desc);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
// Add serde_json so there are 3 candidates for serialization.json
ledger.set({"serde_json", "serialization.json", 0.98f});
ws::PerTaskLibrarySelector sel;
auto r = sel.select("serialization.json",
{"nlohmann_json", "protobuf", "serde_json"}, ledger);
C(r.selectedLibrary == "serde_json", "serde_json selected (0.98)");
C(r.avoidedLibraries.size() == 2, "2 avoided");
C(r.avoidedLibraries[0].name == "nlohmann_json", "nlohmann first avoided (0.92)");
C(r.avoidedLibraries[0].score > r.avoidedLibraries[1].score, "sorted desc");
C(r.avoidedLibraries[1].name == "protobuf", "protobuf second avoided (0.60)");
P();
}
void t4() {
T(empty_selection_when_no_score);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
// "cublas" has no score for "serialization.json"
auto r = sel.select("serialization.json", {"cublas"}, ledger);
C(!r.hasSelection(), "no selection");
C(r.selectedLibrary.empty(), "empty selectedLibrary");
C(r.justification.find("no library") != std::string::npos, "justification says no library");
P();
}
void t5() {
T(single_qualified_library_no_avoided);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
auto r = sel.select("compute.parallel", {"thrust"}, ledger);
C(r.selectedLibrary == "thrust", "thrust selected");
C(r.avoidedLibraries.empty(), "no avoided");
C(r.justification.find("only") != std::string::npos, "justification says only");
P();
}
int main() {
std::cout << "Step 1965: PerTaskLibrarySelector\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
return f > 0 ? 1 : 0;
}

View File

@@ -0,0 +1,99 @@
// Step 1966: SelectionJustificationLog
//
// t1: append and size
// t2: findByDomain returns matching entries only
// t3: findByLibrary filters correctly
// t4: findByTask returns correct entries
// t5: clear resets the log; entries() ordering preserved
#include "SelectionJustificationLog.h"
#include <iostream>
namespace ws = whetstone;
static int p = 0, f = 0;
#define T(n) { std::cout << " " << #n << "... "; }
#define P() { std::cout << "PASS\n"; ++p; }
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
#define C(c, m) if (!(c)) { F(m); return; }
static ws::SelectionJustificationLog makeLog() {
ws::SelectionJustificationLog log;
log.append({"t1", "serialization.json", "nlohmann_json", 0.92f,
"nlohmann_json scores 0.92 vs protobuf at 0.60", "2026-03-02"});
log.append({"t2", "compute.matrix", "cublas", 0.99f,
"cublas scores 0.99 vs numpy at 0.95", "2026-03-02"});
log.append({"t3", "serialization.json", "serde_json", 0.98f,
"serde_json scores 0.98 (only qualified)", "2026-03-02"});
log.append({"t4", "compute.statistics", "numpy", 0.93f,
"numpy scores 0.93 (only qualified library)", "2026-03-02"});
return log;
}
void t1() {
T(append_and_size);
ws::SelectionJustificationLog log;
C(log.size() == 0, "empty initially");
log.append({"t1", "serialization.json", "nlohmann_json", 0.92f, "j", "2026-03-02"});
C(log.size() == 1, "size 1 after append");
log.append({"t2", "compute.matrix", "cublas", 0.99f, "j", "2026-03-02"});
C(log.size() == 2, "size 2 after second append");
C(log.entries().size() == 2, "entries() matches size");
P();
}
void t2() {
T(findByDomain_filters_correctly);
auto log = makeLog();
auto json_entries = log.findByDomain("serialization.json");
C(json_entries.size() == 2, "2 serialization.json entries");
C(json_entries[0].taskId == "t1", "first is t1");
C(json_entries[1].taskId == "t3", "second is t3");
auto none = log.findByDomain("network.async");
C(none.empty(), "no entries for network.async");
P();
}
void t3() {
T(findByLibrary_filters_correctly);
auto log = makeLog();
auto cublas = log.findByLibrary("cublas");
C(cublas.size() == 1, "1 cublas entry");
C(cublas[0].operationDomain == "compute.matrix", "correct domain");
auto numpy = log.findByLibrary("numpy");
C(numpy.size() == 1, "1 numpy entry");
C(numpy[0].capabilityScore == 0.93f, "correct score");
P();
}
void t4() {
T(findByTask_returns_correct_entry);
auto log = makeLog();
auto t2 = log.findByTask("t2");
C(t2.size() == 1, "1 t2 entry");
C(t2[0].selectedLibrary == "cublas", "cublas for t2");
auto missing = log.findByTask("t99");
C(missing.empty(), "no t99 entry");
P();
}
void t5() {
T(clear_resets_and_ordering_preserved);
auto log = makeLog();
C(log.size() == 4, "4 entries before clear");
log.clear();
C(log.size() == 0, "empty after clear");
C(log.entries().empty(), "entries() empty after clear");
// Re-append and verify ordering preserved
log.append({"a", "serialization.json", "nlohmann_json", 0.92f, "j", "2026-03-02"});
log.append({"b", "compute.matrix", "cublas", 0.99f, "j", "2026-03-02"});
C(log.entries()[0].taskId == "a", "first entry preserved");
C(log.entries()[1].taskId == "b", "second entry preserved");
P();
}
int main() {
std::cout << "Step 1966: SelectionJustificationLog\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
return f > 0 ? 1 : 0;
}

View File

@@ -0,0 +1,83 @@
// Step 1967: Sprint 287 Integration
//
// t1: all 8 tasks produce correct library selection
// t2: verifyAllSelections() passes
// t3: avoidedLibraries present for tasks with multiple candidates
// t4: tasks with single qualified library have no avoided entries
// t5: Sprint287IntegrationSummary metadata correct
#include "Sprint287IntegrationSummary.h"
#include <iostream>
namespace ws = whetstone;
static int p = 0, f = 0;
#define T(n) { std::cout << " " << #n << "... "; }
#define P() { std::cout << "PASS\n"; ++p; }
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
#define C(c, m) if (!(c)) { F(m); return; }
void t1() {
T(all_8_tasks_correct_library);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
auto tasks = ws::Sprint287IntegrationSummary::buildTaskSpec();
C(tasks.size() == 8, "8 tasks");
for (const auto& task : tasks) {
auto r = sel.select(task.operationDomain, task.availableLibraries, ledger);
C(r.selectedLibrary == task.expectedLibrary,
task.taskId + ": expected " + task.expectedLibrary + " got " + r.selectedLibrary);
}
P();
}
void t2() {
T(verifyAllSelections_passes);
C(ws::Sprint287IntegrationSummary::verifyAllSelections(), "verifyAllSelections true");
P();
}
void t3() {
T(avoided_libraries_present_for_multi_candidate_tasks);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
// t1: nlohmann vs protobuf -> 1 avoided
auto r1 = sel.select("serialization.json", {"nlohmann_json", "protobuf"}, ledger);
C(!r1.avoidedLibraries.empty(), "t1 has avoided");
C(r1.avoidedLibraries[0].name == "protobuf", "t1 avoided is protobuf");
// t5: tokio vs boost.asio -> 1 avoided
auto r5 = sel.select("network.async", {"tokio", "boost.asio"}, ledger);
C(!r5.avoidedLibraries.empty(), "t5 has avoided");
C(r5.avoidedLibraries[0].name == "boost.asio", "t5 avoided is boost.asio");
P();
}
void t4() {
T(single_qualified_has_no_avoided);
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
ws::PerTaskLibrarySelector sel;
// t4: only numpy for compute.statistics
auto r4 = sel.select("compute.statistics", {"numpy"}, ledger);
C(r4.selectedLibrary == "numpy", "numpy selected");
C(r4.avoidedLibraries.empty(), "no avoided for t4");
// t6: only thrust for compute.parallel
auto r6 = sel.select("compute.parallel", {"thrust"}, ledger);
C(r6.selectedLibrary == "thrust", "thrust selected");
C(r6.avoidedLibraries.empty(), "no avoided for t6");
P();
}
void t5() {
T(sprint287_metadata);
ws::Sprint287IntegrationSummary s;
C(s.stepsCompleted == 5, "5 steps");
C(s.success, "success");
C(s.sprintName() == "Sprint 287: PerTaskLibrarySelector", "name");
P();
}
int main() {
std::cout << "Step 1967: Sprint 287 Integration\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
return f > 0 ? 1 : 0;
}