🧩 feat(shared): definir contratos e deploy entre product e admin
parent
17583236a6
commit
1541948e76
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=AI Orquestrador Admin Runtime
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=vitor
|
||||
Group=vitor
|
||||
WorkingDirectory=/opt/orquestrador
|
||||
EnvironmentFile=/opt/orquestrador/.env.admin
|
||||
Environment=PATH=/opt/orquestrador/venv/bin
|
||||
ExecStart=/opt/orquestrador/venv/bin/python -m uvicorn admin_app.main:app --host 127.0.0.1 --port 8081
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=AI Orquestrador Product Runtime
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=vitor
|
||||
Group=vitor
|
||||
WorkingDirectory=/opt/orquestrador
|
||||
EnvironmentFile=/opt/orquestrador/.env.product
|
||||
Environment=PATH=/opt/orquestrador/venv/bin
|
||||
ExecStart=/opt/orquestrador/venv/bin/python -m app.integrations.telegram_satellite_service
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -0,0 +1,128 @@
|
||||
# Estrategia De Deploy Independente Para Product E Admin
|
||||
|
||||
Este documento define a estrategia de deploy para manter `orquestrador-product`
|
||||
e `orquestrador-admin` como servicos distintos, porem ligados.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Permitir que:
|
||||
|
||||
- o atendimento continue estavel mesmo se o admin estiver fora do ar
|
||||
- o admin evolua com login, painel, relatorios e geracao de tools sem impactar o hot path
|
||||
- os dois servicos possam ser versionados e publicados com cadencias diferentes
|
||||
|
||||
## Servico de produto
|
||||
|
||||
Nome operacional sugerido:
|
||||
|
||||
- `orquestrador-product`
|
||||
|
||||
Responsabilidades:
|
||||
|
||||
- Telegram
|
||||
- orquestracao
|
||||
- execucao de tools publicadas
|
||||
- regras operacionais do atendimento
|
||||
|
||||
Unit `systemd` sugerida:
|
||||
|
||||
- `deploy/systemd/orquestrador-product.service.example`
|
||||
|
||||
## Servico administrativo
|
||||
|
||||
Nome operacional sugerido:
|
||||
|
||||
- `orquestrador-admin`
|
||||
|
||||
Responsabilidades:
|
||||
|
||||
- autenticacao interna
|
||||
- painel administrativo
|
||||
- relatorios
|
||||
- configuracao do sistema
|
||||
- geracao, validacao, aprovacao e publicacao de tools
|
||||
|
||||
Unit `systemd` sugerida:
|
||||
|
||||
- `deploy/systemd/orquestrador-admin.service.example`
|
||||
|
||||
## Principios
|
||||
|
||||
1. Deploy do `admin` nao deve exigir restart do `product`.
|
||||
2. Deploy do `product` nao deve depender do `admin` estar online.
|
||||
3. Mudancas em `shared/contracts` devem ser compativeis para frente e para tras durante a janela de rollout.
|
||||
4. O `product` consome somente estado publicado e aprovado.
|
||||
5. O `admin` nao entra no hot path do atendimento.
|
||||
|
||||
## Configuracao de ambiente
|
||||
|
||||
Sugestao de arquivos distintos:
|
||||
|
||||
- `.env.product`
|
||||
- `.env.admin`
|
||||
|
||||
### `product`
|
||||
|
||||
Mantem:
|
||||
|
||||
- Vertex do atendimento
|
||||
- Redis do estado conversacional
|
||||
- Telegram
|
||||
- bancos do runtime operacional
|
||||
|
||||
### `admin`
|
||||
|
||||
Mantem:
|
||||
|
||||
- credenciais do painel interno
|
||||
- banco administrativo
|
||||
- modelo de geracao de codigo
|
||||
- configuracoes de relatorios e publicacao
|
||||
|
||||
## Estrategia de rollout
|
||||
|
||||
### Mudancas so no admin
|
||||
|
||||
1. publicar codigo do `admin`
|
||||
2. atualizar dependencias do `admin`
|
||||
3. reiniciar apenas `orquestrador-admin`
|
||||
|
||||
### Mudancas so no product
|
||||
|
||||
1. publicar codigo do `product`
|
||||
2. atualizar dependencias do `product`
|
||||
3. reiniciar apenas `orquestrador-product`
|
||||
|
||||
### Mudancas em contratos compartilhados
|
||||
|
||||
1. publicar contrato novo de forma aditiva
|
||||
2. subir primeiro o servico consumidor mais tolerante
|
||||
3. subir o outro servico depois
|
||||
4. so remover campos antigos numa fase posterior
|
||||
|
||||
## Banco e publicacao de estado
|
||||
|
||||
Nesta fase, a estrategia recomendada e:
|
||||
|
||||
- `admin` grava seus proprios metadados e artefatos
|
||||
- `product` consome somente dados publicados e estaveis
|
||||
- nenhuma dependencia sincrona do `product` para consultar `admin` em tempo de atendimento
|
||||
|
||||
## Observabilidade
|
||||
|
||||
Cada servico deve ter:
|
||||
|
||||
- logs proprios
|
||||
- unit `systemd` propria
|
||||
- variaveis de ambiente proprias
|
||||
- healthcheck proprio
|
||||
|
||||
## Situacao atual
|
||||
|
||||
Hoje o runtime real em producao ainda e o de `product`.
|
||||
|
||||
Esta estrategia ja prepara o caminho para:
|
||||
|
||||
- manter o deploy atual do produto
|
||||
- introduzir o `admin` como segundo servico
|
||||
- fazer a transicao sem mover `app/` agora
|
||||
@ -0,0 +1,147 @@
|
||||
# Estrutura Alvo do Monorepo
|
||||
|
||||
Este documento define a estrutura alvo do monorepo apos a decisao de separar o runtime de produto do servico administrativo.
|
||||
|
||||
## Objetivo
|
||||
Manter dois servicos distintos, mas ligados:
|
||||
|
||||
- `orquestrador-product`: atendimento e operacao do produto
|
||||
- `orquestrador-admin`: autenticacao interna, configuracao, relatorios e governanca de tools
|
||||
|
||||
A prioridade desta fase e estrutural:
|
||||
- preservar o runtime atual do produto
|
||||
- introduzir o scaffold do servico administrativo
|
||||
- criar um lugar claro para contratos compartilhados
|
||||
|
||||
## Decisao de transicao
|
||||
Nesta etapa, o codigo atual do produto permanece em `app/`.
|
||||
Nao vamos mover o runtime de produto agora para evitar churn desnecessario, quebra de import e risco operacional.
|
||||
|
||||
Portanto, a estrutura final de curto e medio prazo fica assim:
|
||||
|
||||
```text
|
||||
app/ # runtime atual do produto
|
||||
admin_app/ # novo runtime administrativo
|
||||
shared/ # contratos e artefatos compartilhados entre servicos
|
||||
|
||||
docs/
|
||||
adr/
|
||||
architecture/
|
||||
|
||||
deploy/
|
||||
systemd/
|
||||
# evoluira para suportar servicos distintos
|
||||
```
|
||||
|
||||
## Estrutura do servico de produto
|
||||
O servico de produto continua centralizado em `app/`.
|
||||
|
||||
Responsabilidades:
|
||||
- atendimento conversacional
|
||||
- integracoes com canais externos
|
||||
- orquestracao em tempo de execucao
|
||||
- leitura de tools publicadas
|
||||
- regras operacionais de vendas, revisao e locacao
|
||||
|
||||
## Estrutura do servico administrativo
|
||||
O servico administrativo passa a nascer em `admin_app/`.
|
||||
|
||||
Estrutura alvo inicial:
|
||||
|
||||
```text
|
||||
admin_app/
|
||||
app_factory.py
|
||||
main.py
|
||||
__main__.py
|
||||
api/
|
||||
dependencies.py
|
||||
router.py
|
||||
routes/
|
||||
system.py
|
||||
# auth.py
|
||||
# staff_accounts.py
|
||||
# tool_drafts.py
|
||||
# reports.py
|
||||
core/
|
||||
settings.py
|
||||
# security.py
|
||||
db/
|
||||
models/
|
||||
# staff_account.py
|
||||
# tool_draft.py
|
||||
# tool_generation_job.py
|
||||
# tool_publication.py
|
||||
# audit_log.py
|
||||
repositories/
|
||||
# staff_account_repository.py
|
||||
# tool_draft_repository.py
|
||||
services/
|
||||
# auth_service.py
|
||||
# tool_draft_service.py
|
||||
# report_service.py
|
||||
```
|
||||
|
||||
## Estrutura compartilhada
|
||||
Tudo que for contrato entre servicos deve ficar em `shared/`.
|
||||
|
||||
Regras:
|
||||
- `shared/` nao deve conter regra de negocio de atendimento
|
||||
- `shared/` nao deve conter dependencias do hot path do Telegram
|
||||
- `shared/` deve armazenar apenas contratos, DTOs, enums, nomes de eventos e utilitarios realmente compartilhados
|
||||
|
||||
Estrutura inicial:
|
||||
|
||||
```text
|
||||
shared/
|
||||
contracts/
|
||||
access_control.py
|
||||
tool_publication.py
|
||||
# settings_snapshot.py
|
||||
# report_filters.py
|
||||
```
|
||||
|
||||
## Regras de organizacao do monorepo
|
||||
|
||||
1. `app/` continua sendo o produto ate eventual migracao planejada.
|
||||
2. `admin_app/` nasce isolado e nao deve importar modulos internos de atendimento por conveniencia.
|
||||
3. `shared/` e o unico lugar recomendado para contratos reutilizados por ambos os servicos.
|
||||
4. O servico administrativo nao deve depender do runtime do Telegram para inicializar.
|
||||
5. O servico de produto nao deve depender do servico administrativo no hot path.
|
||||
6. Hierarquia de acesso administrativa deve nascer em shared/contracts/access_control.py.
|
||||
|
||||
## Import boundaries
|
||||
|
||||
### Permitido
|
||||
- `admin_app` importar `shared`
|
||||
- `app` importar `shared`
|
||||
|
||||
### Nao permitido
|
||||
- `admin_app` importar `app.services.orchestration` para executar atendimento
|
||||
- `app` importar `admin_app.services` no fluxo de atendimento
|
||||
- colocar metadados administrativos dentro de `app/services/orchestration`
|
||||
|
||||
## Estrategia de evolucao
|
||||
|
||||
### Fase atual
|
||||
- criar scaffold do `admin_app`
|
||||
- criar `shared/`
|
||||
- documentar a topologia do monorepo
|
||||
|
||||
### Fase seguinte
|
||||
- implementar `StaffAccount`
|
||||
- criar auth administrativa
|
||||
- subir primeiras rotas internas no `admin_app`
|
||||
|
||||
### Fase posterior
|
||||
- publicar contratos compartilhados em `shared/`
|
||||
- plugar pipeline de drafts, validacao e publicacao de tools
|
||||
|
||||
## Impacto em deploy
|
||||
Por enquanto, o deploy atual do produto permanece como esta.
|
||||
Quando o admin ganhar runtime real, o deploy vai evoluir para dois servicos distintos:
|
||||
|
||||
- `orquestrador-product`
|
||||
- `orquestrador-admin`
|
||||
|
||||
Sem mover o runtime atual de `app/` nesta etapa.
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
# Contratos Compartilhados E Hierarquia De Acesso
|
||||
|
||||
Este documento define os primeiros contratos compartilhados entre `orquestrador-product`
|
||||
e `orquestrador-admin`, com foco especial na hierarquia de acesso do runtime administrativo.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Criar uma base comum para:
|
||||
|
||||
- autenticacao e autorizacao administrativa
|
||||
- publicacao de tools do `admin` para o `product`
|
||||
- evolucao independente dos dois servicos sem acoplamento indevido
|
||||
|
||||
## Hierarquia inicial de acesso
|
||||
|
||||
Os papeis administrativos ficam centralizados em `shared/contracts/access_control.py`.
|
||||
|
||||
Hierarquia:
|
||||
|
||||
1. `viewer`
|
||||
2. `staff`
|
||||
3. `admin`
|
||||
|
||||
### `viewer`
|
||||
|
||||
Responsavel por leitura operacional.
|
||||
|
||||
Permissoes iniciais:
|
||||
|
||||
- `view_system`
|
||||
- `view_reports`
|
||||
- `view_audit_logs`
|
||||
|
||||
### `staff`
|
||||
|
||||
Responsavel por operacao interna e governanca de drafts.
|
||||
|
||||
Permissoes iniciais:
|
||||
|
||||
- todas as de `viewer`
|
||||
- `manage_tool_drafts`
|
||||
- `review_tool_generations`
|
||||
|
||||
### `admin`
|
||||
|
||||
Responsavel por configuracao, publicacao e gestao de acesso.
|
||||
|
||||
Permissoes iniciais:
|
||||
|
||||
- todas as de `staff`
|
||||
- `publish_tools`
|
||||
- `manage_settings`
|
||||
- `manage_staff_accounts`
|
||||
|
||||
## Regras de desenho
|
||||
|
||||
1. Os papeis nascem em contrato compartilhado para que `admin` e `product` falem a mesma lingua.
|
||||
2. O `product` nao usa essa hierarquia para atendimento ao cliente final.
|
||||
3. O `admin` usa essa hierarquia para autenticacao, autorizacao e auditoria.
|
||||
4. Toda evolucao deve ser additive-first para nao bloquear deploy independente.
|
||||
|
||||
## Contrato de publicacao de tool
|
||||
|
||||
O contrato inicial fica em `shared/contracts/tool_publication.py`.
|
||||
|
||||
Ele cobre:
|
||||
|
||||
- `ServiceName`
|
||||
- `ToolLifecycleStatus`
|
||||
- `ToolParameterType`
|
||||
- `ToolParameterContract`
|
||||
- `PublishedToolContract`
|
||||
- `ToolPublicationEnvelope`
|
||||
|
||||
## Como isso sera usado depois
|
||||
|
||||
### No `orquestrador-admin`
|
||||
|
||||
- criar `StaffAccount`
|
||||
- associar `StaffAccount.role`
|
||||
- controlar acesso a UI, as rotas e a aprovacao de tools
|
||||
- emitir `ToolPublicationEnvelope` quando uma tool for publicada
|
||||
|
||||
### No `orquestrador-product`
|
||||
|
||||
- consumir apenas tools publicadas
|
||||
- validar status e versao do contrato recebido
|
||||
- evitar dependencia do runtime do admin no hot path
|
||||
|
||||
## Proximos passos naturais
|
||||
|
||||
- criar a entidade `StaffAccount`
|
||||
- plugar a role do usuario interno ao contrato compartilhado
|
||||
- modelar a persistencia de drafts/publicacoes de tool
|
||||
@ -0,0 +1,27 @@
|
||||
# Shared Contracts
|
||||
|
||||
Esta pasta existe para concentrar contratos e artefatos compartilhados entre:
|
||||
|
||||
- `app/` (produto)
|
||||
- `admin_app/` (administrativo)
|
||||
|
||||
Ela nao deve receber regra de negocio do atendimento nem codigo acoplado ao hot path do produto.
|
||||
|
||||
## Contratos iniciais
|
||||
|
||||
Nesta fase, os primeiros contratos compartilhados sao:
|
||||
|
||||
- `access_control.py`
|
||||
- define a hierarquia inicial de acesso interno
|
||||
- papeis: `viewer`, `staff`, `admin`
|
||||
- permissoes iniciais para relatorios, configuracao, revisao e publicacao
|
||||
|
||||
- `tool_publication.py`
|
||||
- define o contrato minimo de publicacao de tools do `admin` para o `product`
|
||||
- inclui envelope de publicacao, status de ciclo de vida e schema de parametros
|
||||
|
||||
## Regras
|
||||
|
||||
- `shared/contracts` deve guardar apenas contratos estaveis entre servicos
|
||||
- nada aqui deve importar modulos internos de `app/` ou `admin_app/`
|
||||
- as mudancas devem ser additive-first para permitir deploy independente entre `product` e `admin`
|
||||
@ -0,0 +1,31 @@
|
||||
"""Contratos compartilhados entre product e admin."""
|
||||
|
||||
from shared.contracts.access_control import (
|
||||
AdminPermission,
|
||||
StaffRole,
|
||||
permissions_for_role,
|
||||
role_has_permission,
|
||||
role_includes,
|
||||
)
|
||||
from shared.contracts.tool_publication import (
|
||||
PublishedToolContract,
|
||||
ServiceName,
|
||||
ToolLifecycleStatus,
|
||||
ToolParameterContract,
|
||||
ToolParameterType,
|
||||
ToolPublicationEnvelope,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AdminPermission",
|
||||
"PublishedToolContract",
|
||||
"ServiceName",
|
||||
"StaffRole",
|
||||
"ToolLifecycleStatus",
|
||||
"ToolParameterContract",
|
||||
"ToolParameterType",
|
||||
"ToolPublicationEnvelope",
|
||||
"permissions_for_role",
|
||||
"role_has_permission",
|
||||
"role_includes",
|
||||
]
|
||||
@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StaffRole(str, Enum):
|
||||
VIEWER = "viewer"
|
||||
STAFF = "staff"
|
||||
ADMIN = "admin"
|
||||
|
||||
|
||||
class AdminPermission(str, Enum):
|
||||
VIEW_SYSTEM = "view_system"
|
||||
VIEW_REPORTS = "view_reports"
|
||||
VIEW_AUDIT_LOGS = "view_audit_logs"
|
||||
MANAGE_TOOL_DRAFTS = "manage_tool_drafts"
|
||||
REVIEW_TOOL_GENERATIONS = "review_tool_generations"
|
||||
PUBLISH_TOOLS = "publish_tools"
|
||||
MANAGE_SETTINGS = "manage_settings"
|
||||
MANAGE_STAFF_ACCOUNTS = "manage_staff_accounts"
|
||||
|
||||
|
||||
_ROLE_HIERARCHY = {
|
||||
StaffRole.VIEWER: 10,
|
||||
StaffRole.STAFF: 20,
|
||||
StaffRole.ADMIN: 30,
|
||||
}
|
||||
|
||||
_ROLE_PERMISSIONS = {
|
||||
StaffRole.VIEWER: frozenset(
|
||||
{
|
||||
AdminPermission.VIEW_SYSTEM,
|
||||
AdminPermission.VIEW_REPORTS,
|
||||
AdminPermission.VIEW_AUDIT_LOGS,
|
||||
}
|
||||
),
|
||||
StaffRole.STAFF: frozenset(
|
||||
{
|
||||
AdminPermission.VIEW_SYSTEM,
|
||||
AdminPermission.VIEW_REPORTS,
|
||||
AdminPermission.VIEW_AUDIT_LOGS,
|
||||
AdminPermission.MANAGE_TOOL_DRAFTS,
|
||||
AdminPermission.REVIEW_TOOL_GENERATIONS,
|
||||
}
|
||||
),
|
||||
StaffRole.ADMIN: frozenset(
|
||||
{
|
||||
AdminPermission.VIEW_SYSTEM,
|
||||
AdminPermission.VIEW_REPORTS,
|
||||
AdminPermission.VIEW_AUDIT_LOGS,
|
||||
AdminPermission.MANAGE_TOOL_DRAFTS,
|
||||
AdminPermission.REVIEW_TOOL_GENERATIONS,
|
||||
AdminPermission.PUBLISH_TOOLS,
|
||||
AdminPermission.MANAGE_SETTINGS,
|
||||
AdminPermission.MANAGE_STAFF_ACCOUNTS,
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def normalize_staff_role(role: StaffRole | str) -> StaffRole:
|
||||
if isinstance(role, StaffRole):
|
||||
return role
|
||||
return StaffRole(str(role).strip().lower())
|
||||
|
||||
|
||||
def normalize_admin_permission(permission: AdminPermission | str) -> AdminPermission:
|
||||
if isinstance(permission, AdminPermission):
|
||||
return permission
|
||||
return AdminPermission(str(permission).strip().lower())
|
||||
|
||||
|
||||
def permissions_for_role(role: StaffRole | str) -> frozenset[AdminPermission]:
|
||||
normalized_role = normalize_staff_role(role)
|
||||
return _ROLE_PERMISSIONS[normalized_role]
|
||||
|
||||
|
||||
def role_includes(role: StaffRole | str, minimum_role: StaffRole | str) -> bool:
|
||||
normalized_role = normalize_staff_role(role)
|
||||
normalized_minimum = normalize_staff_role(minimum_role)
|
||||
return _ROLE_HIERARCHY[normalized_role] >= _ROLE_HIERARCHY[normalized_minimum]
|
||||
|
||||
|
||||
def role_has_permission(
|
||||
role: StaffRole | str, permission: AdminPermission | str
|
||||
) -> bool:
|
||||
normalized_permission = normalize_admin_permission(permission)
|
||||
return normalized_permission in permissions_for_role(role)
|
||||
@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServiceName(str, Enum):
|
||||
PRODUCT = "product"
|
||||
ADMIN = "admin"
|
||||
|
||||
|
||||
class ToolLifecycleStatus(str, Enum):
|
||||
DRAFT = "draft"
|
||||
GENERATED = "generated"
|
||||
VALIDATED = "validated"
|
||||
APPROVED = "approved"
|
||||
ACTIVE = "active"
|
||||
FAILED = "failed"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class ToolParameterType(str, Enum):
|
||||
STRING = "string"
|
||||
INTEGER = "integer"
|
||||
NUMBER = "number"
|
||||
BOOLEAN = "boolean"
|
||||
OBJECT = "object"
|
||||
ARRAY = "array"
|
||||
|
||||
|
||||
class ToolParameterContract(BaseModel):
|
||||
name: str
|
||||
parameter_type: ToolParameterType
|
||||
description: str
|
||||
required: bool = True
|
||||
|
||||
|
||||
class PublishedToolContract(BaseModel):
|
||||
tool_name: str
|
||||
display_name: str
|
||||
description: str
|
||||
version: int = Field(ge=1)
|
||||
status: ToolLifecycleStatus
|
||||
parameters: tuple[ToolParameterContract, ...] = ()
|
||||
implementation_module: str
|
||||
implementation_callable: str
|
||||
checksum: str | None = None
|
||||
published_at: datetime | None = None
|
||||
published_by: str | None = None
|
||||
|
||||
|
||||
class ToolPublicationEnvelope(BaseModel):
|
||||
source_service: ServiceName = ServiceName.ADMIN
|
||||
target_service: ServiceName = ServiceName.PRODUCT
|
||||
publication_id: str
|
||||
published_tool: PublishedToolContract
|
||||
emitted_at: datetime
|
||||
@ -0,0 +1,82 @@
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from shared.contracts import (
|
||||
AdminPermission,
|
||||
PublishedToolContract,
|
||||
ServiceName,
|
||||
StaffRole,
|
||||
ToolLifecycleStatus,
|
||||
ToolParameterContract,
|
||||
ToolParameterType,
|
||||
ToolPublicationEnvelope,
|
||||
permissions_for_role,
|
||||
role_has_permission,
|
||||
role_includes,
|
||||
)
|
||||
|
||||
|
||||
class AccessControlContractTests(unittest.TestCase):
|
||||
def test_role_hierarchy_is_ordered(self):
|
||||
self.assertTrue(role_includes(StaffRole.ADMIN, StaffRole.STAFF))
|
||||
self.assertTrue(role_includes(StaffRole.STAFF, StaffRole.VIEWER))
|
||||
self.assertFalse(role_includes(StaffRole.VIEWER, StaffRole.ADMIN))
|
||||
|
||||
def test_permissions_are_inherited_by_higher_roles(self):
|
||||
self.assertIn(
|
||||
AdminPermission.VIEW_REPORTS,
|
||||
permissions_for_role(StaffRole.VIEWER),
|
||||
)
|
||||
self.assertTrue(
|
||||
role_has_permission(StaffRole.STAFF, AdminPermission.MANAGE_TOOL_DRAFTS)
|
||||
)
|
||||
self.assertTrue(
|
||||
role_has_permission(StaffRole.ADMIN, AdminPermission.MANAGE_SETTINGS)
|
||||
)
|
||||
self.assertFalse(
|
||||
role_has_permission(StaffRole.VIEWER, AdminPermission.PUBLISH_TOOLS)
|
||||
)
|
||||
|
||||
|
||||
class ToolPublicationContractTests(unittest.TestCase):
|
||||
def test_tool_publication_envelope_is_built_with_shared_contract(self):
|
||||
published_tool = PublishedToolContract(
|
||||
tool_name="consultar_financiamento",
|
||||
display_name="Consultar Financiamento",
|
||||
description="Consulta opcoes de financiamento.",
|
||||
version=2,
|
||||
status=ToolLifecycleStatus.APPROVED,
|
||||
parameters=(
|
||||
ToolParameterContract(
|
||||
name="valor_veiculo",
|
||||
parameter_type=ToolParameterType.NUMBER,
|
||||
description="Valor do veiculo em reais.",
|
||||
required=True,
|
||||
),
|
||||
),
|
||||
implementation_module="generated_tools.consultar_financiamento",
|
||||
implementation_callable="run",
|
||||
checksum="sha256:abc123",
|
||||
published_at=datetime(2026, 3, 26, 12, 0, tzinfo=timezone.utc),
|
||||
published_by="staff:1",
|
||||
)
|
||||
|
||||
envelope = ToolPublicationEnvelope(
|
||||
source_service=ServiceName.ADMIN,
|
||||
target_service=ServiceName.PRODUCT,
|
||||
publication_id="pub_001",
|
||||
published_tool=published_tool,
|
||||
emitted_at=datetime(2026, 3, 26, 12, 1, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
self.assertEqual(envelope.source_service, ServiceName.ADMIN)
|
||||
self.assertEqual(envelope.target_service, ServiceName.PRODUCT)
|
||||
self.assertEqual(envelope.published_tool.tool_name, "consultar_financiamento")
|
||||
self.assertEqual(
|
||||
envelope.published_tool.parameters[0].parameter_type,
|
||||
ToolParameterType.NUMBER,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue