Skip to content

Latest commit

 

History

History
164 lines (141 loc) · 9.05 KB

AGENTS.md

File metadata and controls

164 lines (141 loc) · 9.05 KB

AGENTS.md — ServiceManagerBackend

Operating instructions for AI agents working in this repository. The goal is to build and extend this kind of project (a .NET 10 Minimal API that wraps a system service — here: systemd units over D-Bus — with a fully documented, strongly typed REST + SSE surface) with minimal human intervention.

What this project is

  • ServiceManagerBackend/ — .NET 10 Minimal API (net10.0) that starts/stops/monitors whitelisted user systemd units via the session D-Bus bus.
  • example/SystemdServiceController — reference CLI (verified pattern for the D-Bus layer).
  • docs/ — background reading: D-Bus fundamentals, the systemd1 D-Bus API, Tmds.DBus library. See docs/README.md
  • DECISIONS.md — read this first: non-obvious decisions, gotchas, and the D-Bus→HTTP error mapping. Everything in there was verified live or cost a debugging session.
  • .opencode/skills/ — project-local skills with distilled best practices.

Commands

Task Command (run from repo root)
Build dotnet build (expect 0 warnings, 0 errors)
Run dotnet run --project ServiceManagerBackend --no-build --no-launch-profile
OpenAPI document curl -s http://127.0.0.1:5199/openapi/v1.json (port from ASPNETCORE_URLS)
Swagger UI http://127.0.0.1:5199/swagger

Gotchas:

  • Properties/launchSettings.json overrides ASPNETCORE_URLS on dotnet run — always pass --no-launch-profile when you need a known port for verification.
  • The app starts and serves /swagger + /openapi/v1.json even without a session bus; the D-Bus connection is lazy (first real request). So OpenAPI/doc changes can be verified by curl without any systemd involvement.
  • After changing API types or OpenAPI config: build, run, curl /openapi/v1.json, inspect the affected schemas, stop the server.

Information sources — strict priority order

  1. microsoft-learn MCP (highest priority for any .NET / ASP.NET question). Official, version-aligned Microsoft documentation. Always query with the project's major version (here: .NET 10 / aspnetcore-10.0 moniker) and prefer microsoft_docs_search → microsoft_docs_fetch (full page) → microsoft_code_sample_search (code snippets). Do not answer .NET framework questions from memory when this MCP is available — the API surface changes between major versions (e.g. .NET 9 → 10 broke OptionsBuilder.Validate, moved Microsoft.OpenApi.Models types, added OpenAPI transformers).
  2. Project-local skills in .opencode/skills/ — load them when the task matches:
    • Server-Sent events — native .NET 10 SSE: IAsyncEnumerable<SseItem<T>> + TypedResults.ServerSentEvents() + [EnumeratorCancellation].
    • Controlling systemd services using D-Bus with TMDS library — Tmds.DBus.Protocol
      • source generator scaffolding.
    • aspnet — .NET 10 Minimal API patterns (TypedResults, OpenAPI 3.1, SSE, validation).
    • responses — typed results / error response conventions.
  3. DECISIONS.md and docs/ — project history and verified behavior.
  4. Ask user if cannot find a problem solution in a reasonable ammount of attempts.
  5. websearch — only as a last resort (community blog posts are usually outdated or wrong for recent framework versions).

Project conventions

  • Minimal APIs only, no controllers. Routes grouped per resource (IEndpointRouteBuilder extension methods in Endpoints/).
  • Typed results everywhere: Results<NotFound<ErrorDto>, Ok<...>> etc. — no untyped Results.Json in endpoint signatures (it is only allowed inside helper methods that return IResult).
  • Strongly typed SSE: the stream type is SseItem<T> with a single concrete closed union type T (here: JobStreamEvent, an abstract base record in ApiDtos.cs; each event record derives from it). Never SseItem<object>.
  • XML doc comments on every API DTO (<summary> on the type, <param> per constructor parameter). This is what feeds the OpenAPI document — see playbook below.
  • DTOs live in ApiDtos.cs (root). D-Bus mapping helpers stay private inside Endpoints/ServiceEndpoints.cs.
  • D-Bus error replies → HTTP: follow the table in DECISIONS.md (UnitNotFound→404, Masked/NotActive→409, NoSuchUnit→502, AccessDenied→403, NoReply→504, else 500).
  • One session-bus connection for the app lifetime (DI singleton SystemdClient, lazy connect). Manager.Subscribe() must be called before any signal — see DECISIONS.md for the watch-before-call job pattern and race notes.

