Synapse: Schema de Dados e Índice
Documento de referência para a camada de dados: o frontmatter de cada tipo de entidade e como o índice SQLite mapeia esses arquivos.
Princípios
- O markdown é a fonte da verdade. O SQLite é um índice descartável,
reconstruível com
syn reindex. Nada vive só no banco. - Frontmatter para o estruturado, corpo para o livre. Campos consultáveis (status, tags, vínculos) vão no YAML; contexto, notas e histórico vão no corpo markdown.
- IDs estáveis e legíveis. O nome do arquivo (slug) é o ID.
maria-silva.mdé referenciado comomaria-silva. Renomear é uma operação explícita (syn person rename) que atualiza os vínculos. - Vínculos por referência, nunca por cópia. Uma pessoa que atua em três projetos existe uma vez; os projetos a referenciam pelo slug.
Convenções de campo comuns
Todo arquivo carrega um núcleo comum no frontmatter:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | enum | sim | project, person, entity, api, task |
id | string (slug) | sim | Igual ao nome do arquivo sem extensão |
title | string | sim | Nome legível (“Maria Silva”, “API Pagamentos”) |
tags | lista | não | Tags livres, normalizadas em minúsculas |
created | data ISO 8601 | sim | Preenchido na criação |
updated | data ISO 8601 | sim | Atualizado a cada syn ... edit |
Datas sempre em ISO 8601 (2026-05-23). Slugs sempre minúsculos, sem acento,
hífen como separador.
Tipo: project
Arquivo: projects/<slug>/_meta.md
Um projeto é uma pasta, não um arquivo solto, porque agrega sub-documentos
(stakeholders, apis, demandas específicas daquele projeto). O _meta.md é o
manifesto.
---
type: project
id: projeto-alfa
title: Projeto Alfa
status: ativo # ativo | pausado | concluido | arquivado
description: Plataforma de pagamentos B2B
owner: maria-silva # referência a um person.id
priority: alta # baixa | media | alta
tags: [pagamentos, b2b, prioridade]
links:
repo: https://github.com/org/alfa
docs: https://wiki.empresa.com/alfa
created: 2026-01-10
updated: 2026-05-20
---
## Contexto
Texto livre sobre o projeto, decisões, histórico.
## Pontos de atenção
- Migração do webhook v1 → v2 pendente
Campos específicos: status, owner, priority, links.
Tipo: person
Arquivo: people/<slug>.md
Pessoas são globais (não vivem dentro de um projeto) justamente para permitir
o vínculo N:N. O campo roles lista em quais projetos a pessoa atua e com que
papel: esse é o coração do modelo de grafo.
---
type: person
id: maria-silva
title: Maria Silva
email: maria@empresa.com
org: acme-corp # referência opcional a um entity.id
roles: # vínculos N:N com projetos
- project: projeto-alfa
role: ponto-focal
- project: projeto-beta
role: aprovadora
tags: [pagamentos, lideranca]
created: 2026-01-12
updated: 2026-05-18
---
## Notas
- Prefere contato por e-mail, responde rápido
- Fuso: GMT-3
Campos específicos: email, org, roles (lista de {project, role}).
O comando syn person link "Maria Silva" --project gama --role revisora
adiciona uma entrada em roles sem tocar no resto.
Tipo: entity
Arquivo: entities/<slug>.md
Organizações, times, fornecedores, clientes: qualquer ator que não é uma pessoa individual.
---
type: entity
id: acme-corp
title: Acme Corp
kind: fornecedor # fornecedor | cliente | time-interno | parceiro
contact: contato@acme.com
projects: [projeto-alfa] # projetos onde aparece
tags: [pagamentos]
created: 2026-02-01
updated: 2026-04-30
---
## Notas
- SLA de resposta: 48h
Campos específicos: kind, contact, projects.
Tipo: api
Arquivo: apis/<slug>.md
APIs podem ser compartilhadas entre projetos, por isso ficam no diretório global e referenciam projetos por slug.
---
type: api
id: api-pagamentos
title: API Pagamentos
base_url: https://api.pagamentos.com/v2
auth: oauth2 # none | apikey | basic | oauth2 | jwt | mtls
status: producao # producao | homologacao | descontinuada
owner: maria-silva
projects: [projeto-alfa, projeto-beta]
docs_url: https://docs.pagamentos.com
tags: [pagamentos, externa]
created: 2026-01-15
updated: 2026-05-10
---
## Endpoints relevantes
- `POST /charges`: cria cobrança
- `POST /webhooks`: registra webhook
## Observações
- Rate limit: 100 req/s
- Migração v1 → v2 em andamento
Campos específicos: base_url, auth, status, owner, projects,
docs_url.
Tipo: task (demanda)
Arquivo: projects/<projeto>/tasks/<id>.md ou tasks/<id>.md (global)
Demandas são curtas e numerosas, então o id é numérico/sequencial gerado
pelo CLI, e o title carrega a descrição.
---
type: task
id: alfa-042
title: Migrar webhook para v2
project: projeto-alfa
status: aberta # aberta | em-andamento | bloqueada | concluida
owner: maria-silva
api: api-pagamentos # referência opcional
due: 2026-06-15
tags: [migracao, prioridade]
created: 2026-05-20
updated: 2026-05-22
---
## Detalhes
Contexto da demanda, links, critérios de aceite.
Campos específicos: project, status, owner, api, due.
O índice SQLite
O índice existe para uma coisa: busca rápida e filtragem. Ele nunca é a fonte da verdade. Estrutura mínima (fase 1, sem vetores):
Tabela entities (espelho do frontmatter)
CREATE TABLE entities (
id TEXT PRIMARY KEY, -- slug
type TEXT NOT NULL, -- project|person|entity|api|task
title TEXT NOT NULL,
status TEXT,
owner TEXT, -- person.id
path TEXT NOT NULL, -- caminho do .md no disco
frontmatter TEXT NOT NULL, -- JSON do YAML completo (fonte p/ filtros)
body TEXT NOT NULL, -- corpo markdown
created TEXT,
updated TEXT
);
CREATE INDEX idx_entities_type ON entities(type);
CREATE INDEX idx_entities_status ON entities(status);
CREATE INDEX idx_entities_owner ON entities(owner);
O frontmatter guardado como JSON permite filtros arbitrários sem precisar de
uma coluna por campo: você consulta com json_extract().
Tabela tags (relação N:N)
CREATE TABLE tags (
entity_id TEXT NOT NULL REFERENCES entities(id),
tag TEXT NOT NULL,
PRIMARY KEY (entity_id, tag)
);
CREATE INDEX idx_tags_tag ON tags(tag);
Tabela links (o grafo)
Materializa todos os vínculos entre entidades, vindos de roles, projects,
owner, api, etc. É isto que torna possível perguntar “quem são os pontos
focais do projeto alfa” ou “quais tasks dependem da API de pagamentos”.
CREATE TABLE links (
from_id TEXT NOT NULL, -- ex: maria-silva
to_id TEXT NOT NULL, -- ex: projeto-alfa
rel TEXT NOT NULL, -- ex: ponto-focal, owner, uses-api
PRIMARY KEY (from_id, to_id, rel)
);
CREATE INDEX idx_links_to ON links(to_id, rel);
CREATE INDEX idx_links_from ON links(from_id, rel);
Tabela virtual FTS5 (busca textual)
CREATE VIRTUAL TABLE entities_fts USING fts5(
id UNINDEXED,
title,
body,
tags,
content='' -- índice externo, não duplica conteúdo
);
Consulta de busca típica que o syn search executa:
SELECT e.id, e.type, e.title
FROM entities_fts f
JOIN entities e ON e.id = f.id
WHERE entities_fts MATCH 'oauth'
AND e.type = 'api' -- filtro --type
AND e.id IN (SELECT entity_id FROM tags WHERE tag = 'prioridade')
ORDER BY rank;
Fase 2: busca semântica (sqlite-vec)
Quando o FTS não bastar, adiciona-se uma tabela virtual de vetores no mesmo banco:
CREATE VIRTUAL TABLE entities_vec USING vec0(
id TEXT PRIMARY KEY,
embedding FLOAT[384] -- dimensão do modelo de embedding escolhido
);
A busca híbrida combina o rank do FTS5 com a distância vetorial, fundindo os
dois sinais (reciprocal rank fusion é o método simples e robusto). Mas isso só
entra quando a busca textual demonstrar ser insuficiente, não antes.
Fluxo de reindexação
syn reindex faz, de forma idempotente:
- Apaga e recria as tabelas (ou faz upsert por
updated). - Varre
projects/,people/,entities/,apis/,tasks/. - Para cada
.md: faz parse do frontmatter (YAML → JSON), extrai corpo. - Popula
entities,tags,links,entities_fts. - Reporta contagens e inconsistências (ex:
ownerapontando para pessoa inexistente): esse relatório é o que osyn doctorconsome.
A reindexação completa de alguns milhares de arquivos leva milissegundos, então
ela pode rodar automaticamente após qualquer comando de escrita (add, rm,
edit) ou ser disparada por um watcher de filesystem opcional.