# Technical documentation — task-ledger-api

Evidence date: 2026-10-01.

Source: `git@bitbucket.org:overdrive/task-ledger-api.git`, `main` at `8ab244e` (2026-01-03, maanie, "fix: update connection string to use LocalDB and add retry logic").

The connection strings use Windows trusted authentication. No password is stored in git. This document quotes the server and database name because they are in the committed settings.

## What this repository is

An ASP.NET Core Web API on .NET 8 named `TaskLedger.Api`. It exposes create and list for a single `Task` entity and stores rows in SQL Server through Entity Framework Core 8. A weather-forecast minimal API from the project template is still registered.

There is no README. `cursor.rules.md` records stack and editing constraints for a coding agent.

The 2026-09-28 discovery snapshot of the scan-host folder also named `sonar-project.properties` and `sonarqube_scan.bat`. Those paths are not in git at `8ab244e` and are not in any commit. **Unknown / Requires Confirmation** whether they were untracked local files.

## Repository layout

```text
TaskLedger.Api.sln
cursor.rules.md
.gitignore
TaskLedger.Api/
  TaskLedger.Api.csproj
  Program.cs
  TaskLedger.Api.http
  appsettings.json
  appsettings.Development.json
  Properties/launchSettings.json
  Controllers/TasksController.cs
  Services/ITaskService.cs
  Services/TaskService.cs
  Domain/Entities/Task.cs
  Infrastructure/Data/AppDbContext.cs
  Migrations/20260101150955_InitialCreate.cs
  Migrations/20260101150955_InitialCreate.Designer.cs
  Migrations/AppDbContextModelSnapshot.cs
  Migrations/InitialCreate.sql
TaskLedger.Api.Tests/
  TaskLedger.Api.Tests.csproj
  UnitTest1.cs
  Services/TaskServiceTests.cs
```

No `Dockerfile`, pipeline YAML, `README`, `appsettings.Production.json`, or repository class is in the tree.

`.gitignore` ignores `bin/`, `obj/`, `*.user`, `*.suo`, `*.cache`, and `*.vs/`. Earlier commits had committed those build outputs. They were removed on 2026-01-02.

## Solution

`TaskLedger.Api.sln` is Visual Studio solution format 12.00, Visual Studio Version 17. Configurations: Debug and Release, platform Any CPU. Projects:

| Project | SDK | Target | Role |
|---|---|---|---|
| `TaskLedger.Api` | `Microsoft.NET.Sdk.Web` | `net8.0` | Web API |
| `TaskLedger.Api.Tests` | `Microsoft.NET.Sdk` | `net8.0` | xUnit tests, references the API project |

Both projects set `Nullable` enable and `ImplicitUsings` enable. The test project sets `IsPackable` false and `IsTestProject` true.

## Application startup

`TaskLedger.Api/Program.cs` uses the minimal hosting model.

Registered services:

- Endpoint API explorer and Swagger generator.
- Controllers.
- `AppDbContext` with `UseSqlServer`. The connection string name is `DefaultConnection`. SQL options call `EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null)`.
- `ITaskService` implemented by `TaskService`, scoped.

Pipeline:

1. If Development: Swagger, then Swagger UI.
2. HTTPS redirection.
3. `MapControllers`.
4. `MapGet("/weatherforecast")` named `GetWeatherForecast`, with `WithOpenApi()`.
5. `Run`.

`WeatherForecast` is a record in the same file: `Date`, `TemperatureC`, `Summary`, and a calculated `TemperatureF`.

There is no `UseAuthentication`, `UseAuthorization`, `UseCors`, health check, or static files middleware.

## HTTP surface

Controller route template: `api/[controller]` on `TasksController`, so the path prefix is `api/Tasks`.

| Method | Path | Action | Success | Failure handled in the action |
|---|---|---|---|---|
| POST | `/api/Tasks` | `CreateTask` | 201, body is the entity | 400 when the service throws `ArgumentException` |
| GET | `/api/Tasks` | `GetAllTasks` | 200, JSON array | none in the action |
| GET | `/weatherforecast` | `GetWeatherForecast` | 200, five forecasts | none |

`CreateTaskRequest` is a record in the controller file, not a separate DTO project. Properties: `Title` (`string`, default empty), `Description` (`string?`).

