Pular para conteúdo

AUR-407 — Perfil SQLite offline (sqlite-offline-v1)

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

Este documento descreve a entrada sqlite-offline-v1 publicada no registry canônico de profiles. O card publica somente documentos: nenhum engine é invocado, nenhum banco é aberto, nenhum schema de state store é definido e o runner permanece fail-closed para esta chave até um card de implementação dependente habilitá-la explicitamente.

Artefatos

Artefato Caminho
Schema .board/schemas/sqlite-offline-profile.schema.json
Profile .board/oci/profiles/sqlite-offline-v1.json
Lock .board/locks/oci/sqlite-offline-v1.lock.json
Registry .board/oci/profiles/registry.v1.json

O registry passa a publicar exatamente sete chaves, em ordem canônica crescente: bootstrap-readonly-v1, fake-provider-v1, go-git-offline-v1, go-unit-offline-v1, parser-worker-v1, registry-v1, sqlite-offline-v1. Nenhuma chave existente muda de digest, schema ou lock.

Imagem pinada

O lock fixa Go e Bash na imagem de bootstrap já presente localmente, por digest imutável:

aurum-bootstrap-go-bash@sha256:503ac356fa6bca4bad56fade87b5479ff371bd446ffb9c7db91f211323c7c73e

A imagem foi escolhida por prova, não por conveniência. Sob --pull=never --network=none --read-only --user=65534:65534 --cap-drop=ALL (mais --security-opt=no-new-privileges e tmpfs limitado) o probe devolve bash 5.2.26, go1.21.13 e uma única interface de rede, lo; sqlite3, libsqlite3, cc/gcc e git estão ausentes dessa imagem. O mesmo probe na imagem golang@sha256:4746d264… — a outra imagem local com Go — devolve git 2.39.2 e libsqlite3.so.0, mas nenhum binário sqlite3 e nenhum compilador C utilizável sob esse perfil.

Isso é o que determina o plano, e não o contrário: sqlite_driver_cgo: false e CGO_ENABLED: "0" fixam um driver SQLite puro Go, de modo que o plano nunca exige o binário sqlite3 nem uma biblioteca nativa. A ausência dessas duas coisas na imagem pinada não é uma lacuna tolerada — é exatamente o que o profile nega. Um driver ligado por cgo é rejeitado com unsafe-plan.

Somente aurum-bootstrap-go-bash@sha256:<64 hex> é aceito. Uma tag mutável (aurum-bootstrap-go-bash:1.21), um nome sem digest ou outro repositório é rejeitado com mutable-input antes de qualquer resolução de imagem.

Plano declarado

Limites e isolamento:

Chave Valor
network none
user 65534:65534
cap_drop / cap_add ALL / none
mounts / devices / sockets none / none / none
pull never
tmpfs rw,nosuid,nodev
checkout_readonly true
read_only_rootfs true
no_new_privileges true
privileged false
timeout_seconds 60
memory_mb 256
cpu_millis 1000
pids_limit 128
tmpfs_mb 128
stdout_limit_bytes / stderr_limit_bytes 65536 / 65536
max_input_files / max_input_bytes 10000 / 67108864
module_cache /go/pkg/mod (module_cache_read_only: true)

Capacidades do state store, todas verificadas pelo loader:

Chave Valor Efeito
sqlite_driver digest-pinned o driver só é admitido content-addressed
sqlite_driver_cgo false driver puro Go: nenhuma lib nativa, nenhum sqlite3
database_mode ephemeral o banco existe apenas durante a execução
database_root /tmp/aurum-sqlite-state o banco vive no tmpfs limitado, nunca no checkout
host_database denied nenhum arquivo de banco do host é aberto
extension_loading denied load_extension e qualquer extensão são negados
remote_uri denied URI remota (file: com vfs=/host, HTTP) é negada
implicit_persistence denied nada sobrevive à execução sem card explícito
attach_database denied ATTACH de outro banco é negado
journal_mode memory nenhum journal/WAL colateral em disco
max_database_bytes 33554432 teto exato (32 MiB) dentro do tmpfs de 128 MiB
max_open_connections 4 conexões simultâneas limitadas
busy_timeout_ms 2000 espera de lock limitada
query_timeout_seconds 5 wall-clock por consulta

