feat: add structured versioned dialplan and asterisk admin

- packages/database: DialplanEntry + DialplanVersion (schema/config
  gerado, nunca dialplan cru vindo do usuario)
- apps/api/src/dialplan: DialplanService gera extensions a partir de
  entradas estruturadas (campos com allowlist estrita de caracteres),
  escreve em arquivo compartilhado via volume Docker 'dialplan-generated'
  entre api e asterisk, recarrega via AMI ('dialplan reload') e valida
  checando 'dialplan show <contexto>' por marcadores de erro. Falha na
  validacao dispara rollback automatico para a versao anterior; rollback
  manual tambem disponivel para qualquer versao no historico
- infrastructure/asterisk: extensions.conf agora inclui o arquivo gerado
  pela aplicacao; entrypoint garante que ele existe (vazio) no primeiro
  boot antes da primeira publicacao
- apps/api/src/asterisk-admin: status (AMI + heartbeat), module show, e
  diagnostico com ALLOWLIST ESTRITA de comandos exatos (nunca shell
  arbitraria) — agente.md secao 22

Testado ponta a ponta contra o Asterisk real: dialplan publicado fica
ativo imediatamente sem restart (confirmado via 'dialplan show'),
comando de diagnostico fora da allowlist rejeitado com 400.
This commit is contained in:
2026-08-27 13:14:50 -03:00
parent 8a41b5d2b2
commit cf3fc4b3ef
18 changed files with 789 additions and 10 deletions

View File

@@ -212,3 +212,42 @@ model ExtensionState {
@@map("extension_states")
}
// ===========================================================================
// Dialplan estruturado e versionado (agente.md seção 21). O modo "Advanced"
// citado na spec é apenas uma restrição de permissão no frontend sobre os
// mesmos dados — não um formato de armazenamento diferente.
// ===========================================================================
model DialplanEntry {
id String @id @default(uuid())
context String
exten String
priority Int
application String
argument String?
enabled Boolean @default(true)
order Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([context])
@@map("dialplan_entries")
}
enum DialplanVersionStatus {
APPLIED
FAILED
ROLLED_BACK
}
model DialplanVersion {
id String @id @default(uuid())
generatedConfig String @map("generated_config")
status DialplanVersionStatus
reloadResult String? @map("reload_result")
createdById String? @map("created_by_id")
createdAt DateTime @default(now()) @map("created_at")
@@map("dialplan_versions")
}