`CreateTask` returns `CreatedAtAction(nameof(GetAllTasks), new { id = task.Id }, task)`. `GetAllTasks` does not take `id`. See business rule BR-018.

Declared response types on the controller: create documents 201 and 400; list documents 200.

The sample file `TaskLedger.Api.http` sets `@TaskLedger.Api_HostAddress = http://localhost:5037` and requests `GET /weatherforecast/` only.

## Domain model

`TaskLedger.Api.Domain.Entities.Task`:

| Property | CLR type | Database | Notes |
|---|---|---|---|
| `Id` | `int` | `int` identity, seed 1 | Primary key `PK_Tasks` |
| `Title` | `string` | `nvarchar(200)` not null | Required, max length 200 |
| `Description` | `string?` | `nvarchar(1000)` null | Max length 1000 |
| `IsCompleted` | `bool` | `bit` not null | Set false on create |
| `CreatedUtc` | `DateTime` | `datetime2` not null | Set in the service |

The class name `Task` collides with `System.Threading.Tasks.Task`. Call sites use `Domain.Entities.Task` or the alias `TaskEntity` in the test file. Test methods that return a task use `System.Threading.Tasks.Task` as the return type.

Table name is the DbSet name `Tasks` (`AppDbContextModelSnapshot` calls `ToTable("Tasks")`).

History: `e12e94a` used `TaskLedgerDbContext`, `CreatedAt`, and `UpdatedAt`, with migration `20260101145453_InitialCreate`. `310c137` replaced that with the current model and migration `20260101150955_InitialCreate`. The old files are not on `main`.

## Data access

`AppDbContext` constructor takes `DbContextOptions<AppDbContext>`. `DbSet<Domain.Entities.Task> Tasks`. `OnModelCreating` sets the key and the string lengths above, and marks `CreatedUtc` required. There is no query filter, seed, or index beyond the primary key.

`TaskService` is the only writer and reader:

- `CreateTaskAsync` validates the title, builds the entity, `Add`, `SaveChangesAsync`, returns the entity.
- `GetAllTasksAsync` orders by `CreatedUtc` and calls `ToListAsync`.

`ITaskService` declares those two methods. No repository interface exists. Commit `22a5d61` says one was added. The diff does not add a `.cs` type.

EF Core execution strategy from `EnableRetryOnFailure` wraps the SQL operations. `errorNumbersToAdd` is null, so only the provider's default transient errors are retried.

## Configuration

### Connection string

Both `appsettings.json` and `appsettings.Development.json` set:

```text
Server=(localdb)\mssqllocaldb;Database=TaskLedgerDb;Trusted_Connection=true;TrustServerCertificate=true;
```

Before `8ab244e`, `appsettings.json` used `Server=localhost` and the Development file had no `ConnectionStrings` section.

### Logging

Both files: default log level `Information`, `Microsoft.AspNetCore` log level `Warning`.

### Hosts

`appsettings.json` sets `AllowedHosts` to `*`.

### Launch profiles

`Properties/launchSettings.json`:

| Profile | URLs | Launch path | Environment |
|---|---|---|---|
| `http` | `http://localhost:5037` | `swagger` | Development |
| `https` | `https://localhost:7249` and `http://localhost:5037` | `swagger` | Development |
| IIS Express | IIS Express application `http://localhost:35647`, SSL port `44397` | `swagger` | Development |

`iisSettings` disables Windows authentication and enables anonymous authentication.

No production environment file is committed. How this process is hosted outside a developer machine is **Unknown / Requires Confirmation**.

## Migrations

| Artifact | Role |
|---|---|
| `Migrations/20260101150955_InitialCreate.cs` | `Up` creates table `Tasks`. `Down` drops it |
| `Migrations/20260101150955_InitialCreate.Designer.cs` | EF designer companion |
| `Migrations/AppDbContextModelSnapshot.cs` | Snapshot. Product version annotation `8.0.0` |
| `Migrations/InitialCreate.sql` | Idempotent script. Creates `__EFMigrationsHistory` if missing. Creates `Tasks` and inserts history row `20260101150955_InitialCreate` / product version `8.0.0` when that id is absent |

`Program.cs` does not apply migrations. `cursor.rules.md` forbids `dotnet ef database update` and asks for `dotnet ef migrations script --idempotent`. The checked-in SQL matches that request for the initial migration. Whether the script was produced by that command was not re-run during documentation; the file is on `main` since `310c137`.

