🚧 construct(admin): automatizar pipeline governada de tools na fase 6

feat/self-evolving-tools-foundation
parent 2e3a695878
commit 640e422498

@ -10,6 +10,7 @@ from admin_app.api.schemas import (
AdminToolDraftIntakeRequest,
AdminToolDraftIntakeResponse,
AdminToolDraftListResponse,
AdminToolGenerationPipelineResponse,
AdminToolGovernanceTransitionResponse,
AdminToolManagementActionResponse,
AdminToolOverviewResponse,
@ -122,6 +123,34 @@ def panel_tool_draft_intake(
)
@router.post(
"/pipeline/{version_id}/run",
response_model=AdminToolGenerationPipelineResponse,
)
def panel_tool_pipeline_run(
version_id: str,
service: ToolManagementService = Depends(get_tool_management_service),
current_staff: AuthenticatedStaffPrincipal = Depends(
require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS)
),
):
try:
payload = service.run_generation_pipeline(
version_id,
runner_staff_account_id=current_staff.id,
runner_name=current_staff.display_name,
runner_role=current_staff.role,
)
except LookupError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except PermissionError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
return _build_pipeline_response(payload)
@router.get(
"/review-queue",
response_model=AdminToolReviewQueueResponse,
@ -246,6 +275,22 @@ def panel_tool_publications_publish(
def _build_pipeline_response(payload: dict) -> AdminToolGenerationPipelineResponse:
return AdminToolGenerationPipelineResponse(
service="orquestrador-admin",
message=payload["message"],
version_id=payload["version_id"],
tool_name=payload["tool_name"],
version_number=payload["version_number"],
status=payload["status"],
current_step=payload["current_step"],
steps=payload["steps"],
queue_entry=payload["queue_entry"],
automated_validations=payload.get("automated_validations", []),
next_steps=payload["next_steps"],
)
def _build_governance_transition_response(payload: dict) -> AdminToolGovernanceTransitionResponse:
return AdminToolGovernanceTransitionResponse(
service="orquestrador-admin",

@ -10,6 +10,7 @@ from admin_app.api.schemas import (
AdminToolDraftIntakeRequest,
AdminToolDraftIntakeResponse,
AdminToolDraftListResponse,
AdminToolGenerationPipelineResponse,
AdminToolGovernanceTransitionResponse,
AdminToolManagementActionResponse,
AdminToolOverviewResponse,
@ -122,6 +123,34 @@ def tool_draft_intake(
)
@router.post(
"/pipeline/{version_id}/run",
response_model=AdminToolGenerationPipelineResponse,
)
def tool_pipeline_run(
version_id: str,
service: ToolManagementService = Depends(get_tool_management_service),
current_staff: AuthenticatedStaffPrincipal = Depends(
require_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS)
),
):
try:
payload = service.run_generation_pipeline(
version_id,
runner_staff_account_id=current_staff.id,
runner_name=current_staff.display_name,
runner_role=current_staff.role,
)
except LookupError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except PermissionError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
return _build_pipeline_response(payload)
@router.get(
"/review-queue",
response_model=AdminToolReviewQueueResponse,
@ -246,6 +275,22 @@ def tool_publications_publish(
def _build_pipeline_response(payload: dict) -> AdminToolGenerationPipelineResponse:
return AdminToolGenerationPipelineResponse(
service="orquestrador-admin",
message=payload["message"],
version_id=payload["version_id"],
tool_name=payload["tool_name"],
version_number=payload["version_number"],
status=payload["status"],
current_step=payload["current_step"],
steps=payload["steps"],
queue_entry=payload["queue_entry"],
automated_validations=payload.get("automated_validations", []),
next_steps=payload["next_steps"],
)
def _build_governance_transition_response(payload: dict) -> AdminToolGovernanceTransitionResponse:
return AdminToolGovernanceTransitionResponse(
service="orquestrador-admin",

@ -763,6 +763,8 @@ class AdminToolReviewQueueEntryResponse(BaseModel):
gate: str
summary: str
owner_name: str | None = None
automated_validation_status: str | None = None
automated_validation_summary: str | None = None
queued_at: datetime | None = None
@ -817,6 +819,35 @@ class AdminToolGovernanceTransitionResponse(BaseModel):
next_steps: list[str]
class AdminToolAutomatedValidationResponse(BaseModel):
key: str
label: str
status: str
summary: str
blocking_issues: list[str] = Field(default_factory=list)
class AdminToolPipelineStepResponse(BaseModel):
key: str
label: str
state: str
description: str
class AdminToolGenerationPipelineResponse(BaseModel):
service: str
message: str
version_id: str
tool_name: str
version_number: int = Field(ge=1)
status: ToolLifecycleStatus
current_step: str
steps: list[AdminToolPipelineStepResponse]
queue_entry: AdminToolReviewQueueEntryResponse
automated_validations: list[AdminToolAutomatedValidationResponse] = Field(default_factory=list)
next_steps: list[str]
class AdminToolDraftIntakeParameterRequest(BaseModel):
name: str = Field(min_length=1, max_length=64)
parameter_type: ToolParameterType

File diff suppressed because it is too large Load Diff

@ -724,16 +724,30 @@ def _build_tool_review_view(request: Request, settings: AdminSettings) -> AdminT
publications_endpoint=publications_endpoint,
workflow=(
AdminToolReviewWorkflowStep(
eyebrow="Leitura inicial",
title="Revisar fila",
description="Carregar a fila de geracao e entender em que gate cada item se encontra.",
status_label="Revisao",
eyebrow="Cadastro manual",
title="Persistir o draft",
description="Receber o cadastro manual da tool e consolidar a versao administrativa inicial.",
status_label="Draft",
status_variant="info",
),
AdminToolReviewWorkflowStep(
eyebrow="Pipeline",
title="Executar geracao",
description="Rodar a etapa de geracao da implementacao isolada antes da validacao da versao.",
status_label="Geracao",
status_variant="warning",
),
AdminToolReviewWorkflowStep(
eyebrow="Validacao",
title="Conferir a versao gerada",
description="Validar a versao produzida pelo pipeline antes da aprovacao humana da diretoria.",
status_label="Validacao",
status_variant="info",
),
AdminToolReviewWorkflowStep(
eyebrow="Decisao humana",
title="Aprovar com criterio",
description="Conferir contrato, parametros e prontidao tecnica antes de liberar a proxima etapa.",
description="A diretoria revisa a versao validada e decide se ela pode seguir para publicacao controlada.",
status_label="Aprovacao",
status_variant="warning",
),

@ -257,10 +257,10 @@ class _FakeToolArtifactRepository:
def update_artifact(self, artifact: ToolArtifact, **kwargs) -> ToolArtifact:
artifact.artifact_status = kwargs["artifact_status"]
artifact.storage_kind = kwargs["storage_kind"]
artifact.storage_kind = kwargs.get("storage_kind", artifact.storage_kind)
artifact.summary = kwargs["summary"]
artifact.payload_json = kwargs["payload_json"]
artifact.checksum = kwargs["checksum"]
artifact.checksum = kwargs.get("checksum", artifact.checksum)
artifact.author_staff_account_id = kwargs["author_staff_account_id"]
artifact.author_display_name = kwargs["author_display_name"]
artifact.updated_at = datetime(2026, 3, 31, 20, artifact.version_number, tzinfo=timezone.utc)
@ -518,9 +518,45 @@ class AdminPanelToolsWebTests(unittest.TestCase):
self.assertEqual(payload["queue_mode"], "governed_admin_queue")
self.assertEqual(len(payload["items"]), 1)
self.assertEqual(payload["items"][0]["status"], "draft")
self.assertEqual(payload["items"][0]["gate"], "director_review_required")
self.assertEqual(payload["items"][0]["gate"], "generation_pipeline_required")
self.assertEqual(payload["items"][0]["version_number"], 1)
def test_panel_tools_collaborator_can_run_generation_pipeline_after_manual_intake(self):
client, app, _, _, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
intake_response = client.post(
"/admin/panel/tools/drafts/intake",
json={
"domain": "locacao",
"tool_name": "emitir_resumo_locacao",
"display_name": "Emitir resumo de locacao",
"description": "Resume contratos de locacao com filtros operacionais para o time interno.",
"business_goal": "Dar visibilidade rapida aos contratos e aos principais dados da locacao.",
"parameters": [],
},
)
version_id = intake_response.json()["draft_preview"]["version_id"]
response = client.post(f"/admin/panel/tools/pipeline/{version_id}/run")
finally:
app.dependency_overrides.clear()
self.assertEqual(intake_response.status_code, 200)
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["status"], "generated")
self.assertEqual(payload["current_step"], "validation")
self.assertEqual(payload["queue_entry"]["gate"], "validation_required")
self.assertEqual(payload["queue_entry"]["automated_validation_status"], "passed")
self.assertEqual(payload["queue_entry"]["automated_validation_summary"], "4/4 validacoes automaticas passaram antes da revisao humana.")
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "passed")
steps_by_key = {step["key"]: step for step in payload["steps"]}
self.assertEqual(steps_by_key["generation"]["state"], "completed")
self.assertEqual(steps_by_key["validation"]["state"], "current")
def test_panel_tools_publications_require_director_publication_permission(self):
client, app, _, _, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
@ -629,6 +665,8 @@ class AdminPanelToolsWebTests(unittest.TestCase):
)
version_id = intake_response.json()["draft_preview"]["version_id"]
publish_before_approval = client.post(f"/admin/panel/tools/publications/{version_id}/publish")
review_before_pipeline = client.post(f"/admin/panel/tools/review-queue/{version_id}/review")
pipeline_response = client.post(f"/admin/panel/tools/pipeline/{version_id}/run")
review_response = client.post(f"/admin/panel/tools/review-queue/{version_id}/review")
approve_response = client.post(f"/admin/panel/tools/review-queue/{version_id}/approve")
pre_publications = client.get("/admin/panel/tools/publications")
@ -640,6 +678,15 @@ class AdminPanelToolsWebTests(unittest.TestCase):
self.assertEqual(intake_response.status_code, 200)
self.assertEqual(publish_before_approval.status_code, 409)
self.assertIn("approved", publish_before_approval.json()["detail"])
self.assertEqual(review_before_pipeline.status_code, 409)
self.assertIn("generated", review_before_pipeline.json()["detail"])
self.assertEqual(pipeline_response.status_code, 200)
self.assertEqual(pipeline_response.json()["status"], "generated")
self.assertEqual(pipeline_response.json()["queue_entry"]["gate"], "validation_required")
self.assertEqual(pipeline_response.json()["queue_entry"]["automated_validation_status"], "passed")
self.assertEqual(pipeline_response.json()["queue_entry"]["automated_validation_summary"], "4/4 validacoes automaticas passaram antes da revisao humana.")
self.assertEqual(len(pipeline_response.json()["automated_validations"]), 4)
self.assertTrue(all(check["status"] == "passed" for check in pipeline_response.json()["automated_validations"]))
self.assertEqual(review_response.status_code, 200)
self.assertEqual(review_response.json()["status"], "validated")
self.assertEqual(review_response.json()["queue_entry"]["gate"], "director_approval_required")

@ -1,4 +1,5 @@
import unittest
from unittest.mock import patch
from datetime import datetime, timezone
from sqlalchemy import create_engine
@ -335,10 +336,10 @@ class _FakeToolArtifactRepository:
def update_artifact(self, artifact: ToolArtifact, **kwargs) -> ToolArtifact:
artifact.artifact_status = kwargs["artifact_status"]
artifact.storage_kind = kwargs["storage_kind"]
artifact.storage_kind = kwargs.get("storage_kind", artifact.storage_kind)
artifact.summary = kwargs["summary"]
artifact.payload_json = kwargs["payload_json"]
artifact.checksum = kwargs["checksum"]
artifact.checksum = kwargs.get("checksum", artifact.checksum)
artifact.author_staff_account_id = kwargs["author_staff_account_id"]
artifact.author_display_name = kwargs["author_display_name"]
artifact.updated_at = datetime(2026, 3, 31, 18, artifact.version_number, tzinfo=timezone.utc)
@ -654,9 +655,297 @@ class AdminToolManagementServiceTests(unittest.TestCase):
self.assertEqual(payload["items"][0]["version_id"], intake_payload["draft_preview"]["version_id"])
self.assertEqual(payload["items"][0]["version_number"], 1)
self.assertEqual(payload["items"][0]["status"], ToolLifecycleStatus.DRAFT)
self.assertEqual(payload["items"][0]["gate"], "director_review_required")
self.assertEqual(payload["items"][0]["gate"], "generation_pipeline_required")
self.assertIn(ToolLifecycleStatus.APPROVED, payload["supported_statuses"])
def test_run_generation_pipeline_promotes_version_to_generated(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [
{
"name": "placa",
"parameter_type": "string",
"description": "Placa usada na busca da revisao.",
"required": True,
}
],
},
owner_staff_account_id=8,
owner_name="Operacao de Oficina",
owner_role=StaffRole.COLABORADOR,
)
payload = self.service.run_generation_pipeline(
intake_payload["draft_preview"]["version_id"],
runner_staff_account_id=8,
runner_name="Operacao de Oficina",
runner_role=StaffRole.COLABORADOR,
)
self.assertEqual(payload["status"], ToolLifecycleStatus.GENERATED)
self.assertEqual(payload["current_step"], "validation")
self.assertEqual(payload["queue_entry"]["gate"], "validation_required")
self.assertEqual(payload["queue_entry"]["automated_validation_status"], "passed")
self.assertEqual(payload["queue_entry"]["automated_validation_summary"], "4/4 validacoes automaticas passaram antes da revisao humana.")
self.assertEqual(len(payload["automated_validations"]), 4)
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "passed")
steps_by_key = {step["key"]: step for step in payload["steps"]}
self.assertEqual(steps_by_key["manual_intake"]["state"], "completed")
self.assertEqual(steps_by_key["generation"]["state"], "completed")
self.assertEqual(steps_by_key["validation"]["state"], "current")
self.assertEqual(self.draft_repository.drafts[0].status, ToolLifecycleStatus.GENERATED)
self.assertEqual(self.version_repository.versions[0].status, ToolLifecycleStatus.GENERATED)
self.assertEqual(self.metadata_repository.metadata_entries[0].status, ToolLifecycleStatus.GENERATED)
generation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.GENERATION_REQUEST
)
self.assertEqual(generation_artifact.payload_json["pipeline_status"], "completed")
self.assertEqual(generation_artifact.payload_json["triggered_by_role"], StaffRole.COLABORADOR.value)
validation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.VALIDATION_REPORT
)
self.assertEqual(validation_artifact.artifact_status.value, "succeeded")
self.assertEqual(validation_artifact.payload_json["validation_scope"], "tool_contract")
automated_checks = {check["key"]: check for check in validation_artifact.payload_json["automated_checks"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "passed")
self.assertEqual(validation_artifact.payload_json["signature_schema"]["callable_name"], GENERATED_TOOL_ENTRYPOINT)
self.assertEqual(validation_artifact.payload_json["signature_schema"]["signature"], "run(*, placa)")
self.assertEqual(validation_artifact.payload_json["import_loading"]["loaded_callable"], GENERATED_TOOL_ENTRYPOINT)
self.assertEqual(validation_artifact.payload_json["import_loading"]["loaded_signature"], "run(*, placa)")
self.assertEqual(validation_artifact.payload_json["smoke_tests"]["direct_result_type"], "dict")
self.assertEqual(validation_artifact.payload_json["smoke_tests"]["runtime_result_type"], "dict")
self.assertEqual(validation_artifact.payload_json["smoke_tests"]["invocation_arguments"]["placa"], "sample_placa")
self.assertEqual(validation_artifact.payload_json["publication_envelope"]["target_service"], "product")
def test_run_generation_pipeline_marks_version_failed_when_contract_validation_fails(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [],
},
owner_staff_account_id=8,
owner_name="Operacao de Oficina",
owner_role=StaffRole.COLABORADOR,
)
self.metadata_repository.metadata_entries[0].display_name = "No"
payload = self.service.run_generation_pipeline(
intake_payload["draft_preview"]["version_id"],
runner_staff_account_id=8,
runner_name="Operacao de Oficina",
runner_role=StaffRole.COLABORADOR,
)
self.assertEqual(payload["status"], ToolLifecycleStatus.FAILED)
self.assertEqual(payload["current_step"], "generation")
self.assertEqual(payload["queue_entry"]["gate"], "pipeline_retry_required")
self.assertEqual(payload["queue_entry"]["automated_validation_status"], "failed")
self.assertIn("revisar contrato da tool", payload["queue_entry"]["automated_validation_summary"].lower())
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "failed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "passed")
self.assertTrue(automated_checks["tool_contract"]["blocking_issues"])
self.assertEqual(self.draft_repository.drafts[0].status, ToolLifecycleStatus.FAILED)
self.assertEqual(self.version_repository.versions[0].status, ToolLifecycleStatus.FAILED)
self.assertEqual(self.metadata_repository.metadata_entries[0].status, ToolLifecycleStatus.FAILED)
validation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.VALIDATION_REPORT
)
self.assertEqual(validation_artifact.artifact_status.value, "failed")
self.assertIn("display_name", validation_artifact.payload_json["blocking_issues"][0])
def test_run_generation_pipeline_marks_version_failed_when_signature_schema_validation_fails(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [
{
"name": "user_id",
"parameter_type": "string",
"description": "Identificador do usuario usado no filtro administrativo.",
"required": True,
}
],
},
owner_staff_account_id=8,
owner_name="Operacao de Oficina",
owner_role=StaffRole.COLABORADOR,
)
payload = self.service.run_generation_pipeline(
intake_payload["draft_preview"]["version_id"],
runner_staff_account_id=8,
runner_name="Operacao de Oficina",
runner_role=StaffRole.COLABORADOR,
)
self.assertEqual(payload["status"], ToolLifecycleStatus.FAILED)
self.assertEqual(payload["queue_entry"]["gate"], "pipeline_retry_required")
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "failed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "failed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "failed")
self.assertIn("user_id", automated_checks["tool_signature_schema"]["blocking_issues"][0])
validation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.VALIDATION_REPORT
)
self.assertEqual(validation_artifact.artifact_status.value, "failed")
self.assertIn("user_id", validation_artifact.payload_json["signature_schema"]["issues"][0])
def test_run_generation_pipeline_marks_version_failed_when_import_loading_validation_fails(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [
{
"name": "placa",
"parameter_type": "string",
"description": "Placa usada na busca da revisao.",
"required": True,
}
],
},
owner_staff_account_id=8,
owner_name="Operacao de Oficina",
owner_role=StaffRole.COLABORADOR,
)
with patch.object(
self.service,
"_render_generated_tool_module_source",
return_value="async def run(:\n pass\n",
):
payload = self.service.run_generation_pipeline(
intake_payload["draft_preview"]["version_id"],
runner_staff_account_id=8,
runner_name="Operacao de Oficina",
runner_role=StaffRole.COLABORADOR,
)
self.assertEqual(payload["status"], ToolLifecycleStatus.FAILED)
self.assertEqual(payload["queue_entry"]["gate"], "pipeline_retry_required")
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "failed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "failed")
self.assertIn("SyntaxError", automated_checks["tool_import_loading"]["blocking_issues"][0])
validation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.VALIDATION_REPORT
)
self.assertEqual(validation_artifact.artifact_status.value, "failed")
self.assertIn("SyntaxError", validation_artifact.payload_json["import_loading"]["issues"][0])
self.assertIn("import/loading validation did not pass", validation_artifact.payload_json["smoke_tests"]["issues"][0])
def test_run_generation_pipeline_marks_version_failed_when_smoke_tests_fail(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [
{
"name": "placa",
"parameter_type": "string",
"description": "Placa usada na busca da revisao.",
"required": True,
}
],
},
owner_staff_account_id=8,
owner_name="Operacao de Oficina",
owner_role=StaffRole.COLABORADOR,
)
with patch.object(
self.service,
"_render_generated_tool_module_source",
return_value='async def run(*, placa):\n raise RuntimeError("smoke test boom")\n',
):
payload = self.service.run_generation_pipeline(
intake_payload["draft_preview"]["version_id"],
runner_staff_account_id=8,
runner_name="Operacao de Oficina",
runner_role=StaffRole.COLABORADOR,
)
self.assertEqual(payload["status"], ToolLifecycleStatus.FAILED)
self.assertEqual(payload["queue_entry"]["gate"], "pipeline_retry_required")
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "failed")
self.assertIn("RuntimeError", automated_checks["tool_smoke_tests"]["blocking_issues"][0])
validation_artifact = next(
artifact
for artifact in self.artifact_repository.artifacts
if artifact.artifact_kind == ToolArtifactKind.VALIDATION_REPORT
)
self.assertEqual(validation_artifact.artifact_status.value, "failed")
self.assertIn("RuntimeError", validation_artifact.payload_json["smoke_tests"]["issues"][0])
def test_review_requires_generation_pipeline_before_validation(self):
intake_payload = self.service.create_draft_submission(
{
"domain": "locacao",
"tool_name": "emitir_resumo_locacao",
"display_name": "Emitir resumo de locacao",
"description": "Resume contratos de locacao com filtros operacionais para o time interno.",
"business_goal": "Dar visibilidade rapida aos contratos e aos principais dados da locacao.",
"parameters": [],
},
owner_staff_account_id=3,
owner_name="Equipe Interna",
)
with self.assertRaisesRegex(ValueError, "generated"):
self.service.review_version(
intake_payload["draft_preview"]["version_id"],
reviewer_staff_account_id=99,
reviewer_name="Diretoria",
reviewer_role=StaffRole.DIRETOR,
)
def test_director_must_review_approve_and_publish_before_activation(self):
intake_payload = self.service.create_draft_submission(
{
@ -679,6 +968,12 @@ class AdminToolManagementServiceTests(unittest.TestCase):
)
version_id = intake_payload["draft_preview"]["version_id"]
pipeline_payload = self.service.run_generation_pipeline(
version_id,
runner_staff_account_id=3,
runner_name="Equipe Interna",
runner_role=StaffRole.COLABORADOR,
)
review_payload = self.service.review_version(
version_id,
reviewer_staff_account_id=99,
@ -698,6 +993,9 @@ class AdminToolManagementServiceTests(unittest.TestCase):
publisher_role=StaffRole.DIRETOR,
)
self.assertEqual(pipeline_payload["status"], ToolLifecycleStatus.GENERATED)
self.assertEqual(len(pipeline_payload["automated_validations"]), 4)
self.assertTrue(all(check["status"] == "passed" for check in pipeline_payload["automated_validations"]))
self.assertEqual(review_payload["status"], ToolLifecycleStatus.VALIDATED)
self.assertEqual(review_payload["queue_entry"]["gate"], "director_approval_required")
self.assertEqual(approve_payload["status"], ToolLifecycleStatus.APPROVED)
@ -749,6 +1047,7 @@ class AdminToolManagementServiceTests(unittest.TestCase):
owner_name="Equipe Interna",
)
first_version_id = first_intake["draft_preview"]["version_id"]
self.service.run_generation_pipeline(first_version_id, runner_staff_account_id=7, runner_name="Equipe Interna", runner_role=StaffRole.COLABORADOR)
self.service.review_version(first_version_id, reviewer_staff_account_id=99, reviewer_name="Diretoria", reviewer_role=StaffRole.DIRETOR)
self.service.approve_version(first_version_id, approver_staff_account_id=99, approver_name="Diretoria", approver_role=StaffRole.DIRETOR)
self.service.publish_version(first_version_id, publisher_staff_account_id=99, publisher_name="Diretoria", publisher_role=StaffRole.DIRETOR)
@ -766,6 +1065,7 @@ class AdminToolManagementServiceTests(unittest.TestCase):
owner_name="Equipe Interna",
)
second_version_id = second_intake["draft_preview"]["version_id"]
self.service.run_generation_pipeline(second_version_id, runner_staff_account_id=7, runner_name="Equipe Interna", runner_role=StaffRole.COLABORADOR)
self.service.review_version(second_version_id, reviewer_staff_account_id=99, reviewer_name="Diretoria", reviewer_role=StaffRole.DIRETOR)
self.service.approve_version(second_version_id, approver_staff_account_id=99, approver_name="Diretoria", approver_role=StaffRole.DIRETOR)
self.service.publish_version(second_version_id, publisher_staff_account_id=99, publisher_name="Diretoria", publisher_role=StaffRole.DIRETOR)

@ -254,10 +254,10 @@ class _FakeToolArtifactRepository:
def update_artifact(self, artifact: ToolArtifact, **kwargs) -> ToolArtifact:
artifact.artifact_status = kwargs["artifact_status"]
artifact.storage_kind = kwargs["storage_kind"]
artifact.storage_kind = kwargs.get("storage_kind", artifact.storage_kind)
artifact.summary = kwargs["summary"]
artifact.payload_json = kwargs["payload_json"]
artifact.checksum = kwargs["checksum"]
artifact.checksum = kwargs.get("checksum", artifact.checksum)
artifact.author_staff_account_id = kwargs["author_staff_account_id"]
artifact.author_display_name = kwargs["author_display_name"]
artifact.updated_at = datetime(2026, 3, 31, 19, artifact.version_number, tzinfo=timezone.utc)
@ -336,7 +336,7 @@ class AdminToolsWebTests(unittest.TestCase):
self.assertIn("/admin/tools/drafts/intake", [item["href"] for item in payload["actions"]])
self.assertNotIn("/admin/tools/review-queue", [item["href"] for item in payload["actions"]])
self.assertNotIn("/admin/tools/publications", [item["href"] for item in payload["actions"]])
self.assertIn("artefatos", payload["next_steps"][0].lower())
self.assertIn("pipeline de geracao", payload["next_steps"][0].lower())
def test_tools_contracts_return_shared_contract_snapshot(self):
client, app, _, _, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
@ -540,10 +540,50 @@ class AdminToolsWebTests(unittest.TestCase):
self.assertEqual(payload["queue_mode"], "governed_admin_queue")
self.assertEqual(len(payload["items"]), 1)
self.assertEqual(payload["items"][0]["status"], "draft")
self.assertEqual(payload["items"][0]["gate"], "director_review_required")
self.assertEqual(payload["items"][0]["gate"], "generation_pipeline_required")
self.assertEqual(payload["items"][0]["version_number"], 1)
self.assertIn("approved", payload["supported_statuses"])
def test_tools_collaborator_can_run_generation_pipeline_after_manual_intake(self):
client, app, _, _, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
intake_response = client.post(
"/admin/tools/drafts/intake",
headers={"Authorization": "Bearer token"},
json={
"domain": "revisao",
"tool_name": "consultar_revisao_aberta",
"display_name": "Consultar revisao aberta",
"description": "Consulta revisoes abertas com filtros administrativos para a oficina.",
"business_goal": "Ajudar o time a localizar revisoes abertas com mais contexto operacional.",
"parameters": [],
},
)
version_id = intake_response.json()["draft_preview"]["version_id"]
response = client.post(
f"/admin/tools/pipeline/{version_id}/run",
headers={"Authorization": "Bearer token"},
)
finally:
app.dependency_overrides.clear()
self.assertEqual(intake_response.status_code, 200)
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["status"], "generated")
self.assertEqual(payload["current_step"], "validation")
self.assertEqual(payload["queue_entry"]["gate"], "validation_required")
self.assertEqual(payload["queue_entry"]["automated_validation_status"], "passed")
self.assertEqual(payload["queue_entry"]["automated_validation_summary"], "4/4 validacoes automaticas passaram antes da revisao humana.")
automated_checks = {check["key"]: check for check in payload["automated_validations"]}
self.assertEqual(automated_checks["tool_contract"]["status"], "passed")
self.assertEqual(automated_checks["tool_signature_schema"]["status"], "passed")
self.assertEqual(automated_checks["tool_import_loading"]["status"], "passed")
self.assertEqual(automated_checks["tool_smoke_tests"]["status"], "passed")
steps_by_key = {step["key"]: step for step in payload["steps"]}
self.assertEqual(steps_by_key["generation"]["state"], "completed")
self.assertEqual(steps_by_key["validation"]["state"], "current")
def test_tools_publications_require_director_publication_permission(self):
client, app, _, _, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
@ -664,6 +704,14 @@ class AdminToolsWebTests(unittest.TestCase):
f"/admin/tools/publications/{version_id}/publish",
headers={"Authorization": "Bearer token"},
)
review_before_pipeline = client.post(
f"/admin/tools/review-queue/{version_id}/review",
headers={"Authorization": "Bearer token"},
)
pipeline_response = client.post(
f"/admin/tools/pipeline/{version_id}/run",
headers={"Authorization": "Bearer token"},
)
review_response = client.post(
f"/admin/tools/review-queue/{version_id}/review",
headers={"Authorization": "Bearer token"},
@ -684,6 +732,15 @@ class AdminToolsWebTests(unittest.TestCase):
self.assertEqual(intake_response.status_code, 200)
self.assertEqual(publish_before_approval.status_code, 409)
self.assertIn("approved", publish_before_approval.json()["detail"])
self.assertEqual(review_before_pipeline.status_code, 409)
self.assertIn("generated", review_before_pipeline.json()["detail"])
self.assertEqual(pipeline_response.status_code, 200)
self.assertEqual(pipeline_response.json()["status"], "generated")
self.assertEqual(pipeline_response.json()["queue_entry"]["gate"], "validation_required")
self.assertEqual(pipeline_response.json()["queue_entry"]["automated_validation_status"], "passed")
self.assertEqual(pipeline_response.json()["queue_entry"]["automated_validation_summary"], "4/4 validacoes automaticas passaram antes da revisao humana.")
self.assertEqual(len(pipeline_response.json()["automated_validations"]), 4)
self.assertTrue(all(check["status"] == "passed" for check in pipeline_response.json()["automated_validations"]))
self.assertEqual(review_response.status_code, 200)
self.assertEqual(review_response.json()["status"], "validated")
self.assertEqual(review_response.json()["queue_entry"]["gate"], "director_approval_required")

Loading…
Cancel
Save