Skip to content

feat(parsing): detectar layout automaticamente por documento - #222

Merged
elson-vinicius-lopes merged 4 commits into
developfrom
codex/feat-layout-auto-detection
Aug 30, 2026
Merged

feat(parsing): detectar layout automaticamente por documento#222
elson-vinicius-lopes merged 4 commits into
developfrom
codex/feat-layout-auto-detection

Conversation

@elson-vinicius-lopes

Copy link
Copy Markdown
Collaborator

Resultado

Implementa POST /api/parse/auto para detecção determinística de layouts MQSeries/IDoc:

  • unique somente com exatamente um candidato e catálogo completo;
  • ambiguous com top 5 explicável, sem escolha implícita;
  • not_found com sugestões separadas e falha fechada para catálogo parcial;
  • layoutGuidOverride aceito apenas entre candidatos recalculados/rankeados;
  • XML descriptografado permanece interno à API;
  • auditoria com correlation ID, versão do algoritmo e catálogo;
  • normalização/particionamento do documento reutilizados por requisição;
  • nome físico de amostra de aprendizado gerado pelo servidor.

Evidências

  • Build Release aprovado.
  • SecurityCodeScan sem nova origem em /api/parse/auto; achados legados permaneceram nas linhas históricas.
  • 432/432 testes da API.
  • 12/12 testes específicos de detecção automática.
  • MQSeries real: ambiguous, top 5, sem parse antes da escolha, 59 linhas/705 campos após override.
  • IDoc real: unique Marelli, 55 linhas/263 campos e correlation ID preservado.

Contrato consumidor

O PR coordenado do LayoutParserReact consumirá este endpoint após o deploy em development.
A tool MCP tipada detect_layout permanece rastreada separadamente em #216.

Closes #213
Closes #214
Closes #215

