Questões em aberto
Decisões adiadas com o raciocínio preservado, para não serem re-discutidas do zero — e limitações conhecidas que ainda não têm solução.
Extensão de contrato
Seção intitulada “Extensão de contrato”A pergunta. Quando uma implementação oferece mais do que o contrato descreve, o que acontece?
Descartado: extra-args. A v0 tinha uma bag livre ao lado dos argumentos. Não é tipada, não é validada, não aparece no contrato — trocar de implementação pode quebrar o pipeline em silêncio. Perde substituibilidade e verificabilidade de uma vez.
Direção escolhida: extension publicável por terceiros, que é, com precisão, herança múltipla limitada a um nível:
apiVersion: oren.sh/v1kind: TaskExtensionmetadata: name: signing namespace: security-co # outra organização, sem pedir nada à dona do contratospec: extends: techlite/build-docker-image@^1.0.0 inputs: signingKey: { type: string, secret: true } outputs: signature: { type: string }Duas restrições fazem a diferença:
- Profundidade 1. Uma extension não pode ser estendida.
- Só adiciona. Não remove output nem restringe input, então toda implementação continua satisfazendo o contrato base.
Com isso, uma task recebe N extensions em vez de gerar 2ⁿ contratos, e a org B estende o contrato da org A sem permissão.
O argumento que NÃO decide. Substituibilidade (“tudo que vale para a filha vale para o pai”) vale nos dois modelos. Numa herança irrestrita, uma implementação da filha satisfaz o pai; com extension, a implementação é do contrato base e a substituibilidade é trivial. Quem decide é a governança: registry aberto exige extensão descentralizada, e profundidade limitada evita a explosão combinatória.
Por que adiar. Os onze contratos escritos a partir de um pipeline real não precisaram de nenhuma extension — o extra-args inteiro dissolveu em decomposição e inputs de primeira classe. Construir o mecanismo antes de um caso real é adivinhar a forma dele.
Gatilho para retomar. Um caso que o teste do “se eu trocar de implementação e esse valor sumir, o output muda?” não classifique.
Composição de tasks
Seção intitulada “Composição de tasks”O incômodo. Um pipeline de release tem cinco steps: analisar, versionar, construir, publicar, taggear. Repetido em vários projetos, dá vontade de agrupar.
Duas formas foram consideradas e rejeitadas.
A implementação roda o Oren por dentro. Traz o dagger-in-dagger de volta:
exigiria engine/docker, privilégio crítico — exatamente o que foi eliminado de
toda a cadeia de imagem.
Task composta declarativa — um contrato que agrupa outros. O problema é mais
sutil e mais grave: ela esconde justamente o que o projeto existe para tornar
visível. Uma release-node declararia “preciso de gcpCredential, gitToken e
um diretório mutável”, e some qual parte precisa de quê. O consentimento vira
tudo-ou-nada, o cache vira tudo-ou-nada, effects vira o pior caso de todos os
passos, e uma falha reporta “a composição falhou”.
A direção escolhida: template de pipeline. Steps reutilizáveis que a CLI expande na resolução, em vez de encapsular em runtime:
steps: - use: oren/release-node@^1.0.0 with: imageName: acme/apiIsso vira quatro steps de verdade antes de qualquer execução. A partir daí tudo
funciona como já funciona — consent granular, cache por step, effects por step,
privilégio visível por step, e a falha aponta o step real.
A distinção que decide. Composta, a abstração vive em runtime e apaga informação. Expandida, vive só na escrita, e o resultado é idêntico a ter digitado os steps à mão.
Quando a composta seria legítima. Se o agrupamento for uma abstração real, e
não encurtamento — algo como deploy-blue-green, que conceitualmente é uma
operação. Aí a opacidade é o produto, e declarar a união das dependências é o
preço aceito por ela.
O teste: o usuário pensa nisso como uma coisa, ou como N coisas que ele sempre faz juntas? Se é a segunda, é template.
Gatilho para retomar. O mesmo conjunto de steps repetido em três ou mais projetos — aí vale o template. Uma operação que ninguém pensa como sequência — aí vale discutir a composta.
O papel do .oren/registry/ vai inverter
Seção intitulada “O papel do .oren/registry/ vai inverter”Hoje. O oren.yaml referencia tasks por nome e versão — é o que torna o
pipeline portável, já que um caminho de disco não sobrevive a outra máquina nem
a um runner de CI. O registry local é a camada que resolve nome → documento, e
sem portal ele é a fonte: oren add copia os arquivos para lá, e o oren.lock
é derivado deles. Por isso precisa ser commitado — apagá-lo quebra o projeto
mesmo com o lockfile intacto.
Com o portal, a direção inverte. O paralelo com npm deixa claro:
| npm | Oren hoje | Oren com portal |
|---|---|---|
package.json |
oren.yaml |
oren.yaml |
package-lock.json |
oren.lock (derivado) |
oren.lock (fonte da verdade) |
node_modules/ |
.oren/registry/ (fonte) |
.oren/registry/ (cache derivado) |
oren install passará a buscar os contratos do portal e materializá-los, e o
registry vira cache: gitignorado, descartável, reconstruível — como
node_modules.
O caso que sobrevive à inversão. Tasks locais do projeto, que não vale a pena
publicar, continuam sendo fonte e não cache. Elas provavelmente precisam de um
lugar separado do cache — senão um install que limpe o cache apagaria código
sem cópia em lugar nenhum.
Desenho ainda não decidido. Uma possibilidade é .oren/cache/ (derivado,
gitignorado) ao lado de tasks/ na raiz do projeto (fonte, commitado). O
resolver já trata as duas origens da mesma forma, então a separação é de
convenção e ciclo de vida, não de mecanismo.
Gatilho. O primeiro oren install que busque do portal. Antes disso a
inversão não tem como acontecer.
network não é enforçado
Seção intitulada “network não é enforçado”O tipo existe no registry, mas o motor de execução dá rede a todo container e não expõe controle por execução. Um worker que omita network continua alcançando a internet.
É a única lacuna do núcleo — todas as outras dependências são concedidas materializando algo, e omiti-las realmente quebra o worker. O registry marca com enforced: false, para que a diferença entre declarado e garantido fique no dado e não só na documentação.
Enquanto isso não mudar, network serve para o portal exibir e comparar, não como garantia.
Origem image não implementada
Seção intitulada “Origem image não implementada”O spec prevê apontar uma imagem OCI diretamente, com o contrato lido dos labels sh.oren.*. A CLI ainda recusa com mensagem explicando.
É o que tornaria a imagem auto-descritiva e permitiria validar sem portal. Depende de definir a convenção dos labels.
Retomada de execução
Seção intitulada “Retomada de execução”--resume existe, e no CI depende do cache da plataforma. Ver Rodando no CI.
O que continua em aberto — o cache dentro da fronteira de confiança. O estado restaurado decide que steps são pulados e com que saída, e o cache de um projeto de CI costuma ser gravável por qualquer job dele. A revisão gravada impede atravessar commits, mas não impede que outro job escreva no cache deste commit.
Fechar isso pede assinar o estado — chave de assinatura no CI, e quem a guarda. É outro projeto, e por isso --resume é opcional e o arquivo gerado sai com o aviso.
Restrições registradas: steps que produzem output secreto não são retomáveis, porque o valor não é persistido. E steps que escrevem na RAIZ do projeto impedem o cache, porque cachear a raiz restauraria .oren/registry/ e .oren/consent.json de outra execução.
Geração de CI: limitações conhecidas
Seção intitulada “Geração de CI: limitações conhecidas”jq obrigatório no modo nativo. Quando um input vem de outro step, o job usa jq para compor o input.json. A imagem do worker precisa tê-lo. No modo oren isso não se aplica: quem compõe é a CLI.
Validação por plataforma. O GitLab tem a saída conferida contra o schema oficial, e o Cloud Build em --mode oren rodou de ponta a ponta num projeto real — build, publicação e deploy. GitHub Actions e o --mode nativo do Cloud Build continuam sem execução real.
Rodar de verdade encontrou três defeitos que nenhuma leitura de documentação teria achado: diretório mutável que não existia na cópia limpa, refspec em checkout destacado, e credencial de git herdada do CI vencendo a do worker. É a razão de não escrever gerador para plataforma que ninguém vai exercitar.
properties não é suportado no modo nativo. Arquivos de propriedades são resolvidos antes da execução e não existem no runner. No modo oren eles funcionam: quem lê é a CLI, dentro do job.
Outras plataformas de CI
Seção intitulada “Outras plataformas de CI”Jenkins, Azure DevOps e Bitbucket não têm gerador, e a decisão de 2026-08-10 foi deixar em espera.
O oren run já funciona nos três: no modo oren o arquivo é fino — um job rodando a imagem do Oren com daemon Docker, segredos como variáveis e o repositório em disco.
O que pesa contra escrever geradores: cada um é dívida permanente, e sem alguém que execute de verdade ele é uma promessa que quebra na casa de outra pessoa.
Azure DevOps é o candidato mais fácil: YAML de forma parecida com o GitHub, Cache@2 nativo (então --resume funcionaria).
Bitbucket tem o melhor encaixe conceitual — ele já tem seções por branch, que é o mapa branches → propertyFile. Mas o serviço Docker dele tem 1 GB por padrão e teto de ~3 GB, e aumentar o tamanho do step não aumenta a memória do serviço. Se o motor de execução cabe ali é pergunta a medir antes de qualquer código.
Jenkins é o mais caro: Groovy em vez de YAML, sem formato único, e sem cache nativo entre builds.
O caminho de maior retorno é documentar o contrato que um CI precisa cumprir, e deixar quem usa escrever as poucas linhas da plataforma dele.
Não existe. O registry local (.oren/registry/) tem a mesma interface que o cliente do portal vai expor, então trocar a origem não deve mexer no resolver nem no executor.
Decisões de produto já tomadas: registry aberto entre organizações, contratos oficiais curados pela org oren, implementações oficiais marcadas como referência e sem peso no ranking, plano pago para implementação privada com os benefícios do portal (resolução por versão, auto-wiring, auditoria, catálogo interno).
Em aberto: política de curadoria do namespace oren, e o desenho da lista de implementações aprovadas por organização — que precisa aprovar versão, não nome, e funcionar offline pelo lockfile.
Identidade e permissão no portal
Seção intitulada “Identidade e permissão no portal”Base decidida. Modelo próximo ao do npm: usuários, organizações, e membros com papéis. Namespace pertence à organização.
Ajuste sugerido: tudo é organização. Criar conta cria uma organização homônima automaticamente. Evita dois tipos de dono — usuário e org — com regras distintas, que é onde o npm acumulou complexidade. Um usuário solo publica sem pensar em org; quando precisar de time, cria outra e o modelo é o mesmo.
Papéis.
| Papel | Pode |
|---|---|
viewer |
ver conteúdo privado da organização |
publisher |
publicar versões novas e depreciar |
admin |
gerenciar membros, aprovar implementações, transferir namespace |
publisher tem peso permanente. Versão publicada é imutável e não existe
deleção — conceder o papel é conceder poder irreversível sobre o namespace. Vale
a interface dizer isso ao convidar, e talvez confirmação extra na primeira
publicação de um namespace, que é quando o nome fica cravado.
Depreciar é do publisher. Sem deleção, depreciação é o único mecanismo de
correção; quem publica precisa poder corrigir sem passar por admin.
viewer só faz sentido com conteúdo privado. No catálogo aberto tudo é
público. Não é problema — significa que o papel existe para o plano pago.
Namespace precisa de owner explícito e rota de transferência. Como nada é
deletável, um namespace abandonado fica abandonado para sempre — o problema que o
npm colheu com pacotes órfãos. Ter um dono nominal (não apenas “quem tem role
admin”) e um caminho de transferência é barato agora e caro depois.
Aprovar implementações é de admin. A lista do que a organização confia é
governança, não publicação.
Refinamento possível, não agora. Separar “publicar contrato” de “publicar implementação” em papéis distintos — mudar um contrato afeta terceiros, publicar uma implementação afeta menos. Defensável, mas é otimização sem caso real.
Gatilho. Antes do primeiro oren publish. A sequência recomendada é começar
pelo catálogo somente-leitura, que exercita a API inteira sem exigir autenticação
nem permissões — a parte cara vem quando o modelo de leitura já estiver provado.
Identidade de task com portal self-hosted
Seção intitulada “Identidade de task com portal self-hosted”A decisão de produto. O portal é self-hostable e gratuito; a cobrança é pelo uso da instância pública. Coerente com o resto: self-host dá controle, a instância pública dá a rede — e cobrar pela rede é mais honesto que reter funcionalidade.
A implicação que ainda não está resolvida. Uma task é referenciada como
techlite/analyze-commits@^1.0.0 — namespace, nome e range, sem origem. Numa
instalação só isso basta. Com várias instâncias, techlite/build-docker-image pode
ser um contrato numa e outro noutra, e o mesmo oren.yaml produziria builds
diferentes conforme quem executa.
Direção provável, pelo caminho do npm. Separar as três coisas:
- Referência continua
namespace/nome@range— legível e portável. - Origem é configuração do ambiente, não do pipeline (o análogo do
.npmrc). - Lockfile registra de onde cada task veio de fato.
O lockfile já tem o lugar certo para isso: hoje guarda task como
namespace/nome@versão, e passaria a guardar também a origem resolvida. Um
projeto que troque de instância vê o diff.
O que NÃO fazer. Colocar o host na referência do oren.yaml
(portal.acme.com/techlite/build-docker-image). Isso amarra o pipeline a uma
instância e quebra a portabilidade que a referência por nome existe para dar.
Consequência aceita. Cada self-host é uma ilha: o reaproveitamento entre organizações só acontece dentro de uma instância. Federação entre instâncias seria a alternativa, e é bem mais máquina. É o mesmo trade-off já aceito para contrato privado — você troca rede por controle.
Gatilho. Antes do portal aceitar publicação de terceiros. Depois disso, mudar
a forma da referência quebra oren.yaml de todo mundo.
MCP para construção de pipeline
Seção intitulada “MCP para construção de pipeline”Ideia registrada: o portal expõe um MCP que devolve contratos e implementações de uma organização, para um modelo montar o oren.yaml. Recorte definido: somente leitura, execução local — o modelo sugere, o usuário executa.
O que já favorece isso: os contratos são JSON Schema, que é o formato que modelos consomem para tool use; o validate é um verificador determinístico que roda em um segundo; e o consentimento continua sendo do usuário, no terminal dele.
Cuidado registrado: descrições de contratos vêm de organizações terceiras. São dados, não instruções.
Visão unificada de execuções
Seção intitulada “Visão unificada de execuções”A ideia. A CLI reporta o andamento ao portal, e você acompanha pipelines num lugar só — em vez de abrir a interface do GitLab para uns e do Cloud Build para outros.
Por que não é só mais um dashboard. Um CI mostra “job passou em 4s”. O Oren sabe qual task, qual implementação, qual digest executou, qual privilégio foi concedido, o que veio do cache e o que executou de fato. Nenhum CI tem essa informação porque nenhum sabe o que é uma task — e é exatamente o que se quer olhar quando algo dá errado. É consequência natural de o pipeline não pertencer à plataforma: se ele roda em três lugares, olhar três interfaces é o preço que essa visão elimina.
O problema óbvio. Exige rede no CI.
O problema maior, e menos óbvio. O que exatamente sai da máquina. Outputs contêm dados do usuário — nomes de imagem, versões, caminhos, contagens. “Não é secret” não significa “pode ser exfiltrado”. Uma ferramenta de CI que liga para casa por padrão é barrada em muita organização, e com razão.
Desenho que resolve os dois:
- Metadados, nunca conteúdo. Step, duração, resultado, task e versão, digest, privilégio concedido, cache hit. Jamais valores de input ou output.
- Não-bloqueante. Falha de telemetria não falha pipeline. Portal fora do ar não impede um build.
- Opt-in explícito. Nunca padrão.
- Self-hostable. Uma organização que queira a visão sem mandar nada para fora aponta para o coletor dela.
O que já existe. .oren/runs/<id>/ grava manifest e status por step, com
exatamente esses campos. Falta o transporte e a decisão de o quê sai — não o
modelo de dados.
Gatilho. Depois do portal, e depois de a visão de catálogo estar de pé. Antes disso não há onde exibir.
Auto-hospedagem
Seção intitulada “Auto-hospedagem”As tasks node-build e npm-publish existem e foram testadas contra um registry local. Falta apontar para o npmjs com credencial real e fazer o Oren publicar o Oren.
Ovo e galinha resolvido: a primeira publicação de cada pacote é manual; da segunda em diante o pipeline se encarrega.
Encerrar conta e organização
Seção intitulada “Encerrar conta e organização”Uma pessoa tem direito a apagar seus dados, e uma organização a encerrar a conta. Isso precisa existir. O que ainda não está decidido é o que acontece com o que já foi publicado.
A tensão
Seção intitulada “A tensão”A proposta natural — marcar os artefatos como órfãos e removê-los depois de um prazo — colide de frente com uma vez público, sempre público. Remover é a mesma quebra que tornar privado, só com um atraso: o pipeline de terceiros funciona hoje e falha daqui a noventa dias, sem ninguém ter tocado nele.
O precedente é conhecido. Em 2016 um pacote de onze linhas foi removido do npm e derrubou builds no mundo inteiro; a política mudou logo depois, e hoje npm, crates.io e Maven Central não removem versões publicadas. Nenhum deles trata isso como detalhe de implementação — é a promessa central de um registry.
O caminho que parece certo
Seção intitulada “O caminho que parece certo”Separar duas coisas que a proposta junta:
Dado pessoal é apagável, e deve ser. Nome, e-mail, apelido, tokens, histórico de sessão. Isso é o que a LGPD e o GDPR alcançam, e não há motivo para reter.
Artefato publicado permanece, com a autoria anonimizada. O documento não é
dado pessoal: é um contrato publicado para consumo de terceiros, e quem o
consome tem uma expectativa legítima de que continue existindo. publicada_por
vira nulo; o conteúdo fica.
Isso satisfaz o direito ao apagamento sem transferir o custo para quem não escolheu nada.
O que sobra da proposta original, e é bom
Seção intitulada “O que sobra da proposta original, e é bom”Não listar. Um artefato órfão sair da listagem e da busca — aparecendo só com um filtro explícito — desincentiva adoção nova sem quebrar adoção existente. É a diferença entre “não recomendamos” e “não existe mais”, e só a segunda quebra alguém.
Depreciação obrigatória. Ao encerrar, tudo que a organização publicou é marcado como depreciado, com motivo. Quem resolver a versão recebe o aviso, e quem estiver escolhendo uma task vê que ela não tem mais dono.
Transferência. Uma organização que encerra pode passar seus contratos a outra que os assuma. É o melhor desfecho, e vale oferecer antes das outras opções.
O que precisa ser decidido
Seção intitulada “O que precisa ser decidido”- A organização de órfãos é real (uma linha em
organizacao) ou é um estado do contrato? Uma organização de verdade mantém o namespace vivo — e o namespace é o que aparece nas referências, então ele precisa continuar resolvendo. - Um namespace liberado pode ser registrado por outra pessoa? Quase certamente
não: quem pegasse
acmepassaria a poder publicar versões novas deacme/build, e pipelines que usam^1.0.0as receberiam. É sequestro de dependência, e o npm já viveu isso. - Retenção de dado pessoal em backup, e o prazo real para eles saírem de lá.
Cobrança: o starter, e por que o webhook espera
Seção intitulada “Cobrança: o starter, e por que o webhook espera”A cobrança não está no jar comunitário. Ela é um starter Spring separado
(portal-cobranca) que o portal público acrescenta como dependência.
A razão prática vem antes da comercial: regra de cota em código aberto é regra removível. No jar comunitário ela não protegeria nada — só documentaria o que alguém precisaria apagar. E o auto-hospedado não tem a quem cobrar: a compra já aconteceu, por contrato, antes da instalação.
O que ficou em cada lado:
- comunitário —
grupo,grupo_admin,organizacao.grupo_id, a contagem de artefatos privados, e a portaLimiteDePublicacaocujo padrão não faz nada. Não é “ilimitado”: o conceito de limite não existe ali; - starter —
assinatura,evento_de_cobranca, os planos, e a implementação que limita.
O grupo NÃO foi junto, embora exista por causa da cobrança: organizacao.grupo_id
é obrigatório, e agrupar equipes é útil num portal que não cobra de ninguém.
O que falta, e por que está parado. O recebedor de webhook do Paddle não existe. Ele é a parte fácil — o esquema já tem a chave que torna o processamento idempotente e o carimbo que ordena eventos fora de ordem —, mas não pode ser exercitado sem uma conta.
E a conta depende do portal estar no ar: o Paddle é merchant of record, então ele revisa o site antes de aceitar um vendedor. Sem site, sem preço publicado e sem termos, a inscrição não passa. Construir o webhook antes seria escrever contra documentação sem poder rodar — que é exatamente o que produziu os geradores de CI não validados.
Decidido em 2026-08-11: portal primeiro, webhook depois.