Fiz uma leitura extremamente criteriosa da v5.9, olhando não só a intenção arquitetural, mas também a consistência documental, a executabilidade técnica, a estabilidade operacional e o risco real de implementação equivocada.
Minha conclusão, de forma direta, é esta:
a v5.9 melhorou de forma relevante sobre a v5.8, mas ainda não está no ponto que eu chamaria de versão final blindada.
Ela já está muito próxima disso, porém ainda restam alguns pontos que, sob a ótica de arquitetura de software e operação em produção, eu trataria como obrigatórios antes de carimbar como “ótima”.
Minha nota atual seria algo em torno de 9,6 de 10.
O que mudou de verdade nesta versão foi bom. O problema é que alguns resíduos conceituais da v5.8 continuam vivos dentro da v5.9, e agora eles aparecem mais como inconsistências entre trechos do documento do que como ausência de conteúdo.
1. O que realmente ficou melhor
Antes das críticas, vale registrar que houve avanços claros.
1.1 Consolidação dos arquivos externos
A redução de 7 arquivos para 4 faz sentido arquiteturalmente. Você reduziu dispersão, simplificou a governança e deixou mais claro o papel de cada artefato.
1.2 Versionamento automático por hash
A direção é correta. Remover dependência manual de TAXONOMY_DICT_VERSION é um ganho real de robustez operacional.
1.3 PASSO 3 do CRON deixou de ser placeholder
Isso era uma lacuna séria e agora existe uma direção real de execução.
1.4 Correção de alias_of
A limpeza semântica do enum foi importante.
1.5 Query de confidence_median
Trocar filtro por UUID e incluir pending_review foi uma correção boa e necessária.
1.6 Cobertura de Estagiário e Estagiária
Foi um ajuste pequeno, mas tecnicamente correto e importante para não perder sinal semântico.
Ou seja, a v5.9 não é uma revisão cosmética. Ela avançou de verdade.
2. Os bloqueadores que ainda ficaram abertos
Aqui estão os pontos que eu considero mais sérios.
2.1 O versionamento automático ficou conceitualmente melhor, mas documentalmente ainda está contraditório
Esse é o principal problema da v5.9.
Você diz que:
-
TAXONOMY_DICT_VERSIONfoi removida -
o sistema virou a fonte de verdade
-
o campo
versioninterno dos JSONs deixa de ser mantido manualmente
Só que, ao mesmo tempo:
-
o schema zod ainda exige
version -
todos os exemplos dos 4 arquivos continuam com
"version": "1.2" -
a seção “Versão atual” ainda mostra
dict 1.2 -
o texto continua tratando a versão do dicionário como algo estático e humano legível
Isso cria uma contradição de implementação.
Hoje a spec está dizendo duas coisas ao mesmo tempo:
-
a versão é automática e derivada por hash
-
a versão continua vivendo dentro dos arquivos e sendo exibida manualmente
As duas abordagens não coexistem bem sem uma regra explícita.
Minha recomendação
Escolher uma só verdade.
A solução mais limpa seria:
-
remover o campo
versiondos arquivos JSON -
remover
versiondo schema zod -
persistir o hash e a versão derivada em uma estrutura própria do banco
-
deixar claro que o número de versão apresentado ao sistema é derivado e não autoral
Se quiser manter version dentro do arquivo por legibilidade humana, então precisa dizer explicitamente que:
-
ele é apenas informativo
-
ele não é usado como fonte de verdade
-
o loader pode até validá lo, mas não confiar nele
Hoje isso ainda não está fechado.
2.2 O uso de job_runs.metadata reabre uma dependência estrutural que a própria spec vinha reduzindo
Esse ponto é importante.
Ao longo da arquitetura, você separou bem:
-
curate_runspara histórico operacional da curadoria -
curate_run_itemspara detalhe por vaga
Só que o versionamento automático dos arquivos foi jogado em job_runs.metadata.
Isso traz vários problemas:
-
job_runsnão está formalmente modelado nesta spec como peça central da curadoria -
a shape de
metadatanão foi formalizada -
a retenção desse conteúdo não foi definida
-
a relação entre
job_runsecurate_runsficou conceitualmente torta -
você passa a depender de uma tabela que antes parecia estar saindo do centro da arquitetura da curadoria
Além disso, o próprio código de exemplo busca o último run em job_runs, não em curate_runs, o que fragiliza a rastreabilidade.
Minha recomendação
Criar uma estrutura dedicada, algo como taxonomy_revisions ou taxonomy_versions, com:
-
id -
dict_version -
hash_bundle -
files_payload -
created_at -
created_by_run_id
Isso deixa o versionamento:
-
transacional
-
auditável
-
independente do legado de
job_runs -
semanticamente alinhado ao problema
Hoje a solução funciona como ideia, mas ainda está encaixada na peça errada.
2.3 A rotina de hash tem risco de corrida e promoção de versão não determinística
A pré rotina detectDictChanges() ainda não está pronta para concorrência real.
O problema é simples:
-
dois runs podem começar quase ao mesmo tempo
-
ambos leem o mesmo último estado
-
ambos detectam mudança
-
ambos geram a mesma próxima versão
-
ambos persistem resultados em paralelo
Dependendo da ordem de gravação, você pode ter:
-
mesma versão representando snapshots diferentes
-
mesma mudança registrada duas vezes
-
competição entre Fluxo A e Fluxo B por avanço de versão
Outro ponto: o exemplo de código fala em buscar o último run “bem sucedido”, mas a query demonstrada só exclui running. Isso inclui error e partial, o que contradiz o texto.
Minha recomendação
O avanço de versão precisa ser serializado.
A forma mais segura seria:
-
criar tabela própria de revisões
-
calcular o bundle hash de todos os arquivos
-
verificar se esse bundle já existe
-
se existir, reutilizar a versão existente
-
se não existir, criar nova versão em transação única com lock
Ou seja, a versão deveria nascer do snapshot, e não de uma contagem oportunista feita no começo de qualquer run.
2.4 curation_status continua sem dominar o documento inteiro
Esse continua sendo o maior problema semântico do pipeline.
A v5.9 afirma corretamente que curation_status é a fonte de verdade. Mas vários trechos críticos continuam usando a lógica antiga:
-
query de cache ainda usa
low_quality = false -
índice de pending ainda usa
skills IS NULL AND low_quality IS NULL -
observação de race condition do Fluxo B ainda usa a lógica antiga
-
relatórios continuam filtrando por
low_quality -
o próprio texto de Fluxo A ainda descreve pendência com os critérios antigos em alguns pontos
Isso não é só ruído editorial. Isso cria risco real de duas implementações coexistirem.
Minha recomendação
Fazer uma varredura total e padronizar assim:
-
fila de curadoria usa
curation_status -
cache usa
curation_status -
relatórios operacionais usam
curation_status -
índices parciais usam
curation_status -
low_qualityfica declarado apenas como compatibilidade legada
Hoje a spec ainda mistura os dois modelos.
2.5 retryable_error continua sem caminho formal de reentrada automática
Esse ainda é um bloqueador real.
A busca de pendentes está definida como:
WHERE curation_status = 'pending'
Só que falhas transitórias movem a vaga para:
retryable_error
Resultado:
a vaga falhou, deveria voltar para a fila, mas o próprio pipeline deixa de buscá la.
O CRON PASSO 3 só trata quarentena por mudança de versão. Não trata retryable_error.
Minha recomendação
Escolher uma das duas abordagens e documentar de forma única:
-
ou a fila busca
IN ('pending', 'retryable_error') -
ou existe um job explícito de requeue que converte
retryable_errorparapending
Enquanto isso não for resolvido, o comportamento real diverge da intenção da spec.
2.6 O caminho de gravação em job_postings continua incompleto
O trecho [CÓDIGO — Atualizar job_postings] ainda não grava explicitamente:
-
curation_status -
reset de
llm_parse_fail_countem sucesso -
last_curation_error_type -
is_internshipem reingestão ou em persistência normal -
limpeza de quarentena após sucesso
-
atualização coerente entre sucesso, fallback, low_quality e erro transitório
Hoje você descreve bem o que fazer em falha e quarentena, mas não fechou a matriz completa de persistência do caminho feliz.
Minha recomendação
Adicionar uma matriz explícita do tipo:
-
status='success'oupending_review→curation_status='curated' -
status='fallback'→ decidir se continuacuratedou se ganha status próprio operacional -
status='low_quality'→curation_status='low_quality' -
status='failed'transitório →curation_status='retryable_error' -
quarentena →
curation_status='quarantined_llm_output'
Hoje esse fechamento ainda não está completo.
2.7 circuit_breaker_state continua aparecendo como conceito, não como schema
Na v5.8 você já tinha anunciado a persistência do circuit breaker. Na v5.9 isso continua sem schema formal.
Não vi aqui:
-
migration da tabela
-
colunas
-
regra de abertura
-
regra de fechamento
-
janela de observação persistida
-
cooldown efetivo
-
estratégia de leitura por múltiplas instâncias
Isso é um buraco importante porque o circuit breaker é peça de confiabilidade, não detalhe secundário.
Minha recomendação
Adicionar seção formal com tabela própria, por exemplo:
-
breaker_name -
state -
window_started_at -
window_failures -
window_total -
opened_at -
cooldown_until -
last_transition_at
Sem isso, a solução ainda está só na intenção.
2.8 similarity_score continua sendo mencionado sem existir no schema
Você ainda escreve:
“registrar similarity_score no curate_run_items”
Mas ele não existe no schema da tabela.
Esse é um problema objetivo.
Minha recomendação
Adicionar explicitamente:
similarity_score NUMERIC(5,4)
Sem isso, a spec manda persistir algo que a migration não suporta.
2.9 A distinção entre batch_output_parse_error e item_output_parse_error ainda não foi refletida no modelo persistido
A ideia está correta e madura. Mas o schema continua sem espelhar isso.
Hoje o enum de error_type continua com llm_output_parse_error genérico.
Então a spec pede uma distinção operacional que o banco não consegue representar claramente.
Minha recomendação
Ou você:
-
adiciona dois tipos distintos ao enum
ou
-
diz explicitamente que essa distinção é apenas lógica interna e nunca é persistida
Hoje ainda ficou ambíguo.
3. Inconsistências novas ou remanescentes
3.1 domains.json foi simplificado, mas a lógica antiga ainda aparece no pipeline
Você removeu is_idiomatic e is_compound_valid, mas ainda restaram referências a eles no texto.
Exemplos claros:
-
o fluxo de validação ainda pergunta sobre
is_compound_valid -
a gramática ainda fala em domínios adjetivais não idiomáticos
-
o documento ainda raciocina em cima de uma semântica que a nova versão declarou removida
Isso precisa ser limpo.
3.2 family_synonyms.json introduziu uma família que não existe na lista fechada
Você colocou:
"Assessor": "Assessor"
Só que Assessor não está na lista oficial de famílias válidas.
Isso quebra a coerência da taxonomia fechada.
Minha recomendação
Ou:
-
adiciona
Assessorao conjunto fechado com regra própria
ou
-
remove essa entrada
Do jeito que está, o arquivo contradiz a gramática oficial.
3.3 As referências a arquivos antigos não foram totalmente removidas
Ainda encontrei resíduos de nomenclatura antiga, como:
-
siglas.json -
equivalencias.json -
cross_domain_map.json
Eles aparecem em comentários, exemplos, notas de governança e até em trechos de lógica.
Isso é perigoso porque abre espaço para implementação híbrida.
3.4 O prompt ainda continua duplicando mapeamentos exatos
A tabela inicial diz que os mapeamentos explícitos saíram do prompt. Mas no P9 eles continuam listados de forma concreta.
Mesmo com a nota dizendo que são “referência semântica”, na prática continuam sendo mapeamentos literais dentro do prompt.
Isso enfraquece o princípio:
o prompt ensina a pensar, o JSON guarda o que saber.
Minha recomendação
Deixar no prompt apenas o princípio e 1 ou 2 exemplos ilustrativos, não a lista inteira.
3.5 A seção mensal ainda cita cross_domain_map.json
Esse é um resíduo objetivo da versão anterior.
Se o arquivo foi absorvido por domains.json, ele não deveria mais aparecer em governança mensal.
4. Problemas de integridade de dados que ainda merecem correção
4.1 source em job_canonical_roles ainda não está NOT NULL
Hoje está como default, mas sem NOT NULL.
Eu colocaria NOT NULL sem hesitar.
4.2 Falta garantir rejected_reason quando status='rejected'
Isso está dito semanticamente, mas não está fechado como constraint.
Eu adicionaria:
CHECK (status != 'rejected' OR rejected_reason IS NOT NULL)
4.3 Falta impedir auto referência em alias_of_id e merged_into
Hoje um canônico pode, em tese, apontar para si mesmo.
Mesmo que a aplicação nunca faça isso, eu barraria no banco.
4.4 Falta garantir que destino de alias_of e deprecated seja semanticamente válido
Idealmente:
-
alias_of_iddeve apontar para um canônicoactive -
merged_intodeve apontar para um canônicoactive
A spec hoje descreve isso conceitualmente, mas não fecha a integridade.
5. Problemas operacionais e métricos
5.1 O recálculo de distinct_sources_count continua estruturalmente frágil
Esse problema ainda não foi resolvido.
O PASSO 2 do CRON faz join de job_canonical_role_sources com job_postings apenas por canonical_role_id.
Isso não garante que a empresa contada seja exatamente a mesma empresa que ainda possui vaga ativa naquela janela.
Em outras palavras:
você pode recontar fontes históricas de um canônico só porque existe alguma vaga ativa daquele canônico.
Minha recomendação
A solução correta passa por uma destas opções:
-
armazenar
normalized_companytambém emjob_postings -
vincular a evidência da source a uma vaga específica
-
recalcular por
last_seen_atconfiável e vinculado ao posting
Do jeito atual, o recálculo ainda pode super contar.
5.2 partial continua com regra estreita demais
Hoje continua assim:
partial = failed > 0 AND curated > 0
O problema continua igual:
-
se houver apenas
fallbacke falha, semcurated, o run não é claramentepartial -
se todas as vagas falharem por timeout, mas sem falha estrutural de banco, a classificação fica ambígua
Minha recomendação
Redefinir assim:
-
successquandofailed = 0 -
partialquando houver mistura de resultado útil e falha técnica -
errorquando nenhuma vaga puder ser processada por falha sistêmica real
Hoje a semântica ainda está mais apertada do que o comportamento operacional pede.
5.3 A query “Throughput por hora” continua contando runs, não batches
Ela usa curate_runs, mas chama a contagem de “batches”.
Está metodologicamente errada.
5.4 A query de “Cobertura de cache” ainda não mede hit rate real
Ela mede volume manual versus volume via API.
Isso é uma métrica útil, mas não é cobertura de cache no sentido técnico.
Para cache de verdade, faltam eventos como:
-
cache_hit -
cache_miss -
fantastic_called -
fantastic_returned_zero -
chave de busca normalizada
5.5 O checklist de deploy ainda não incorporou tudo que a própria v5.9 exige
Você acrescentou a variável EMBEDDING_SIMILARITY_REPLACE_THRESHOLD, o que foi bom.
Mas ainda faltam itens explícitos para:
-
circuit_breaker_state -
estrutura de versionamento de taxonomia
-
revisão completa de renome de arquivos
-
validação de ausência de conceitos removidos como
is_compound_valid
6. Questões de modelagem e implementação que ainda merecem endurecimento
6.1 O schema zod de domains.json ainda aceita combinações semanticamente inválidas
Hoje ele permite coisas como:
-
is_prohibited=truesemprohibited_reason -
canonical="X"junto comis_prohibited=true -
canonicalapontando para si mesmo -
redirecionamentos em cadeia não documentados
Minha recomendação
Adicionar superRefine para impor:
-
is_prohibited=trueexigeprohibited_reason -
is_prohibited=truenão pode coexistir comcanonical -
canonicalnão pode gerar ciclo -
redirecionamento deve ser de um salto só, ou então a resolução precisa ser documentada com detecção de ciclo
6.2 O lookup de equivalences.json ainda precisa de regra lexical mais explícita
Você escreve que a sigla é expandida antes de ruído geográfico, mas ainda não ficou suficientemente formalizado se o matcher é:
-
exato no título inteiro
-
por token
-
por expressão
-
por fronteira de palavra
-
com precedência contextual
No caso de AM, isso é especialmente crítico.
Sem esse fechamento, um desenvolvedor pode implementar do jeito errado e reabrir o bug.
6.3 O reingestion ainda continua resetando menos do que deveria
O bloco de ON CONFLICT melhorou, mas eu ainda resetaria também:
-
seniority_inferred -
low_quality -
last_curation_error_type -
quarantined_at -
quarantine_reason -
quarantined_at_prompt_version -
quarantined_at_dict_version -
is_internship
Se a vaga foi republicada com conteúdo novo, o estado de curadoria antigo deve morrer por completo.
7. Meu veredito final sobre a v5.9
A v5.9 está melhor que a v5.8. Isso é inequívoco.
Mas, sob uma ótica de arquitetura madura, ainda não é a versão final ótima por cinco razões principais:
-
o versionamento automático ainda está contraditório e mal ancorado estruturalmente
-
curation_statusainda não dominou toda a spec -
retryable_errorainda não volta automaticamente para a fila -
há conceitos removidos que continuam vivos em vários trechos
-
ainda faltam peças formais importantes, como
circuit_breaker_stateesimilarity_score
Minha classificação
Aprovada para staging forte e shadow mode sério.
Ainda não aprovada como spec final definitiva de produção plena sem mais uma rodada de endurecimento.
O que eu trataria como obrigatório antes de chamar de versão final
-
fechar o desenho de versionamento automático em estrutura própria
-
migrar toda a spec operacional para
curation_status -
resolver o fluxo de reentrada de
retryable_error -
formalizar
circuit_breaker_state -
adicionar
similarity_score -
limpar todos os resíduos de arquivos e flags removidas
-
corrigir o recálculo de
distinct_sources_count -
endurecer constraints de integridade em
job_canonical_roles -
fechar a persistência completa do caminho feliz em
job_postings