This commit implements Phase 17 of the Aphoria roadmap, adding: **Inline Claim Markers (@aphoria:claim):** - New extractor for detecting inline markers in comments - Pending markers tracked in .aphoria/pending_markers.toml - CLI commands: list-markers, formalize-marker, reject-marker - Support for all major comment styles (Rust, Python, SQL, etc.) - Auto-sync during scan (configurable) **Claim Enrichment:** - ClaimEnrichment type with source attribution (inline, extractor, manual) - EnrichedClaimInfo with full enrichment metadata - Extended AuthoredClaim with optional enrichment field - API endpoints for enriched claim queries - Dashboard UI components (enrichment badge, verdict badge) **Enhanced Extractor Trait:** - verifiable_predicates() method for declaring (tail_path, predicate) pairs - 10 security extractors now implement verifiable_predicates - Enables claim suggester skill to find unclaimed patterns **Documentation:** - Phase 17 summary with complete implementation details - Gap fixes summary documenting 8 closed vision gaps - Updated CLI reference with new commands - New aphoria-docs skill for documentation maintenance - Updated roadmap with Phase 17 completion **Integration:** - ClaimsFile support for claim enrichment persistence - Pattern aggregate store support for enrichment queries - Dashboard filters and display for enrichment metadata - API handlers for list-markers and enrichment queries **Tests:** - New gap_fixes_integration test suite - Corpus enricher module with best practices ingestion Closes: VG-005, VG-017, VG-018, VG-019, VG-020, VG-021, VG-022, VG-023 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
141 lines
5.3 KiB
Rust
141 lines
5.3 KiB
Rust
//! Conversion functions between core types and DTOs.
|
|
|
|
use stemedb_core::types::{Assertion, LifecycleStage, ObjectValue, SignatureEntry, SourceClass};
|
|
|
|
use super::enums::{LifecycleDto, ObjectValueDto, SignatureDto, SourceClassDto};
|
|
use super::requests::{CreateAssertionRequest, HlcTimestampDto};
|
|
|
|
/// Convert an Assertion to a CreateAssertionRequest.
|
|
///
|
|
/// This is an infallible conversion that hex-encodes all binary fields.
|
|
/// Includes timestamp and HLC timestamp for v2 signature verification.
|
|
pub fn assertion_to_request(assertion: &Assertion) -> CreateAssertionRequest {
|
|
CreateAssertionRequest {
|
|
subject: assertion.subject.clone(),
|
|
predicate: assertion.predicate.clone(),
|
|
object: object_value_to_dto(&assertion.object),
|
|
confidence: assertion.confidence,
|
|
signatures: assertion.signatures.iter().map(signature_to_dto).collect(),
|
|
source_hash: hex::encode(assertion.source_hash),
|
|
parent_hash: assertion.parent_hash.map(hex::encode),
|
|
source_class: Some(source_class_to_dto(assertion.source_class)),
|
|
lifecycle: Some(lifecycle_to_dto(assertion.lifecycle)),
|
|
source_metadata: assertion
|
|
.source_metadata
|
|
.as_ref()
|
|
.map(|b| String::from_utf8_lossy(b).into_owned()),
|
|
// Include timestamps for v2 signature verification
|
|
timestamp: Some(assertion.timestamp),
|
|
hlc_timestamp: Some(HlcTimestampDto {
|
|
time_ntp64: assertion.hlc_timestamp.time_ntp64,
|
|
node_id: hex::encode(assertion.hlc_timestamp.node_id),
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Convert ObjectValue to DTO.
|
|
pub fn object_value_to_dto(value: &ObjectValue) -> ObjectValueDto {
|
|
match value {
|
|
ObjectValue::Text(s) => ObjectValueDto::Text(s.clone()),
|
|
ObjectValue::Number(n) => ObjectValueDto::Number(*n),
|
|
ObjectValue::Boolean(b) => ObjectValueDto::Boolean(*b),
|
|
ObjectValue::Reference(r) => ObjectValueDto::Reference(r.clone()),
|
|
}
|
|
}
|
|
|
|
/// Convert SignatureEntry to DTO.
|
|
pub fn signature_to_dto(entry: &SignatureEntry) -> SignatureDto {
|
|
SignatureDto {
|
|
agent_id: hex::encode(entry.agent_id),
|
|
signature: hex::encode(entry.signature),
|
|
timestamp: entry.timestamp,
|
|
version: Some(entry.version),
|
|
}
|
|
}
|
|
|
|
/// Convert SourceClass to DTO.
|
|
pub fn source_class_to_dto(sc: SourceClass) -> SourceClassDto {
|
|
match sc {
|
|
SourceClass::Regulatory => SourceClassDto::Regulatory,
|
|
SourceClass::Clinical => SourceClassDto::Clinical,
|
|
SourceClass::Observational => SourceClassDto::Observational,
|
|
SourceClass::TeamPolicy => SourceClassDto::TeamPolicy,
|
|
SourceClass::Expert => SourceClassDto::Expert,
|
|
SourceClass::Community => SourceClassDto::Community,
|
|
SourceClass::Anecdotal => SourceClassDto::Anecdotal,
|
|
}
|
|
}
|
|
|
|
/// Convert LifecycleStage to DTO.
|
|
pub fn lifecycle_to_dto(stage: LifecycleStage) -> LifecycleDto {
|
|
match stage {
|
|
LifecycleStage::Proposed => LifecycleDto::Proposed,
|
|
LifecycleStage::UnderReview => LifecycleDto::UnderReview,
|
|
LifecycleStage::Approved => LifecycleDto::Approved,
|
|
LifecycleStage::Deprecated => LifecycleDto::Deprecated,
|
|
LifecycleStage::Rejected => LifecycleDto::Rejected,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use stemedb_core::types::HlcTimestamp;
|
|
|
|
#[test]
|
|
fn test_assertion_to_request_basic() {
|
|
let assertion = Assertion {
|
|
subject: "Semaglutide:Type2Diabetes".to_string(),
|
|
predicate: "hba1c_change_percent".to_string(),
|
|
object: ObjectValue::Number(-1.2),
|
|
parent_hash: None,
|
|
source_hash: [0u8; 32],
|
|
source_class: SourceClass::Regulatory,
|
|
visual_hash: None,
|
|
epoch: None,
|
|
source_metadata: None,
|
|
lifecycle: LifecycleStage::Approved,
|
|
signatures: vec![SignatureEntry {
|
|
agent_id: [1u8; 32],
|
|
signature: [2u8; 64],
|
|
timestamp: 1234567890,
|
|
version: 2,
|
|
}],
|
|
confidence: 0.95,
|
|
timestamp: 1234567890,
|
|
hlc_timestamp: HlcTimestamp::default(),
|
|
vector: None,
|
|
};
|
|
|
|
let request = assertion_to_request(&assertion);
|
|
|
|
assert_eq!(request.subject, "Semaglutide:Type2Diabetes");
|
|
assert_eq!(request.predicate, "hba1c_change_percent");
|
|
assert_eq!(request.confidence, 0.95);
|
|
assert_eq!(request.signatures.len(), 1);
|
|
assert_eq!(request.signatures[0].version, Some(2));
|
|
assert!(matches!(request.source_class, Some(SourceClassDto::Regulatory)));
|
|
assert!(matches!(request.lifecycle, Some(LifecycleDto::Approved)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_object_value_conversion() {
|
|
assert!(matches!(
|
|
object_value_to_dto(&ObjectValue::Text("test".to_string())),
|
|
ObjectValueDto::Text(s) if s == "test"
|
|
));
|
|
assert!(matches!(
|
|
object_value_to_dto(&ObjectValue::Number(42.5)),
|
|
ObjectValueDto::Number(n) if (n - 42.5).abs() < f64::EPSILON
|
|
));
|
|
assert!(matches!(
|
|
object_value_to_dto(&ObjectValue::Boolean(true)),
|
|
ObjectValueDto::Boolean(true)
|
|
));
|
|
assert!(matches!(
|
|
object_value_to_dto(&ObjectValue::Reference("ref".to_string())),
|
|
ObjectValueDto::Reference(r) if r == "ref"
|
|
));
|
|
}
|
|
}
|