sqlite_driver: "digest-pinned" pina a exigência, não os bytes: o driver, o schema do state store, migrações e consultas são não-objetivos deste card e pertencem ao card de implementação dependente. O que este card garante é que um driver sem digest nunca é resolvido.

Os limites são constantes exatas, nunca mínimos: max_database_bytes: 0 e max_database_bytes: 34359738368 são ambos rejeitados, o primeiro por ser ilimitado e o segundo por não caber no tmpfs que teria de segurá-lo. O loader verifica ainda, de forma independente das constantes, que max_database_bytes <= tmpfs_mb * 1024 * 1024 — um banco que não cabe no próprio limite teria de transbordar para fora dele.

database_root é validado por expressão ancorada nas duas pontas (^/tmp/aurum-sqlite-state$), então /workspace/.board e /tmp/aurum-sqlite-state/../../etc são negados pelo mesmo motivo: nenhum dos dois é o diretório limitado.

Comandos permitidos

["go", "test", "-mod=readonly", "./..."]

Qualquer outro comando, ou o mesmo comando com aridade diferente, invalida o plano. O ambiente é fechado sobre exatamente estas chaves:

Variável Valor
GOPROXY off
GOSUMDB off
GONOSUMDB *
GOTOOLCHAIN local
CGO_ENABLED 0
AURUM_SQLITE_DATABASE_ROOT /tmp/aurum-sqlite-state
SQLITE_TMPDIR /tmp/aurum-sqlite-state
HTTP_PROXY (vazio)
HTTPS_PROXY (vazio)
NO_PROXY *

AURUM_SQLITE_DATABASE_ROOT, SQLITE_TMPDIR e database_root são três grafias do mesmo diretório; o teste de integração recusa qualquer divergência entre elas, porque SQLITE_TMPDIR é exatamente onde um arquivo temporário de spill apareceria se apontasse para fora do tmpfs limitado. Nenhuma credencial, valor de ambiente do host ou segredo entra no documento, e o loader recusa qualquer valor com forma de credencial mesmo em campo cujo valor seja pinado por constante.

Códigos de rejeição

O loader (ValidateRegistryAUR407 / ValidateProfileAUR407) é side-effect-free, não abre socket, não abre banco, não cria subprocesso e devolve exatamente um código:

Código Causa
registry-missing registry ausente ou não regular
schema-invalid registry, schema ou profile fora da forma canônica (inclui chave duplicada dentro de um objeto JSON e limite obrigatório ausente, como query_timeout_seconds)
schema-missing documento de schema ausente
schema-digest-mismatch schema_digest não reproduz os bytes do schema
profile-missing documento do profile ausente
profile-unregistered chave desconhecida, ou sqlite-offline-v1 ausente do registry
duplicate-profile a mesma chave publicada duas vezes
order-invalid chaves fora da ordem canônica crescente
credential-present valor de ambiente com forma de credential real
digest-invalid digest vizinho fora do formato sha256:<64 hex>, ou driver SQLite sem digest
unsafe-plan limite obrigatório relaxado ou excedido, tmpfs ilimitado, banco fora do tmpfs, database host, extension loading, URI remota, persistência implícita ou ATTACH habilitados, driver cgo, journal persistente, rede, mount, socket, device, root ou capability
lock-missing lock ausente
mutable-input imagem por tag mutável, repositório inesperado ou lock fora do schema
lock-digest-mismatch lock_digest não reproduz os bytes do lock

As constantes de segurança do schema são pinadas duas vezes: nos bytes do documento de schema e em profileSchemaConstants407 / environmentSchemaConstants407, dentro de tests/unit/AUR-407.go. Relaxar o schema e recomputar o schema_digest no registry move os dois arquivos de forma consistente e ainda assim falha, porque o mapa em Go não se move com eles.

Mutantes provados

O acceptance executa cada mutante contra bytes reais em disco, na cópia descartável, e exige o código declarado; o restore volta byte-idêntico.

