Skip to content

Commit 97b5b6e

Browse files
docs(arch): desenha integracao in-process do RepairOrchestrator (XslSynth.Core) no runtime da API
1 parent dcc7e96 commit 97b5b6e

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Design: integrar RepairOrchestrator (ai/XslSynth.Core) ao runtime da API
2+
3+
Data: 2026-08-21 · Autor: @lp-architect (Aria) · Para: @lp-parser-llm (Lia)
4+
5+
## 1. O boundary Linux não é real
6+
7+
`ai\**` está excluído do build da API via `DefaultItemExcludes` em `LayoutParserApi.csproj`
8+
(linha 12-14), mas o comentário no próprio `.csproj` diz o motivo: **"não colidir no build
9+
(Program.cs duplicado + tipos de outros pacotes)"** — organização de projeto, não portabilidade.
10+
11+
Confirmado lendo os dois `.csproj` do lado gerador:
12+
- `XslSynth.Core.csproj`: "100% .NET puro — sem dependência de cripto Sysmiddle nem de Windows."
13+
- `XslSynth.Contracts.csproj`: "Zero dependência de HTTP/Ollama/cripto Sysmiddle."
14+
15+
`RepairOrchestrator`, `OllamaXslSynthesizer` e `OllamaClient` (`ai/XslSynth.Core/Core/` e
16+
`Synthesis/`) não chamam `xsltproc`/`libxml2` nem qualquer binário externo via shell — a geração
17+
de XSLT roda em .NET puro (validação XSD via `System.Xml`, diff canônico próprio) e a chamada ao
18+
Ollama é HTTP (`OllamaClient`), igual ao que a API já faz noutros lugares. O único I/O é
19+
`File.ReadAllBytes`/paths, portável. **Não existe boundary técnico Linux/WSL** — é rótulo
20+
herdado do CLI standalone (`ai/XslSynth/Program.cs`), que é só o *host* console, não o motor.
21+
22+
Isso invalida as Opções A (`wsl.exe`) e C (microsserviço remoto) como necessárias — ambas
23+
adicionariam latência de rede/processo e uma nova dependência externa "que pode cair" sem
24+
nenhum ganho técnico. O próprio design doc `design-xslsynth-runtime-e-reversibilidade-2026-08-16.md`
25+
§1 já antecipou isso ao extrair `XslSynth.Contracts` justamente para ser referenciado por
26+
`Services/` da API — o precedente de arquitetura já aponta pra dentro do processo.
27+
28+
## 2. Opção recomendada: **B — referenciar `XslSynth.Core` in-process, dentro da API**
29+
30+
Igual ao padrão já usado por `XslSynth.Contracts` (referenciado por `ai/XslSynth.Core` **e**
31+
puxável por `Services/`), a API deve referenciar `ai/XslSynth.Core/XslSynth.Core.csproj`
32+
diretamente via `<ProjectReference>` — assim como o CLI standalone já faz. Não é preciso portar
33+
nada: o código já é .NET 10 puro compatível com o `TargetFramework` da API.
34+
35+
**Trade-offs vs. as descartadas:**
36+
37+
| | B (in-process, recomendada) | A (WSL) | C (microsserviço remoto) |
38+
|---|---|---|---|
39+
| Nova dependência externa | Nenhuma | WSL2 no host Windows Server 2022 (não confirmado habilitado em produção) | Nova VM/serviço HTTP interno a manter |
40+
| Latência | Zero (in-process) | Overhead de processo cruzando boundary WSL | Round-trip de rede |
41+
| Resiliência | Falha = exceção .NET capturável no próprio try/catch do fire-and-forget | Novo ponto de falha (processo `wsl.exe` pode travar/não existir) | Novo ponto de falha (serviço fora do ar) — mas o projeto já tolera isso pro Ollama |
42+
| Esforço | Baixo — 1 `ProjectReference` + wiring de DI | Médio-alto — infra + wrapper de processo | Alto — novo deploy, novo serviço a operar |
43+
| Justificativa técnica real | Nenhuma dependência Linux existe | Nenhuma (dependência não existe) | Nenhuma (dependência não existe) |
44+
45+
Não há razão técnica pra pagar o custo de A ou C quando a causa raiz do isolamento é só
46+
organização de arquivos de build.
47+
48+
## 3. Plano de execução em fases (para @lp-parser-llm)
49+
50+
### Fase 0 — Ajuste de build (pré-requisito)
51+
- `LayoutParserApi.csproj`: adicionar `<ProjectReference Include="ai\XslSynth.Core\XslSynth.Core.csproj" />`
52+
explícita — hoje o `.csproj` **já tem** essa referência na linha 63 (verificado nesta sessão:
53+
`<ProjectReference Include="ai\XslSynth.Core\XslSynth.Core.csproj" />` existe desde a extração
54+
do `CanonicalDiffer`). Confirmar que o `DefaultItemExcludes` (`ai\**`) não conflita com a
55+
`ProjectReference` explícita — teste com `dotnet build` limpo; se colidir (glob duplicado do
56+
`Program.cs` do CLI sendo incluído via referência transitiva), isolar só os arquivos de
57+
`Program.cs`/CLI-only do XslSynth.Core em `<Compile Remove>` no `.csproj` dele, não na API.
58+
59+
### Fase 1 — Novo serviço de domínio na API
60+
- Criar `Services/Transformation/Ai/RepairOrchestratorXslSynthesizerService.cs` (nome sugestivo,
61+
ajustar ao padrão do time), implementando uma nova interface `IXslSynthesizerService` com um
62+
único método assíncrono:
63+
```csharp
64+
Task<XslSynthesisResult> SynthesizeAsync(
65+
MapperVo mapper, // já resolvido via XslSynth.Contracts
66+
string inputContent,
67+
string? groundTruthXml,
68+
int maxIterations,
69+
CancellationToken ct);
70+
```
71+
Internamente, instancia `RepairOrchestrator` + `OllamaXslSynthesizer` (via `OllamaClient`
72+
apontando pra config `Ollama:Url` já existente na API) e delega o loop gerar→validar→corrigir.
73+
- `XslSynthesisResult` contém: `string GeneratedXslt`, `bool Converged`, `int IterationsUsed`,
74+
`IReadOnlyList<string> ValidationErrors` (mesmo vocabulário que `AiCandidateDiagnostics` já usa,
75+
pra não duplicar contrato).
76+
77+
### Fase 2 — DI em `Program.cs`
78+
- Registrar no grupo **Transformation** (já existente), `Scoped`:
79+
```csharp
80+
builder.Services.AddScoped<IXslSynthesizerService, RepairOrchestratorXslSynthesizerService>();
81+
```
82+
- `OllamaClient` já é HTTP — reaproveitar o `HttpClient` nomeado que a API já registra pro Ollama
83+
(checar se existe um `IHttpClientFactory` client "ollama" já configurado; se não, criar um,
84+
seguindo o padrão de resiliência do Redis opcional — timeout curto, sem exceção não capturada
85+
subindo até o request principal).
86+
87+
### Fase 3 — Plugar no ponto de disparo existente
88+
- **Não é pathway adicional** — é a implementação real por trás de
89+
`IAiTransformationCandidateService.EnqueueAsync` (`Services/Transformation/Ai/
90+
AiTransformationCandidateService.cs`), que hoje gera XML direto via Ollama. Trocar o motor
91+
interno desse serviço para chamar `IXslSynthesizerService.SynthesizeAsync` em vez do caminho
92+
atual — o contrato externo (`ticket`, `GetStatusAsync`, `AiCandidateStatus`) não muda, só o
93+
que acontece dentro do loop.
94+
- Isso preserva o disparo automático já existente em `execute-candidates` (sem gabarito) e no
95+
fallback de IA (`design-fallback-ia-automatico-2026-08-16.md`) — nenhum novo endpoint/trigger
96+
necessário.
97+
98+
### Fase 4 — Persistência do XSLT gerado (para o pathway `tcl-xsl` achar)
99+
- Confirmar com `@lp-backend-dev` onde `TransformationPipelineService.cs` resolve `.tcl`/XSLT
100+
hoje (catálogo em disco vs. banco) e gravar o `GeneratedXslt` resultante no **mesmo local e
101+
convenção de nome** que os XSLT "reais" pré-existentes usam — provavelmente por
102+
`layoutGuid`/`mapperGuid`, mesmo padrão do catálogo GUID→XPath já usado pelo `XslSynth.Contracts`.
103+
Não inventar um segundo local de armazenamento — isso recria a duplicação de pathway já
104+
registrada em memória (`transformation-pathway-duplication.md`).
105+
106+
### Onde o TCL entra depois (não desenhar agora)
107+
Quando XSLT/XML estiverem resolvidos, o mesmo `RepairOrchestrator`/loop de correção é o
108+
candidato natural pra gerar TCL também — `ai/XslSynth.Core` já separa Synthesis de Model, então
109+
adicionar um segundo `ISynthesizer` (TCL) reaproveitando o mesmo orquestrador de repair é o
110+
encaixe esperado. Não requer redesenho do boundary — só um novo `IXslSynthesizerService`
111+
overload ou implementação irmã.
112+
113+
## 4. Riscos explícitos da opção escolhida
114+
- **Acoplamento de assembly**: a API passa a carregar `System.Reflection.MetadataLoadContext`
115+
(via `XslSynth.Contracts`) e todo o código de síntese em processo — aumenta a superfície de
116+
memória/GC do processo principal. Mitigação: é `Scoped`, não `Singleton`; e o loop já é
117+
fire-and-forget/background, não bloqueia request síncrono.
118+
- **Falha do Ollama dentro do loop**: já coberto pelo padrão de resiliência existente
119+
(`AiCandidateStatus` vira "failed" consultável, nunca derruba o request — mesmo comportamento
120+
que `AiTransformationCandidateService` já tem hoje).
121+
- **Build glob**: risco técnico real é só a Fase 0 (colisão de `DefaultItemExcludes` com
122+
`ProjectReference` explícita) — validar com `dotnet build` limpo antes de prosseguir.

0 commit comments

Comments
 (0)