@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@elson-vinicius-lopes
elson-vinicius-lopes merged commit 565d8f5 into develop Aug 30, 2026
3 checks passed
@elson-vinicius-lopes
elson-vinicius-lopes deleted the codex/feat-layout-auto-detection branch August 30, 2026 16:56
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* Develop (#237)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 MappingDraft human-in-the-loop (issue #230)

- Modelo MappingDraft/MappingDraftRule/MappingDraftRuleDecision + SqlMappingDraftStore
  (ADO.NET cru, ROWVERSION nativo do SQL Server para ETag)
- IMappingSuggestionService/MappingSuggestionService: job fire-and-forget via
  IServiceScopeFactory, idempotente, cancelavel, observavel; prompt novo ao Ollama
  (nao reutiliza RepairOrchestrator); forca needs_input sem evidencia/confianca suficiente
- MappingEngineGuardFilter: recusa centralizada de engine=sysmiddle (422), reutilizavel
  pelos Slices 4/5
- MappingDraftsController: 5 rotas (create draft, get draft, create/get/cancel
  suggestion job, patch rule com If-Match obrigatorio -> 428/412)
- Testes: MappingEngineGuardFilterTests (5) + MappingDraftsControllerTests (7)

Build verde, 481/481 testes passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(filters): MappingEngineGuardFilter checa query E body, nao so o primeiro

Achado da revisao da Quinn: query engine=xslt fazia return imediato antes de
inspecionar o body, permitindo engine=sysmiddle no body passar sem recusa.
Nao explorado no Slice 3 (controller tem allowlist propria), mas real para
Slices futuros que reusem so o filtro. Fix: recusa se sysmiddle aparecer em
query OU body. XML doc do filtro agora deixa explicito que e defesa em
profundidade, nao substitui allowlist no controller.

Teste novo cobrindo o caso exato (query engine=xslt + body sysmiddle -> 422).
Suite: 482 verdes (481+1).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 - MappingDraft human-in-the-loop (#238)

* Develop (#237)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 MappingDraft human-in-the-loop (issue #230)

- Modelo MappingDraft/MappingDraftRule/MappingDraftRuleDecision + SqlMappingDraftStore
  (ADO.NET cru, ROWVERSION nativo do SQL Server para ETag)
- IMappingSuggestionService/MappingSuggestionService: job fire-and-forget via
  IServiceScopeFactory, idempotente, cancelavel, observavel; prompt novo ao Ollama
  (nao reutiliza RepairOrchestrator); forca needs_input sem evidencia/confianca suficiente
- MappingEngineGuardFilter: recusa centralizada de engine=sysmiddle (422), reutilizavel
  pelos Slices 4/5
- MappingDraftsController: 5 rotas (create draft, get draft, create/get/cancel
  suggestion job, patch rule com If-Match obrigatorio -> 428/412)
- Testes: MappingEngineGuardFilterTests (5) + MappingDraftsControllerTests (7)

Build verde, 481/481 testes passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(filters): MappingEngineGuardFilter checa query E body, nao so o primeiro

Achado da revisao da Quinn: query engine=xslt fazia return imediato antes de
inspecionar o body, permitindo engine=sysmiddle no body passar sem recusa.
Nao explorado no Slice 3 (controller tem allowlist propria), mas real para
Slices futuros que reusem so o filtro. Fix: recusa se sysmiddle aparecer em
query OU body. XML doc do filtro agora deixa explicito que e defesa em
profundidade, nao substitui allowlist no controller.

Teste novo cobrindo o caso exato (query engine=xslt + body sysmiddle -> 422).
Suite: 482 verdes (481+1).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* Develop (#239)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 - MappingDraft human-in-the-loop (#238)

* Develop (#237)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 MappingDraft human-in-the-loop (issue #230)

- Modelo MappingDraft/MappingDraftRule/MappingDraftRuleDecision + SqlMappingDraftStore
  (ADO.NET cru, ROWVERSION nativo do SQL Server para ETag)
- IMappingSuggestionService/MappingSuggestionService: job fire-and-forget via
  IServiceScopeFactory, idempotente, cancelavel, observavel; prompt novo ao Ollama
  (nao reutiliza RepairOrchestrator); forca needs_input sem evidencia/confianca suficiente
- MappingEngineGuardFilter: recusa centralizada de engine=sysmiddle (422), reutilizavel
  pelos Slices 4/5
- MappingDraftsController: 5 rotas (create draft, get draft, create/get/cancel
  suggestion job, patch rule com If-Match obrigatorio -> 428/412)
- Testes: MappingEngineGuardFilterTests (5) + MappingDraftsControllerTests (7)

Build verde, 481/481 testes passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(filters): MappingEngineGuardFilter checa query E body, nao so o primeiro

Achado da revisao da Quinn: query engine=xslt fazia return imediato antes de
inspecionar o body, permitindo engine=sysmiddle no body passar sem recusa.
Nao explorado no Slice 3 (controller tem allowlist propria), mas real para
Slices futuros que reusem so o filtro. Fix: recusa se sysmiddle aparecer em
query OU body. XML doc do filtro agora deixa explicito que e defesa em
profundidade, nao substitui allowlist no controller.

Teste novo cobrindo o caso exato (query engine=xslt + body sysmiddle -> 422).
Suite: 482 verdes (481+1).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 4 - MappingExplanation (issues #226/#227)

Contrato canonico independente de motor + 3 adapters deterministicos
(sem LLM) + endpoint de leitura:

- Models/Dtos/Fiscal/MappingExplanation.cs: MappingExplanation/ExplainedRule/
  EngineCapabilities/EvidenceRef/SchemaRef + MappingExplanationSupportLevel
  (authoritative|best_effort|opaque|unsupported).
- SysmiddleExplanationAdapter: reaproveita XslSynth.Core.DslStructuredParser
  (Camada 0 do RAG) sobre MapperVo/RealMapperParser reais. Catalogo fechado
  de 4 funcoes conhecidas -> resto vira opaque. Capabilities.Author sempre
  false, hard-coded (garantia central do produto).
- TclExplanationAdapter: traducao quase 1:1 de MappingDraftRule (Slice 3)
  para ExplainedRule; SupportLevel derivado do Status humano.
- XsltExplanationAdapter: parser real de arvore XSLT (System.Xml.Linq) para
  value-of/for-each/if/choose/when/variable, testavel isoladamente; como
  nao ha MappingRelease compilado ainda (Slice 5), ExplainAsync sempre
  retorna unsupported com limitations, nunca inventa.
- MappingExplanationController: GET .../mappings/{mappingId}/versions/{version}/explanation,
  resolve engine tentando draftId primeiro, senao MapperGuid Sysmiddle;
  MappingEngineGuardFilter deliberadamente NAO aplicado (rota de leitura,
  explain Sysmiddle e o caso permitido).
- DI: 3 adapters via IEnumerable<IMappingExplanationAdapter> + factory por Engine.
- 10 testes novos cobrindo os 5 obrigatorios do prompt + 3 extras.

dotnet build: 0 erros. dotnet test: 551 testes, 0 falhas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
elson-vinicius-lopes added a commit that referenced this pull request Aug 31, 2026
* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 - MappingDraft human-in-the-loop (#238)

* Develop (#237)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 MappingDraft human-in-the-loop (issue #230)

- Modelo MappingDraft/MappingDraftRule/MappingDraftRuleDecision + SqlMappingDraftStore
  (ADO.NET cru, ROWVERSION nativo do SQL Server para ETag)
- IMappingSuggestionService/MappingSuggestionService: job fire-and-forget via
  IServiceScopeFactory, idempotente, cancelavel, observavel; prompt novo ao Ollama
  (nao reutiliza RepairOrchestrator); forca needs_input sem evidencia/confianca suficiente
- MappingEngineGuardFilter: recusa centralizada de engine=sysmiddle (422), reutilizavel
  pelos Slices 4/5
- MappingDraftsController: 5 rotas (create draft, get draft, create/get/cancel
  suggestion job, patch rule com If-Match obrigatorio -> 428/412)
- Testes: MappingEngineGuardFilterTests (5) + MappingDraftsControllerTests (7)

Build verde, 481/481 testes passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(filters): MappingEngineGuardFilter checa query E body, nao so o primeiro

Achado da revisao da Quinn: query engine=xslt fazia return imediato antes de
inspecionar o body, permitindo engine=sysmiddle no body passar sem recusa.
Nao explorado no Slice 3 (controller tem allowlist propria), mas real para
Slices futuros que reusem so o filtro. Fix: recusa se sysmiddle aparecer em
query OU body. XML doc do filtro agora deixa explicito que e defesa em
profundidade, nao substitui allowlist no controller.

Teste novo cobrindo o caso exato (query engine=xslt + body sysmiddle -> 422).
Suite: 482 verdes (481+1).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 4 - MappingExplanation (issues #226/#227) (#240)

* Develop (#239)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 - MappingDraft human-in-the-loop (#238)

* Develop (#237)

* feat(parsing): detectar layout automaticamente por documento (#222)

* feat(parsing): add automatic layout detection contract

* feat(parsing): harden automatic layout detection

* fix(security): preserve scanner baseline positions

* chore(actions): Bump dawidd6/action-send-mail (#224)

Bumps the github-actions group with 1 update: [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail).


Updates `dawidd6/action-send-mail` from 3.12.0 to 18
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](dawidd6/action-send-mail@2cea961...94de994)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-version: '18'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump xunit.runner.visualstudio from 3.1.5 to 4.0.0 (#223)

---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(identity): implementa Slice 1 - ExternalIdentity/UserId/FiscalWorkspace com isolamento cross-workspace (#234)

- Modelo de dominio: FiscalUser, ExternalIdentity, FiscalWorkspace, WorkspaceMembership
- SqlIdentityWorkspaceStore: persistencia idempotente (UNIQUE constraint + retry em corrida)
- IdentityWorkspaceService: trava em processo + politica fail-closed
- TrustedIdentityMiddleware estendido (nao substituido) para os headers
  x-layoutparser-identity-{provider,subject,tenant}, mesma guarda de loopback
- WorkspacesController: GET /api/workspaces/me e GET /api/workspaces/{workspaceId}
  (404 uniforme para nao-existe/nao-e-seu, 401 explicito no /me sem identidade)
- Testes de isolamento cross-workspace, idempotencia sob concorrencia e
  subject-nunca-logado (issues #225/#228)

dotnet build e dotnet test (437/437) verdes.

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (#229) (#236)

* feat(fiscal): implementa Slice 2 - FiscalMappingPackage (issue #229)

Modelo FiscalProject/FiscalMappingPackage/Revision/PackageArtifact, upload multipart
com validacao fail-closed (MIME real via magic bytes, defesa XXE, guarda de zip bomb
em XLSX, limite de 50MB/artefato), storage filesystem+SQL seguindo o padrao ja usado
pelo projeto, antivirus assincrono via Windows Defender (fire-and-forget, degrada se
indisponivel), upload idempotente por hash/header, isolamento por workspace
fail-closed (mesmo padrao do Slice 1). Só os 2 endpoints do escopo: POST
.../mapping-packages e GET .../mapping-packages/{packageId}.

19 testes novos cobrindo MIME spoofing, XXE, zip bomb, idempotencia e ausencia de
conteudo bruto em log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(fiscal): trata violacao de UNIQUE de IdempotencyKey como corrida, nao erro

Sob 2 uploads concorrentes com a mesma IdempotencyKey, o segundo INSERT
batia no UNIQUE (WorkspaceId, ProjectId, IdempotencyKey) e propagava
SqlException (2601/2627), virando 503 pro cliente perdedor da corrida.
Fix reaproveita o padrao ja usado em EnsureProjectExistsAsync: captura a
violacao especifica e devolve o pacote ja criado pelo vencedor.

Teste de corrida real com 2 Tasks concorrentes confirma que ambos
convergem pro mesmo PackageId sem erro. 469 testes, build verde.

Bug encontrado por @lp-qa (Quinn) na revisao do Slice 2.

* chore(security): baseline SCS0016 em FiscalMappingPackagesController.CreatePackage

Mesmo padrao ja aceito em ParseController.Upload (SCS0016:84): endpoint POST
multipart sem antiforgery token, mas a API nao usa cookie de sessao - o vetor
classico de CSRF nao se aplica da mesma forma. Identidade vem do BFF via
TrustedIdentityMiddleware, que so confia nos headers x-iis-user/x-iis-roles
quando a origem e loopback. Nao ha exposicao adicional real (mesmos limites
de tamanho/quantidade de artefato do Upload ja aceito, sem execucao de arquivo).

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 3 MappingDraft human-in-the-loop (issue #230)

- Modelo MappingDraft/MappingDraftRule/MappingDraftRuleDecision + SqlMappingDraftStore
  (ADO.NET cru, ROWVERSION nativo do SQL Server para ETag)
- IMappingSuggestionService/MappingSuggestionService: job fire-and-forget via
  IServiceScopeFactory, idempotente, cancelavel, observavel; prompt novo ao Ollama
  (nao reutiliza RepairOrchestrator); forca needs_input sem evidencia/confianca suficiente
- MappingEngineGuardFilter: recusa centralizada de engine=sysmiddle (422), reutilizavel
  pelos Slices 4/5
- MappingDraftsController: 5 rotas (create draft, get draft, create/get/cancel
  suggestion job, patch rule com If-Match obrigatorio -> 428/412)
- Testes: MappingEngineGuardFilterTests (5) + MappingDraftsControllerTests (7)

Build verde, 481/481 testes passando.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(filters): MappingEngineGuardFilter checa query E body, nao so o primeiro

Achado da revisao da Quinn: query engine=xslt fazia return imediato antes de
inspecionar o body, permitindo engine=sysmiddle no body passar sem recusa.
Nao explorado no Slice 3 (controller tem allowlist propria), mas real para
Slices futuros que reusem so o filtro. Fix: recusa se sysmiddle aparecer em
query OU body. XML doc do filtro agora deixa explicito que e defesa em
profundidade, nao substitui allowlist no controller.

Teste novo cobrindo o caso exato (query engine=xslt + body sysmiddle -> 422).
Suite: 482 verdes (481+1).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(fiscal): Slice 4 - MappingExplanation (issues #226/#227)

Contrato canonico independente de motor + 3 adapters deterministicos
(sem LLM) + endpoint de leitura:

- Models/Dtos/Fiscal/MappingExplanation.cs: MappingExplanation/ExplainedRule/
  EngineCapabilities/EvidenceRef/SchemaRef + MappingExplanationSupportLevel
  (authoritative|best_effort|opaque|unsupported).
- SysmiddleExplanationAdapter: reaproveita XslSynth.Core.DslStructuredParser
  (Camada 0 do RAG) sobre MapperVo/RealMapperParser reais. Catalogo fechado
  de 4 funcoes conhecidas -> resto vira opaque. Capabilities.Author sempre
  false, hard-coded (garantia central do produto).
- TclExplanationAdapter: traducao quase 1:1 de MappingDraftRule (Slice 3)
  para ExplainedRule; SupportLevel derivado do Status humano.
- XsltExplanationAdapter: parser real de arvore XSLT (System.Xml.Linq) para
  value-of/for-each/if/choose/when/variable, testavel isoladamente; como
  nao ha MappingRelease compilado ainda (Slice 5), ExplainAsync sempre
  retorna unsupported com limitations, nunca inventa.
- MappingExplanationController: GET .../mappings/{mappingId}/versions/{version}/explanation,
  resolve engine tentando draftId primeiro, senao MapperGuid Sysmiddle;
  MappingEngineGuardFilter deliberadamente NAO aplicado (rota de leitura,
  explain Sysmiddle e o caso permitido).
- DI: 3 adapters via IEnumerable<IMappingExplanationAdapter> + factory por Engine.
- 10 testes novos cobrindo os 5 obrigatorios do prompt + 3 extras.

dotnet build: 0 erros. dotnet test: 551 testes, 0 falhas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant