@@ -5,11 +5,13 @@ use crate::graph::cosine_similarity;
55use crate :: types:: {
66 AddEvidenceRequest , AppState , BetaParams , BrainMemory , ChallengeResponse ,
77 ConsensusLoraWeights , CreatePageRequest , DriftQuery , DriftReport , HealthResponse ,
8- LoraLatestResponse , LoraSubmission , LoraSubmitResponse , PageDelta , PageDetailResponse ,
9- PageResponse , PageStatus , PartitionQuery , PartitionResult , PublishNodeRequest ,
10- SearchQuery , ShareRequest , ShareResponse , StatusResponse , SubmitDeltaRequest ,
11- TemporalResponse , TrainingPreferencesResponse , TrainingQuery , TransferRequest ,
12- TransferResponse , VoteDirection , VoteRequest , WasmNode , WasmNodeSummary ,
8+ ListPagesResponse , ListQuery , ListResponse , ListSort , LoraLatestResponse , LoraSubmission ,
9+ LoraSubmitResponse , PageDelta , PageDetailResponse , PageResponse , PageStatus , PageSummary ,
10+ PartitionQuery , PartitionResult , PublishNodeRequest , ScoredBrainMemory , SearchQuery ,
11+ ShareRequest , ShareResponse ,
12+ StatusResponse , SubmitDeltaRequest , TemporalResponse , TrainingPreferencesResponse ,
13+ TrainingQuery , TransferRequest , TransferResponse , VerifyRequest , VerifyResponse ,
14+ VoteDirection , VoteRequest , WasmNode , WasmNodeSummary ,
1315} ;
1416use axum:: {
1517 extract:: { Path , Query , State } ,
@@ -224,6 +226,7 @@ pub async fn create_router() -> Router {
224226 . route ( "/v1/memories/:id/vote" , post ( vote_memory) )
225227 . route ( "/v1/memories/:id" , delete ( delete_memory) )
226228 . route ( "/v1/transfer" , post ( transfer) )
229+ . route ( "/v1/verify" , post ( verify_endpoint) )
227230 . route ( "/v1/drift" , get ( drift_report) )
228231 . route ( "/v1/partition" , get ( partition) )
229232 . route ( "/v1/status" , get ( status) )
@@ -235,7 +238,7 @@ pub async fn create_router() -> Router {
235238 . route ( "/v1/lora/submit" , post ( lora_submit) )
236239 . route ( "/v1/training/preferences" , get ( training_preferences) )
237240 // Brainpedia (ADR-062)
238- . route ( "/v1/pages" , post ( create_page) )
241+ . route ( "/v1/pages" , get ( list_pages ) . post ( create_page) )
239242 . route ( "/v1/pages/:id" , get ( get_page) )
240243 . route ( "/v1/pages/:id/deltas" , post ( submit_delta) )
241244 . route ( "/v1/pages/:id/deltas" , get ( list_deltas) )
@@ -622,7 +625,7 @@ async fn search_memories(
622625 State ( state) : State < AppState > ,
623626 contributor : AuthenticatedContributor ,
624627 Query ( query) : Query < SearchQuery > ,
625- ) -> Result < Json < Vec < BrainMemory > > , ( StatusCode , String ) > {
628+ ) -> Result < Json < Vec < ScoredBrainMemory > > , ( StatusCode , String ) > {
626629 if !state. rate_limiter . check_read ( & contributor. pseudonym ) {
627630 return Err ( ( StatusCode :: TOO_MANY_REQUESTS , "Read rate limit exceeded" . into ( ) ) ) ;
628631 }
@@ -767,7 +770,7 @@ async fn search_memories(
767770 // Build scored list: keyword-dominant with embedding + graph + vote signals
768771 let mut scored: Vec < ( f64 , BrainMemory ) > = raw
769772 . into_iter ( )
770- . map ( |m | {
773+ . map ( |( _ , m ) | {
771774 let rep = state
772775 . store
773776 . get_contributor_reputation ( & m. contributor_id )
@@ -977,16 +980,16 @@ async fn search_memories(
977980 // Single final sort after all AGI + midstream scoring layers
978981 scored. sort_unstable_by ( |a, b| b. 0 . partial_cmp ( & a. 0 ) . unwrap_or ( std:: cmp:: Ordering :: Equal ) ) ;
979982 scored. truncate ( limit) ;
980- let results: Vec < BrainMemory > = scored. into_iter ( ) . map ( |( _ , m ) | m ) . collect ( ) ;
983+ let results: Vec < ScoredBrainMemory > = scored. into_iter ( ) . map ( |( score , memory ) | ScoredBrainMemory { memory , score } ) . collect ( ) ;
981984
982985 // ── SONA: Record search trajectory for learning ──
983986 if state. rvf_flags . sona_enabled && !results. is_empty ( ) {
984987 let sona = state. sona . read ( ) ;
985988 let mut builder = sona. begin_trajectory ( query_embedding. clone ( ) ) ;
986989 builder. add_step (
987- results[ 0 ] . embedding . clone ( ) ,
990+ results[ 0 ] . memory . embedding . clone ( ) ,
988991 vec ! [ ] ,
989- results[ 0 ] . quality_score . mean ( ) as f32 ,
992+ results[ 0 ] . memory . quality_score . mean ( ) as f32 ,
990993 ) ;
991994 sona. end_trajectory ( builder, 0.5 ) ;
992995 }
@@ -997,21 +1000,29 @@ async fn search_memories(
9971000async fn list_memories (
9981001 State ( state) : State < AppState > ,
9991002 contributor : AuthenticatedContributor ,
1000- Query ( query) : Query < SearchQuery > ,
1001- ) -> Result < Json < Vec < BrainMemory > > , ( StatusCode , String ) > {
1003+ Query ( query) : Query < ListQuery > ,
1004+ ) -> Result < Json < ListResponse > , ( StatusCode , String ) > {
10021005 if !state. rate_limiter . check_read ( & contributor. pseudonym ) {
10031006 return Err ( ( StatusCode :: TOO_MANY_REQUESTS , "Read rate limit exceeded" . into ( ) ) ) ;
10041007 }
10051008
10061009 let limit = query. limit . unwrap_or ( 20 ) . min ( 100 ) ;
1010+ let offset = query. offset . unwrap_or ( 0 ) ;
1011+ let sort = query. sort . unwrap_or_default ( ) ;
1012+ let tags: Option < Vec < String > > = query. tags . map ( |t| t. split ( ',' ) . map ( |s| s. trim ( ) . to_string ( ) ) . collect ( ) ) ;
10071013
1008- let results = state
1014+ let ( memories , total_count ) = state
10091015 . store
1010- . list_memories ( query. category . as_ref ( ) , limit)
1016+ . list_memories ( query. category . as_ref ( ) , tags . as_deref ( ) , limit, offset , & sort )
10111017 . await
10121018 . map_err ( |e| ( StatusCode :: INTERNAL_SERVER_ERROR , e. to_string ( ) ) ) ?;
10131019
1014- Ok ( Json ( results) )
1020+ Ok ( Json ( ListResponse {
1021+ memories,
1022+ total_count,
1023+ offset,
1024+ limit,
1025+ } ) )
10151026}
10161027
10171028async fn get_memory (
@@ -1205,6 +1216,14 @@ async fn transfer(
12051216 )
12061217 } ;
12071218
1219+ let mut warnings = Vec :: new ( ) ;
1220+ if source_memories. is_empty ( ) {
1221+ warnings. push ( format ! ( "No memories found matching source domain '{}'" , req. source_domain) ) ;
1222+ }
1223+ if target_memories. is_empty ( ) {
1224+ warnings. push ( format ! ( "No memories found matching target domain '{}'" , req. target_domain) ) ;
1225+ }
1226+
12081227 Ok ( Json ( TransferResponse {
12091228 source_domain : req. source_domain ,
12101229 target_domain : req. target_domain ,
@@ -1214,9 +1233,124 @@ async fn transfer(
12141233 "Transfer initiated by {} (acceleration: {:.2}x, promotable: {})" ,
12151234 contributor. pseudonym, verification. acceleration_factor, verification. promotable
12161235 ) ,
1236+ source_memory_count : source_memories. len ( ) ,
1237+ target_memory_count : target_memories. len ( ) ,
1238+ warnings,
12171239 } ) )
12181240}
12191241
1242+ async fn verify_endpoint (
1243+ State ( state) : State < AppState > ,
1244+ contributor : AuthenticatedContributor ,
1245+ Json ( req) : Json < VerifyRequest > ,
1246+ ) -> Result < Json < VerifyResponse > , ( StatusCode , String ) > {
1247+ if !state. rate_limiter . check_read ( & contributor. pseudonym ) {
1248+ return Err ( ( StatusCode :: TOO_MANY_REQUESTS , "Read rate limit exceeded" . into ( ) ) ) ;
1249+ }
1250+
1251+ // Method 1: Witness chain steps + hash
1252+ if let ( Some ( steps) , Some ( hash) ) = ( & req. witness_steps , & req. witness_hash ) {
1253+ let verifier = state. verifier . read ( ) ;
1254+ let step_refs: Vec < & str > = steps. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
1255+ return match verifier. verify_witness_chain ( & step_refs, hash) {
1256+ Ok ( ( ) ) => Ok ( Json ( VerifyResponse {
1257+ valid : true ,
1258+ method : "witness_chain" . into ( ) ,
1259+ message : "Witness chain verification passed" . into ( ) ,
1260+ } ) ) ,
1261+ Err ( e) => Ok ( Json ( VerifyResponse {
1262+ valid : false ,
1263+ method : "witness_chain" . into ( ) ,
1264+ message : format ! ( "Witness chain verification failed: {e}" ) ,
1265+ } ) ) ,
1266+ } ;
1267+ }
1268+
1269+ // Method 2: Memory ID lookup
1270+ if let Some ( memory_id) = req. memory_id {
1271+ return match state. store . get_memory ( & memory_id) . await {
1272+ Ok ( Some ( mem) ) => {
1273+ // If witness_hash provided, verify it matches
1274+ if let Some ( ref hash) = req. witness_hash {
1275+ let equal = subtle:: ConstantTimeEq :: ct_eq (
1276+ mem. witness_hash . as_bytes ( ) ,
1277+ hash. as_bytes ( ) ,
1278+ ) ;
1279+ if bool:: from ( equal) {
1280+ Ok ( Json ( VerifyResponse {
1281+ valid : true ,
1282+ method : "memory_id" . into ( ) ,
1283+ message : format ! ( "Memory {} witness hash verified" , memory_id) ,
1284+ } ) )
1285+ } else {
1286+ Ok ( Json ( VerifyResponse {
1287+ valid : false ,
1288+ method : "memory_id" . into ( ) ,
1289+ message : format ! ( "Memory {} witness hash mismatch" , memory_id) ,
1290+ } ) )
1291+ }
1292+ } else {
1293+ Ok ( Json ( VerifyResponse {
1294+ valid : true ,
1295+ method : "memory_id" . into ( ) ,
1296+ message : format ! ( "Memory {} exists and is valid" , memory_id) ,
1297+ } ) )
1298+ }
1299+ }
1300+ Ok ( None ) => Ok ( Json ( VerifyResponse {
1301+ valid : false ,
1302+ method : "memory_id" . into ( ) ,
1303+ message : format ! ( "Memory {} not found" , memory_id) ,
1304+ } ) ) ,
1305+ Err ( e) => Err ( ( StatusCode :: INTERNAL_SERVER_ERROR , e. to_string ( ) ) ) ,
1306+ } ;
1307+ }
1308+
1309+ // Method 3: Content hash verification
1310+ if let ( Some ( hash) , Some ( data) ) = ( & req. content_hash , & req. content_data ) {
1311+ let verifier = state. verifier . read ( ) ;
1312+ return match verifier. verify_content_hash ( data. as_bytes ( ) , hash) {
1313+ Ok ( ( ) ) => Ok ( Json ( VerifyResponse {
1314+ valid : true ,
1315+ method : "content_hash" . into ( ) ,
1316+ message : "Content hash verification passed" . into ( ) ,
1317+ } ) ) ,
1318+ Err ( e) => Ok ( Json ( VerifyResponse {
1319+ valid : false ,
1320+ method : "content_hash" . into ( ) ,
1321+ message : format ! ( "Content hash verification failed: {e}" ) ,
1322+ } ) ) ,
1323+ } ;
1324+ }
1325+
1326+ // Method 4: Binary witness chain bytes (base64)
1327+ if let Some ( ref b64) = req. witness_chain_bytes {
1328+ use base64:: Engine as _;
1329+ let verifier = state. verifier . read ( ) ;
1330+ return match base64:: engine:: general_purpose:: STANDARD . decode ( b64) {
1331+ Ok ( bytes) => match verifier. verify_rvf_witness_chain ( & bytes) {
1332+ Ok ( entries) => Ok ( Json ( VerifyResponse {
1333+ valid : true ,
1334+ method : "witness_chain_bytes" . into ( ) ,
1335+ message : format ! ( "Binary witness chain valid ({} entries)" , entries. len( ) ) ,
1336+ } ) ) ,
1337+ Err ( e) => Ok ( Json ( VerifyResponse {
1338+ valid : false ,
1339+ method : "witness_chain_bytes" . into ( ) ,
1340+ message : format ! ( "Binary witness chain invalid: {e}" ) ,
1341+ } ) ) ,
1342+ } ,
1343+ Err ( e) => Ok ( Json ( VerifyResponse {
1344+ valid : false ,
1345+ method : "witness_chain_bytes" . into ( ) ,
1346+ message : format ! ( "Invalid base64 encoding: {e}" ) ,
1347+ } ) ) ,
1348+ } ;
1349+ }
1350+
1351+ Err ( ( StatusCode :: BAD_REQUEST , "No verification method specified. Provide witness_steps+witness_hash, memory_id, content_hash+content_data, or witness_chain_bytes" . into ( ) ) )
1352+ }
1353+
12201354async fn drift_report (
12211355 State ( state) : State < AppState > ,
12221356 _contributor : AuthenticatedContributor ,
@@ -1588,6 +1722,68 @@ async fn training_preferences(
15881722// Brainpedia endpoints (ADR-062)
15891723// ──────────────────────────────────────────────────────────────────────
15901724
1725+ /// GET /v1/pages — list Brainpedia pages with pagination
1726+ #[ derive( Debug , serde:: Deserialize ) ]
1727+ struct ListPagesQuery {
1728+ limit : Option < usize > ,
1729+ offset : Option < usize > ,
1730+ status : Option < String > ,
1731+ }
1732+
1733+ async fn list_pages (
1734+ State ( state) : State < AppState > ,
1735+ _contributor : AuthenticatedContributor ,
1736+ Query ( query) : Query < ListPagesQuery > ,
1737+ ) -> Json < ListPagesResponse > {
1738+ let limit = query. limit . unwrap_or ( 20 ) . min ( 100 ) ;
1739+ let offset = query. offset . unwrap_or ( 0 ) ;
1740+
1741+ let ( page_ids, total_count) = state. store . list_pages ( limit + offset, 0 ) ;
1742+ let status_filter = query. status . as_deref ( ) ;
1743+
1744+ let mut summaries: Vec < PageSummary > = Vec :: new ( ) ;
1745+ for id in & page_ids {
1746+ let page_status = match state. store . get_page_status ( id) {
1747+ Some ( s) => s,
1748+ None => continue ,
1749+ } ;
1750+ // Apply status filter if provided
1751+ if let Some ( filter) = status_filter {
1752+ let status_str = page_status. to_string ( ) ;
1753+ if status_str != filter {
1754+ continue ;
1755+ }
1756+ }
1757+ if let Ok ( Some ( mem) ) = state. store . get_memory ( id) . await {
1758+ let deltas = state. store . get_deltas ( id) ;
1759+ let evidence = state. store . get_evidence ( id) ;
1760+ summaries. push ( PageSummary {
1761+ id : * id,
1762+ title : mem. title ,
1763+ category : mem. category ,
1764+ status : page_status,
1765+ quality_score : mem. quality_score . mean ( ) ,
1766+ delta_count : deltas. len ( ) as u32 ,
1767+ evidence_count : evidence. len ( ) as u32 ,
1768+ updated_at : mem. updated_at ,
1769+ } ) ;
1770+ }
1771+ }
1772+
1773+ // Sort by updated_at descending
1774+ summaries. sort_by ( |a, b| b. updated_at . cmp ( & a. updated_at ) ) ;
1775+
1776+ let total_filtered = summaries. len ( ) ;
1777+ let paginated: Vec < PageSummary > = summaries. into_iter ( ) . skip ( offset) . take ( limit) . collect ( ) ;
1778+
1779+ Json ( ListPagesResponse {
1780+ pages : paginated,
1781+ total_count : total_filtered,
1782+ offset,
1783+ limit,
1784+ } )
1785+ }
1786+
15911787/// POST /v1/pages — create a new Brainpedia page (Draft)
15921788/// Requires reputation >= 0.5 and contribution_count >= 10 (unless system)
15931789async fn create_page (
0 commit comments