REF-202-03: Parent-Child Hierarchy
ADR: ADR-202 — Entitlement: Agregador CentralEscopo: Campo parent_entitlement_id, profundidade, materialized path, créditos compartilhados, expiração independente, desvinculação, hooks
1. Estrutura
| Aspecto | Detalhe |
|---|---|
| Campo | parent_entitlement_id (FK nullable para Entitlement) |
| Quem pode ser pai | Qualquer entitlement. Caso principal: SVC (contrato-âncora), ENV (agrupa PLGs) |
| Profundidade | Default: 3 níveis. Configurável via POLICY. |
| Validação | Detecção de ciclos (anti-loop) obrigatória. Rejeição se profundidade excede limite. |
| Persistência | Materialized path (/SVC-001/ENV-002/PLG-003) para queries eficientes |
2. Exemplo Multinível
Organization: Cliente X
│
└── SVC-2026040001 (Gestão de Projetos — contrato-âncora, nível 1)
├── ENV-2026040002 (Moodle produção, nível 2)
│ └── PLG-2026040003 (Licença Connector, nível 3 — máximo default)
├── ENV-2026040004 (Moodle homologação, nível 2)
│ └── PLG-2026040003 (mesma licença, ativada em 2 ENVs, nível 3)
└── SVC-2026040005 (Projeto custom pontual, nível 2, lifecycle: project)3. Materialized Path
Stored em meta do entitlement para queries eficientes sem recursive CTEs em wp_posts.
| Entitlement | materialized_path | Nível |
|---|---|---|
| SVC-2026040001 | /SVC-2026040001 | 1 |
| ENV-2026040002 | /SVC-2026040001/ENV-2026040002 | 2 |
| PLG-2026040003 | /SVC-2026040001/ENV-2026040002/PLG-2026040003 | 3 |
Queries:
- "Todos os filhos de SVC-001" →
WHERE materialized_path LIKE '/SVC-2026040001/%' - "Profundidade de PLG-003" → contar separadores no path
Manutenção: Path recalculado quando entitlement é vinculado/desvinculado.
4. Créditos Compartilhados
| Regra | Descrição |
|---|---|
| Flag por filho | use_parent_credits (boolean, default: true) |
| Quando true | ServiceRequests do filho consomem CreditBalance do pai (SVC) |
| Quando false | Filho precisa de créditos/billing próprio |
| Sem CreditBalance no pai | Flag ignorada — SR do filho é billing avulso |
Reserva e Consumo
| Etapa | Comportamento |
|---|---|
| Abertura de SR | Créditos estimados reservados (soft-lock) |
| Conclusão de SR | Créditos reais consumidos (débito efetivo). Diferença ajustada. |
| Cancelamento de SR | Reserva liberada integralmente |
| Limite excedido | Bloqueio se consumo ultrapassa limite configurável (CreditPolicy) |
| FIFO | Créditos mais antigos consumidos primeiro |
5. Expiração Independente
| Regra | Comportamento |
|---|---|
| Cada entitlement tem sua expiração | Pai expira dez/2026, filho PLG pode expirar mar/2027 |
| Pai expira ou cancela | Filhos são desvinculados (parent_entitlement_id = null), NÃO expirados |
| Filho com lifecycle próprio | Continua ativo (ex: PLG com Stripe subscription ativa) |
| Filho sem lifecycle próprio | Fica órfão — notificação ao admin |
| Materialized path | Recalculado nos filhos desvinculados |
6. Operações
| Operação | Quem | Validação |
|---|---|---|
| Vincular filho | Admin / Auto | Profundidade ≤ max, sem ciclos, mesma org |
| Desvincular filho | Admin | Sempre permitido. Path recalculado. |
| Mover filho (troca pai) | Admin | Profundidade nova ≤ max, sem ciclos, mesma org |
| Auto-vincular (order) | Sistema | SVC no order → demais items viram filhos |
7. Hooks
| Hook | Parâmetros | Quando |
|---|---|---|
middag_entitlement_orphaned | $child, $former_parent | Pai expirou/cancelou, filho desvinculado |
middag_entitlement_linked | $child, $parent | Filho vinculado a pai |
middag_entitlement_unlinked | $child, $former_parent | Filho desvinculado de pai |