Skip to content

Commit 3e55b08

Browse files
hyperpolymathclaude
andcommitted
feat(verisim-api): Phase 1 Step 3 — ?include=embedding byte-exact round-trip
Wire the previously-parsed `embedding` IncludeFlag through to the GET response so callers can pull back the stored Vec<f32> verbatim and verify byte-exact identity against the input vector. - OctadResponse.embedding: Option<Vec<f32>>, populated only when the caller passes ?include=embedding - OctadResponse::with_includes copies h.embedding.vector when the flag is set; default GET still omits the field Closes the vector-shape gap from email-octad-experiment 2026-04-27 veridicality run (genesis run scored 0.84 on Vector via indirect self-NN probe; with byte-exact round-trip the shape is now directly verifiable rather than inferred). Test: test_include_embedding_byte_exact_round_trip — POST a 3-element embedding, GET without include (must be absent), GET with ?include=embedding (must match `to_bits()` exactly per component). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ca52651 commit 3e55b08

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

  • verisimdb/rust-core/verisim-api/src

verisimdb/rust-core/verisim-api/src/lib.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ pub struct OctadResponse {
399399
/// the default response shape stays small.
400400
#[serde(default, skip_serializing_if = "Option::is_none")]
401401
pub semantic_types: Option<Vec<String>>,
402+
/// Populated only when the caller passes `?include=embedding` on a GET.
403+
/// The stored vector is returned verbatim (same dimension, same
404+
/// component values) so the Vector shape is byte-exactly verifiable.
405+
#[serde(default, skip_serializing_if = "Option::is_none")]
406+
pub embedding: Option<Vec<f32>>,
402407
}
403408

404409
/// Per-request flags controlling which expensive or large fields the GET
@@ -473,6 +478,11 @@ impl OctadResponse {
473478
} else {
474479
None
475480
};
481+
let embedding = if flags.embedding {
482+
h.embedding.as_ref().map(|e| e.vector.clone())
483+
} else {
484+
None
485+
};
476486
Self {
477487
id: h.id.to_string(),
478488
status: OctadStatusResponse {
@@ -491,6 +501,7 @@ impl OctadResponse {
491501
version_count: h.version_count,
492502
provenance_chain_length: h.provenance_chain_length,
493503
semantic_types,
504+
embedding,
494505
}
495506
}
496507
}
@@ -2381,6 +2392,106 @@ mod tests {
23812392
assert_eq!(response.status(), StatusCode::OK);
23822393
}
23832394

2395+
#[tokio::test]
2396+
async fn test_include_embedding_byte_exact_round_trip() {
2397+
// Phase 1 gap closure: GET /octads/{id}?include=embedding must
2398+
// surface the stored Vec<f32> verbatim — same dimension, same
2399+
// component values, no truncation, no reorder. This is what makes
2400+
// the Vector shape byte-exactly veridical.
2401+
let state = create_test_state().await;
2402+
let app = build_router(state);
2403+
2404+
let original: Vec<f32> = vec![0.1, 0.2, 0.3];
2405+
let create_request = OctadRequest {
2406+
title: Some("Vec round-trip".to_string()),
2407+
body: Some("Body".to_string()),
2408+
embedding: Some(original.clone()),
2409+
types: None,
2410+
relationships: None,
2411+
tensor: None,
2412+
temporal: None,
2413+
metadata: None,
2414+
provenance: None,
2415+
spatial: None,
2416+
};
2417+
let response = app
2418+
.clone()
2419+
.oneshot(
2420+
Request::builder()
2421+
.method("POST")
2422+
.uri("/octads")
2423+
.header("content-type", "application/json")
2424+
.body(Body::from(
2425+
serde_json::to_string(&create_request).expect("serialize create request"),
2426+
))
2427+
.expect("build POST request"),
2428+
)
2429+
.await
2430+
.expect("oneshot create");
2431+
assert_eq!(response.status(), StatusCode::CREATED);
2432+
let body = axum::body::to_bytes(response.into_body(), 1024 * 1024)
2433+
.await
2434+
.expect("read create body");
2435+
let created: OctadResponse =
2436+
serde_json::from_slice(&body).expect("decode create response");
2437+
assert!(
2438+
created.embedding.is_none(),
2439+
"POST response must not surface embedding by default"
2440+
);
2441+
2442+
// GET without include — embedding must be omitted
2443+
let response = app
2444+
.clone()
2445+
.oneshot(
2446+
Request::builder()
2447+
.uri(format!("/octads/{}", created.id))
2448+
.body(Body::empty())
2449+
.expect("build GET request"),
2450+
)
2451+
.await
2452+
.expect("oneshot get");
2453+
let body = axum::body::to_bytes(response.into_body(), 1024 * 1024)
2454+
.await
2455+
.expect("read get body");
2456+
let got: OctadResponse = serde_json::from_slice(&body).expect("decode get response");
2457+
assert!(
2458+
got.embedding.is_none(),
2459+
"default GET must not surface embedding"
2460+
);
2461+
2462+
// GET with ?include=embedding — bytes must match exactly
2463+
let response = app
2464+
.oneshot(
2465+
Request::builder()
2466+
.uri(format!("/octads/{}?include=embedding", created.id))
2467+
.body(Body::empty())
2468+
.expect("build GET request"),
2469+
)
2470+
.await
2471+
.expect("oneshot get with include=embedding");
2472+
let body = axum::body::to_bytes(response.into_body(), 1024 * 1024)
2473+
.await
2474+
.expect("read get body");
2475+
let got: OctadResponse =
2476+
serde_json::from_slice(&body).expect("decode get response with embedding");
2477+
let returned = got
2478+
.embedding
2479+
.as_ref()
2480+
.expect("?include=embedding must surface embedding");
2481+
assert_eq!(
2482+
returned.len(),
2483+
original.len(),
2484+
"byte-exact round-trip — same dimension"
2485+
);
2486+
for (i, (a, b)) in original.iter().zip(returned.iter()).enumerate() {
2487+
assert_eq!(
2488+
a.to_bits(),
2489+
b.to_bits(),
2490+
"byte-exact round-trip — component {i} differs ({a} vs {b})"
2491+
);
2492+
}
2493+
}
2494+
23842495
#[tokio::test]
23852496
async fn test_include_types_round_trip() {
23862497
// Phase 1 gap closure: GET /octads/{id}?include=types must surface the

0 commit comments

Comments
 (0)