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

@@ -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