Add strict/hybrid library policy with auto/ask/options modes

This commit is contained in:
Bill
2026-03-03 10:22:05 -07:00
parent e24f677dde
commit 452603ae7f
5 changed files with 278 additions and 13 deletions

View File

@@ -40,9 +40,12 @@ public:
// network.*.*
t.add("network.http.client", "HTTP Client",
{{"http", "client"}, {"rest", "client"}, {"curl"}});
{{"http", "client"}, {"rest", "client"}, {"curl"}, {"api", "client"}, {"http request"},
{"requests"}, {"axios"}});
t.add("network.http.server", "HTTP Server",
{{"http", "server"}, {"web server"}, {"rest", "server"}, {"http", "listen"}});
{{"http", "server"}, {"web server"}, {"rest", "server"}, {"http", "listen"},
{"api", "endpoint"}, {"api", "route"}, {"backend", "api"},
{"fastapi"}, {"express"}, {"gin"}, {"axum"}, {"spring-boot"}});
// io.*.*
t.add("io.stream.audio", "Audio Stream IO",
@@ -88,7 +91,7 @@ public:
// network.*
t.add("network.websocket", "WebSocket",
{{"websocket"}, {"ws://"}});
{{"websocket"}, {"ws://"}, {"realtime", "socket"}, {"live", "notifications"}});
t.add("network.async", "Async Network IO",
{{"async", "io"}, {"async", "network"}, {"event loop"}, {"non-blocking", "io"}});
t.add("network.grpc", "gRPC",
@@ -124,7 +127,8 @@ public:
t.add("db.transaction", "Database Transaction",
{{"transaction"}, {"commit"}, {"rollback"}});
t.add("db.orm", "ORM",
{{"orm"}, {"object relational"}, {"entity", "mapping"}});
{{"orm"}, {"object relational"}, {"entity", "mapping"}, {"data model"}, {"schema", "model"},
{"sqlalchemy"}, {"prisma"}, {"jooq"}, {"sqlx"}});
t.add("db.migration", "Database Migration",
{{"migration"}, {"schema change"}, {"db upgrade"}, {"db", "migrate"}});
@@ -132,7 +136,7 @@ public:
t.add("auth.oauth", "OAuth",
{{"oauth"}});
t.add("auth.jwt", "JWT",
{{"jwt"}, {"json web token"}});
{{"jwt"}, {"json web token"}, {"auth", "token"}});
t.add("auth.session", "Session Auth",
{{"session"}, {"cookie", "auth"}});
t.add("auth.mfa", "Multi-Factor Auth",
@@ -142,9 +146,15 @@ public:
t.add("storage.object", "Object Storage",
{{"object storage"}, {"s3"}, {"blob storage"}});
t.add("storage.cache", "Cache Storage",
{{"cache"}, {"redis"}, {"memcache"}});
{{"cache"}, {"redis"}, {"memcache"}, {"rate limit"}});
t.add("storage.queue", "Message Queue",
{{"kafka"}, {"rabbitmq"}, {"message queue"}, {"message broker"}});
{{"kafka"}, {"rabbitmq"}, {"message queue"}, {"message broker"},
{"job queue"}, {"background worker"}, {"queue worker"}, {"bullmq"}, {"kafka-go"}});
// validation.*
t.add("validation.schema", "Schema Validation",
{{"schema", "validation"}, {"validate", "input"}, {"request", "validation"},
{"type validation"}, {"runtime validation"}, {"zod"}, {"pydantic"}});
// --- 0-dot (top-level, checked last) ---
t.add("serialization", "Serialization",

View File

@@ -88,6 +88,68 @@ struct Sprint286IntegrationSummary {
{"verbose-api", "requires-beast-for-http"},
false, "2026-03-02", "api-analysis"});
// Modern web/data/realtime stacks
ledger.set({"express", "network.http.server", 0.93f,
{"express()", "app.get()", "app.post()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"fastapi", "network.http.server", 0.95f,
{"FastAPI()", "@app.get", "@app.post"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"gin", "network.http.server", 0.92f,
{"gin.Default()", "router.GET()", "router.POST()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"axum", "network.http.server", 0.91f,
{"Router::new()", "routing::get()", "routing::post()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"spring-boot", "network.http.server", 0.94f,
{"@RestController", "@GetMapping", "@PostMapping"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"requests", "network.http.client", 0.93f,
{"requests.get()", "requests.post()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"axios", "network.http.client", 0.92f,
{"axios.get()", "axios.post()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"sqlalchemy", "db.orm", 0.94f,
{"Session()", "select()", "mapped_column()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"prisma", "db.orm", 0.93f,
{"prisma.user.findMany()", "prisma.$transaction()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"jooq", "db.orm", 0.90f,
{"DSLContext.selectFrom()", "DSLContext.transaction()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"flyway", "db.migration", 0.95f,
{"migrate()", "baseline()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"zod", "validation.schema", 0.94f,
{"z.object()", "schema.parse()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"pydantic", "validation.schema", 0.95f,
{"BaseModel", "model_validate()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"bullmq", "storage.queue", 0.93f,
{"new Queue()", "new Worker()", "queue.add()"},
{},
false, "2026-03-03", "web-stack-seed"});
ledger.set({"redis", "storage.cache", 0.94f,
{"set()", "get()", "hset()"},
{},
false, "2026-03-03", "web-stack-seed"});
return ledger;
}

View File

@@ -52,6 +52,93 @@ struct Sprint288IntegrationSummary {
"auto j = nlohmann::json::parse(input_string);",
{"throws-parse_error-on-invalid-json"}});
// Web/data/realtime stack symbols
cat.add({"express", "app.get",
"app.get(path, handler)",
"network.http.server",
"app.get('/health', (req, res) => res.json({ok:true}))",
{}});
cat.add({"express", "app.post",
"app.post(path, handler)",
"network.http.server",
"app.post('/tweets', createTweetHandler)",
{}});
cat.add({"fastapi", "FastAPI",
"FastAPI()",
"network.http.server",
"app = FastAPI()",
{}});
cat.add({"fastapi", "app.get",
"@app.get(path)",
"network.http.server",
"@app.get('/health')",
{}});
cat.add({"gin", "router.GET",
"router.GET(path, handler)",
"network.http.server",
"router.GET('/health', healthHandler)",
{}});
cat.add({"axum", "Router::route",
"Router::route(path, method_router)",
"network.http.server",
"Router::new().route('/health', get(health))",
{}});
cat.add({"spring-boot", "@RestController",
"@RestController",
"network.http.server",
"@RestController class HealthController { ... }",
{}});
cat.add({"requests", "requests.get",
"requests.get(url, **kwargs)",
"network.http.client",
"resp = requests.get(url, timeout=5)",
{}});
cat.add({"axios", "axios.get",
"axios.get(url, config)",
"network.http.client",
"const r = await axios.get(url)",
{}});
cat.add({"sqlalchemy", "select",
"sqlalchemy.select(model)",
"db.orm",
"stmt = select(User).where(User.id == user_id)",
{}});
cat.add({"prisma", "prisma.user.findMany",
"prisma.user.findMany(args)",
"db.orm",
"const users = await prisma.user.findMany()",
{}});
cat.add({"jooq", "DSLContext.selectFrom",
"DSLContext.selectFrom(table)",
"db.orm",
"ctx.selectFrom(USERS).fetch()",
{}});
cat.add({"flyway", "migrate",
"Flyway.migrate()",
"db.migration",
"flyway.migrate()",
{}});
cat.add({"zod", "z.object",
"z.object(shape)",
"validation.schema",
"const schema = z.object({ id: z.string() })",
{}});
cat.add({"pydantic", "BaseModel",
"class BaseModel",
"validation.schema",
"class TweetCreate(BaseModel): ...",
{}});
cat.add({"bullmq", "Queue.add",
"Queue.add(name, data, opts)",
"storage.queue",
"await queue.add('notify', payload)",
{}});
cat.add({"redis", "set",
"redis.set(key, value)",
"storage.cache",
"await redis.set('timeline:uid', data)",
{}});
return cat;
}

View File

@@ -20,6 +20,8 @@
{"description", "Optional requirement conflicts from whetstone_architect_intake."}}},
{"availableLibraries", {{"type", "array"},
{"description", "Optional candidate libraries for deterministic library/API dispatch."}}},
{"libraryPolicy", {{"type", "object"},
{"description", "Library selection policy: mode=strict|hybrid, expansionMode=auto_include|ask_user|suggest_options."}}},
{"strictExecutionContract", {{"type", "boolean"},
{"description", "Require concrete execution contract metadata (stepIds/files/tools/tests)."}}}
}}, {"required", json::array({"normalizedRequirements"})}}
@@ -506,6 +508,44 @@
if (!seen) availableLibraries.push_back(lib);
}
}
std::string libraryMode = "strict";
std::string libraryExpansionMode = "auto_include";
int maxAdditionalLibraries = 3;
if (args.contains("libraryPolicy")) {
if (!args["libraryPolicy"].is_object()) {
return {
{"success", false},
{"error", "library_policy_not_object"}
};
}
const json& policy = args["libraryPolicy"];
if (policy.contains("mode")) {
if (!policy["mode"].is_string()) {
return {{"success", false}, {"error", "library_policy_mode_not_string"}};
}
libraryMode = policy["mode"].get<std::string>();
}
if (policy.contains("expansionMode")) {
if (!policy["expansionMode"].is_string()) {
return {{"success", false}, {"error", "library_policy_expansion_mode_not_string"}};
}
libraryExpansionMode = policy["expansionMode"].get<std::string>();
}
if (policy.contains("maxAdditionalLibraries")) {
if (!policy["maxAdditionalLibraries"].is_number_integer()) {
return {{"success", false}, {"error", "library_policy_max_additional_not_int"}};
}
maxAdditionalLibraries = std::max(0, policy["maxAdditionalLibraries"].get<int>());
}
}
if (libraryMode != "strict" && libraryMode != "hybrid") {
return {{"success", false}, {"error", "library_policy_mode_invalid"}};
}
if (libraryExpansionMode != "auto_include" &&
libraryExpansionMode != "ask_user" &&
libraryExpansionMode != "suggest_options") {
return {{"success", false}, {"error", "library_policy_expansion_mode_invalid"}};
}
const bool libraryDispatchEnabled = !availableLibraries.empty();
auto capabilityLedger = whetstone::Sprint286IntegrationSummary::buildSeedLedger();
auto symbolCatalog = whetstone::Sprint288IntegrationSummary::buildSeedCatalog();
@@ -523,6 +563,7 @@
int escalateCount = 0;
int missingContractCount = 0;
int enrichedTaskCount = 0;
int pendingLibrarySelectionCount = 0;
json tasksJson = json::array();
for (const auto& task : annotated) {
json taskJson = {
@@ -560,7 +601,58 @@
});
}
contract["avoidedLibraries"] = avoided;
contract["librarySelectionMode"] = "direct";
++enrichedTaskCount;
} else if (libraryMode == "hybrid") {
auto classifier = whetstone::OperationClassifier::withDefaultTaxonomy();
auto classified = classifier.classify(dispatchText);
if (classified.isKnown) {
auto domainCaps = capabilityLedger.allForDomain(classified.domain);
std::sort(domainCaps.begin(), domainCaps.end(),
[](const auto& a, const auto& b) {
return a.score > b.score;
});
std::vector<std::string> additional;
for (const auto& rec : domainCaps) {
bool exists = false;
for (const auto& chosen : availableLibraries) {
if (chosen == rec.libraryId) {
exists = true;
break;
}
}
if (exists) continue;
additional.push_back(rec.libraryId);
if (maxAdditionalLibraries > 0 &&
static_cast<int>(additional.size()) >= maxAdditionalLibraries) {
break;
}
}
if (!additional.empty() && libraryExpansionMode == "auto_include") {
std::vector<std::string> expanded = availableLibraries;
expanded.insert(expanded.end(), additional.begin(), additional.end());
auto fallback = libraryAnnotator.annotate(dispatchText, expanded, capabilityLedger, symbolCatalog);
if (fallback.isEnriched()) {
contract["selectedLibrary"] = fallback.selectedLibrary;
contract["operationDomain"] = fallback.operationDomain;
contract["capabilityScore"] = fallback.capabilityScore;
contract["preferredAPIs"] = fallback.preferredAPIs;
contract["justification"] = fallback.justification;
contract["librarySelectionMode"] = "hybrid_auto_include";
contract["autoIncludedLibraries"] = additional;
++enrichedTaskCount;
}
} else if (!additional.empty()) {
contract["librarySelectionMode"] = libraryExpansionMode == "ask_user"
? "hybrid_ask_user"
: "hybrid_suggest_options";
contract["librarySelectionPending"] = (libraryExpansionMode == "ask_user");
contract["librarySelectionOptions"] = additional;
contract["operationDomain"] = classified.domain;
contract["justification"] = "No match in user-selected libraries for classified domain.";
++pendingLibrarySelectionCount;
}
}
}
}
taskJson["executionContract"] = contract;
@@ -593,7 +685,13 @@
{"libraryDispatch", {
{"enabled", libraryDispatchEnabled},
{"availableLibraries", availableLibraries},
{"enrichedTaskCount", enrichedTaskCount}
{"policy", {
{"mode", libraryMode},
{"expansionMode", libraryExpansionMode},
{"maxAdditionalLibraries", maxAdditionalLibraries}
}},
{"enrichedTaskCount", enrichedTaskCount},
{"pendingSelectionCount", pendingLibrarySelectionCount}
}}
};
}

