feat: scope Editor app administration by group (#990 slice 1) - #1046
Merged
Conversation
…#990 slice 1) The slice caught a real leak: the snapshot cache key was locale-only, so a scoped Editor could be served another team's container rows. Its fix was to skip the cache entirely for scoped Editors — correct, but it throws away the reason the cache exists. That cache is #291: building a snapshot reads the registry, queries the effective catalog and assembles the rows, and N dashboard tabs each poll on their own 5 s interval. Without it, every Editor tab rebuilds every tick — the same many-tabs pressure that made the admin hang behind an HTTP/1.1 front end in #1039/#1040. Keyed by `(locale, scope)` instead: the unscoped Admin/break-glass view shares one entry, two Editors with the same group set share theirs, and no one can read another scope's. The scope key sorts and dedups the group list, since `["a","b"]` and `["b","a"]` are the same scope and must not split the cache, and joins on the unit separator so a group literally named `a,b` cannot forge the key of `["a","b"]`. A test pins all of that. The cache map's type is factored into `SnapshotCacheKey` / `CachedSnapshot` — clippy's `type_complexity` fails the gate otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fatia 1 de 5 da #990, a maior delas. Implementada por agente
codex exec(gpt-5.6-sol, high); diff e gate revisados e reexecutados por mim, com uma correção minha no caminho do cache (e92c2ad).O que muda
O escopo por grupo do Editor passa a valer nas Aplicações:
specs.rs,spec_form.rse as réplicas dodashboard.rs. Apps abertos seguem Editor-globais, como você decidiu. Admin e sessão de token seguem irrestritos.images.rsficou de fora de propósito — mídia é biblioteca compartilhada, não tem dono.Rota fora do escopo responde 404, não 403, para não vazar a existência do app.
O buraco que a revisão da fatia 0 tinha antecipado
specs.rs::indexnão iteraSpec: faz o SELECT enxuto do #588 e depois enriquece as linhas com metadados do catálogo efetivo. Um id ausente desse mapa deixaaccess_groupsvazio, e o app restrito pareceria aberto — que, sendo app aberto Editor-global, seria bypass silencioso com o teste feliz verde.Tratado nos dois caminhos da listagem (linhas do banco e linhas "config-defined" do YAML): sem resolver no catálogo efetivo, o Editor escopado não vê. Nas mutações a autorização usa
db::specs::fetch_one, a fonte autoritativa do spec no banco.Vazamento que o agente achou, e a correção que eu fiz por cima
Achado dele: a chave do cache do snapshot do dashboard era só o locale — reaproveitá-la para Editor escopado serviria as linhas de um time para outro. Vazamento entre times via cache.
A correção dele foi desligar o cache para escopados. Correta, mas joga fora a razão de o cache existir: o #291 existe porque construir um snapshot lê o registry, consulta o catálogo efetivo e monta as linhas, e cada aba faz polling no seu próprio intervalo de 5 s. Sem cache, toda aba de Editor reconstrói a cada tick — exatamente a pressão de múltiplas abas que travou a admin atrás de front HTTP/1.1 no #1039/#1040.
Minha correção (
e92c2ad): chave(locale, escopo). Admin/break-glass compartilham uma entrada, dois Editores do mesmo conjunto de grupos compartilham a deles, e ninguém lê a de outro escopo. A chave de escopo ordena e deduplica (["a","b"]e["b","a"]são o mesmo escopo e não devem partir o cache) e junta pelo separador de unidade, para um grupo chamadoa,bnão forjar a chave de["a","b"]. Teste prende os três casos.Enumeração de rotas: conferida, não aceita de boa-fé
O teste
scoped_editor_gets_404_on_every_foreign_app_or_replica_id_routelista 7 rotas de app e 4 de réplica. Comparei com o roteador: são exatamente 11 as rotas com id nesses três arquivos (edit,duplicate,POST /admin/specs/{id},delete,repull,state/toggle,featured/toggle,logs,logs/stream,stop,restart). Cobertura exaustiva.Outras verificações da revisão
may_assign_groups+may_touch_specsobre o candidato); app aberto passa poris_open.AdminSessionpara preservar o redirect do Viewer para/(Viewer role: drop admin-panel access — only change-password + logout #857) em vez de trocá-lo por 403.scoped_replica_specresolve o spec dono antes de qualquer acesso ao backend e falha fechado quando o metadado não resolve.scope.rs, +10 linhas): sófrom_editorvirandopub(crate)e um helperactor(). Nenhum predicado foi afrouxado.Gate reexecutado por mim:
cargo testlimpo,clippy --all-targets -- -D warningssem warning,i18n-checkOK.🤖 Generated with Claude Code