# CareFix for developers

A tour for whoever maintains this code. Read `DEPLOYMENT.md` first for how it is installed.

## The one rule

**The AI can read and propose. Only a human-approved, server-validated fix can write, and only through the hospital's guarded procedure.** Every design decision below serves that. If a change you are making weakens it, it needs the Support Head's sign-off, not just a code review.

## Projects

| Project | What lives here |
|---|---|
| `CareFix.Core` | Everything that touches a hospital database: `SqlGuard` (the SELECT-only parser) and `SqlOps` (read, guarded write, schema read). Shared so the server and the on-prem agent enforce identical rules. Plus the agent wire contracts. |
| `CareFix.Api` | The service: AI engine, ticket and fix workflow, knowledge base, auth, integrations, and the built console in `wwwroot`. |
| `CareFix.Agent` | Windows service for on-prem hospitals. Long-polls, runs jobs locally, posts results. |
| `CareFix.Tests` | xUnit over the safety-critical logic. |

## Code map (CareFix.Api)

```
Program.cs            wiring, auth, rate limits, middleware order
Endpoints/            every HTTP route, one line each, no logic
Options/              strongly typed config (CareFix section)
Infrastructure/       ControlDb, AppException + ErrorMiddleware, AuditService, Csv, roles
Security/             CredentialVault (AES-GCM), PiiMasker
Hospitals/            IHospitalExecutor and its three implementations, hospital admin
Knowledge/            data dictionary, rules, playbooks, schema snapshot, CSV import
Tickets/              ticket lifecycle, detail assembly, reports
Fixes/                FixService (propose, approve, execute, verify, rollback), ApprovalRules
Ai/                   ClaudeClient, tool definitions, orchestrator, queue, usage metering
Learning/             ticket history -> draft playbooks
Integration/          helpdesk inbound API, signed outbound webhook
```

Services are singletons; they open a short-lived `SqlConnection` per operation rather than holding one. Nothing is scoped per request.

## The two flows worth understanding

**Diagnosis.** A message arrives → `TicketService` masks it and pushes an `AiJob` → `AiWorker` (four parallel loops) picks it up → `AiOrchestrator` takes a per-ticket lock, loads the stored transcript, builds the system prompt from `KnowledgeBase.ContextForTicketAsync`, and runs the Claude tool loop. Tool results and assistant text are persisted after each cycle, so a crash resumes cleanly. `ask_engineer` parks the run with a pending tool-use id; the engineer's next message becomes that tool's result.

**A fix.** `propose_fix` → `FixService.ProposeAsync` re-reads every current value itself (it never trusts the AI's idea of the old value), rejects key columns, non-editable columns and no-ops, runs the lock-rule queries, works out the risk tier → rows in `CF_FIX`/`CF_FIX_STEP`. Approval rules are in `ApprovalRules`. Execution re-checks every old value, snapshots whole rows, then calls `carefix.usp_CF_UpdateRow` once per step inside one transaction. The procedure itself re-checks the allow-list and the expected old value, so a bug on the server still cannot write a column the hospital did not permit.

## Invariants to preserve

1. No SQL string from the AI reaches a database without `SqlGuard.ValidateSelect` and the read-only login.
2. Writes go only through `SqlOps.ApplyStepsAsync`. Do not add another write path.
3. Old values come from the database, never from AI output.
4. The person who raised a ticket can never approve its fix; Medium risk needs two different people.
5. Results are masked before they are stored or sent to the AI.
6. `CF_AUDIT` is append-only, enforced by a trigger. Keep it that way.
7. Agent job results are wiped from `CF_AGENT_JOB` as soon as they are read; they can hold unmasked data.

## How to extend

**Add an AI tool.** Define it in `AiTools.Definitions`, handle it in `AiOrchestrator.ExecuteToolAsync`, return a string (JSON is fine). Throw `AppException` for anything the AI did wrong; the message goes back as a tool error and it will retry sensibly. Keep the description short and concrete.

**Add an agent job kind.** Add the constant and payload record to `AgentProtocol`, a case in `JobRunner.RunAsync`, and a method on `AgentHospitalExecutor`. The agent must validate the payload itself.

**Add a risk rule.** `RiskRules` for column patterns, `CF_KB_RULE` rows for anything hospital-specific, including lock checks. Prefer data over code.

**Change what the AI knows.** Almost always the data dictionary, not the prompt. `AiTools.SystemPrompt` is deliberately short; per-hospital knowledge belongs in `CF_KB_*`.

**Add a screen.** Endpoint in `ApiEndpoints`, a page in `frontend/src/pages`, a route and nav entry in `App.tsx`. `npm run build` writes straight into `wwwroot`.

## Gotchas

- This code has never been compiled: no .NET SDK was available where it was written. Expect a handful of small fixes on the first `dotnet build`, most likely in package versions (`.csproj` uses floating versions), Dapper mappings and nullability warnings.
- `carefix.fn_CF_CanonExpr` decides the text form of a value. Reads and the "value unchanged" check must use the same expression, or every execution will fail with "value no longer matches". Change it in one place only.
- Legacy HIS tables often have no primary key. The guarded procedure therefore requires the key to match exactly one row, whatever column is used.
- `CF_HOSPITAL_SCHEMA` is a snapshot. After a HIS upgrade at a hospital, capture it again or the AI will not see new tables.
- The transcript in `CF_AI_TRANSCRIPT` grows with the conversation. Long tickets cost more; the per-ticket cap exists for this.
- Minimal APIs bind arrays from the query string by default; that is why `[FromBody]` appears on the user-hospitals endpoint.

## Before you ship a change

`dotnet test` must pass. If you touched the guard, masking, approval rules or the update procedure, add a test for the case you changed and walk the demo hospital (`DEMO.md`) end to end.