View File

@@ -19,6 +19,9 @@ fi
WORKSPACE="${WSTONE_WORKSPACE:-$ROOT_DIR}"
LANGUAGE="${WSTONE_LANGUAGE:-cpp}"
STRICT_EXECUTION_CONTRACT="${WSTONE_STRICT_EXECUTION_CONTRACT:-1}"
LIBRARY_POLICY_MODE="${WSTONE_LIBRARY_POLICY_MODE:-strict}"
LIBRARY_EXPANSION_MODE="${WSTONE_LIBRARY_EXPANSION_MODE:-auto_include}"
LIBRARY_MAX_ADDITIONAL="${WSTONE_LIBRARY_MAX_ADDITIONAL:-3}"
CALIBRATE_AFTER_RUN="${WSTONE_CALIBRATE_AFTER_RUN:-0}"
RUN_READINESS_SUITE="${WSTONE_RUN_READINESS_SUITE:-1}"
SPEC_READINESS_PRECHECK="${WSTONE_SPEC_READINESS_PRECHECK:-1}"
@@ -424,7 +427,8 @@ else
fi
CONFLICTS="$(printf '%s' "$INTAKE_JSON" | jq '.conflicts // []')"
GEN_ARGS="$(jq -nc --argjson nr "$NORMALIZED_REQS" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')"
--arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},strictExecutionContract:($strict == "1")}')"
GEN_RESP_RAW="$(call_tool "whetstone_generate_taskitems" "$GEN_ARGS")"
printf '%s\n' "$GEN_RESP_RAW" > "$OUT_DIR/02_generate_taskitems_raw.ndjson.json"
GEN_JSON="$(extract_tool_text_json "$GEN_RESP_RAW")"
@@ -448,7 +452,8 @@ if [[ "$NATIVE_DECOMP_RETRY" == "1" && "$native_task_count_initial" -lt "$NATIVE
}]
')"
RETRY_GEN_ARGS="$(jq -nc --argjson nr "$RETRY_REQS" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')"
--arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},strictExecutionContract:($strict == "1")}')"
RETRY_GEN_RESP_RAW="$(call_tool "whetstone_generate_taskitems" "$RETRY_GEN_ARGS")"
printf '%s\n' "$RETRY_GEN_RESP_RAW" > "$OUT_DIR/02aa_generate_taskitems_retry_raw.ndjson.json"
RETRY_GEN_JSON="$(extract_tool_text_json "$RETRY_GEN_RESP_RAW")"
@@ -561,7 +566,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then
req_bundle="$(printf '%s' "$VARIANTS_JSON" | jq ".variants[$idx]")"
cand_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson bundle "$req_bundle" '$base + $bundle')"
cand_args="$(jq -nc --argjson nr "$cand_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')"
--arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},strictExecutionContract:($strict == "1")}')"
cand_raw="$(call_tool "whetstone_generate_taskitems" "$cand_args")"
printf '%s\n' "$cand_raw" > "$OUT_DIR/02ae_candidate_${variant}_raw.ndjson.json"
cand_json="$(extract_tool_text_json "$cand_raw")"
@@ -625,7 +631,8 @@ if [[ "$NATIVE_RAW_CANDIDATE_SEARCH" == "1" ]]; then
retry_top_gap_reqs="$(cat "$OUT_DIR/02af_raw_adaptive_retry_requirements.json")"
retry_cand_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson extra "$retry_top_gap_reqs" '$base + $extra')"
retry_cand_args="$(jq -nc --argjson nr "$retry_cand_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')"
--arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},strictExecutionContract:($strict == "1")}')"
retry_raw="$(call_tool "whetstone_generate_taskitems" "$retry_cand_args")"
printf '%s\n' "$retry_raw" > "$OUT_DIR/02af_raw_adaptive_retry_raw.ndjson.json"
retry_json="$(extract_tool_text_json "$retry_raw")"
@@ -813,7 +820,8 @@ if [[ "$NATIVE_MULTISHOT_DECOMP" == "1" ]]; then
shot_reqs="$(jq -nc --argjson base "$NORMALIZED_REQS" --argjson req "$req_i" '$base + [$req]')"
shot_args="$(jq -nc --argjson nr "$shot_reqs" --argjson cf "$CONFLICTS" --argjson libs "$AVAILABLE_LIBRARIES_JSON" --arg strict "$STRICT_EXECUTION_CONTRACT" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,strictExecutionContract:($strict == "1")}')"
--arg mode "$LIBRARY_POLICY_MODE" --arg expansion "$LIBRARY_EXPANSION_MODE" --argjson max_additional "$LIBRARY_MAX_ADDITIONAL" \
'{normalizedRequirements:$nr,conflicts:$cf,availableLibraries:$libs,libraryPolicy:{mode:$mode,expansionMode:$expansion,maxAdditionalLibraries:$max_additional},strictExecutionContract:($strict == "1")}')"
shot_raw="$(call_tool "whetstone_generate_taskitems" "$shot_args")"
printf '%s\n' "$shot_raw" > "$OUT_DIR/02ac_generate_taskitems_multishot_${i}_raw.ndjson.json"
shot_json="$(extract_tool_text_json "$shot_raw")"