devfactory-lib-platform
Código compartilhado (pacote único) referenciado por todos os serviços da plataforma Dev Factory,
Endpoints HTTP 8
| Método | Rota | Arquivo |
|---|---|---|
| GET | /{**path} | DataPlaneEndpoints.cs |
| POST | /{**path} | DataPlaneEndpoints.cs |
| PUT | /{**path} | DataPlaneEndpoints.cs |
| PATCH | /{**path} | DataPlaneEndpoints.cs |
| DELETE | /{**path} | DataPlaneEndpoints.cs |
| GET | /relatorios | AppAuth.cs |
| POST | /admin/... | AppAuth.cs |
| POST | /aprovar | AppAuth.cs |
Estrutura
README
lib-platform — DevFactory.Platform
Código compartilhado (pacote único) referenciado por todos os serviços da plataforma Dev Factory, para manter uma só implementação de tenancy + governança e evitar reescrever (e errar) o isolamento por serviço. Montado reaproveitando o código que já está bom hoje (core-control Platform/* + services-ai Auth/*), não reimplementando.
Faz parte da migração para serviços standalone com chamada direta (apps chamam os serviços direto;
core-control— eraapis-aws— vira control-plane). Plano e contexto em
~/.claude/plans/devfactory-coordination-docs-limp-partitioned-koala.md.
Por que existe
No modelo de chamada direta, cada serviço (services-documents, services-files, …) precisa sozinho de auth, isolamento por tenant, RBAC, rate-limit e metering — coisas que hoje só existem dentro do data-plane do apis-aws. Esta lib carrega isso uma vez só. O contrato (headers/claims) é igual ao que o services-ai já usa:
- App envia
x-project-key: pk_live_...(ouAuthorization: Bearer pk_...) +x-project-env: {env}. - Usuário do console envia
Authorization: Bearer <Entra JWT>. - O serviço valida a credencial e deriva o
instanceIda partir do projeto autenticado, nunca do path.
Invariante de segurança central — ProjectMatchGuard
O instanceId = {project}-{env}-{service} é a única chave de isolamento (Mongo db, prefixo S3, pk DynamoDB, fila SQS, secret, db SQL df_{project}_{env}). Hoje o projectId vem do path — então, na chamada direta, a key do projeto A conseguiria endereçar dados do projeto B.
ProjectMatchGuard fecha isso: se o {projectId} do path não for o da credencial validada → 403. E TenantScope.TryResolve constrói o instanceId sempre a partir do ProjectId da credencial, nunca do path. Este guard é código novo (o services-ai não tem, pois não recebe projectId no path) e é coberto por testes unitários — é o item nº1 da revisão de segurança.
Conteúdo (incremento 1)
| Arquivo | Origem | O quê |
|---|---|---|
Tenancy/Contracts.cs | lift de apis-aws Platform/Contracts.cs | TenantContext, IResourceProvider, ListQuery, Page<T>, Operation |
Tenancy/InstanceResolver.cs | lift de apis-aws Platform/InstanceResolver.cs | deriva o instanceId (formato preservado byte-a-byte) |
Tenancy/ValidatedCredential.cs | novo | a credencial já validada (espelha KeyValidation + allowedEnvironments/allowedServices) |
Tenancy/ProjectMatchGuard.cs | novo | o guard + TenantScope.TryResolve (isolamento) |
tests/…/InstanceResolverGoldenTests.cs | novo | trava o formato do instanceId (compat. com dados existentes) |
tests/…/ProjectMatchGuardTests.cs | novo | key de A no path de B → negado; instanceId vem do projeto autenticado |
Conteúdo (incremento 2 — governança de auth/tenancy)
| Arquivo | Origem | O quê |
|---|---|---|
Auth/KeyValidation.cs | lift+estendido de services-ai | resposta do validate + ponte ToCredential() (null allow-list = todos) |
Auth/ProjectKeyValidator.cs | lift de services-ai | valida a project key no core-control (cache 15s, fail-closed); aceita CORE_CONTROL_URL/APIS_AWS_URL |
Auth/UsageReporter.cs | lift de services-ai | metering fire-and-forget p/ core-control /internal/usage (genérico: requests+bytes) |
Tenancy/RouteScope.cs | novo | extrai projectId/env do path (puro, testável) |
Auth/PlatformGovernance.cs | novo | AddPlatformGovernance() + UsePlatformGovernance() (auth → ProjectMatchGuard → TenantContext → metering) + AssertProductionReady() fail-closed + HttpContext.GetTenant() |
Uso num serviço (Program.cs):
builder.Services.AddPlatformGovernance(o => o.ServiceKey = "documents");
PlatformGovernance.AssertProductionReady(); // fail-closed em Production
...
app.UsePlatformGovernance(); // antes do mapeamento das rotas de dados
// dentro do provider/endpoint: var tenant = http.GetTenant(); // já isolado por projeto autenticado
Conteúdo (incremento 3 — controller genérico + envelope)
| Arquivo | Origem | O quê |
|---|---|---|
ApiResults.cs | lift de apis-aws (sem AWS) | envelopes {data} / {data,pagination} + problem+json (RFC 9457) |
DataPlaneEndpoints.cs | lift+adaptado de apis-aws | MapDataPlane(): CRUD + custom verbs (:verb) p/ um provider, contexto via GetTenant(); mapper de exceção opcional (IDataPlaneExceptionMapper) mantém a lib cloud-agnostic |
Tenancy/DataPlaneRoute.cs | novo (puro) | parse de collection[/id][:verb] + verbos de leitura |
Um serviço completo agora é ~30 linhas: AddPlatformGovernance + registrar 1 IResourceProvider + UsePlatformGovernance() + MapDataPlane().
A liftar nos próximos incrementos
RBAC (Rbac/RbacClient → core-auth) no caminho de JWT do console, validação de JWT Entra (EntraIdentity), rate-limit (TenantRateLimiting/RedisRateLimiting), e o AddPlatform()/UsePlatform() único que compõe governança+dataplane numa chamada. (Project-key + tenancy + metering + CRUD já estão prontos.)
Build / test (no seu lado — a máquina de dev não tem dotnet no PATH)
dotnet test tests/DevFactory.Platform.Tests/DevFactory.Platform.Tests.csproj