Compare commits

..
Author SHA1 Message Date
Sharang ParnerkarandClaude Fable 5 7cd4ffdaab feat(controls): B3 — categorize the rest of needs_tooling (architectural + RBAC)
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m41s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
Closes out B's coverage of the 16 needs_tooling CRA controls:

- cra-ai-2,3,4,5 (minimal attack surface, secure architecture, least-privilege,
  tamper protection) are design properties, not local code patterns -> marked
  not_code_checkable (out of static-scan scope) with reviewer notes.
- cra-ai-12 (RBAC) is surface-checkable (authorization points) -> added to the
  grounded surface pass; note points to the gated grounded mechanism.

Final CRA coverage: covered 13 | needs_tooling 8 (all grounded-covered, gated) |
not_code_checkable 19. The 16 needs_tooling now fully categorized:
4 custom-semgrep (B1) + 8 grounded surface (B2/B3, gated) + 4 architectural (B3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:50:50 +02:00
+1 -49
View File
@@ -22,11 +22,6 @@ struct EmbeddingData {
index: usize,
}
/// Max inputs per embedding request. The bge/OpenAI-like backends cap the input
/// array (bge-multilingual-gemma2 rejects >25 with "batch size overflow"), so we
/// chunk larger corpora — a whole control catalog (~1.8k) would otherwise 500.
const EMBED_BATCH_SIZE: usize = 16;
// ── Embedding implementation ───────────────────────────────────
impl LlmClient {
@@ -34,21 +29,8 @@ impl LlmClient {
&self.embed_model
}
/// Generate embeddings for a batch of texts, chunking into backend-sized
/// requests and preserving input order across chunks.
/// Generate embeddings for a batch of texts
pub async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f64>>, AgentError> {
if texts.is_empty() {
return Ok(Vec::new());
}
let mut out = Vec::with_capacity(texts.len());
for chunk in texts.chunks(EMBED_BATCH_SIZE) {
out.extend(self.embed_batch(chunk.to_vec()).await?);
}
Ok(out)
}
/// Embed one backend-sized batch (≤ [`EMBED_BATCH_SIZE`]) in a single request.
async fn embed_batch(&self, texts: Vec<String>) -> Result<Vec<Vec<f64>>, AgentError> {
let url = format!("{}/v1/embeddings", self.base_url.trim_end_matches('/'));
let request_body = EmbeddingRequest {
@@ -90,33 +72,3 @@ impl LlmClient {
Ok(data.into_iter().map(|d| d.embedding).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use secrecy::SecretString;
fn client() -> LlmClient {
LlmClient::new(
"http://unused".into(),
SecretString::from(String::new()),
"m".into(),
"e".into(),
)
}
#[tokio::test]
async fn empty_input_makes_no_request() {
// Must short-circuit before any HTTP call (base_url is unroutable).
let out = client().embed(Vec::new()).await.unwrap();
assert!(out.is_empty());
}
#[test]
fn batch_size_is_within_backend_cap() {
assert!(
EMBED_BATCH_SIZE <= 25,
"must stay under the bge 25-input cap"
);
}
}