diff --git a/crates/tracedecay-contracts/src/doctor/adapters.rs b/crates/tracedecay-contracts/src/doctor/adapters.rs index 506a3ad942..799b089ab9 100644 --- a/crates/tracedecay-contracts/src/doctor/adapters.rs +++ b/crates/tracedecay-contracts/src/doctor/adapters.rs @@ -1,59 +1,26 @@ -//! Doctor source-port adapters and the composed-report factory. +//! Doctor kernel read mappers and the resolved-input bundle. //! -//! Each adapter holds one already-resolved kernel *read* and implements the -//! matching source port. Surfaces that own live signals (the daemon doctor -//! reader, tests) map those signals into reads, then hand the bundle to -//! [`compose_doctor_report`]. This module owns no store, scheduler, or -//! transport; daemon I/O that gathers the signals stays in the composition -//! root. +//! Pure functions map live daemon signals into typed kernel reads. The +//! composition root wires those reads into [`DoctorReportComposerV1`] through +//! the genuine source ports defined in [`super::sources`]. This module owns no +//! store, scheduler, or transport. //! //! [`DaemonRuntimeHealthSignalV1`] is the boundary type daemon writers fill. //! Mapping it through [`runtime_health_read`] never imports daemon types. use tracedecay_domain::CodeGenerationId; -use crate::RequestContext; -use crate::error::ApplicationContractError; use crate::feedback::FeedbackCompletedPublicationV1; -use super::report::{DoctorReportComposerV1, DoctorReportV1}; -#[cfg(test)] -use super::sources::SemanticOwnerStateV1; use super::sources::{ - AdvisoryFeedbackDoctorPort, AdvisoryFeedbackFindingReadV1, AdvisoryFeedbackReadV1, - AdvisoryFeedbackSummaryReadV1, CodeIndexMountDoctorPort, CodeIndexMountReadV1, - ConfigurationAuthorityDoctorPort, ConfigurationAuthorityReadV1, DoctorSourceFuture, - DoctorStorageFamilyReadV1, DoctorStorageIncompleteReasonV1, HostIntegrationDoctorPort, - HostIntegrationReadV1, IngestRefusalCensusReadV1, LanguageServerDoctorPort, - LanguageServerReadV1, ObservabilityDoctorPort, ObservabilityReadV1, OperationalAuditDoctorPort, - OperationalAuditReadV1, RuntimeHealthDoctorPort, RuntimeHealthReadV1, RuntimeLivenessV1, - SemanticOwnerDoctorPort, SemanticOwnerReadV1, StorageDoctorPort, + AdvisoryFeedbackFindingReadV1, AdvisoryFeedbackReadV1, AdvisoryFeedbackSummaryReadV1, + CodeIndexMountReadV1, ConfigurationAuthorityReadV1, DoctorStorageFamilyReadV1, + DoctorStorageIncompleteReasonV1, HostIntegrationReadV1, IngestRefusalCensusReadV1, + LanguageServerReadV1, ObservabilityReadV1, OperationalAuditReadV1, RuntimeHealthReadV1, + RuntimeLivenessV1, SemanticOwnerReadV1, }; use super::types::{DoctorCoverageCompletenessV1, DoctorStorageFindingV1}; -/// Adapter over the configuration authority (Configuration family). -pub struct ConfigurationAuthorityDoctorAdapterV1 { - read: ConfigurationAuthorityReadV1, -} - -impl ConfigurationAuthorityDoctorAdapterV1 { - /// Build the adapter from an already-resolved kernel read. - #[must_use] - pub fn from_read(read: ConfigurationAuthorityReadV1) -> Self { - Self { read } - } -} - -impl ConfigurationAuthorityDoctorPort for ConfigurationAuthorityDoctorAdapterV1 { - fn configuration_health<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, ConfigurationAuthorityReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - /// The real daemon/runtime health signals a serving runtime writes. /// /// This is the *writer-side* snapshot: the daemon fills each field from its @@ -120,74 +87,6 @@ pub fn runtime_health_read(signal: &DaemonRuntimeHealthSignalV1) -> RuntimeHealt } } -/// Adapter over the daemon/runtime health snapshot (`StorageRuntime` family). -pub struct RuntimeHealthDoctorAdapterV1 { - read: RuntimeHealthReadV1, -} - -impl RuntimeHealthDoctorAdapterV1 { - /// Build the adapter from an already-resolved kernel read. - #[must_use] - pub fn from_read(read: RuntimeHealthReadV1) -> Self { - Self { read } - } -} - -impl RuntimeHealthDoctorPort for RuntimeHealthDoctorAdapterV1 { - fn runtime_health<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, RuntimeHealthReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over Remote HTTPS and registered-profile operational authority. -pub struct OperationalAuditDoctorAdapterV1 { - read: OperationalAuditReadV1, -} - -impl OperationalAuditDoctorAdapterV1 { - #[must_use] - pub fn from_read(read: OperationalAuditReadV1) -> Self { - Self { read } - } -} - -impl OperationalAuditDoctorPort for OperationalAuditDoctorAdapterV1 { - fn operational_audit<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, OperationalAuditReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over host/agent integration conformance (Advisory family). -pub struct HostIntegrationDoctorAdapterV1 { - read: HostIntegrationReadV1, -} - -impl HostIntegrationDoctorAdapterV1 { - /// Build the adapter from an already-resolved kernel read. - #[must_use] - pub fn from_read(read: HostIntegrationReadV1) -> Self { - Self { read } - } -} - -impl HostIntegrationDoctorPort for HostIntegrationDoctorAdapterV1 { - fn host_conformance<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, HostIntegrationReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - /// Project the latest exact-scope durable feedback publication into Doctor's /// distinct advisory port. Host conformance remains a separate source. #[must_use] @@ -258,135 +157,6 @@ pub fn advisory_feedback_read_from_publication( } } -/// Adapter over the mounted feedback owner's canonical read store. -pub struct AdvisoryFeedbackDoctorAdapterV1 { - read: AdvisoryFeedbackReadV1, -} - -impl AdvisoryFeedbackDoctorAdapterV1 { - #[must_use] - pub fn from_read(read: AdvisoryFeedbackReadV1) -> Self { - Self { read } - } -} - -impl AdvisoryFeedbackDoctorPort for AdvisoryFeedbackDoctorAdapterV1 { - fn advisory_feedback<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, AdvisoryFeedbackReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over the code-index mount state (`SemanticIndex` family). -pub struct CodeIndexMountDoctorAdapterV1 { - read: CodeIndexMountReadV1, -} - -impl CodeIndexMountDoctorAdapterV1 { - /// Build the adapter from an already-resolved kernel read. - #[must_use] - pub fn from_read(read: CodeIndexMountReadV1) -> Self { - Self { read } - } -} - -impl CodeIndexMountDoctorPort for CodeIndexMountDoctorAdapterV1 { - fn code_index_mount<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, CodeIndexMountReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over the independently scheduled semantic owner. -pub struct SemanticOwnerDoctorAdapterV1 { - read: SemanticOwnerReadV1, -} - -impl SemanticOwnerDoctorAdapterV1 { - #[must_use] - pub fn from_read(read: SemanticOwnerReadV1) -> Self { - Self { read } - } -} - -impl SemanticOwnerDoctorPort for SemanticOwnerDoctorAdapterV1 { - fn semantic_owner<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, SemanticOwnerReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over live language-server/analyzer state. -pub struct LanguageServerDoctorAdapterV1 { - read: LanguageServerReadV1, -} - -impl LanguageServerDoctorAdapterV1 { - #[must_use] - pub fn from_read(read: LanguageServerReadV1) -> Self { - Self { read } - } -} - -impl LanguageServerDoctorPort for LanguageServerDoctorAdapterV1 { - fn language_server_health<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, LanguageServerReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// Adapter over the canonical durable Plan-26 observation read model. -pub struct ObservabilityDoctorAdapterV1 { - read: ObservabilityReadV1, - refusals: IngestRefusalCensusReadV1, -} - -impl ObservabilityDoctorAdapterV1 { - #[must_use] - pub fn from_read(read: ObservabilityReadV1) -> Self { - Self { - read, - refusals: IngestRefusalCensusReadV1::Unknown, - } - } - - #[must_use] - pub fn with_refusals(mut self, refusals: IngestRefusalCensusReadV1) -> Self { - self.refusals = refusals; - self - } -} - -impl ObservabilityDoctorPort for ObservabilityDoctorAdapterV1 { - fn observability_health<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, ObservabilityReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } - - fn ingest_refusal_census<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, IngestRefusalCensusReadV1> { - let read = self.refusals.clone(); - Box::pin(async move { read }) - } -} - /// Wrap a set of typed storage findings the retention producers emitted into a /// kernel read. /// @@ -477,34 +247,12 @@ fn storage_read_parts( } } -/// Adapter over storage retention/size findings (Storage family). -pub struct StorageDoctorAdapterV1 { - read: DoctorStorageFamilyReadV1, -} - -impl StorageDoctorAdapterV1 { - /// Build the adapter from an already-resolved kernel read. - #[must_use] - pub fn from_read(read: DoctorStorageFamilyReadV1) -> Self { - Self { read } - } -} - -impl StorageDoctorPort for StorageDoctorAdapterV1 { - fn storage_findings<'a>( - &'a self, - _context: &'a RequestContext, - ) -> DoctorSourceFuture<'a, DoctorStorageFamilyReadV1> { - let read = self.read.clone(); - Box::pin(async move { read }) - } -} - -/// The seven resolved kernel reads a Doctor report composes from. +/// The resolved kernel reads a Doctor report composes from. /// -/// A surface builds this bundle from the real signals it can reach and hands -/// it to [`compose_doctor_report`]. A signal the surface cannot obtain carries -/// its honest typed absence rather than a fabricated healthy read. +/// The composition root builds this bundle from the real signals it can reach +/// and wires each read into [`DoctorReportComposerV1`]. A signal the surface +/// cannot obtain carries its honest typed absence rather than a fabricated +/// healthy read. #[derive(Clone, Debug)] pub struct DoctorKernelInputsV1 { /// Configuration-authority read (Configuration family). @@ -531,112 +279,18 @@ pub struct DoctorKernelInputsV1 { pub storage: DoctorStorageFamilyReadV1, } -/// Compose a Doctor report from already-resolved source adapters. -/// -/// Wires all seven adapters into the kernel [`DoctorReportComposerV1`] and -/// composes. The composer enumerates every finding family truthfully: a family -/// whose read is unavailable is carried with its real evidence state and an -/// explicit coverage record, and the report asserts health only when every -/// family was consulted with complete coverage and every finding is healthy. -#[hotpath::measure(label = "daemon.doctor.compose", future = true)] -pub async fn compose_doctor_report( - context: &RequestContext, - inputs: &DoctorKernelInputsV1, -) -> Result { - let configuration = - ConfigurationAuthorityDoctorAdapterV1::from_read(inputs.configuration.clone()); - let runtime = RuntimeHealthDoctorAdapterV1::from_read(inputs.runtime.clone()); - let operational_audit = - OperationalAuditDoctorAdapterV1::from_read(inputs.operational_audit.clone()); - let host = HostIntegrationDoctorAdapterV1::from_read(inputs.host.clone()); - let advisory_feedback = - AdvisoryFeedbackDoctorAdapterV1::from_read(inputs.advisory_feedback.clone()); - let language_server = LanguageServerDoctorAdapterV1::from_read(inputs.language_server.clone()); - let code_index = CodeIndexMountDoctorAdapterV1::from_read(inputs.code_index.clone()); - let semantic_owner = SemanticOwnerDoctorAdapterV1::from_read(inputs.semantic_owner.clone()); - let observability = ObservabilityDoctorAdapterV1::from_read(inputs.observability.clone()) - .with_refusals(inputs.ingest_refusals.clone()); - let storage = StorageDoctorAdapterV1::from_read(inputs.storage.clone()); - - let composer = DoctorReportComposerV1::new() - .with_configuration(&configuration) - .with_runtime(&runtime) - .with_operational_audit(&operational_audit) - .with_host(&host) - .with_advisory_feedback(&advisory_feedback) - .with_language_server(&language_server) - .with_code_index(&code_index) - .with_semantic_owner(&semantic_owner) - .with_observability(&observability) - .with_storage(&storage); - - composer.compose(context).await -} - #[cfg(test)] #[allow(clippy::unwrap_used, clippy::expect_used)] mod tests { - use std::collections::BTreeSet; - - use tracedecay_domain::{ - ActorId, ManifestDigest, ProjectId, RepositoryId, UtcMicros, WorktreeId, - }; - use tracedecay_tool_catalog::{CapabilityId, UseCaseId}; - use crate::doctor::{ - CodeIndexMountReadV1, CodeIndexMountStateV1, ConfigurationAuthorityReadV1, - ConfigurationDriftV1, DoctorCoverageCompletenessV1, DoctorCoverageStatementV1, - DoctorEvidenceRefV1, DoctorEvidenceReferenceV1, DoctorEvidenceStateV1, - DoctorFamilyConsultationV1, DoctorFamilyCoverageV1, DoctorFamilyUnavailableReasonV1, - DoctorFindingFamilyV1, DoctorFindingV1, DoctorStorageFamilyReadV1, - DoctorStorageFindingKindV1, DoctorStorageFindingV1, DoctorStorageIncompleteReasonV1, - HostConformanceV1, HostIntegrationReadV1, IngestRefusalCensusReadV1, IngestRefusalCountV1, - LanguageServerReadV1, LanguageServerStateV1, ObservabilityReadV1, ObservabilityStateV1, - OperationalAuditReadV1, ProfileAuthorityReadV1, RemoteOperationalReadV1, - RuntimeHealthReadV1, RuntimeLivenessV1, - }; - use crate::{ - CancellationContext, CapabilityGrantId, CapabilityGrantSnapshot, Deadline, DisclosureClass, - RequestContext, RequestId, ResolvedScope, + DoctorCoverageCompletenessV1, DoctorCoverageStatementV1, DoctorEvidenceRefV1, + DoctorEvidenceReferenceV1, DoctorEvidenceStateV1, DoctorFindingFamilyV1, DoctorFindingV1, + DoctorStorageFamilyReadV1, DoctorStorageFindingKindV1, DoctorStorageFindingV1, + DoctorStorageIncompleteReasonV1, RuntimeHealthReadV1, RuntimeLivenessV1, }; use super::*; - fn context() -> RequestContext { - let actor = ActorId::new("actor.doctor-adapter-test").unwrap(); - let scope = ResolvedScope::new( - ProjectId::new("project.doctor-adapter-test").unwrap(), - RepositoryId::new("repository.doctor-adapter-test").unwrap(), - WorktreeId::new("worktree.doctor-adapter-test").unwrap(), - None, - ) - .unwrap(); - let capability = CapabilityId::new("capability.doctor-adapter-test").unwrap(); - let use_case = UseCaseId::new("use-case.doctor-adapter-test").unwrap(); - let grant = CapabilityGrantSnapshot::new( - CapabilityGrantId::new("grant.doctor-adapter-test").unwrap(), - 1, - ManifestDigest::new(format!("sha256:{}", "11".repeat(32))).unwrap(), - actor.clone(), - UtcMicros(1), - UtcMicros(10_000), - scope.clone(), - BTreeSet::from([capability]), - BTreeSet::from([use_case]), - DisclosureClass::Evidence, - ) - .unwrap(); - RequestContext::new( - actor, - scope, - grant, - RequestId::new("request.doctor-adapter-test").unwrap(), - Deadline::new(UtcMicros(9_000)).unwrap(), - CancellationContext::active("cancel.doctor-adapter-test").unwrap(), - ) - .unwrap() - } - fn orphan_storage_finding() -> DoctorStorageFindingV1 { let evidence = DoctorEvidenceRefV1::new( DoctorFindingFamilyV1::Storage, @@ -657,23 +311,6 @@ mod tests { DoctorStorageFindingV1::new(DoctorStorageFindingKindV1::OrphanStore, finding).unwrap() } - #[tokio::test] - async fn configuration_adapter_returns_seeded_read() { - let ctx = context(); - for read in [ - ConfigurationAuthorityReadV1::Resolved { - drift: ConfigurationDriftV1::InSync, - coverage: DoctorCoverageCompletenessV1::Complete, - }, - ConfigurationAuthorityReadV1::Absent, - ConfigurationAuthorityReadV1::Denied, - ConfigurationAuthorityReadV1::Unknown, - ] { - let adapter = ConfigurationAuthorityDoctorAdapterV1::from_read(read.clone()); - assert_eq!(adapter.configuration_health(&ctx).await, read); - } - } - #[test] fn runtime_healthy_requires_all_signals_observed_for_complete_coverage() { let healthy = DaemonRuntimeHealthSignalV1 { @@ -747,33 +384,6 @@ mod tests { ); } - #[tokio::test] - async fn runtime_adapter_returns_seeded_read() { - let ctx = context(); - let read = RuntimeHealthReadV1::Denied; - let adapter = RuntimeHealthDoctorAdapterV1::from_read(read.clone()); - assert_eq!(adapter.runtime_health(&ctx).await, read); - } - - #[tokio::test] - async fn host_adapter_returns_seeded_read() { - let ctx = context(); - let read = HostIntegrationReadV1::Observed { - conformance: HostConformanceV1::ProtocolDrift, - coverage: DoctorCoverageCompletenessV1::Complete, - }; - let adapter = HostIntegrationDoctorAdapterV1::from_read(read.clone()); - assert_eq!(adapter.host_conformance(&ctx).await, read); - } - - #[tokio::test] - async fn code_index_adapter_returns_seeded_read() { - let ctx = context(); - let read = CodeIndexMountReadV1::Absent; - let adapter = CodeIndexMountDoctorAdapterV1::from_read(read.clone()); - assert_eq!(adapter.code_index_mount(&ctx).await, read); - } - #[test] fn storage_family_read_absent_when_empty() { assert_eq!( @@ -794,17 +404,6 @@ mod tests { } } - #[tokio::test] - async fn storage_adapter_returns_seeded_read() { - let ctx = context(); - let adapter = - StorageDoctorAdapterV1::from_read(storage_family_read(vec![orphan_storage_finding()])); - match adapter.storage_findings(&ctx).await { - DoctorStorageFamilyReadV1::Observed { findings } => assert_eq!(findings.len(), 1), - other => panic!("expected observed, got {other:?}"), - } - } - #[test] fn unresolved_storage_producers_preserve_findings_and_weaken_coverage() { for (unresolved, expected_reason) in [ @@ -837,133 +436,4 @@ mod tests { } } } - - #[tokio::test] - async fn composed_report_carries_real_states_and_enumerates_coverage() { - let ctx = context(); - let inputs = DoctorKernelInputsV1 { - configuration: ConfigurationAuthorityReadV1::Resolved { - drift: ConfigurationDriftV1::InSync, - coverage: DoctorCoverageCompletenessV1::Complete, - }, - runtime: runtime_health_read(&DaemonRuntimeHealthSignalV1 { - serving: true, - startup_converged: false, - ..DaemonRuntimeHealthSignalV1::default() - }), - operational_audit: OperationalAuditReadV1 { - remote: RemoteOperationalReadV1::Unconfigured, - profile_authority: ProfileAuthorityReadV1::Unavailable, - }, - host: HostIntegrationReadV1::Denied, - advisory_feedback: AdvisoryFeedbackReadV1::Absent, - language_server: LanguageServerReadV1::Observed { - state: LanguageServerStateV1::Ready, - coverage: DoctorCoverageCompletenessV1::Complete, - }, - code_index: CodeIndexMountReadV1::Observed { - state: CodeIndexMountStateV1::Mounted, - coverage: DoctorCoverageCompletenessV1::Complete, - }, - semantic_owner: SemanticOwnerReadV1::Observed { - state: SemanticOwnerStateV1::Ready, - coverage: DoctorCoverageCompletenessV1::Complete, - }, - observability: ObservabilityReadV1::Observed { - state: ObservabilityStateV1::Current, - total_count: 7, - last_observed_at_micros: Some(42), - coverage: DoctorCoverageCompletenessV1::Partial, - }, - ingest_refusals: IngestRefusalCensusReadV1::Observed { - refusals: vec![IngestRefusalCountV1 { - provider: "cursor".to_owned(), - reason: "admission_refused".to_owned(), - count: 160, - }], - }, - storage: merge_storage_reads( - storage_family_read(vec![orphan_storage_finding()]), - DoctorStorageFamilyReadV1::Unknown, - ), - }; - - let report = compose_doctor_report(&ctx, &inputs).await.expect("report"); - - assert_eq!(report.coverage().families().len(), 7); - - let family_state = |family: DoctorFindingFamilyV1| { - report - .findings() - .find(|finding| finding.family() == family) - .map(DoctorFindingV1::state) - }; - assert_eq!( - family_state(DoctorFindingFamilyV1::Configuration), - Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::StorageRuntime), - Some(DoctorEvidenceStateV1::Degraded) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::Advisory), - Some(DoctorEvidenceStateV1::Denied) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::SemanticIndex), - Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::Storage), - Some(DoctorEvidenceStateV1::Degraded) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::LanguageServer), - Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) - ); - assert_eq!( - family_state(DoctorFindingFamilyV1::Observability), - Some(DoctorEvidenceStateV1::Partial) - ); - - assert!(!report.is_healthy_complete()); - assert_ne!( - report.coverage().completeness(), - DoctorCoverageCompletenessV1::Complete - ); - - let consultation = |family: DoctorFindingFamilyV1| { - report - .coverage() - .families() - .iter() - .find(|record| record.family() == family) - .map(DoctorFamilyCoverageV1::consultation) - }; - assert_eq!( - consultation(DoctorFindingFamilyV1::LanguageServer), - Some(DoctorFamilyConsultationV1::Consulted) - ); - assert_eq!( - consultation(DoctorFindingFamilyV1::Observability), - Some(DoctorFamilyConsultationV1::Consulted) - ); - assert_eq!( - consultation(DoctorFindingFamilyV1::Advisory), - Some(DoctorFamilyConsultationV1::Unavailable { - reason: DoctorFamilyUnavailableReasonV1::Denied, - }) - ); - assert_eq!( - consultation(DoctorFindingFamilyV1::Configuration), - Some(DoctorFamilyConsultationV1::Consulted) - ); - assert_eq!( - consultation(DoctorFindingFamilyV1::Storage), - Some(DoctorFamilyConsultationV1::Unavailable { - reason: DoctorFamilyUnavailableReasonV1::Unknown, - }) - ); - } } diff --git a/crates/tracedecay-contracts/src/doctor/mod.rs b/crates/tracedecay-contracts/src/doctor/mod.rs index 8431d80fbb..a9d43a858b 100644 --- a/crates/tracedecay-contracts/src/doctor/mod.rs +++ b/crates/tracedecay-contracts/src/doctor/mod.rs @@ -13,11 +13,7 @@ mod sources; mod types; pub use adapters::{ - AdvisoryFeedbackDoctorAdapterV1, CodeIndexMountDoctorAdapterV1, - ConfigurationAuthorityDoctorAdapterV1, DaemonRuntimeHealthSignalV1, DoctorKernelInputsV1, - HostIntegrationDoctorAdapterV1, LanguageServerDoctorAdapterV1, ObservabilityDoctorAdapterV1, - OperationalAuditDoctorAdapterV1, RuntimeHealthDoctorAdapterV1, SemanticOwnerDoctorAdapterV1, - StorageDoctorAdapterV1, advisory_feedback_read_from_publication, compose_doctor_report, + DaemonRuntimeHealthSignalV1, DoctorKernelInputsV1, advisory_feedback_read_from_publication, merge_storage_reads, runtime_health_read, storage_family_read, }; pub use report::{ diff --git a/crates/tracedecay-contracts/src/lib.rs b/crates/tracedecay-contracts/src/lib.rs index b8270a04f1..b5491e25f1 100644 --- a/crates/tracedecay-contracts/src/lib.rs +++ b/crates/tracedecay-contracts/src/lib.rs @@ -134,30 +134,26 @@ pub use diagnostics::{ ProviderSourceIdentity, RevisionDigest, }; pub use doctor::{ - AdvisoryFeedbackDoctorAdapterV1, AdvisoryFeedbackDoctorPort, AdvisoryFeedbackFindingReadV1, - AdvisoryFeedbackReadV1, AdvisoryFeedbackSummaryReadV1, CodeIndexMountDoctorAdapterV1, - CodeIndexMountDoctorPort, CodeIndexMountReadV1, CodeIndexMountStateV1, - ConfigurationAuthorityDoctorAdapterV1, ConfigurationAuthorityDoctorPort, - ConfigurationAuthorityReadV1, ConfigurationDriftV1, DOCTOR_FINDING_FAMILIES, - DaemonRuntimeHealthSignalV1, DoctorCoverageCompletenessV1, DoctorCoverageStatementV1, - DoctorEvidenceRefV1, DoctorEvidenceReferenceV1, DoctorEvidenceStateV1, - DoctorFamilyConsultationV1, DoctorFamilyCoverageV1, DoctorFamilyUnavailableReasonV1, - DoctorFindingFamilyV1, DoctorFindingV1, DoctorKernelInputsV1, DoctorReportComposerV1, - DoctorReportCoverageV1, DoctorReportEntryV1, DoctorReportV1, DoctorSourceFuture, - DoctorStorageFamilyReadV1, DoctorStorageFindingKindV1, DoctorStorageFindingV1, - HostConformanceV1, HostIntegrationDoctorAdapterV1, HostIntegrationDoctorPort, - HostIntegrationReadV1, IngestRefusalCensusReadV1, IngestRefusalCountV1, - LanguageServerDoctorAdapterV1, LanguageServerDoctorPort, LanguageServerReadV1, - LanguageServerStateV1, ObservabilityDoctorAdapterV1, ObservabilityDoctorPort, - ObservabilityReadV1, ObservabilityStateV1, OperationalAuditDoctorAdapterV1, - OperationalAuditDoctorPort, OperationalAuditReadV1, ProfileAuthorityReadV1, - RemoteAuthorityReadV1, RemoteListenerReadV1, RemoteOperationalReadV1, - RuntimeHealthDoctorAdapterV1, RuntimeHealthDoctorPort, RuntimeHealthReadV1, RuntimeLivenessV1, - StorageDoctorAdapterV1, StorageDoctorPort, advisory_feedback_findings, - advisory_feedback_read_from_publication, code_index_finding, compose_doctor_report, - configuration_finding, doctor_finding_family_label, host_integration_finding, - ingest_refusal_finding, language_server_finding, merge_storage_reads, observability_finding, - operational_audit_findings, runtime_health_finding, runtime_health_read, storage_family_read, + AdvisoryFeedbackDoctorPort, AdvisoryFeedbackFindingReadV1, AdvisoryFeedbackReadV1, + AdvisoryFeedbackSummaryReadV1, CodeIndexMountDoctorPort, CodeIndexMountReadV1, + CodeIndexMountStateV1, ConfigurationAuthorityDoctorPort, ConfigurationAuthorityReadV1, + ConfigurationDriftV1, DOCTOR_FINDING_FAMILIES, DaemonRuntimeHealthSignalV1, + DoctorCoverageCompletenessV1, DoctorCoverageStatementV1, DoctorEvidenceRefV1, + DoctorEvidenceReferenceV1, DoctorEvidenceStateV1, DoctorFamilyConsultationV1, + DoctorFamilyCoverageV1, DoctorFamilyUnavailableReasonV1, DoctorFindingFamilyV1, + DoctorFindingV1, DoctorKernelInputsV1, DoctorReportComposerV1, DoctorReportCoverageV1, + DoctorReportEntryV1, DoctorReportV1, DoctorSourceFuture, DoctorStorageFamilyReadV1, + DoctorStorageFindingKindV1, DoctorStorageFindingV1, HostConformanceV1, + HostIntegrationDoctorPort, HostIntegrationReadV1, IngestRefusalCensusReadV1, + IngestRefusalCountV1, LanguageServerDoctorPort, LanguageServerReadV1, LanguageServerStateV1, + ObservabilityDoctorPort, ObservabilityReadV1, ObservabilityStateV1, OperationalAuditDoctorPort, + OperationalAuditReadV1, ProfileAuthorityReadV1, RemoteAuthorityReadV1, RemoteListenerReadV1, + RemoteOperationalReadV1, RuntimeHealthDoctorPort, RuntimeHealthReadV1, RuntimeLivenessV1, + StorageDoctorPort, advisory_feedback_findings, advisory_feedback_read_from_publication, + code_index_finding, configuration_finding, doctor_finding_family_label, + host_integration_finding, ingest_refusal_finding, language_server_finding, merge_storage_reads, + observability_finding, operational_audit_findings, runtime_health_finding, runtime_health_read, + storage_family_read, }; pub use error::ApplicationContractError; pub use execution_topology_metrics::*; diff --git a/crates/tracedecay/src/daemon/doctor_kernel.rs b/crates/tracedecay/src/daemon/doctor_kernel.rs index 3e59dfcc1c..add4d86a11 100644 --- a/crates/tracedecay/src/daemon/doctor_kernel.rs +++ b/crates/tracedecay/src/daemon/doctor_kernel.rs @@ -1,12 +1,10 @@ //! Daemon-side Doctor signal gatherers for the read-only kernel source ports. //! -//! The transport-neutral Doctor kernel -//! ([`tracedecay_contracts::doctor`]) owns the seven source-port adapters, -//! [`DaemonRuntimeHealthSignalV1`], and [`compose_doctor_report`]. This module -//! gathers live daemon signals (scheduler, diagnostic broker, registered -//! stores, host-bundle receipts) and maps daemon-owned types into those kernel -//! reads. Truthfulness is preserved end to end: a signal that cannot be -//! consulted maps to the kernel's typed +//! The transport-neutral Doctor kernel ([`tracedecay_contracts::doctor`]) owns +//! the source-port traits, read mappers, and [`DoctorReportComposerV1`]. This +//! module gathers live daemon signals, maps daemon-owned types into kernel +//! reads, and wires those reads into the composer. Truthfulness is preserved +//! end to end: a signal that cannot be consulted maps to the kernel's typed //! `Unsupported`/`Absent`/`Denied`/`Unknown` read — never a fabricated healthy //! result — and partial coverage carries its real reason. //! @@ -18,14 +16,18 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::config::PinnedRuntimeConfiguration; use tracedecay_application::semantic_runtime::ProjectSemanticActivationExt; use tracedecay_contracts::doctor::{ - AdvisoryFeedbackReadV1, CodeIndexMountReadV1, CodeIndexMountStateV1, + AdvisoryFeedbackDoctorPort, AdvisoryFeedbackReadV1, CodeIndexMountDoctorPort, + CodeIndexMountReadV1, CodeIndexMountStateV1, ConfigurationAuthorityDoctorPort, ConfigurationAuthorityReadV1, ConfigurationDriftV1, DaemonRuntimeHealthSignalV1, - DoctorCoverageCompletenessV1, DoctorKernelInputsV1, DoctorStorageFamilyReadV1, - DoctorStorageIncompleteReasonV1, HostConformanceV1, HostIntegrationReadV1, - IngestRefusalCensusReadV1, IngestRefusalCountV1, LanguageServerReadV1, LanguageServerStateV1, - ObservabilityReadV1, ObservabilityStateV1, OperationalAuditReadV1, ProfileAuthorityReadV1, - RemoteOperationalReadV1, SemanticOwnerReadV1, advisory_feedback_read_from_publication, - compose_doctor_report, merge_storage_reads, runtime_health_read, storage_family_read, + DoctorCoverageCompletenessV1, DoctorKernelInputsV1, DoctorReportComposerV1, DoctorReportV1, + DoctorSourceFuture, DoctorStorageFamilyReadV1, DoctorStorageIncompleteReasonV1, + HostConformanceV1, HostIntegrationDoctorPort, HostIntegrationReadV1, IngestRefusalCensusReadV1, + IngestRefusalCountV1, LanguageServerDoctorPort, LanguageServerReadV1, LanguageServerStateV1, + ObservabilityDoctorPort, ObservabilityReadV1, ObservabilityStateV1, OperationalAuditDoctorPort, + OperationalAuditReadV1, ProfileAuthorityReadV1, RemoteOperationalReadV1, + RuntimeHealthDoctorPort, RuntimeHealthReadV1, SemanticOwnerDoctorPort, SemanticOwnerReadV1, + StorageDoctorPort, advisory_feedback_read_from_publication, merge_storage_reads, + runtime_health_read, storage_family_read, }; use tracedecay_contracts::request_identity::{GlobalRequestSurface, mint_global_request_id}; use tracedecay_contracts::{ @@ -851,6 +853,147 @@ pub(super) async fn collect_code_generation_retention_findings( pub(in crate::daemon) type RemoteOperationalReadProviderV1 = Arc RemoteOperationalReadV1 + Send + Sync>; +/// Resolved kernel reads wired into the Doctor composer for one report. +struct KernelDoctorSources<'a> { + inputs: &'a DoctorKernelInputsV1, +} + +impl ConfigurationAuthorityDoctorPort for KernelDoctorSources<'_> { + fn configuration_health<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, ConfigurationAuthorityReadV1> { + let read = self.inputs.configuration.clone(); + Box::pin(async move { read }) + } +} + +impl RuntimeHealthDoctorPort for KernelDoctorSources<'_> { + fn runtime_health<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, RuntimeHealthReadV1> { + let read = self.inputs.runtime.clone(); + Box::pin(async move { read }) + } +} + +impl OperationalAuditDoctorPort for KernelDoctorSources<'_> { + fn operational_audit<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, OperationalAuditReadV1> { + let read = self.inputs.operational_audit.clone(); + Box::pin(async move { read }) + } +} + +impl HostIntegrationDoctorPort for KernelDoctorSources<'_> { + fn host_conformance<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, HostIntegrationReadV1> { + let read = self.inputs.host.clone(); + Box::pin(async move { read }) + } +} + +impl AdvisoryFeedbackDoctorPort for KernelDoctorSources<'_> { + fn advisory_feedback<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, AdvisoryFeedbackReadV1> { + let read = self.inputs.advisory_feedback.clone(); + Box::pin(async move { read }) + } +} + +impl LanguageServerDoctorPort for KernelDoctorSources<'_> { + fn language_server_health<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, LanguageServerReadV1> { + let read = self.inputs.language_server.clone(); + Box::pin(async move { read }) + } +} + +impl CodeIndexMountDoctorPort for KernelDoctorSources<'_> { + fn code_index_mount<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, CodeIndexMountReadV1> { + let read = self.inputs.code_index.clone(); + Box::pin(async move { read }) + } +} + +impl SemanticOwnerDoctorPort for KernelDoctorSources<'_> { + fn semantic_owner<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, SemanticOwnerReadV1> { + let read = self.inputs.semantic_owner.clone(); + Box::pin(async move { read }) + } +} + +impl ObservabilityDoctorPort for KernelDoctorSources<'_> { + fn observability_health<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, ObservabilityReadV1> { + let read = self.inputs.observability.clone(); + Box::pin(async move { read }) + } + + fn ingest_refusal_census<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, IngestRefusalCensusReadV1> { + let read = self.inputs.ingest_refusals.clone(); + Box::pin(async move { read }) + } +} + +impl StorageDoctorPort for KernelDoctorSources<'_> { + fn storage_findings<'b>( + &'b self, + _context: &'b RequestContext, + ) -> DoctorSourceFuture<'b, DoctorStorageFamilyReadV1> { + let read = self.inputs.storage.clone(); + Box::pin(async move { read }) + } +} + +/// Compose a Doctor report from already-resolved kernel reads. +/// +/// Wires the resolved bundle into [`DoctorReportComposerV1`]. The composer +/// enumerates every finding family truthfully: a family whose read is +/// unavailable is carried with its real evidence state and an explicit coverage +/// record, and the report asserts health only when every family was consulted +/// with complete coverage and every finding is healthy. +#[hotpath::measure(label = "daemon.doctor.compose", future = true)] +pub(in crate::daemon) async fn compose_doctor_report( + context: &RequestContext, + inputs: &DoctorKernelInputsV1, +) -> Result { + let sources = KernelDoctorSources { inputs }; + DoctorReportComposerV1::new() + .with_configuration(&sources) + .with_runtime(&sources) + .with_operational_audit(&sources) + .with_host(&sources) + .with_advisory_feedback(&sources) + .with_language_server(&sources) + .with_code_index(&sources) + .with_semantic_owner(&sources) + .with_observability(&sources) + .with_storage(&sources) + .compose(context) + .await +} + /// Build the daemon-owned live Doctor reader installed into a project MCP /// server. Every read re-resolves exact project/worktree identity, observes the /// current registered runtimes, and composes through the sole application diff --git a/crates/tracedecay/src/daemon/doctor_kernel/tests.rs b/crates/tracedecay/src/daemon/doctor_kernel/tests.rs index 3bc5feeb8e..ed1a139a00 100644 --- a/crates/tracedecay/src/daemon/doctor_kernel/tests.rs +++ b/crates/tracedecay/src/daemon/doctor_kernel/tests.rs @@ -1,9 +1,8 @@ -//! Daemon-owned Doctor signal-mapper tests. +//! Daemon-owned Doctor signal-mapper and composition tests. //! -//! Adapter structs, [`DaemonRuntimeHealthSignalV1`], and -//! [`compose_doctor_report`] live in `tracedecay-contracts::doctor` and are -//! covered there. This module keeps the mappers that still read daemon, -//! global-db, LSP, and host-bundle types. +//! Read mappers and [`DaemonRuntimeHealthSignalV1`] live in +//! `tracedecay-contracts::doctor`. Composition from resolved kernel reads is +//! owned here at the composition root. use tracedecay_contracts::doctor::{ DoctorCoverageCompletenessV1, HostConformanceV1, HostIntegrationReadV1, @@ -322,3 +321,205 @@ fn one_unavailable_refusal_census_makes_the_merged_read_unknown() { assert_eq!(merged, IngestRefusalCensusReadV1::Unknown); } + +#[tokio::test] +async fn composed_report_carries_real_states_and_enumerates_coverage() { + use std::collections::BTreeSet; + + use tracedecay_contracts::doctor::{ + AdvisoryFeedbackReadV1, CodeIndexMountReadV1, CodeIndexMountStateV1, + ConfigurationAuthorityReadV1, ConfigurationDriftV1, DoctorCoverageCompletenessV1, + DoctorCoverageStatementV1, DoctorEvidenceRefV1, DoctorEvidenceReferenceV1, + DoctorEvidenceStateV1, DoctorFamilyConsultationV1, DoctorFamilyCoverageV1, + DoctorFamilyUnavailableReasonV1, DoctorFindingFamilyV1, DoctorFindingV1, + DoctorKernelInputsV1, DoctorStorageFamilyReadV1, DoctorStorageFindingKindV1, + DoctorStorageFindingV1, OperationalAuditReadV1, ProfileAuthorityReadV1, + RemoteOperationalReadV1, SemanticOwnerReadV1, SemanticOwnerStateV1, + }; + use tracedecay_contracts::{ + CancellationContext, CapabilityGrantId, CapabilityGrantSnapshot, Deadline, DisclosureClass, + RequestContext, RequestId, ResolvedScope, + }; + use tracedecay_domain::{ + ActorId, ManifestDigest, ProjectId, RepositoryId, UtcMicros, WorktreeId, + }; + use tracedecay_tool_catalog::{CapabilityId, UseCaseId}; + + let actor = ActorId::new("actor.doctor-kernel-compose-test").unwrap(); + let scope = ResolvedScope::new( + ProjectId::new("project.doctor-kernel-compose-test").unwrap(), + RepositoryId::new("repository.doctor-kernel-compose-test").unwrap(), + WorktreeId::new("worktree.doctor-kernel-compose-test").unwrap(), + None, + ) + .unwrap(); + let capability = CapabilityId::new("capability.doctor-kernel-compose-test").unwrap(); + let use_case = UseCaseId::new("use-case.doctor-kernel-compose-test").unwrap(); + let grant = CapabilityGrantSnapshot::new( + CapabilityGrantId::new("grant.doctor-kernel-compose-test").unwrap(), + 1, + ManifestDigest::new(format!("sha256:{}", "11".repeat(32))).unwrap(), + actor.clone(), + UtcMicros(1), + UtcMicros(10_000), + scope.clone(), + BTreeSet::from([capability]), + BTreeSet::from([use_case]), + DisclosureClass::Evidence, + ) + .unwrap(); + let ctx = RequestContext::new( + actor, + scope, + grant, + RequestId::new("request.doctor-kernel-compose-test").unwrap(), + Deadline::new(UtcMicros(9_000)).unwrap(), + CancellationContext::active("cancel.doctor-kernel-compose-test").unwrap(), + ) + .unwrap(); + + let orphan_evidence = DoctorEvidenceRefV1::new( + DoctorFindingFamilyV1::Storage, + DoctorEvidenceReferenceV1::new("storage.orphan_store.fixture.age-42d").unwrap(), + ); + let orphan_coverage = DoctorCoverageStatementV1::new( + DoctorCoverageCompletenessV1::Complete, + "orphan store identity no longer resolves", + ) + .unwrap(); + let orphan_finding = DoctorFindingV1::new( + DoctorFindingFamilyV1::Storage, + DoctorEvidenceStateV1::Degraded, + vec![orphan_evidence], + orphan_coverage, + ) + .unwrap(); + let orphan_storage = + DoctorStorageFindingV1::new(DoctorStorageFindingKindV1::OrphanStore, orphan_finding) + .unwrap(); + + let inputs = DoctorKernelInputsV1 { + configuration: ConfigurationAuthorityReadV1::Resolved { + drift: ConfigurationDriftV1::InSync, + coverage: DoctorCoverageCompletenessV1::Complete, + }, + runtime: runtime_health_read(&DaemonRuntimeHealthSignalV1 { + serving: true, + startup_converged: false, + ..DaemonRuntimeHealthSignalV1::default() + }), + operational_audit: OperationalAuditReadV1 { + remote: RemoteOperationalReadV1::Unconfigured, + profile_authority: ProfileAuthorityReadV1::Unavailable, + }, + host: HostIntegrationReadV1::Denied, + advisory_feedback: AdvisoryFeedbackReadV1::Absent, + language_server: LanguageServerReadV1::Observed { + state: LanguageServerStateV1::Ready, + coverage: DoctorCoverageCompletenessV1::Complete, + }, + code_index: CodeIndexMountReadV1::Observed { + state: CodeIndexMountStateV1::Mounted, + coverage: DoctorCoverageCompletenessV1::Complete, + }, + semantic_owner: SemanticOwnerReadV1::Observed { + state: SemanticOwnerStateV1::Ready, + coverage: DoctorCoverageCompletenessV1::Complete, + }, + observability: ObservabilityReadV1::Observed { + state: ObservabilityStateV1::Current, + total_count: 7, + last_observed_at_micros: Some(42), + coverage: DoctorCoverageCompletenessV1::Partial, + }, + ingest_refusals: IngestRefusalCensusReadV1::Observed { + refusals: vec![IngestRefusalCountV1 { + provider: "cursor".to_owned(), + reason: "admission_refused".to_owned(), + count: 160, + }], + }, + storage: merge_storage_reads( + storage_family_read(vec![orphan_storage]), + DoctorStorageFamilyReadV1::Unknown, + ), + }; + + let report = compose_doctor_report(&ctx, &inputs).await.expect("report"); + + assert_eq!(report.coverage().families().len(), 7); + + let family_state = |family: DoctorFindingFamilyV1| { + report + .findings() + .find(|finding| finding.family() == family) + .map(DoctorFindingV1::state) + }; + assert_eq!( + family_state(DoctorFindingFamilyV1::Configuration), + Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::StorageRuntime), + Some(DoctorEvidenceStateV1::Degraded) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::Advisory), + Some(DoctorEvidenceStateV1::Denied) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::SemanticIndex), + Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::Storage), + Some(DoctorEvidenceStateV1::Degraded) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::LanguageServer), + Some(DoctorEvidenceStateV1::HealthyCompleteCoverage) + ); + assert_eq!( + family_state(DoctorFindingFamilyV1::Observability), + Some(DoctorEvidenceStateV1::Partial) + ); + + assert!(!report.is_healthy_complete()); + assert_ne!( + report.coverage().completeness(), + DoctorCoverageCompletenessV1::Complete + ); + + let consultation = |family: DoctorFindingFamilyV1| { + report + .coverage() + .families() + .iter() + .find(|record| record.family() == family) + .map(DoctorFamilyCoverageV1::consultation) + }; + assert_eq!( + consultation(DoctorFindingFamilyV1::LanguageServer), + Some(DoctorFamilyConsultationV1::Consulted) + ); + assert_eq!( + consultation(DoctorFindingFamilyV1::Observability), + Some(DoctorFamilyConsultationV1::Consulted) + ); + assert_eq!( + consultation(DoctorFindingFamilyV1::Advisory), + Some(DoctorFamilyConsultationV1::Unavailable { + reason: DoctorFamilyUnavailableReasonV1::Denied, + }) + ); + assert_eq!( + consultation(DoctorFindingFamilyV1::Configuration), + Some(DoctorFamilyConsultationV1::Consulted) + ); + assert_eq!( + consultation(DoctorFindingFamilyV1::Storage), + Some(DoctorFamilyConsultationV1::Unavailable { + reason: DoctorFamilyUnavailableReasonV1::Unknown, + }) + ); +}