Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ pub const fn http_application_owner_kind(
| ApplicationSurfaceOperation::SkillView
| ApplicationSurfaceOperation::HermesSkillBridge
| ApplicationSurfaceOperation::Analytics
| ApplicationSurfaceOperation::Search => return None,
| ApplicationSurfaceOperation::Search
| ApplicationSurfaceOperation::RunAffectedTests
| ApplicationSurfaceOperation::Dashboard => return None,
ApplicationSurfaceOperation::FactStoreCurate
| ApplicationSurfaceOperation::FactStoreAdd
| ApplicationSurfaceOperation::FactStoreSearch
Expand Down
9 changes: 6 additions & 3 deletions crates/tracedecay-contracts/src/capability_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ pub(crate) struct ApplicationCapabilityManifestInput {
pub(crate) fn application_capability_manifest(
input: ApplicationCapabilityManifestInput,
) -> Result<CapabilityManifestV1, CatalogValidationError> {
// Owner side effects settle within the call, so they carry the read
// receipt shape rather than a durable, idempotent effect journey.
let is_effect = input.effect.is_effect();
let durable_effect = is_effect && !input.effect.is_owner_side_effect();
CapabilityManifestV1::new(CapabilityManifestInputV1 {
capability_id: input.capability_id,
use_case_id: input.use_case_id,
Expand All @@ -51,7 +54,7 @@ pub(crate) fn application_capability_manifest(
cancellation: input.cancellation,
deadline: input.deadline,
pagination: input.pagination,
idempotency: if is_effect {
idempotency: if durable_effect {
IdempotencyContract::Required
} else {
IdempotencyContract::NotRequired
Expand All @@ -66,12 +69,12 @@ pub(crate) fn application_capability_manifest(
}
}),
authority_revalidation: input.authority_revalidation,
reconciliation: if is_effect {
reconciliation: if durable_effect {
ReconciliationContract::Required
} else {
ReconciliationContract::NotRequired
},
receipt: if is_effect {
receipt: if durable_effect {
ReceiptContract::DurableEffect
} else {
ReceiptContract::Operation
Expand Down
25 changes: 16 additions & 9 deletions crates/tracedecay-contracts/src/graph_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ use crate::retrieval::{
AutomationRunListResultV1, AutomationRunViewResultV1, BranchDiffResultV1, BranchListResultV1,
BranchSearchResultV1, ByQualifiedNameResultV1, ChangelogResultV1, CircularResultV1,
CommitContextResultV1, ComplexityReportV1, ConfigResultV1, ConstructorsResultV1,
ContextResultV1, CouplingResultV1, DeadCodeResultV1, DependencyDepthResultV1, DerivesResultV1,
DiagnoseResultV1, DiffContextResultV1, DistributionResultV1, DocCoverageResultV1, DsmResultV1,
FieldSitesResultV1, FilesResultV1, FindExactSymbolResultV1, GiniResultV1, GodClassResultV1,
GrepSearchResultV1, HealthResultV1, HermesSkillBridgeResultV1, HotspotsResultV1,
ImpactResultV1, InheritanceDepthResultV1, LargestResultV1, NodeResultV1, PortOrderResultV1,
PortStatusResultV1, PrContextResultV1, RankResultV1, RecursionResultV1, RedundancyResultV1,
RenamePreviewPrimitiveOutcomeV1, RetrieveResultV1, SearchResultV1, SignatureResultV1,
SimilarResultV1, SkillListResultV1, SkillViewResultV1, TestMapResultV1, TestRiskResultV1,
TodosResultV1, UnmountedFilesResultV1, UnsafePatternsResultV1,
ContextResultV1, CouplingResultV1, DashboardResultV1, DeadCodeResultV1,
DependencyDepthResultV1, DerivesResultV1, DiagnoseResultV1, DiffContextResultV1,
DistributionResultV1, DocCoverageResultV1, DsmResultV1, FieldSitesResultV1, FilesResultV1,
FindExactSymbolResultV1, GiniResultV1, GodClassResultV1, GrepSearchResultV1, HealthResultV1,
HermesSkillBridgeResultV1, HotspotsResultV1, ImpactResultV1, InheritanceDepthResultV1,
LargestResultV1, NodeResultV1, PortOrderResultV1, PortStatusResultV1, PrContextResultV1,
RankResultV1, RecursionResultV1, RedundancyResultV1, RenamePreviewPrimitiveOutcomeV1,
RetrieveResultV1, RunAffectedTestsResultV1, SearchResultV1, SignatureResultV1, SimilarResultV1,
SkillListResultV1, SkillViewResultV1, TestMapResultV1, TestRiskResultV1, TodosResultV1,
UnmountedFilesResultV1, UnsafePatternsResultV1,
};

/// One graph read's typed result, tagged by its operation.
Expand Down Expand Up @@ -83,6 +84,8 @@ pub enum GraphToolResultV1 {
HermesSkillBridge(Box<HermesSkillBridgeResultV1>),
Analytics(Box<AnalyticsResultV1>),
Search(Box<SearchResultV1>),
RunAffectedTests(RunAffectedTestsResultV1),
Dashboard(DashboardResultV1),
}

impl GraphToolResultV1 {
Expand Down Expand Up @@ -152,6 +155,8 @@ impl GraphToolResultV1 {
Operation::HermesSkillBridge => Self::HermesSkillBridge(serde_json::from_value(value)?),
Operation::Analytics => Self::Analytics(serde_json::from_value(value)?),
Operation::Search => Self::Search(serde_json::from_value(value)?),
Operation::RunAffectedTests => Self::RunAffectedTests(serde_json::from_value(value)?),
Operation::Dashboard => Self::Dashboard(serde_json::from_value(value)?),
operation => {
return Err(serde::de::Error::custom(format!(
"{} is not a graph-tool operation",
Expand Down Expand Up @@ -221,6 +226,8 @@ impl GraphToolResultV1 {
Self::HermesSkillBridge(result) => serde_json::to_value(result),
Self::Analytics(result) => serde_json::to_value(result),
Self::Search(result) => serde_json::to_value(result),
Self::RunAffectedTests(result) => serde_json::to_value(result),
Self::Dashboard(result) => serde_json::to_value(result),
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions crates/tracedecay-contracts/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,10 +536,12 @@ fn route_effect(effect: EffectClass) -> Result<CapabilityEffectClassV1, Applicat
EffectClass::GitIndexStage => Ok(CapabilityEffectClassV1::GitIndexStage),
EffectClass::GitIndexUnstage => Ok(CapabilityEffectClassV1::GitIndexUnstage),
EffectClass::GitIndexCommit => Ok(CapabilityEffectClassV1::GitIndexCommit),
EffectClass::SourceEdit | EffectClass::ConfigurationWrite | EffectClass::Administrative => {
Err(ApplicationContractError::Inconsistent {
field: "capability routing effect class",
})
}
EffectClass::SourceEdit
| EffectClass::ConfigurationWrite
| EffectClass::Administrative
| EffectClass::SpawnsProcess
| EffectClass::BindsServer => Err(ApplicationContractError::Inconsistent {
field: "capability routing effect class",
}),
}
}
121 changes: 101 additions & 20 deletions crates/tracedecay-contracts/src/retrieval/catalog.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use crate::retrieval::owner_effect_surface::{
DashboardResultV1, DashboardSurfaceRequestV1, RunAffectedTestsResultV1,
RunAffectedTestsSurfaceRequestV1,
};
use schemars::JsonSchema;
use tracedecay_tool_catalog::{
ApplicationSurfaceOperation, AvailabilityContract, BindingId, BindingSurface,
CancellationContract, CancellationPoint, CapabilityId, CatalogContributionInputV1,
CatalogContributionV1, ContributionContractRef, ContributionId, CoverageContractRef,
DeadlineBehavior, DeadlineContract, DeniedDisclosurePolicy, EffectClass,
ExecutableSchemaAuthority, LifecycleClass, OmissionContractRef, PaginationContract,
PrivacyClass, ProfileId, ProtocolRevisionRange, RetrievalFamily,
ExecutableSchemaAuthority, LifecycleClass, OmissionContractRef, OwnerSideEffectEntryV1,
PaginationContract, PrivacyClass, ProfileId, ProtocolRevisionRange, RetrievalFamily,
RetrievalPrimitiveManifestInputV1, RetrievalPrimitiveManifestV1, RetrieverId,
RevalidationContract, RevalidationPoint, RoutingContractV1, SchemaId, SchemaRef,
ScopeDimension, ScopeRequirement, ScoringContractRef, SortContract, SortContractId,
Expand Down Expand Up @@ -154,6 +158,9 @@ struct PrimitiveReadSpec {
/// answer in one response and advertise no pagination.
paginated: bool,
deadline_millis: u64,
/// The tool-catalog entry of an operation that acts beyond reading; its
/// effect class and ceiling replace the read defaults.
side_effect: Option<OwnerSideEffectEntryV1>,
}

fn primitive_profile_ids(operation: &str) -> &'static [&'static str] {
Expand Down Expand Up @@ -255,6 +262,8 @@ const PRIMITIVE_READ_SPECS: &[PrimitiveReadSpec] = &[
graph_report_spec("hermes_skill_bridge"),
graph_report_spec("analytics"),
graph_report_spec("search"),
owner_side_effect_spec(ApplicationSurfaceOperation::RunAffectedTests),
owner_side_effect_spec(ApplicationSurfaceOperation::Dashboard),
git_context_spec("affected"),
git_context_spec("diff_context"),
git_context_spec("changelog"),
Expand Down Expand Up @@ -352,7 +361,9 @@ fn primitive_read_surfaces(spec: &PrimitiveReadSpec) -> &'static [BindingSurface
| "skill_view"
| "hermes_skill_bridge"
| "analytics"
| "search" => &CLI_MCP_PRIMITIVE_SURFACES,
| "search"
| "run_affected_tests"
| "dashboard" => &CLI_MCP_PRIMITIVE_SURFACES,
"health_read" | "storage_status" | "diagnostics_read" => &DASHBOARD_PRIMITIVE_SURFACES,
_ => &PRE_DASHBOARD_PRIMITIVE_SURFACES,
}
Expand Down Expand Up @@ -574,6 +585,12 @@ fn primitive_read_description(operation: &str) -> &'static str {
"search" => {
"Rank symbols by name, identifier fragment, signature, path, or phrase through exact and lexical routes."
}
"run_affected_tests" => {
"Run the libtest tests that cover a changed-path manifest and report each observed outcome."
}
"dashboard" => {
"Start or stop this project's loopback dashboard server and report its bound address."
}
_ => "Read bounded data from the admitted project's current retained state.",
}
}
Expand All @@ -593,6 +610,7 @@ const fn primitive_spec_with_default_page_size(
default_page_size,
paginated: true,
deadline_millis: 10_000,
side_effect: None,
}
}

Expand All @@ -616,6 +634,63 @@ const fn graph_report_spec(operation: &'static str) -> PrimitiveReadSpec {
default_page_size: CALLABLE_CODE_DEFAULT_PAGE_SIZE,
paginated: false,
deadline_millis: 120_000,
side_effect: None,
}
}

/// A read observes cancellation until it answers. A spawned process is killed
/// when its call is cancelled; binding a server is not interruptible.
fn primitive_cancellation(
spec: &PrimitiveReadSpec,
) -> Result<CancellationContract, ApplicationContractError> {
let points = match spec.side_effect.map(|entry| entry.effect) {
None => vec![
CancellationPoint::BeforeAdmission,
CancellationPoint::BeforeRead,
CancellationPoint::DuringRead,
],
Some(EffectClass::SpawnsProcess) => vec![
CancellationPoint::BeforeAdmission,
CancellationPoint::EffectInFlight,
],
Some(_) => return Ok(CancellationContract::NotCancellable),
};
Ok(CancellationContract::cooperative(points)?)
}

fn primitive_terminal_states(spec: &PrimitiveReadSpec) -> Vec<TerminalState> {
let mut states = vec![TerminalState::Completed];
if !matches!(
spec.side_effect.map(|entry| entry.effect),
Some(EffectClass::BindsServer)
) {
states.push(TerminalState::Cancelled);
}
states.extend([
TerminalState::TimedOut,
TerminalState::Failed,
TerminalState::Unavailable,
TerminalState::Partial,
]);
states
}

/// An owner-served operation that acts beyond reading. Its tool-catalog side
/// effect entry is the authority for its effect class and ceiling; an
/// operation without one yields a zero deadline the catalog refuses.
const fn owner_side_effect_spec(operation: ApplicationSurfaceOperation) -> PrimitiveReadSpec {
let side_effect = operation.owner_side_effect();
PrimitiveReadSpec {
operation: operation.as_str(),
capability: operation.as_str(),
use_case: operation.as_str(),
default_page_size: CALLABLE_CODE_DEFAULT_PAGE_SIZE,
paginated: false,
deadline_millis: match side_effect {
Some(entry) => entry.ceiling_millis,
None => 0,
},
side_effect,
}
}

Expand Down Expand Up @@ -720,6 +795,11 @@ pub fn primitive_read_contribution() -> Result<CatalogContributionV1, Applicatio
})?);
binding_ids.push(binding_id);
}
let verb = if spec.side_effect.is_some() {
"Run"
} else {
"Read"
};
capabilities.push(application_capability_manifest(
ApplicationCapabilityManifestInput {
capability_id,
Expand All @@ -729,23 +809,25 @@ pub fn primitive_read_contribution() -> Result<CatalogContributionV1, Applicatio
))?,
routing: RoutingContractV1::new(
1,
format!("Read {}", spec.operation.replace('_', " ")),
format!("{verb} {}", spec.operation.replace('_', " ")),
primitive_read_description(spec.operation),
vec![format!("Read {}", spec.operation.replace('_', " "))],
vec![format!("{verb} {}", spec.operation.replace('_', " "))],
)?,
request_schema: primitive_schema(spec.operation, "request")?,
result_schema: primitive_schema(spec.operation, "result")?,
effect: EffectClass::Read,
effect: spec
.side_effect
.map_or(EffectClass::Read, |entry| entry.effect),
scope: symbol_search_scope()?,
denied_disclosure: DeniedDisclosurePolicy::Indistinguishable,
privacy: PrivacyClass::ScopedMetadata,
lifecycle: LifecycleClass::Resumable,
lifecycle: if spec.side_effect.is_some() {
LifecycleClass::Stateless
} else {
LifecycleClass::Resumable
},
streaming: StreamingContract::Unsupported,
cancellation: CancellationContract::cooperative(vec![
CancellationPoint::BeforeAdmission,
CancellationPoint::BeforeRead,
CancellationPoint::DuringRead,
])?,
cancellation: primitive_cancellation(spec)?,
deadline: DeadlineContract::new(
spec.deadline_millis,
DeadlineBehavior::ReturnOperationReceipt,
Expand All @@ -766,14 +848,7 @@ pub fn primitive_read_contribution() -> Result<CatalogContributionV1, Applicatio
RevalidationPoint::Policy,
RevalidationPoint::Configuration,
])?,
terminal_states: TerminalStateContract::new(vec![
TerminalState::Completed,
TerminalState::Cancelled,
TerminalState::TimedOut,
TerminalState::Failed,
TerminalState::Unavailable,
TerminalState::Partial,
])?,
terminal_states: TerminalStateContract::new(primitive_terminal_states(spec))?,
availability: AvailabilityContract::Available,
binding_ids,
profile_eligibility: application_profile_ids(primitive_profile_ids(
Expand Down Expand Up @@ -1040,6 +1115,12 @@ fn primitive_executable_schemas(
);
add!("analytics", AnalyticsSurfaceRequestV1, AnalyticsResultV1);
add!("search", SearchSurfaceRequestV1, SearchResultV1);
add!(
"run_affected_tests",
RunAffectedTestsSurfaceRequestV1,
RunAffectedTestsResultV1
);
add!("dashboard", DashboardSurfaceRequestV1, DashboardResultV1);
Ok(schemas)
}

Expand Down
7 changes: 7 additions & 0 deletions crates/tracedecay-contracts/src/retrieval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod git_topology_anchor;
mod graph_lookup_surface;
mod graph_report_surface;
pub mod grep_analysis;
mod owner_effect_surface;
mod ports;
mod primitive_surface;
mod project_file_surface;
Expand Down Expand Up @@ -173,6 +174,12 @@ pub use graph_report_surface::{
TestRiskResultV1, TestRiskSummaryV1, TestRiskSurfaceRequestV1,
};
pub use grep_analysis::{DependencyDepthChainV1, DependencyDepthResultV1};
pub use owner_effect_surface::{
AffectedTestErrorV1, AffectedTestOutcomeV1, AffectedTestRunV1, AffectedTestsNotRunV1,
DashboardActionV1, DashboardBoundV1, DashboardResultV1, DashboardSurfaceRequestV1,
ManagedTestTerminalV1, RunAffectedTestsResultV1, RunAffectedTestsSurfaceRequestV1,
TestProfileV1,
};
pub use ports::{
AffectedTestsRetrievalPort, OperationalRetrievalPort, RetrievalPortContext,
RetrievalPortOutcome, SessionRetrievalBudgetAccountingV1, SessionRetrievalBudgetObservationV1,
Expand Down
Loading
Loading