Mutante Mudança Código
MUT-001 chave sqlite-offline-v1 duplicada com bytes diferentes duplicate-profile
MUT-002 imagem do lock por tag mutável mutable-input
MUT-003 network de none para host unsafe-plan
mount habilitado mounts para bind unsafe-plan
socket habilitado sockets para unix unsafe-plan
device habilitado devices para /dev/fuse unsafe-plan
root user para 0:0 unsafe-plan
capability adicionada cap_add para SYS_ADMIN unsafe-plan
database host host_database para read-only unsafe-plan
extension loading extension_loading para allowed unsafe-plan
URI remota remote_uri para allowed unsafe-plan
persistência implícita implicit_persistence para allowed unsafe-plan
ATTACH attach_database para allowed unsafe-plan
journal persistente journal_mode para wal unsafe-plan
banco fora do tmpfs database_root para /workspace/.board unsafe-plan
traversal do root database_root para /tmp/aurum-sqlite-state/../../etc unsafe-plan
driver cgo sqlite_driver_cgo para true unsafe-plan
banco ilimitado max_database_bytes para 0 unsafe-plan
banco gigante max_database_bytes para 34359738368 unsafe-plan
tmpfs ilimitado tmpfs_mb para 0 unsafe-plan
driver sem digest sqlite_driver para unpinned digest-invalid
limite ausente query_timeout_seconds removido do documento schema-invalid
credential real HTTP_PROXY para uma chave com forma real (montada em runtime) credential-present

O vetor de credential nunca aparece como literal em arquivo versionado: ele é montado em runtime, dentro da cópia descartável do acceptance, para que o portão de credenciais de oci-run não veja forma de segredo em nenhuma entrada materializada nem em nenhuma saída.

Loaders vizinhos

Registrar uma sétima chave fecha a allowlist dos loaders já entregues, então este card os estende no mesmo commit:

  • tests/unit/AUR-402.go — allowlist de chaves do registry canônico.
  • tests/unit/AUR-403.go — allowlist, aridade (7) e ordem posicional.
  • tests/unit/AUR-404.go — conjunto de chaves registradas e switch do loader.
  • tests/unit/AUR-405.go — conjunto de chaves registradas e switch do loader.
  • tests/unit/AUR-406.go — conjunto de chaves registradas e switch do loader.
  • tests/contracts/AUR-404.go — aridade exata do registry (6 para 7).

Cada extensão é aditiva: nenhuma rejeição anterior desaparece, nenhuma igualdade vira desigualdade, nenhuma aridade exata vira mínimo e nenhuma chave sai de allowlist. Nenhum desses loaders lê os documentos do state store: cada um checa apenas os caminhos declarados e a forma do digest, de modo que registrar o SQLite não pode re-apontar silenciosamente o plano Go unit, o plano Git, o plano do fake provider nem o plano do parser worker.

A aridade total do registry é asserida em tests/unit/AUR-407.go, que este card possui. tests/contracts/AUR-407.go não assere aridade: este card é dono da chave sqlite-offline-v1, não do tamanho do registry, e a próxima carta de profile deve quebrar apenas onde sua posse estiver declarada.

Diagnóstico offline

# a chave está publicada e em ordem?
grep -n '"key"' .board/oci/profiles/registry.v1.json

# os digests do registry reproduzem os bytes?
sha256sum .board/schemas/sqlite-offline-profile.schema.json \
          .board/locks/oci/sqlite-offline-v1.lock.json
printf '%s' "$(sed -n 's/.*"image": "\(.*\)".*/\1/p' \
  .board/locks/oci/sqlite-offline-v1.lock.json)" | sha256sum

# o runtime da imagem pinada ainda satisfaz o plano puro Go?
docker run --rm --pull=never --network=none --read-only \
  --user=65534:65534 --cap-drop=ALL --entrypoint= \
  aurum-bootstrap-go-bash@sha256:503ac356fa6bca4bad56fade87b5479ff371bd446ffb9c7db91f211323c7c73e \
  bash -c 'command -v go; command -v sqlite3 || echo sqlite3-absent; ls /sys/class/net'