## Packages

API project (`TaskLedger.Api.csproj`):

| Package | Version |
|---|---|
| Microsoft.AspNetCore.OpenApi | 8.0.5 |
| Microsoft.EntityFrameworkCore | 8.0.0 |
| Microsoft.EntityFrameworkCore.SqlServer | 8.0.0 |
| Microsoft.EntityFrameworkCore.Tools | 8.0.0 (private assets) |
| Swashbuckle.AspNetCore | 6.4.0 |

Test project (`TaskLedger.Api.Tests.csproj`):

| Package | Version |
|---|---|
| coverlet.collector | 6.0.0 |
| Microsoft.EntityFrameworkCore.InMemory | 8.0.0 |
| Microsoft.NET.Test.Sdk | 17.8.0 |
| Moq | 4.20.72 |
| xunit | 2.5.3 |
| xunit.runner.visualstudio | 2.5.3 |

No `packages.lock.json`. Moq is unused by the test sources. coverlet is referenced and no coverage gate is configured in the repository.

## Authentication and security

- No authentication scheme is registered.
- No authorization policies.
- Anonymous IIS Express authentication in launch settings.
- Database access is trusted connection to LocalDB, not a SQL password.
- `TrustServerCertificate=true` is set on that local connection.
- `AllowedHosts` is `*`.
- HTTPS redirection is enabled. The `http` launch profile serves HTTP only, so redirection depends on the HTTPS port being available.
- Task payloads are not ownership-checked.
- No rate limiting, CORS policy, or security headers middleware is configured.
- `cursor.rules.md` says not to connect this project to a production database. That is an editing rule. The running code will use whatever `DefaultConnection` the configuration supplies.

## Background work, queues, storage, monitoring

None. There is no hosted service, no queue client, no blob or file storage, and no application-insights or other monitoring package.

Logging is the default ASP.NET Core logger configuration above.

## Development and testing

`cursor.rules.md` allows `dotnet new`, `dotnet restore`, `dotnet build`, `dotnet test`, `dotnet ef migrations add`, and `dotnet ef migrations script --idempotent`. It denies `dotnet ef database update`, `sqlcmd DROP`, `rm -rf`, and "any network calls".

Tests are xUnit `[Fact]` methods in `TaskServiceTests`. Each test builds `DbContextOptions` with `UseInMemoryDatabase` and a new GUID database name, then constructs `TaskService` with that context. They do not use SQL Server, do not use Moq, and do not send HTTP.

`UnitTest1.Test1` is an empty fact from the template.

No controller test, migration test, or pipeline test is in the tree. `dotnet test` was not run while writing this document.

Commit style in history is mixed. Some subjects are conventional (`feat:`, `fix:`, `chore:`). Others are sentence-style ("Add .gitignore to ignore build artifacts", "Merged development into main"). `cursor.rules.md` asks for conventional commits, one logical change per commit, feature branches, and pull requests rather than pushing to `main`. The history contains merge commits into `main` (`3b07ed4` by Abdurahmaan Ismail, `0c1c087` by maanie). Whether those used Bitbucket pull requests is **Unknown / Requires Confirmation**.

## Branches

| Remote ref | Commit | Relation to `main` |
|---|---|---|
| `origin/main` | `8ab244e` | Default |
| `origin/development` | `8ab244e` | Same commit |
| `origin/feature/ef-core` | `0c1c087` | Parent side of `main`; no unique files |
| `origin/feature/task-service` | `0c1c087` | Same as `feature/ef-core` |

## Integrations

The only external system named in configuration is SQL Server LocalDB. There is no HTTP client, no message bus, and no third-party SaaS SDK in the project files.

## Known discrepancies

1. Commit `22a5d61` describes `TaskRepository`. No such type was ever committed.
2. Commit `8f0658b` describes `UpdateTaskAsync` and `DeleteTaskAsync` and tests for them. The commit only deletes build outputs. The methods are absent.
3. Discovery files `sonar-project.properties` and `sonarqube_scan.bat` are not in git.
4. `cursor.rules.md` says SQL Server is `localhost`. The current connection string is LocalDB.
5. The weather endpoint and `UnitTest1` are template leftovers that still compile as part of the product.
6. `CreatedAtAction` targets the list action while passing an `id` the list action does not use. There is no `GET /api/Tasks/{id}`.
