Parte II — Tronco e raízes
03 · O tronco: onde a água fica guardada
Este saber ainda não tem versão em SITEMAP.XML — mostrando em Português.
O baobá acumula até milhares de litros de água no tronco e atravessa meses de seca. O núcleo do Baobáxia guarda os saberes em arquivos simples, que sobrevivem a quedas de luz, discos trocados e anos sem rede.
Arquivos para ler, nesta ordem
baobaxia/core/saber.py— o modelo (≈ 250 linhas).baobaxia/infra/storage.py— ler e gravarsaber.bbx.baobaxia/core/repository.py—SaberRepository: uma galáxia.baobaxia/core/index.py—GalaxyIndex: o cache SQLite.baobaxia/core/relation_graph.pyecore/registry.py.
O arquivo saber.bbx
---
uuid: 9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f
nature: midia
slug: pescaria
name: Pescaria no rio
galaxia: 201b8213-…
mucua: 373cc7ef-…
creator: 4890f893-…
created: '2026-05-02T14:10:00'
visibility: public
applications: [acervo]
data:
title: {pt: Pescaria no rio, en: Fishing on the river}
tags: [pesca, rio]
protecao: publico
content:
- {filename: foto.jpg, mimetype: image/jpeg, size: 182044,
hash: SHA256E-s182044--3f2a….jpg}
---
Texto livre em markdown (vira data.body).
Legível por gente, versionável pelo git, e foto.jpg é um link simbólico do git-annex: o binário só existe nas mucuas que pediram uma cópia.
SaberRepository: uma galáxia
classDiagram
class SaberRepository {
+Path root
+GalaxyIndex index
+create(saber, author, commit) Saber
+upsert_saber(saber) Saber
+update(uuid, data, …) Saber
+delete(uuid) bool
+get(uuid) Saber
+add_contents(uuid, files) Saber
+reindex(incremental) int
+reindex_saber(uuid) bool
+refrescar_presenca() int
-_saber_dir(saber) Path
-_merge_content(dir, refs) list
-_index_entry(saber) dict
-_load(dir) Saber
-_commit(msg, author)
}
class GalaxyIndex {
+upsert(entry)
+upsert_many(entries) int
+aplicar_lote(entradas, remover, marcador)
+get(uuid) dict
+query(nature, tag, text, is_local, …) dict
+assinaturas() dict
+ultimo_commit_indexado() str
-_criar_acesso()
}
class storage {
<<module>>
+read_bbx(path) (meta, body)
+write_bbx(path, meta, body)
+list_content_files(dir) list
}
class Sankofa {
<<fachada>>
+init(path)
+add(path, root)
+commit(root, msg)
+sync(root, remote, data) bool
+get / drop / whereis
}
SaberRepository *-- GalaxyIndex
SaberRepository ..> storage
SaberRepository ..> Sankofa
Gravar um saber (create)
sequenceDiagram
participant S as SaberService
participant R as SaberRepository
participant St as storage
participant G as Sankofa (git)
participant I as GalaxyIndex
S->>S: permissions.require(CREATE) e validate_data (schema da nature)
S->>S: authorship.try_sign (assina com a chave do autor, se houver)
S->>R: create(saber, author)
R->>R: aplicações padrão da nature, slug, pasta
R->>R: _merge_content (refs declarados + disco)
R->>St: write_bbx(saber.bbx, to_metadata(), body)
R->>G: add(saber.bbx) e commit("create midia: pescaria")
R->>I: upsert(_index_entry(saber))
S->>S: _pos_escrita: invalida o grafo e propaga às cópias em outras galáxias
O índice SQLite (core/index.py)
Uma tabela larga (saberes), tabelas auxiliares e uma tabela estreita acesso mantida por triggers — as listagens checam permissão de milhares de linhas sem ler os blobs JSON.
erDiagram
saberes {
TEXT uuid PK
TEXT nature
TEXT slug
TEXT name
TEXT galaxia
TEXT mucua
TEXT creator
TEXT visibility
TEXT created
TEXT rel_path
REAL lat
REAL lon
TEXT territorio
TEXT busca "minúsculo e sem acento"
TEXT meta_json
TEXT data_json
TEXT fs_sig "mtime:tamanho"
INTEGER is_local
}
acesso {
TEXT uuid PK
TEXT protecao
TEXT leitores
TEXT editores
TEXT status
INTEGER ok "json válido"
}
tags { TEXT uuid TEXT tag }
tipos { TEXT uuid TEXT tipo "imagem video audio pdf…" }
applications { TEXT uuid TEXT application }
fts { TEXT uuid TEXT text "FTS5" }
meta { TEXT chave PK TEXT valor "ultimo_commit_indexado" }
saberes ||--|| acesso : "trigger"
saberes ||--o{ tags : ""
saberes ||--o{ tipos : ""
saberes ||--o{ applications : ""
saberes ||--|| fts : ""
Detalhes que valem a leitura:
- Leitores separados (
_Leitores,_leitura("rapida"|"pesada")): uma consulta rápida (por uuid) nunca espera uma listagem pesada. geracao(): um contador global de escritas; o cache de leitura (services/cache_leitura.py) usa esse número como chave — qualquer escrita invalida tudo de uma vez.- Busca sem acento:
normalizar_busca(NFKD + minúsculas) dos dois lados. "acai" acha "açaí".
Reindex incremental: o tempo espiralar
reindex(incremental=True) pergunta ao git o que mudou desde o ultimo_commit_indexado (_pastas_mudadas_no_git) e relê só essas pastas. Sem marcador, compara a assinatura de disco (fs_sig). O marcador é gravado na mesma transação dos dados: adiantado, ele faria saberes sumirem para sempre.
flowchart TD
A[reindex incremental] --> B{marcador existe e é<br/>ancestral do HEAD?}
B -- sim --> C[git diff marcador..HEAD<br/>→ pastas tocadas]
C --> D[relê só essas pastas<br/>+ linhas com fs_sig vazio]
B -- não --> E[varre saber.bbx do disco<br/>abre só os com fs_sig diferente]
D --> F[aplicar_lote: entradas + remoções + marcador<br/>numa transação]
E --> F
F --> G[saber.bbx ilegível?<br/>mantém a versão anterior e avisa no log]
Presença de binário (git annex get/drop) não muda nenhum arquivo versionado — por isso existe refrescar_presenca(), que faz um listdir por pasta com anexo.
Práticas
- Em
test_reindex_incremental.py, ache o teste que conta quantossaber.bbxforam relidos. Mude um saber e confira o número. - Corrompa à mão o YAML de um
saber.bbx(como um conflito de merge faria) e rodebbx reindex. O saber some da busca? Por quê não? (_load,reindex.) - Abra o índice com
sqlite3 .bbx/index.sqlitee rodeSELECT nature, count(*) FROM acesso GROUP BY nature;. - Leia
_index_entrye explique de onde vem a colunaterritoriode um saber que não declarou território.
Para conversar
- O índice pode ser apagado a qualquer momento. Que liberdade isso dá a quem mantém o sistema? Que cuidado isso exige?
