Part II — Trunk and roots
03 · The trunk: where the water is stored
The baobab stores up to thousands of litres of water in its trunk and gets through months of drought. Baobáxia's core keeps saberes in plain files that survive power cuts, swapped disks and years without a network.
Files to read, in this order
baobaxia/core/saber.py— the model (≈ 250 lines).baobaxia/infra/storage.py— reading and writingsaber.bbx.baobaxia/core/repository.py—SaberRepository: one galaxy.baobaxia/core/index.py—GalaxyIndex: the SQLite cache.baobaxia/core/relation_graph.pyandcore/registry.py.
The saber.bbx file
---
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}
---
Free markdown text (becomes data.body).
Readable by people, versionable by git, and foto.jpg is a git-annex symbolic link: the binary only exists in the mucuas that asked for a copy.
SaberRepository: one galaxy
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 {
<<facade>>
+init(path)
+add(path, root)
+commit(root, msg)
+sync(root, remote, data) bool
+get / drop / whereis
}
SaberRepository *-- GalaxyIndex
SaberRepository ..> storage
SaberRepository ..> Sankofa
Writing a 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) and validate_data (the nature schema)
S->>S: authorship.try_sign (signs with the author's key, if any)
S->>R: create(saber, author)
R->>R: nature default applications, slug, folder
R->>R: _merge_content (declared refs + disk)
R->>St: write_bbx(saber.bbx, to_metadata(), body)
R->>G: add(saber.bbx) and commit("create midia: pescaria")
R->>I: upsert(_index_entry(saber))
S->>S: _pos_escrita: invalidates the graph and propagates to copies in other galaxies
The SQLite index (core/index.py)
A wide table (saberes), auxiliary tables and a narrow acesso table kept up to date by triggers — listings check permission for thousands of rows without reading the JSON blobs.
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 "lowercase, no accents"
TEXT meta_json
TEXT data_json
TEXT fs_sig "mtime:size"
INTEGER is_local
}
acesso {
TEXT uuid PK
TEXT protecao
TEXT leitores
TEXT editores
TEXT status
INTEGER ok "valid json"
}
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 : ""
Details worth reading:
- Separate readers (
_Leitores,_leitura("rapida"|"pesada")): a quick query (by uuid) never waits behind a heavy listing. geracao(): a global write counter; the read cache (services/cache_leitura.py) uses this number as its key — any write invalidates everything at once.- Accent-insensitive search:
normalizar_busca(NFKD + lowercase) on both sides. "acai" finds "açaí".
Incremental reindex: spiral time
reindex(incremental=True) asks git what changed since ultimo_commit_indexado (_pastas_mudadas_no_git) and rereads only those folders. Without a marker, it compares the disk signature (fs_sig). The marker is written in the same transaction as the data: if it got ahead, saberes would disappear for good.
flowchart TD
A[incremental reindex] --> B{marker exists and is<br/>an ancestor of HEAD?}
B -- yes --> C[git diff marker..HEAD<br/>→ folders touched]
C --> D[rereads only those folders<br/>+ rows with empty fs_sig]
B -- no --> E[scans saber.bbx on disk<br/>opens only those with a different fs_sig]
D --> F[aplicar_lote: entries + removals + marker<br/>in one transaction]
E --> F
F --> G[unreadable saber.bbx?<br/>keeps the previous version and logs a warning]
The presence of a binary (git annex get/drop) does not change any versioned file — that is why refrescar_presenca() exists, doing one listdir per folder that has attachments.
Practice
- In
test_reindex_incremental.py, find the test that counts how manysaber.bbxfiles were reread. Change a saber and check the number. - Corrupt the YAML of a
saber.bbxby hand (as a merge conflict would) and runbbx reindex. Does the saber vanish from search? Why not? (_load,reindex.) - Open the index with
sqlite3 .bbx/index.sqliteand runSELECT nature, count(*) FROM acesso GROUP BY nature;. - Read
_index_entryand explain where theterritoriocolumn comes from for a saber that did not declare a territory.
To talk about
- The index can be deleted at any time. What freedom does that give the people who maintain the system? What care does it demand?
