From d0bc3ebd09303c82a869253f3ae887fb5f0904a1 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 12:50:10 +0100 Subject: [PATCH 1/2] Define control-plane domain contracts --- DotPilot.Core/AGENTS.md | 1 + .../ControlPlaneIdentifiers.cs | 87 +++++++ .../ControlPlaneDomain/ControlPlaneStates.cs | 105 ++++++++ .../ParticipantContracts.cs | 40 +++ .../ProviderAndToolContracts.cs | 50 ++++ .../SessionExecutionContracts.cs | 92 +++++++ .../RuntimeFoundationContracts.cs | 13 +- .../RuntimeFoundationIdentifiers.cs | 45 ---- .../RuntimeFoundationStates.cs | 24 -- .../DeterministicAgentRuntimeClient.cs | 22 +- .../ProviderToolchainProbe.cs | 14 +- .../RuntimeFoundationCatalog.cs | 18 +- .../ControlPlaneDomainContractsTests.cs | 232 ++++++++++++++++++ DotPilot.Tests/GlobalUsings.cs | 1 + .../RuntimeFoundationCatalogTests.cs | 16 +- .../Controls/RuntimeFoundationPanel.xaml | 3 +- docs/Architecture.md | 8 +- .../agent-control-plane-experience.md | 1 + docs/Features/control-plane-domain-model.md | 69 ++++++ issue-22-domain-model.plan.md | 109 ++++++++ 20 files changed, 852 insertions(+), 98 deletions(-) create mode 100644 DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneIdentifiers.cs create mode 100644 DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneStates.cs create mode 100644 DotPilot.Core/Features/ControlPlaneDomain/ParticipantContracts.cs create mode 100644 DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs create mode 100644 DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs delete mode 100644 DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIdentifiers.cs create mode 100644 DotPilot.Tests/ControlPlaneDomainContractsTests.cs create mode 100644 docs/Features/control-plane-domain-model.md create mode 100644 issue-22-domain-model.plan.md diff --git a/DotPilot.Core/AGENTS.md b/DotPilot.Core/AGENTS.md index fa352e1..2937429 100644 --- a/DotPilot.Core/AGENTS.md +++ b/DotPilot.Core/AGENTS.md @@ -12,6 +12,7 @@ Stack: `.NET 10`, class library, feature-aligned contracts and provider-independ - `DotPilot.Core.csproj` - `Features/ApplicationShell/AppConfig.cs` +- `Features/ControlPlaneDomain/*` - `Features/RuntimeFoundation/*` ## Boundaries diff --git a/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneIdentifiers.cs b/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneIdentifiers.cs new file mode 100644 index 0000000..8281823 --- /dev/null +++ b/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneIdentifiers.cs @@ -0,0 +1,87 @@ +using System.Globalization; + +namespace DotPilot.Core.Features.ControlPlaneDomain; + +public readonly record struct WorkspaceId(Guid Value) +{ + public static WorkspaceId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct AgentProfileId(Guid Value) +{ + public static AgentProfileId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct SessionId(Guid Value) +{ + public static SessionId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct FleetId(Guid Value) +{ + public static FleetId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct ProviderId(Guid Value) +{ + public static ProviderId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct ModelRuntimeId(Guid Value) +{ + public static ModelRuntimeId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct ToolCapabilityId(Guid Value) +{ + public static ToolCapabilityId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct ApprovalId(Guid Value) +{ + public static ApprovalId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct ArtifactId(Guid Value) +{ + public static ArtifactId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct TelemetryRecordId(Guid Value) +{ + public static TelemetryRecordId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +public readonly record struct EvaluationId(Guid Value) +{ + public static EvaluationId New() => new(ControlPlaneIdentifier.NewValue()); + + public override string ToString() => ControlPlaneIdentifier.Format(Value); +} + +static file class ControlPlaneIdentifier +{ + public static Guid NewValue() => Guid.CreateVersion7(); + + public static string Format(Guid value) => value.ToString("N", CultureInfo.InvariantCulture); +} diff --git a/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneStates.cs b/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneStates.cs new file mode 100644 index 0000000..5318f6a --- /dev/null +++ b/DotPilot.Core/Features/ControlPlaneDomain/ControlPlaneStates.cs @@ -0,0 +1,105 @@ +namespace DotPilot.Core.Features.ControlPlaneDomain; + +public enum SessionPhase +{ + Plan, + Execute, + Review, + Paused, + Completed, + Failed, +} + +public enum ProviderConnectionStatus +{ + Available, + Unavailable, + RequiresAuthentication, + Misconfigured, + Outdated, +} + +public enum ApprovalState +{ + NotRequired, + Pending, + Approved, + Rejected, +} + +public enum AgentRoleKind +{ + Coding, + Research, + Analyst, + Reviewer, + Operator, + Orchestrator, +} + +public enum FleetExecutionMode +{ + SingleAgent, + Parallel, + Orchestrated, +} + +public enum RuntimeKind +{ + Provider, + LocalModel, +} + +public enum ToolCapabilityKind +{ + Command, + FileSystem, + Git, + Mcp, + Diagnostics, +} + +public enum ApprovalScope +{ + FileWrite, + CommandExecution, + ToolCall, + NetworkAccess, + SessionResume, +} + +public enum ArtifactKind +{ + Plan, + Snapshot, + Diff, + Log, + Screenshot, + Transcript, + Report, +} + +public enum TelemetrySignalKind +{ + Trace, + Metric, + Log, + Event, +} + +public enum EvaluationMetricKind +{ + Relevance, + Groundedness, + Completeness, + TaskAdherence, + ToolCallAccuracy, + Safety, +} + +public enum EvaluationOutcome +{ + Passed, + NeedsReview, + Failed, +} diff --git a/DotPilot.Core/Features/ControlPlaneDomain/ParticipantContracts.cs b/DotPilot.Core/Features/ControlPlaneDomain/ParticipantContracts.cs new file mode 100644 index 0000000..43b63e3 --- /dev/null +++ b/DotPilot.Core/Features/ControlPlaneDomain/ParticipantContracts.cs @@ -0,0 +1,40 @@ +namespace DotPilot.Core.Features.ControlPlaneDomain; + +public sealed record WorkspaceDescriptor +{ + public WorkspaceId Id { get; init; } + + public string Name { get; init; } = string.Empty; + + public string RootPath { get; init; } = string.Empty; + + public string BranchName { get; init; } = string.Empty; +} + +public sealed record AgentProfileDescriptor +{ + public AgentProfileId Id { get; init; } + + public string Name { get; init; } = string.Empty; + + public AgentRoleKind Role { get; init; } + + public ProviderId? ProviderId { get; init; } + + public ModelRuntimeId? ModelRuntimeId { get; init; } + + public IReadOnlyList ToolCapabilityIds { get; init; } = []; + + public IReadOnlyList Tags { get; init; } = []; +} + +public sealed record FleetDescriptor +{ + public FleetId Id { get; init; } + + public string Name { get; init; } = string.Empty; + + public FleetExecutionMode ExecutionMode { get; init; } = FleetExecutionMode.SingleAgent; + + public IReadOnlyList AgentProfileIds { get; init; } = []; +} diff --git a/DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs b/DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs new file mode 100644 index 0000000..127a62e --- /dev/null +++ b/DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs @@ -0,0 +1,50 @@ +namespace DotPilot.Core.Features.ControlPlaneDomain; + +public sealed record ToolCapabilityDescriptor +{ + public ToolCapabilityId Id { get; init; } + + public string Name { get; init; } = string.Empty; + + public string DisplayName { get; init; } = string.Empty; + + public ToolCapabilityKind Kind { get; init; } + + public bool RequiresApproval { get; init; } + + public bool IsEnabledByDefault { get; init; } + + public IReadOnlyList Tags { get; init; } = []; +} + +public sealed record ProviderDescriptor +{ + public ProviderId Id { get; init; } + + public string DisplayName { get; init; } = string.Empty; + + public string CommandName { get; init; } = string.Empty; + + public ProviderConnectionStatus Status { get; init; } = ProviderConnectionStatus.Unavailable; + + public string StatusSummary { get; init; } = string.Empty; + + public bool RequiresExternalToolchain { get; init; } + + public IReadOnlyList SupportedToolIds { get; init; } = []; +} + +public sealed record ModelRuntimeDescriptor +{ + public ModelRuntimeId Id { get; init; } + + public string DisplayName { get; init; } = string.Empty; + + public string EngineName { get; init; } = string.Empty; + + public RuntimeKind RuntimeKind { get; init; } + + public ProviderConnectionStatus Status { get; init; } = ProviderConnectionStatus.Unavailable; + + public IReadOnlyList SupportedModelFamilies { get; init; } = []; +} diff --git a/DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs b/DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs new file mode 100644 index 0000000..30488e8 --- /dev/null +++ b/DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs @@ -0,0 +1,92 @@ +namespace DotPilot.Core.Features.ControlPlaneDomain; + +public sealed record SessionDescriptor +{ + public SessionId Id { get; init; } + + public WorkspaceId WorkspaceId { get; init; } + + public string Title { get; init; } = string.Empty; + + public SessionPhase Phase { get; init; } = SessionPhase.Plan; + + public ApprovalState ApprovalState { get; init; } = ApprovalState.NotRequired; + + public FleetId? FleetId { get; init; } + + public IReadOnlyList AgentProfileIds { get; init; } = []; + + public DateTimeOffset CreatedAt { get; init; } + + public DateTimeOffset UpdatedAt { get; init; } +} + +public sealed record SessionApprovalRecord +{ + public ApprovalId Id { get; init; } + + public SessionId SessionId { get; init; } + + public ApprovalScope Scope { get; init; } + + public ApprovalState State { get; init; } = ApprovalState.Pending; + + public string RequestedAction { get; init; } = string.Empty; + + public string RequestedBy { get; init; } = string.Empty; + + public DateTimeOffset RequestedAt { get; init; } + + public DateTimeOffset? ResolvedAt { get; init; } +} + +public sealed record ArtifactDescriptor +{ + public ArtifactId Id { get; init; } + + public SessionId SessionId { get; init; } + + public AgentProfileId? AgentProfileId { get; init; } + + public string Name { get; init; } = string.Empty; + + public ArtifactKind Kind { get; init; } + + public string RelativePath { get; init; } = string.Empty; + + public DateTimeOffset CreatedAt { get; init; } +} + +public sealed record TelemetryRecord +{ + public TelemetryRecordId Id { get; init; } + + public SessionId SessionId { get; init; } + + public TelemetrySignalKind Kind { get; init; } + + public string Name { get; init; } = string.Empty; + + public string Summary { get; init; } = string.Empty; + + public DateTimeOffset RecordedAt { get; init; } +} + +public sealed record EvaluationRecord +{ + public EvaluationId Id { get; init; } + + public SessionId SessionId { get; init; } + + public ArtifactId? ArtifactId { get; init; } + + public EvaluationMetricKind Metric { get; init; } + + public decimal Score { get; init; } + + public EvaluationOutcome Outcome { get; init; } = EvaluationOutcome.NeedsReview; + + public string Summary { get; init; } = string.Empty; + + public DateTimeOffset EvaluatedAt { get; init; } +} diff --git a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs b/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs index 674ed97..48532f3 100644 --- a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs +++ b/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs @@ -1,3 +1,5 @@ +using DotPilot.Core.Features.ControlPlaneDomain; + namespace DotPilot.Core.Features.RuntimeFoundation; public sealed record RuntimeSliceDescriptor( @@ -7,20 +9,13 @@ public sealed record RuntimeSliceDescriptor( string Summary, RuntimeSliceState State); -public sealed record ProviderToolchainStatus( - string DisplayName, - string CommandName, - ProviderConnectionStatus Status, - string StatusSummary, - bool RequiresExternalToolchain); - public sealed record RuntimeFoundationSnapshot( string EpicLabel, string Summary, string DeterministicClientName, string DeterministicProbePrompt, IReadOnlyList Slices, - IReadOnlyList Providers); + IReadOnlyList Providers); public sealed record AgentTurnRequest( SessionId SessionId, @@ -32,4 +27,4 @@ public sealed record AgentTurnResult( string Summary, SessionPhase NextPhase, ApprovalState ApprovalState, - IReadOnlyList ProducedArtifacts); + IReadOnlyList ProducedArtifacts); diff --git a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIdentifiers.cs b/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIdentifiers.cs deleted file mode 100644 index 3a9abde..0000000 --- a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIdentifiers.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Globalization; - -namespace DotPilot.Core.Features.RuntimeFoundation; - -public readonly record struct WorkspaceId(Guid Value) -{ - public static WorkspaceId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} - -public readonly record struct AgentProfileId(Guid Value) -{ - public static AgentProfileId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} - -public readonly record struct SessionId(Guid Value) -{ - public static SessionId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} - -public readonly record struct FleetId(Guid Value) -{ - public static FleetId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} - -public readonly record struct ProviderId(Guid Value) -{ - public static ProviderId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} - -public readonly record struct ModelRuntimeId(Guid Value) -{ - public static ModelRuntimeId New() => new(Guid.CreateVersion7()); - - public override string ToString() => Value.ToString("N", CultureInfo.InvariantCulture); -} diff --git a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationStates.cs b/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationStates.cs index f147e4f..2256cd2 100644 --- a/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationStates.cs +++ b/DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationStates.cs @@ -1,29 +1,5 @@ namespace DotPilot.Core.Features.RuntimeFoundation; -public enum SessionPhase -{ - Plan, - Execute, - Review, - Paused, - Completed, - Failed, -} - -public enum ProviderConnectionStatus -{ - Available, - Unavailable, -} - -public enum ApprovalState -{ - NotRequired, - Pending, - Approved, - Rejected, -} - public enum RuntimeSliceState { Planned, diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs index f9a34d6..4207af2 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using DotPilot.Core.Features.ControlPlaneDomain; using DotPilot.Core.Features.RuntimeFoundation; namespace DotPilot.Runtime.Features.RuntimeFoundation; @@ -29,22 +30,22 @@ public ValueTask ExecuteAsync(AgentTurnRequest request, Cancell PlanSummary, SessionPhase.Plan, ApprovalState.NotRequired, - [PlanArtifact]), + [CreateArtifact(request.SessionId, PlanArtifact, ArtifactKind.Plan)]), AgentExecutionMode.Execute when RequiresApproval(request.Prompt) => new AgentTurnResult( PendingApprovalSummary, SessionPhase.Paused, ApprovalState.Pending, - [ExecuteArtifact]), + [CreateArtifact(request.SessionId, ExecuteArtifact, ArtifactKind.Snapshot)]), AgentExecutionMode.Execute => new AgentTurnResult( ExecuteSummary, SessionPhase.Execute, ApprovalState.NotRequired, - [ExecuteArtifact]), + [CreateArtifact(request.SessionId, ExecuteArtifact, ArtifactKind.Snapshot)]), AgentExecutionMode.Review => new AgentTurnResult( ReviewSummary, SessionPhase.Review, ApprovalState.Approved, - [ReviewArtifact]), + [CreateArtifact(request.SessionId, ReviewArtifact, ArtifactKind.Report)]), _ => throw new UnreachableException(), }); } @@ -53,4 +54,17 @@ private static bool RequiresApproval(string prompt) { return prompt.Contains(ApprovalKeyword, StringComparison.OrdinalIgnoreCase); } + + private static ArtifactDescriptor CreateArtifact(SessionId sessionId, string artifactName, ArtifactKind artifactKind) + { + return new ArtifactDescriptor + { + Id = ArtifactId.New(), + SessionId = sessionId, + Name = artifactName, + Kind = artifactKind, + RelativePath = artifactName, + CreatedAt = DateTimeOffset.UtcNow, + }; + } } diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs b/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs index 12ea1b8..183dfc3 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs @@ -1,5 +1,5 @@ using System.Runtime.InteropServices; -using DotPilot.Core.Features.RuntimeFoundation; +using DotPilot.Core.Features.ControlPlaneDomain; namespace DotPilot.Runtime.Features.RuntimeFoundation; @@ -12,7 +12,7 @@ internal sealed class ProviderToolchainProbe private static readonly System.Text.CompositeFormat AvailableStatusSummaryCompositeFormat = System.Text.CompositeFormat.Parse(AvailableStatusSummaryFormat); - public static ProviderToolchainStatus Probe(string displayName, string commandName, bool requiresExternalToolchain) + public static ProviderDescriptor Probe(string displayName, string commandName, bool requiresExternalToolchain) { var status = ResolveExecutablePath(commandName) is null ? ProviderConnectionStatus.Unavailable @@ -24,7 +24,15 @@ status is ProviderConnectionStatus.Available : MissingStatusSummaryCompositeFormat, displayName); - return new(displayName, commandName, status, statusSummary, requiresExternalToolchain); + return new ProviderDescriptor + { + Id = ProviderId.New(), + DisplayName = displayName, + CommandName = commandName, + Status = status, + StatusSummary = statusSummary, + RequiresExternalToolchain = requiresExternalToolchain, + }; } private static string? ResolveExecutablePath(string commandName) diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs index 64f4211..02cf9a0 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs @@ -1,3 +1,4 @@ +using DotPilot.Core.Features.ControlPlaneDomain; using DotPilot.Core.Features.RuntimeFoundation; namespace DotPilot.Runtime.Features.RuntimeFoundation; @@ -70,16 +71,19 @@ private static IReadOnlyList CreateSlices() ]; } - private IReadOnlyList CreateProviders() + private IReadOnlyList CreateProviders() { return [ - new( - ProviderToolchainNames.DeterministicClientDisplayName, - ProviderToolchainNames.DeterministicClientCommandName, - ProviderConnectionStatus.Available, - _deterministicClient.GetType().Name, - false), + new ProviderDescriptor + { + Id = ProviderId.New(), + DisplayName = ProviderToolchainNames.DeterministicClientDisplayName, + CommandName = ProviderToolchainNames.DeterministicClientCommandName, + Status = ProviderConnectionStatus.Available, + StatusSummary = _deterministicClient.GetType().Name, + RequiresExternalToolchain = false, + }, ProviderToolchainProbe.Probe( ProviderToolchainNames.CodexDisplayName, ProviderToolchainNames.CodexCommandName, diff --git a/DotPilot.Tests/ControlPlaneDomainContractsTests.cs b/DotPilot.Tests/ControlPlaneDomainContractsTests.cs new file mode 100644 index 0000000..1593b0d --- /dev/null +++ b/DotPilot.Tests/ControlPlaneDomainContractsTests.cs @@ -0,0 +1,232 @@ +using System.Text.Json; + +namespace DotPilot.Tests; + +public class ControlPlaneDomainContractsTests +{ + private static readonly DateTimeOffset CreatedAt = new(2026, 3, 13, 10, 15, 30, TimeSpan.Zero); + private static readonly DateTimeOffset UpdatedAt = new(2026, 3, 13, 10, 45, 30, TimeSpan.Zero); + private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web); + + [Test] + public void ControlPlaneIdentifiersProduceStableNonEmptyRepresentations() + { + IReadOnlyList values = + [ + WorkspaceId.New().ToString(), + AgentProfileId.New().ToString(), + SessionId.New().ToString(), + FleetId.New().ToString(), + ProviderId.New().ToString(), + ModelRuntimeId.New().ToString(), + ToolCapabilityId.New().ToString(), + ApprovalId.New().ToString(), + ArtifactId.New().ToString(), + TelemetryRecordId.New().ToString(), + EvaluationId.New().ToString(), + ]; + + values.Should().OnlyContain(value => !string.IsNullOrWhiteSpace(value)); + values.Should().OnlyHaveUniqueItems(); + } + + [Test] + public void ControlPlaneContractsRoundTripThroughSystemTextJson() + { + var envelope = CreateEnvelope(); + + var payload = JsonSerializer.Serialize(envelope, SerializerOptions); + var roundTrip = JsonSerializer.Deserialize(payload, SerializerOptions); + + roundTrip.Should().NotBeNull(); + roundTrip!.Should().BeEquivalentTo(envelope); + } + + [Test] + public void ControlPlaneContractsModelMixedProviderAndLocalRuntimeSessions() + { + var envelope = CreateEnvelope(); + + envelope.Session.AgentProfileIds.Should().ContainInOrder( + envelope.CodingAgent.Id, + envelope.ReviewerAgent.Id); + envelope.CodingAgent.ProviderId.Should().Be(envelope.Provider.Id); + envelope.ReviewerAgent.ModelRuntimeId.Should().Be(envelope.LocalRuntime.Id); + envelope.Fleet.ExecutionMode.Should().Be(FleetExecutionMode.Orchestrated); + envelope.Approval.Scope.Should().Be(ApprovalScope.CommandExecution); + envelope.Artifact.Kind.Should().Be(ArtifactKind.Snapshot); + envelope.Telemetry.Kind.Should().Be(TelemetrySignalKind.Trace); + envelope.Evaluation.Metric.Should().Be(EvaluationMetricKind.ToolCallAccuracy); + } + + private static ControlPlaneDomainEnvelope CreateEnvelope() + { + var tool = new ToolCapabilityDescriptor + { + Id = ToolCapabilityId.New(), + Name = "workspace-edit", + DisplayName = "Workspace Edit", + Kind = ToolCapabilityKind.FileSystem, + RequiresApproval = true, + IsEnabledByDefault = true, + Tags = ["write", "filesystem"], + }; + + var provider = new ProviderDescriptor + { + Id = ProviderId.New(), + DisplayName = "Codex", + CommandName = "codex", + Status = ProviderConnectionStatus.Available, + StatusSummary = "codex is available on PATH.", + RequiresExternalToolchain = true, + SupportedToolIds = [tool.Id], + }; + + var localRuntime = new ModelRuntimeDescriptor + { + Id = ModelRuntimeId.New(), + DisplayName = "Local ONNX Runtime", + EngineName = "ONNX Runtime", + RuntimeKind = RuntimeKind.LocalModel, + Status = ProviderConnectionStatus.Available, + SupportedModelFamilies = ["phi", "qwen"], + }; + + var workspace = new WorkspaceDescriptor + { + Id = WorkspaceId.New(), + Name = "dotPilot", + RootPath = "/Users/ksemenenko/Developer/dotPilot", + BranchName = "codex/issue-22-domain-model", + }; + + var codingAgent = new AgentProfileDescriptor + { + Id = AgentProfileId.New(), + Name = "Implementation Agent", + Role = AgentRoleKind.Coding, + ProviderId = provider.Id, + ToolCapabilityIds = [tool.Id], + Tags = ["implementation", "provider"], + }; + + var reviewerAgent = new AgentProfileDescriptor + { + Id = AgentProfileId.New(), + Name = "Runtime Reviewer", + Role = AgentRoleKind.Reviewer, + ModelRuntimeId = localRuntime.Id, + ToolCapabilityIds = [tool.Id], + Tags = ["review", "local"], + }; + + var fleet = new FleetDescriptor + { + Id = FleetId.New(), + Name = "Mixed Provider Fleet", + ExecutionMode = FleetExecutionMode.Orchestrated, + AgentProfileIds = [codingAgent.Id, reviewerAgent.Id], + }; + + var session = new SessionDescriptor + { + Id = SessionId.New(), + WorkspaceId = workspace.Id, + Title = "Issue 22 domain slice rollout", + Phase = SessionPhase.Execute, + ApprovalState = ApprovalState.Pending, + FleetId = fleet.Id, + AgentProfileIds = [codingAgent.Id, reviewerAgent.Id], + CreatedAt = CreatedAt, + UpdatedAt = UpdatedAt, + }; + + var approval = new SessionApprovalRecord + { + Id = ApprovalId.New(), + SessionId = session.Id, + Scope = ApprovalScope.CommandExecution, + State = ApprovalState.Pending, + RequestedAction = "Run full solution tests", + RequestedBy = codingAgent.Name, + RequestedAt = UpdatedAt, + }; + + var artifact = new ArtifactDescriptor + { + Id = ArtifactId.New(), + SessionId = session.Id, + AgentProfileId = codingAgent.Id, + Name = "runtime-foundation.snapshot.json", + Kind = ArtifactKind.Snapshot, + RelativePath = "artifacts/runtime-foundation.snapshot.json", + CreatedAt = UpdatedAt, + }; + + var telemetry = new TelemetryRecord + { + Id = TelemetryRecordId.New(), + SessionId = session.Id, + Kind = TelemetrySignalKind.Trace, + Name = "RuntimeFoundation.Execute", + Summary = "Deterministic provider-independent trace", + RecordedAt = UpdatedAt, + }; + + var evaluation = new EvaluationRecord + { + Id = EvaluationId.New(), + SessionId = session.Id, + ArtifactId = artifact.Id, + Metric = EvaluationMetricKind.ToolCallAccuracy, + Score = 0.98m, + Outcome = EvaluationOutcome.Passed, + Summary = "Tool calls matched the expected deterministic sequence.", + EvaluatedAt = UpdatedAt, + }; + + return new ControlPlaneDomainEnvelope + { + Workspace = workspace, + Tool = tool, + Provider = provider, + LocalRuntime = localRuntime, + CodingAgent = codingAgent, + ReviewerAgent = reviewerAgent, + Fleet = fleet, + Session = session, + Approval = approval, + Artifact = artifact, + Telemetry = telemetry, + Evaluation = evaluation, + }; + } + + private sealed record ControlPlaneDomainEnvelope + { + public WorkspaceDescriptor Workspace { get; init; } = new(); + + public ToolCapabilityDescriptor Tool { get; init; } = new(); + + public ProviderDescriptor Provider { get; init; } = new(); + + public ModelRuntimeDescriptor LocalRuntime { get; init; } = new(); + + public AgentProfileDescriptor CodingAgent { get; init; } = new(); + + public AgentProfileDescriptor ReviewerAgent { get; init; } = new(); + + public FleetDescriptor Fleet { get; init; } = new(); + + public SessionDescriptor Session { get; init; } = new(); + + public SessionApprovalRecord Approval { get; init; } = new(); + + public ArtifactDescriptor Artifact { get; init; } = new(); + + public TelemetryRecord Telemetry { get; init; } = new(); + + public EvaluationRecord Evaluation { get; init; } = new(); + } +} diff --git a/DotPilot.Tests/GlobalUsings.cs b/DotPilot.Tests/GlobalUsings.cs index 1013040..ed663e5 100644 --- a/DotPilot.Tests/GlobalUsings.cs +++ b/DotPilot.Tests/GlobalUsings.cs @@ -1,4 +1,5 @@ global using DotPilot.Core.Features.ApplicationShell; +global using DotPilot.Core.Features.ControlPlaneDomain; global using DotPilot.Core.Features.RuntimeFoundation; global using DotPilot.Runtime.Features.RuntimeFoundation; global using FluentAssertions; diff --git a/DotPilot.Tests/RuntimeFoundationCatalogTests.cs b/DotPilot.Tests/RuntimeFoundationCatalogTests.cs index f445e47..487e13b 100644 --- a/DotPilot.Tests/RuntimeFoundationCatalogTests.cs +++ b/DotPilot.Tests/RuntimeFoundationCatalogTests.cs @@ -46,7 +46,9 @@ public async Task DeterministicClientReturnsPendingApprovalWhenPromptRequestsApp result.NextPhase.Should().Be(SessionPhase.Paused); result.ApprovalState.Should().Be(ApprovalState.Pending); - result.ProducedArtifacts.Should().ContainSingle("runtime-foundation.snapshot.json"); + result.ProducedArtifacts.Should().ContainSingle(artifact => + artifact.Name == "runtime-foundation.snapshot.json" && + artifact.Kind == ArtifactKind.Snapshot); } [Test] @@ -58,7 +60,9 @@ public async Task DeterministicClientReturnsPlanArtifactsForPlanMode() result.NextPhase.Should().Be(SessionPhase.Plan); result.ApprovalState.Should().Be(ApprovalState.NotRequired); - result.ProducedArtifacts.Should().ContainSingle("runtime-foundation.plan.md"); + result.ProducedArtifacts.Should().ContainSingle(artifact => + artifact.Name == "runtime-foundation.plan.md" && + artifact.Kind == ArtifactKind.Plan); } [Test] @@ -70,7 +74,9 @@ public async Task DeterministicClientReturnsExecuteResultsWhenApprovalIsNotReque result.NextPhase.Should().Be(SessionPhase.Execute); result.ApprovalState.Should().Be(ApprovalState.NotRequired); - result.ProducedArtifacts.Should().ContainSingle("runtime-foundation.snapshot.json"); + result.ProducedArtifacts.Should().ContainSingle(artifact => + artifact.Name == "runtime-foundation.snapshot.json" && + artifact.Kind == ArtifactKind.Snapshot); } [Test] @@ -104,7 +110,9 @@ public async Task DeterministicClientReturnsApprovedReviewResults() result.NextPhase.Should().Be(SessionPhase.Review); result.ApprovalState.Should().Be(ApprovalState.Approved); - result.ProducedArtifacts.Should().ContainSingle("runtime-foundation.review.md"); + result.ProducedArtifacts.Should().ContainSingle(artifact => + artifact.Name == "runtime-foundation.review.md" && + artifact.Kind == ArtifactKind.Report); } [TestCase(CodexCommandName)] diff --git a/DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml b/DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml index 705ab93..f4f2cb2 100644 --- a/DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml +++ b/DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml @@ -1,6 +1,7 @@ + x:DataType="domain:ProviderDescriptor"> diff --git a/docs/Architecture.md b/docs/Architecture.md index cea272e..bafa987 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -9,6 +9,7 @@ This file is the required start-here architecture map for non-trivial tasks. - **System:** `DotPilot` is a `.NET 10` `Uno Platform` desktop-first application that is evolving from a static prototype into a local-first control plane for agent operations. - **Presentation boundary:** [../DotPilot/](../DotPilot/) is now the presentation host only. It owns XAML, routing, desktop startup, and UI composition, while non-UI feature logic moves into separate DLLs. - **Runtime foundation boundary:** [../DotPilot.Core/](../DotPilot.Core/) owns issue-aligned contracts, typed identifiers, and public slice interfaces; [../DotPilot.Runtime/](../DotPilot.Runtime/) owns provider-independent runtime implementations such as the deterministic test client, toolchain probing, and future embedded-host integration points. +- **Domain slice boundary:** issue [#22](https://github.com/managedcode/dotPilot/issues/22) now lives in `DotPilot.Core/Features/ControlPlaneDomain`, which defines the shared agent, session, fleet, provider, runtime, approval, artifact, telemetry, and evaluation model that later slices reuse. - **First implementation slice:** epic [#12](https://github.com/managedcode/dotPilot/issues/12) is represented locally through the `RuntimeFoundation` slice, which sequences issues `#22`, `#23`, `#24`, and `#25` behind a stable contract surface instead of mixing runtime work into the Uno app. - **Automated verification:** [../DotPilot.Tests/](../DotPilot.Tests/) covers API-style and contract flows through the new DLL boundaries; [../DotPilot.UITests/](../DotPilot.UITests/) covers the visible workbench flow and the runtime-foundation UI surface. Provider-independent flows must pass in CI through the deterministic test client, while provider-specific checks can run only when the matching toolchain is available. @@ -64,6 +65,7 @@ flowchart TD Comm["#23 Communication contracts"] Host["#24 Embedded Orleans host"] MAF["#25 Agent Framework runtime"] + DomainSlice["DotPilot.Core/Features/ControlPlaneDomain"] CoreSlice["DotPilot.Core/Features/RuntimeFoundation"] RuntimeSlice["DotPilot.Runtime/Features/RuntimeFoundation"] UiSlice["DotPilot runtime panel + banner"] @@ -72,7 +74,8 @@ flowchart TD Epic --> Comm Epic --> Host Epic --> MAF - Domain --> CoreSlice + Domain --> DomainSlice + DomainSlice --> CoreSlice Comm --> CoreSlice Host --> RuntimeSlice MAF --> RuntimeSlice @@ -112,6 +115,7 @@ flowchart LR - `Primary architecture decision` — [ADR-0001](./ADR/ADR-0001-agent-control-plane-architecture.md) - `Vertical-slice solution decision` — [ADR-0003](./ADR/ADR-0003-vertical-slices-and-ui-only-uno-app.md) - `Feature spec` — [Agent Control Plane Experience](./Features/agent-control-plane-experience.md) +- `Issue #22 feature doc` — [Control Plane Domain Model](./Features/control-plane-domain-model.md) ### Modules @@ -130,6 +134,8 @@ flowchart LR - `Reusable runtime panel` — [../DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml](../DotPilot/Presentation/Controls/RuntimeFoundationPanel.xaml) - `Shell configuration contract` — [../DotPilot.Core/Features/ApplicationShell/AppConfig.cs](../DotPilot.Core/Features/ApplicationShell/AppConfig.cs) - `Runtime foundation contracts` — [../DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs](../DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs) +- `Control-plane domain contracts` — [../DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs](../DotPilot.Core/Features/ControlPlaneDomain/SessionExecutionContracts.cs) +- `Provider and tool contracts` — [../DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs](../DotPilot.Core/Features/ControlPlaneDomain/ProviderAndToolContracts.cs) - `Runtime issue catalog` — [../DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIssues.cs](../DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationIssues.cs) - `Runtime catalog implementation` — [../DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs](../DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs) - `Deterministic test client` — [../DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs](../DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs) diff --git a/docs/Features/agent-control-plane-experience.md b/docs/Features/agent-control-plane-experience.md index ce10098..a49acaa 100644 --- a/docs/Features/agent-control-plane-experience.md +++ b/docs/Features/agent-control-plane-experience.md @@ -181,6 +181,7 @@ stateDiagram-v2 - `docs/Architecture.md` reflects the same boundaries described here. - `docs/ADR/ADR-0001-agent-control-plane-architecture.md` records the architectural choice and trade-offs. +- `docs/Features/control-plane-domain-model.md` captures the reusable issue `#22` contract relationships for agents, sessions, fleets, providers, runtimes, approvals, artifacts, telemetry, and evaluations. - GitHub issues map back to the capabilities and flows in this spec. ### Future Product Verification diff --git a/docs/Features/control-plane-domain-model.md b/docs/Features/control-plane-domain-model.md new file mode 100644 index 0000000..9e2bb31 --- /dev/null +++ b/docs/Features/control-plane-domain-model.md @@ -0,0 +1,69 @@ +# Control Plane Domain Model + +## Summary + +Issue [#22](https://github.com/managedcode/dotPilot/issues/22) defines the first stable domain contracts that later runtime, communication, Orleans, and orchestration slices will share. The goal is to keep these shapes broad enough for coding and non-coding agents while staying serialization-safe and independent from the Uno UI host. + +## Scope + +### In Scope + +- typed identifiers for workspaces, agents, sessions, fleets, providers, runtimes, tools, approvals, artifacts, telemetry, and evaluations +- shared state enums for session lifecycle, provider readiness, approval status, execution modes, artifacts, telemetry, and evaluation outcomes +- DTO-style records for workspaces, agents, fleets, providers, runtimes, sessions, approvals, artifacts, telemetry, and evaluations +- references from runtime-foundation contracts back into this domain slice + +### Out Of Scope + +- Orleans grain implementations +- live provider execution or SDK adapters +- transport contracts for issue `#23` + +## Relationships + +```mermaid +flowchart LR + Workspace["WorkspaceDescriptor"] + Session["SessionDescriptor"] + Fleet["FleetDescriptor"] + Agent["AgentProfileDescriptor"] + Provider["ProviderDescriptor"] + Runtime["ModelRuntimeDescriptor"] + Tool["ToolCapabilityDescriptor"] + Approval["SessionApprovalRecord"] + Artifact["ArtifactDescriptor"] + Telemetry["TelemetryRecord"] + Evaluation["EvaluationRecord"] + + Workspace --> Session + Fleet --> Session + Agent --> Fleet + Provider --> Agent + Runtime --> Agent + Tool --> Agent + Session --> Approval + Session --> Artifact + Session --> Telemetry + Artifact --> Evaluation + Session --> Evaluation +``` + +## Contract Notes + +- `ControlPlaneIdentifiers` use `Guid.CreateVersion7()` so new IDs are sortable and modern without leaking UI concerns into the core domain slice. +- DTOs are modeled as non-positional `sealed record` types with `init` properties and safe defaults so `System.Text.Json` can round-trip them without custom infrastructure. +- `ProviderConnectionStatus` includes non-happy-path states such as `RequiresAuthentication`, `Misconfigured`, and `Outdated` because the operator UI must surface these explicitly before live sessions start. +- `AgentProfileDescriptor` supports mixed provider and local-runtime participation by allowing either `ProviderId`, `ModelRuntimeId`, or both depending on future orchestration needs. +- `SessionDescriptor`, `SessionApprovalRecord`, `ArtifactDescriptor`, `TelemetryRecord`, and `EvaluationRecord` carry the minimum shared flow data needed by issues `#23`, `#24`, and `#25`. + +## Verification + +- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj` +- `dotnet test DotPilot.slnx` + +## Dependencies + +- Parent epic: [#12](https://github.com/managedcode/dotPilot/issues/12) +- Runtime communication follow-up: [#23](https://github.com/managedcode/dotPilot/issues/23) +- Embedded host follow-up: [#24](https://github.com/managedcode/dotPilot/issues/24) +- Agent Framework follow-up: [#25](https://github.com/managedcode/dotPilot/issues/25) diff --git a/issue-22-domain-model.plan.md b/issue-22-domain-model.plan.md new file mode 100644 index 0000000..d47f865 --- /dev/null +++ b/issue-22-domain-model.plan.md @@ -0,0 +1,109 @@ +# Issue 22 Domain Model Plan + +## Goal + +Define the first stable `dotPilot` control-plane domain contracts for agents, sessions, fleets, providers, runtimes, tools, artifacts, telemetry, approvals, and evaluations so later runtime and UI slices can build on versionable, serialization-safe shapes. + +## Scope + +### In Scope + +- Introduce an issue-aligned domain slice in `DotPilot.Core` for the control-plane model behind issue `#22` +- Move typed identifiers and state enums out of the temporary runtime-foundation slice into the new domain slice +- Define serialization-safe records for agents, sessions, fleets, providers, runtimes, tool capabilities, approvals, artifacts, telemetry, and evaluations +- Update existing runtime-foundation contracts to depend on the new domain slice instead of owning domain primitives directly +- Update architecture and feature documentation so issue `#22` relationships are explicit and navigable +- Add automated tests for identifier semantics, DTO round-tripping, and the updated runtime-foundation composition path + +### Out Of Scope + +- Concrete Orleans grain implementations +- Microsoft Agent Framework runtime orchestration +- Live provider SDK adapters +- UI redesign beyond the minimum text or binding updates required by the new contract names + +## Constraints And Risks + +- Keep the Uno app presentation-only; the domain model must live outside `DotPilot` +- Preserve the already-reviewed runtime-foundation slice while extracting shared domain concepts from it +- Prefer versionable DTO shapes that remain safe for `System.Text.Json` serialization and future expansion +- Use modern stable `.NET 10` and `C#` features only when they improve clarity and maintainability +- Keep branch and type boundaries explicit so issue `#23`, `#24`, and `#25` can reference the new contracts directly +- Protect the deterministic CI baseline; external provider availability remains optional and self-gated + +## Testing Methodology + +- Baseline proof: run the full solution test command after this plan is created +- Contract proof: add focused tests for typed identifiers, default-safe DTO shapes, and JSON round-tripping of the new domain records +- Integration proof: rerun the runtime-foundation tests that consume the moved domain contracts +- UI proof: rerun the full UI suite to confirm the presentation host still renders the runtime foundation surfaces after the contract move +- Final proof: run repo-ordered validation with `format`, `build`, full `test`, and the coverage command +- Quality bar: domain contracts remain deterministic, future-facing, and serialization-safe; the full solution stays green; coverage remains at or above the prior baseline + +## Ordered Plan + +- [x] Step 1: Establish the full baseline for the new branch after the plan is prepared. + Verification: + - `dotnet test DotPilot.slnx` + Done when: this file records whether any failures predate the issue `#22` changes. + +- [x] Step 2: Introduce the `#22` control-plane domain slice in `DotPilot.Core`. + Verification: typed identifiers, shared state enums, and the new domain DTOs live under an issue-aligned feature folder instead of the runtime-foundation slice. + Done when: agents, sessions, fleets, providers, runtimes, tools, artifacts, telemetry, approvals, and evaluations all have stable contract shapes. + +- [x] Step 3: Rewire the runtime-foundation slice to depend on the new domain contracts. + Verification: `RuntimeFoundation` keeps its feature-specific contracts, but all reused domain primitives come from the `#22` domain slice. + Done when: runtime-foundation no longer owns cross-cutting control-plane primitives. + +- [x] Step 4: Update durable docs for issue `#22`. + Verification: architecture and feature docs show the new domain slice, its relationships, and how later issues build on it. + Done when: future agents can trace `#22` from docs without reverse-engineering the code. + +- [x] Step 5: Add or update automated tests for the new domain model and moved runtime dependencies. + Verification: tests cover identifier creation and formatting, JSON round-tripping, representative domain DTOs, and the runtime slice's continued behavior. + Done when: the moved contracts are protected by meaningful assertions instead of compile-only confidence. + +- [x] Step 6: Run final validation in repo order and record the results. + Verification: + - `dotnet format DotPilot.slnx --verify-no-changes` + - `dotnet build DotPilot.slnx -warnaserror` + - `dotnet test DotPilot.slnx` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` + Done when: the commands are green or any remaining blocker is explicitly documented here. + +- [ ] Step 7: Create the PR for issue `#22`, then continue from a fresh branch for the next slice. + Verification: the branch and PR flow matches the enforced repo workflow after completing a slice. + Done when: the issue `#22` PR exists and follow-up work no longer accumulates on its review branch. + +## Validation Notes + +- `dotnet test DotPilot.slnx` passed with `0` failed, `34` passed, and `0` skipped on the new `codex/issue-22-domain-model` branch baseline. +- `DotPilot.Core/Features/ControlPlaneDomain/*` now owns typed identifiers, shared state enums, and stable DTOs for workspaces, agents, fleets, providers, runtimes, approvals, artifacts, telemetry, and evaluations. +- `DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs` now consumes `ProviderDescriptor` and `ArtifactDescriptor` from the `#22` domain slice instead of owning those cross-cutting shapes locally. +- `DotPilot.Runtime/Features/RuntimeFoundation/*` now builds provider readiness and deterministic runtime artifacts on top of the shared control-plane domain contracts. +- `docs/Features/control-plane-domain-model.md` documents the issue `#22` relationships and downstream issue references with a Mermaid map. +- `DotPilot.Tests/ControlPlaneDomainContractsTests.cs` adds identifier, JSON round-trip, and mixed provider/local-runtime session coverage for the new contract set. +- `dotnet format DotPilot.slnx --verify-no-changes` passed. +- `dotnet build DotPilot.slnx -warnaserror` passed with `0` warnings and `0` errors. +- `dotnet test DotPilot.slnx` passed with `0` failed, `37` passed, and `0` skipped. +- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with `0` failed, `23` passed, and `0` skipped. +- Coverage from `DotPilot.Tests/TestResults/cb30b645-002f-45bf-af57-b224bd5073c1/coverage.cobertura.xml` is `99.43%` line coverage and `85.00%` branch coverage across the covered production surface. + +## Failing Tests Tracker + +- [x] Baseline solution verification + Failure symptom: none. + Root cause: baseline solution tests passed before the issue `#22` changes started. + Fix path: preserve the green baseline while moving shared domain primitives into the new slice. + Status: complete. + +## Final Validation Skills + +1. `mcaf-dotnet` +Reason: the change reshapes core contracts and must stay aligned with the repo .NET toolchain. + +2. `mcaf-testing` +Reason: domain DTOs and moved runtime dependencies need durable regression coverage. + +3. `mcaf-architecture-overview` +Reason: issue `#22` adds a new reusable slice that must appear in the architecture map. From 954d0710ae7e6be324949c8be1b02dc87f746a0d Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 12:51:21 +0100 Subject: [PATCH 2/2] Finalize issue 22 plan record --- issue-22-domain-model.plan.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/issue-22-domain-model.plan.md b/issue-22-domain-model.plan.md index d47f865..71b7e72 100644 --- a/issue-22-domain-model.plan.md +++ b/issue-22-domain-model.plan.md @@ -71,7 +71,7 @@ Define the first stable `dotPilot` control-plane domain contracts for agents, se - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` Done when: the commands are green or any remaining blocker is explicitly documented here. -- [ ] Step 7: Create the PR for issue `#22`, then continue from a fresh branch for the next slice. +- [x] Step 7: Create the PR for issue `#22`, then continue from a fresh branch for the next slice. Verification: the branch and PR flow matches the enforced repo workflow after completing a slice. Done when: the issue `#22` PR exists and follow-up work no longer accumulates on its review branch. @@ -88,6 +88,7 @@ Define the first stable `dotPilot` control-plane domain contracts for agents, se - `dotnet test DotPilot.slnx` passed with `0` failed, `37` passed, and `0` skipped. - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with `0` failed, `23` passed, and `0` skipped. - Coverage from `DotPilot.Tests/TestResults/cb30b645-002f-45bf-af57-b224bd5073c1/coverage.cobertura.xml` is `99.43%` line coverage and `85.00%` branch coverage across the covered production surface. +- PR created: `https://github.com/managedcode/dotPilot/pull/74`. ## Failing Tests Tracker