#![forbid(unsafe_code)]
pub mod barrier;
pub mod diag;
pub mod lang;
pub mod prompts;
pub mod registry;
pub mod runtime;
pub mod symbols;
pub use barrier::OperationalSafetyBarrier;
pub use registry::{CodingAdapterRegistry, LanguageId};
pub use runtime::{crash_marker, SmokeInvocation};
pub use symbols::{defined_symbols, expected_symbols};
use perspt_sdk::{
AgentDomainPackage, BarrierChannel, BarrierSpec, CorrectionDirection, DomainDetection,
DomainId, DomainScope, EnergyComponent, EnergyModel, HardGatePolicy, ResidualClass,
ResidualEvent, ResidualSchema, ResidualWeight, SafetyBarrierDisposition, StabilityClaim,
VerificationCadence, VerifierSuiteSpec, WorkspaceSnapshot,
};
#[derive(Debug, Clone, Default)]
pub struct CodingDomain;
impl CodingDomain {
pub fn new() -> Self {
Self
}
fn classes() -> Vec<ResidualClass> {
vec![
ResidualClass::Syntax,
ResidualClass::Type,
ResidualClass::Build,
ResidualClass::TestFailure,
ResidualClass::Lint,
ResidualClass::Format,
ResidualClass::Runtime,
ResidualClass::Dependency,
ResidualClass::Manifest,
ResidualClass::ImportGraph,
ResidualClass::SymbolMismatch,
ResidualClass::InterfaceMismatch,
ResidualClass::OwnershipViolation,
ResidualClass::Policy,
ResidualClass::Regression,
ResidualClass::SensorUnavailable,
ResidualClass::ToolFailure,
ResidualClass::SheafInconsistency,
]
}
}
impl AgentDomainPackage for CodingDomain {
fn domain_id(&self) -> DomainId {
DomainId::new("coding")
}
fn detect(&self, workspace: &WorkspaceSnapshot) -> DomainDetection {
let mut evidence = Vec::new();
for marker in ["Cargo.toml", "pyproject.toml", "package.json", "go.mod"] {
if workspace.has_file_named(marker) {
evidence.push(format!("found {marker}"));
}
}
let activated = !evidence.is_empty();
DomainDetection {
domain: self.domain_id(),
activated,
confidence: if activated { 0.95 } else { 0.0 },
evidence,
}
}
fn residual_schema(&self, _scope: &DomainScope) -> ResidualSchema {
ResidualSchema::new(Self::classes())
}
fn energy_model(&self, scope: &DomainScope) -> EnergyModel {
use EnergyComponent::*;
use ResidualClass::*;
let weights = vec![
ResidualWeight::new(Syntax, Syn, 4.0).with_hard_threshold(0.0),
ResidualWeight::new(Type, Syn, 3.0).with_hard_threshold(0.0),
ResidualWeight::new(Build, Syn, 3.0).with_hard_threshold(0.0),
ResidualWeight::new(ImportGraph, Str, 2.0),
ResidualWeight::new(SymbolMismatch, Str, 2.0),
ResidualWeight::new(InterfaceMismatch, Str, 2.5),
ResidualWeight::new(OwnershipViolation, Str, 2.0),
ResidualWeight::new(Policy, Str, 1.0),
ResidualWeight::new(Dependency, Str, 1.5),
ResidualWeight::new(Manifest, Str, 1.5),
ResidualWeight::new(Lint, Str, 0.5),
ResidualWeight::new(Format, Str, 0.25),
ResidualWeight::new(TestFailure, Log, 2.0),
ResidualWeight::new(Runtime, Log, 2.0),
ResidualWeight::new(Regression, Log, 3.0),
ResidualWeight::new(SensorUnavailable, Boot, 1.0),
ResidualWeight::new(ToolFailure, Boot, 1.0),
ResidualWeight::new(SheafInconsistency, Sheaf, 2.0),
];
let mut model = EnergyModel::new("coding", 0.5).with_correction_budget(4);
model.residual_weights = weights;
model.energy_tolerance = 0.0;
model.stability_claim = Some(StabilityClaim::not_claimed(format!(
"coding scope: {}",
scope.label
)));
model
}
fn correction_directions(&self, residuals: &[ResidualEvent]) -> Vec<CorrectionDirection> {
let registry = crate::CodingAdapterRegistry::with_builtins();
residuals
.iter()
.filter_map(|residual| {
language_for_sensor(&residual.sensor.id)
.and_then(|language| registry.get(&language))
.and_then(|adapter| adapter.correction_for(residual))
.or_else(|| correction_for(residual))
})
.collect()
}
fn correction_packet(
&self,
residuals: &[ResidualEvent],
) -> Option<perspt_sdk::CorrectionPacket> {
build_correction_packet(self, residuals)
}
fn tool_entries(&self, _scope: &DomainScope) -> Vec<perspt_sdk::ToolEntry> {
Vec::new()
}
fn hard_gate_policy(&self, _scope: &DomainScope) -> HardGatePolicy {
HardGatePolicy {
required_stages: vec!["syntax".into(), "build".into(), "test".into()],
}
}
fn verifier_suite(&self, _scope: &DomainScope) -> VerifierSuiteSpec {
VerifierSuiteSpec {
stages: vec![
"syntax".into(),
"build".into(),
"test".into(),
"lint".into(),
],
cadence: VerificationCadence::default(),
}
}
fn safety_barrier(&self, _scope: &DomainScope) -> SafetyBarrierDisposition {
SafetyBarrierDisposition::Registered(BarrierSpec {
channels: [
"protected-path-modification",
"sandbox-escape",
"secret-exposure",
"unapproved-network-reachability",
"dependency-policy-violation",
"resource-limits",
]
.iter()
.map(|name| BarrierChannel {
name: (*name).to_string(),
policy_version: "1".to_string(),
})
.collect(),
})
}
}
fn language_for_sensor(sensor_id: &str) -> Option<crate::LanguageId> {
match sensor_id {
"rustc" | "cargo-test" => Some(crate::LanguageId::new("rust")),
"ty" | "pyright" | "pytest" | "python-check" => Some(crate::LanguageId::new("python")),
"tsc" => Some(crate::LanguageId::new("typescript")),
_ => None,
}
}
fn fold_evidence(
residuals: &[ResidualEvent],
) -> (
Vec<perspt_sdk::StructuredDiagnosticRef>,
perspt_sdk::AffectedSet,
) {
let mut diagnostics = Vec::new();
let mut affected = perspt_sdk::AffectedSet::default();
for residual in residuals {
if let Some(refs) = residual.evidence.structured.as_ref().and_then(|value| {
serde_json::from_value::<Vec<perspt_sdk::StructuredDiagnosticRef>>(value.clone()).ok()
}) {
for item in &refs {
if let (Some(path), Some(line)) = (&item.path, item.line) {
affected
.spans
.push(format!("{path}:{line}:{}", item.column.unwrap_or(1)));
}
if residual.class == ResidualClass::TestFailure {
if let Some(test) = &item.code {
affected.tests.push(test.clone());
}
}
}
diagnostics.extend(refs);
}
affected
.paths
.extend(residual.affected_paths.iter().cloned());
affected
.symbols
.extend(residual.affected_symbols.iter().cloned());
}
affected.paths.sort();
affected.paths.dedup();
(diagnostics, affected)
}
fn build_correction_packet(
domain: &CodingDomain,
residuals: &[ResidualEvent],
) -> Option<perspt_sdk::CorrectionPacket> {
let dominant = residuals
.iter()
.filter(|residual| residual.class != ResidualClass::SensorUnavailable)
.max_by(|a, b| a.score.total_cmp(&b.score))?;
let (diagnostics, affected) = fold_evidence(residuals);
let operators: Vec<perspt_sdk::CorrectionOperator> = domain
.correction_directions(residuals)
.into_iter()
.map(|direction| perspt_sdk::CorrectionOperator {
operator_id: direction.direction_id.clone(),
instruction: direction.instruction.clone(),
addresses: direction.addresses,
rationale: direction.rationale.clone(),
})
.collect();
let uncertainty: Vec<perspt_sdk::MissingSensorDecl> = residuals
.iter()
.filter(|residual| residual.class == ResidualClass::SensorUnavailable)
.map(|residual| perspt_sdk::MissingSensorDecl {
sensor: residual.sensor.id.clone(),
reason: residual.evidence.summary.clone(),
})
.collect();
if operators.is_empty() && diagnostics.is_empty() {
return None;
}
let member_count = dominant
.evidence
.structured
.as_ref()
.and_then(|value| value.as_array().map(|items| items.len() as u32))
.unwrap_or(1);
Some(perspt_sdk::CorrectionPacket {
dominant_cluster: perspt_sdk::ResidualClusterRef {
cluster_id: dominant.residual_id.clone(),
class: dominant.class,
root_cause: dominant.evidence.summary.clone(),
member_count,
magnitude: dominant.score,
},
diagnostics,
affected,
operators,
expected_footprint: perspt_sdk::FootprintSpec::new(Vec::new()),
follow_up_stages: Vec::new(),
no_goods: Vec::new(),
uncertainty,
})
}
fn correction_for(residual: &ResidualEvent) -> Option<CorrectionDirection> {
match residual.class {
ResidualClass::ImportGraph => {
let symbol = residual
.affected_symbols
.first()
.map(|s| s.name.clone())
.unwrap_or_else(|| "the missing item".to_string());
Some(
CorrectionDirection::new(
ResidualClass::ImportGraph,
format!(
"resolve the unresolved import for `{symbol}`: add the missing `use` path \
or declare the missing `mod`, do not regenerate unrelated code"
),
)
.with_paths(residual.affected_paths.clone())
.with_rationale(
"unresolved imports are structural; the fix is an import/module \
declaration, not a behavioral rewrite",
),
)
}
ResidualClass::Type => Some(
CorrectionDirection::new(
ResidualClass::Type,
"reconcile the type mismatch at the reported span; adjust the expression or the \
declared signature, keeping the public interface stable",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::Dependency | ResidualClass::Manifest => Some(
CorrectionDirection::new(
residual.class,
"repair the dependency/manifest: add or pin the missing crate/package and sync \
the lockfile through an approved dependency-mutation effect",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::TestFailure => Some(
CorrectionDirection::new(
ResidualClass::TestFailure,
"address the failing test by fixing the implementation it attributes to; do not \
weaken or delete the assertion",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::SensorUnavailable | ResidualClass::ToolFailure => None,
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use perspt_sdk::{score_candidate, IndependenceRoute, ResidualSeverity, SensorRef, SymbolRef};
fn lsp_import_residual() -> ResidualEvent {
let mut r = ResidualEvent::new(
"n1",
0,
ResidualClass::ImportGraph,
ResidualSeverity::Error,
1.0,
SensorRef::new("rust-analyzer", IndependenceRoute::Lsp),
)
.unwrap();
r.affected_symbols = vec![SymbolRef {
name: "Bar".into(),
container: Some("crate::foo".into()),
}];
r.affected_paths = vec!["src/main.rs".into()];
r
}
#[test]
fn detects_coding_domain_from_cargo_toml() {
let domain = CodingDomain::new();
let ws = WorkspaceSnapshot::new("/repo", vec!["Cargo.toml".into(), "src/main.rs".into()]);
let detection = domain.detect(&ws);
assert!(detection.activated);
assert_eq!(detection.domain, DomainId::new("coding"));
}
#[test]
fn energy_model_validates_and_is_measured_only() {
let domain = CodingDomain::new();
let model = domain.energy_model(&DomainScope::default());
assert!(model.validate().is_ok());
let claim = model.stability_claim.unwrap();
assert!(
!claim.claims_floor(),
"coding domain must remain NotClaimed"
);
}
#[test]
fn rust_unresolved_import_yields_import_direction_not_retry() {
let domain = CodingDomain::new();
let directions = domain.correction_directions(&[lsp_import_residual()]);
assert_eq!(directions.len(), 1);
assert_eq!(directions[0].addresses, ResidualClass::ImportGraph);
assert!(directions[0].instruction.contains("Bar"));
assert!(directions[0].instruction.contains("use"));
}
#[test]
fn degraded_sensor_has_no_correction_direction() {
let domain = CodingDomain::new();
let r = ResidualEvent::new(
"n1",
0,
ResidualClass::SensorUnavailable,
ResidualSeverity::Blocking,
1.0,
SensorRef::new("cargo", IndependenceRoute::DeterministicTool),
)
.unwrap();
assert!(domain.correction_directions(&[r]).is_empty());
}
#[test]
fn coding_energy_model_scores_real_residuals() {
let domain = CodingDomain::new();
let model = domain.energy_model(&DomainScope::default());
let score = score_candidate(&model, &[lsp_import_residual()]).unwrap();
assert_eq!(score.total, 2.0);
assert_eq!(score.components.v_str, 2.0);
}
}