Skip to content

The data

Thirteen tables. The centre is items —the archive— and everything else hangs off cuentas, which is what this system knows how to talk to.

Table and column names stay in Spanish. They are what the database, the migrations and the code actually say: translating them here would leave you looking for a flows table that does not exist. Each one is explained in English; only the identifier is left untouched.

The diagram

is source of

is destination of

has permission over

advances through

source

destination

has

is published as

goes out in

measures

measures

CUENTAS

uuid

id

PK

text

plataforma

instagram facebook mastodon linkedin web telegram rss

text

rol

read write both

text

handle

text

id_remoto

the id on ITS network

bytea

credenciales_cifradas

AES-256-GCM

jsonb

config

tap, via, instance...

text

salud

ok no_credentials error_auth degraded

bool

activa_lectura

bool

activa_escritura

timestamptz

retirada_en

logical removal

ITEMS

uuid

id

PK

uuid

cuenta_origen_id

FK

text

id_remoto

text

permalink

mandatory if regime is without permission

text

caption

text_array

tags

text

autor

text

regimen

consentido | atribucion_sin_permiso

text

archivo_ruta

where the bytes are

bigint

archivo_bytes

text

archivo_estado

pending downloading ready failed purged no_media

timestamptz

publicado_en_origen

EMISIONES

uuid

id

PK

uuid

item_id

FK

uuid

cuenta_destino_id

FK

text

estado

int

ronda

text

aprobado_por

who looked at it

timestamptz

aprobado_en

text

id_remoto_publicado

timestamptz

publicada_en

bool

detener

CONSENTIMIENTOS

uuid

id

PK

uuid

cuenta_id

FK

text

otorgado_por

a person, by name

timestamptz

otorgado_en

timestamptz

revocado_en

text

alcance

uuid_array

cuentas_destino

per ACCOUNT, not per network

text

evidencia

CURSORES

uuid

cuenta_id

PK

text

tipo

poll | backfill

text

cursor

bool

agotado

RUTAS

uuid

id

PK

uuid

origen_id

FK

uuid

destino_id

FK

text

clave

UK

stable and immutable identity

jsonb

condicion

the filter

bool

activa

bool

auto_publica

ITEM_MEDIOS

uuid

id

PK

uuid

item_id

FK

int

indice

text

archivo_ruta

text

archivo_sha256

text

archivo_estado

EMISION_PARTES

uuid

id

PK

uuid

emision_id

FK

int

indice

int

partes_total

numeric

desde_seg

numeric

hasta_seg

text

url_remota

METRICAS

uuid

id

PK

uuid

item_id

FK

uuid

emision_id

FK

text

lado

source | destination

int

likes

int

alcance

Four more tables hang off nothing and so stay out of the diagram: ajustes (key/value, including the general brake), alertas (the notices and whether they were delivered), latidos (which worker beats and when) and metricas_diarias (the daily summary).

The constraints that are guards

These are not shape validations: they are project rules held up by the database, where they do not depend on a programmer remembering them.

ConstraintWhat it prevents
items_atribucion_obligatoriaThat an atribucion_sin_permiso item exists without a link to the original. Attribution is not a convention: it is a CHECK.
items_regimen_checkAn invented regime. Only consentido or atribucion_sin_permiso.
cuentas_rol_plataforma_coherenteDeclaring that you publish to Instagram (you cannot) or that you read from LinkedIn (there is no client). It mirrors the platform table in the code.
consentimientos_destino_no_vacioA permission naming an empty list of destinations: it would read as “permission granted” and enable nothing.
rutas_clave_unica + immutability triggerThat a flow’s identity changes. Without it, every rename would come in as a new flow on import.
UNIQUE (item_id, cuenta_destino_id, ronda)Publishing the same thing twice to the same destination. It is what makes “disseminate” idempotent.

Why is cuentas_destino an array of UUIDs and not a text with the network?

Because permission is per account, not per network. With two Mastodon accounts, a permission scoped to mastodon enabled both, and there was no way to consent to one and not the other. The previous version stored 'mastodon' and that was exactly the flaw: wider than what the person actually asserted.

Why the archive stores bytes and not URLs

Storing the bytes

new post

the URL is stored

the bytes are fetched NOW

archivo_estado: ready

useful forever,

and for republishing

Storing only the link

new post

the CDN URL is stored

two days later: 403

an archive of dead links

archivo_estado has six values and two of them do not heal on their own:

StateMeansDoes it fix itself?
pendienteNot downloaded yetYes, the downloader picks it up
descargandoIn progressYes
listoThe bytes are there
sin_mediosA text-only post: there is nothing to download— (not a shortfall)
fallidoThe download failedNo. The downloader only takes pendiente/descargando
purgadoDisk was freed on purposeNo. It has to be fetched again

sin_medios exists so as not to lie. A text post has no file, and marking it pendiente would leave it forever in the downloader’s queue, counting as work that never gets done. It is not a shortfall: there is simply nothing to fall short of.