.NET 10 OpenAPI + XML documentation playbook

OpenAPI document generation is the built-in Microsoft.AspNetCore.OpenApi (AddOpenApi() / app.MapOpenApi()). Swashbuckle.AspNetCore.SwashbuckleUI-style generation is NOT used — Swashbuckle.AspNetCore.SwaggerUI is UI-only and just points at /openapi/v1.json via options.SwaggerEndpoint(...).

To make XML doc comments on types/parameters appear in the OpenAPI document:

  1. csproj must have:
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);CS1591</NoWarn>
    NoWarn for CS1591 is required — otherwise every undocumented public member (thousands of them in Tmds.DBus.Generator-generated proxies) floods the build. Suppressing the missing comment warning does not affect the generated document; the source generator reads comments straight from the compilation.
  2. No code wiring is needed: AddOpenApi() overloads are intercepted automatically.
  3. <param> comments on a record's primary constructor DO map to the property descriptions in the generated schema (verified).
  4. XML comments cannot be attached to lambda expressions — if an endpoint handler needs docs, extract it to a method and reference that method from MapXxx.

Expanding a polymorphic/abstract schema (verified pattern)

When a response type is an abstract base (e.g. SseItem<JobStreamEvent>), the document shows only the empty base schema. Fix with a schema transformer (ServiceManagerBackend/OpenApi/JobStreamEventSchemaTransformer.cs):

public sealed class MySchemaTransformer : IOpenApiSchemaTransformer
{
    public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken ct)
    {
        if (context.JsonTypeInfo.Type != typeof(BaseType)) return;

        schema.OneOf = [];
        foreach (Type concrete in ConcreteTypes)
        {
            OpenApiSchema s = await context.GetOrCreateSchemaAsync(concrete, null, ct);
            context.Document?.AddComponent(concrete.Name, s);
            schema.OneOf.Add(new OpenApiSchemaReference(concrete.Name, context.Document));
        }
    }
}
// Program.cs:
builder.Services.AddOpenApi(options => options.AddSchemaTransformer<MySchemaTransformer>());

API notes (bitten while building this — verify with microsoft-learn MCP if unsure):

  • GetOrCreateSchemaAsync(Type, ApiParameterDescription?, CancellationToken) — the second parameter is not optional, pass null.
  • Microsoft.OpenApi 2.x (bundled with .NET 10): model types live in the root Microsoft.OpenApi namespace — there is no Microsoft.OpenApi.Models.
  • Per-route customization: AddOpenApiOperationTransformer(...) (the old WithOpenApi(op => ...) is deprecated, ASPDEPR002).
  • Transformer execution order: schema → operation → document transformers (see microsoft-learn: "Customize OpenAPI documents").

D-Bus / Tmds.DBus wiring

  • Proxies are generated from dbus-xml/*.xml via csproj AdditionalFiles (DBusGeneratorMode="Proxy"); no hand-written D-Bus client code.
  • CompilerGeneratedFilesOutputPath must stay under $(BaseIntermediateOutputPath) — a path inside the project directory compiles the emitted file twice (CS0101 storm).
  • For any new unit/job/manager member: extend the XML introspection files and rebuild; consult the Controlling systemd services using D-Bus with TMDS library skill.

Autonomy and escalation rules

  1. Work autonomously: read DECISIONS.md and relevant skills before coding, make the change, verify (build + runtime/curl checks when the change affects runtime behavior), and report the result. Do not ask permission for routine steps.
  2. Ask user when a choice is genuinely ambiguous. Pick the option consistent with existing project conventions and note it in the final summary.
  3. Escalation exception to the no-intervention rule: if you cannot solve a problem after a reasonable number of attempts (guideline: 3 distinct approaches), stop trying and ask the user to adjust the environment — e.g. add or fix a SKILL file under .opencode/skills/, enable an MCP server (such as microsoft-learn), add a NuGet package, install a tool, or grant missing permissions. State concretely what was tried, what failed, and exactly which environment change would unblock. This request for environment adjustment is an explicit exception to the minimal-human-intervention requirement and is the correct behavior, not a failure.
  4. Never commit unless explicitly asked.