feat: DuckDB como fuente de verdad (multi-valor, ownership selectivo, escritura, libretas)
F1 — migraciones: 002_multivalue (persons +telefonos/emails/direcciones/extra_fm JSON,
backfill desde singulares con to_json) + 003_addressbooks (tabla addressbooks + seed
idempotente de la libreta por defecto). Conteos intactos (697/1065/98).
F2 — ingest_vault selectivo (anti-pisado): personas que ya existen en DB solo actualizan
note_path + extra_fm vía duckdb_upsert(update_cols=...), NO pisan los campos OWNED por la
DB; personas nuevas = bootstrap completo. _link_contacts enlaza por listas telefonos[]/
emails[] además del singular. ingest_dav itera todas las libretas de la tabla addressbooks.
F3 — escritura estructurada (server/writes.py + endpoints en main.py): CRUD
/api/person|contact|event, /api/addressbook, /api/calendar, /api/person/{slug}/render
(DB→nota preservando la prosa del cuerpo), /api/push/dav (reconcilia DB→Xandikos). El push
DAV y el render ocurren fuera de la transacción de escritura para no bloquear la DB con
latencia de red. registry_bridge.py importa las funciones nuevas; app.md actualizado.
Verificado: 18 tests verdes; ownership probado sobre datos reales (un centinela DB-owned
sobrevivió a POST /api/ingest/vault sobre las 697 fichas); person CRUD + materialización
de la ficha .md en vivo, con cleanup sin residuo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,8 +89,22 @@ def _load_registry_fn(package: str, module_name: str, attr: str):
|
||||
# Grupo dav: lectura bulk de colecciones Xandikos (CardDAV/CalDAV).
|
||||
dav_get_collection = _load_registry_fn("infra", "dav_get_collection", "dav_get_collection")
|
||||
dav_list_calendars = _load_registry_fn("infra", "dav_list_calendars", "dav_list_calendars")
|
||||
dav_list_addressbooks = _load_registry_fn(
|
||||
"infra", "dav_list_addressbooks", "dav_list_addressbooks"
|
||||
)
|
||||
dav_collection_ctag = _load_registry_fn("infra", "dav_collection_ctag", "dav_collection_ctag")
|
||||
|
||||
# Grupo dav: escritura (push DB -> Xandikos) y creación de colecciones.
|
||||
carddav_put_vcard = _load_registry_fn("infra", "carddav_put_vcard", "carddav_put_vcard")
|
||||
caldav_put_event = _load_registry_fn("infra", "caldav_put_event", "caldav_put_event")
|
||||
dav_delete_resource = _load_registry_fn(
|
||||
"infra", "dav_delete_resource", "dav_delete_resource"
|
||||
)
|
||||
dav_make_addressbook = _load_registry_fn(
|
||||
"infra", "dav_make_addressbook", "dav_make_addressbook"
|
||||
)
|
||||
dav_make_calendar = _load_registry_fn("infra", "dav_make_calendar", "dav_make_calendar")
|
||||
|
||||
# Secretos via pass (credencial Xandikos, nunca hardcodeada).
|
||||
pass_get_secret = _load_registry_fn("infra", "pass_get_secret", "pass_get_secret")
|
||||
|
||||
@@ -98,6 +112,12 @@ pass_get_secret = _load_registry_fn("infra", "pass_get_secret", "pass_get_secret
|
||||
duckdb_query_readonly = _load_registry_fn(
|
||||
"infra", "duckdb_query_readonly", "duckdb_query_readonly"
|
||||
)
|
||||
# Escritura DuckDB del grupo: DDL/DML directo + UPSERT con ownership selectivo.
|
||||
duckdb_execute = _load_registry_fn("infra", "duckdb_execute", "duckdb_execute")
|
||||
duckdb_upsert = _load_registry_fn("infra", "duckdb_upsert", "duckdb_upsert")
|
||||
|
||||
# Composición del vCard multi-valor (DB -> Xandikos), puro.
|
||||
build_vcard = _load_registry_fn("core", "build_vcard", "build_vcard")
|
||||
|
||||
# Render de tablas Markdown + bloques sentinel idempotentes para las notas.
|
||||
render_markdown_table = _load_registry_fn(
|
||||
@@ -116,8 +136,17 @@ __all__ = [
|
||||
"dav_collection_ctag",
|
||||
"dav_get_collection",
|
||||
"dav_list_calendars",
|
||||
"dav_list_addressbooks",
|
||||
"carddav_put_vcard",
|
||||
"caldav_put_event",
|
||||
"dav_delete_resource",
|
||||
"dav_make_addressbook",
|
||||
"dav_make_calendar",
|
||||
"pass_get_secret",
|
||||
"duckdb_query_readonly",
|
||||
"duckdb_execute",
|
||||
"duckdb_upsert",
|
||||
"build_vcard",
|
||||
"render_markdown_table",
|
||||
"upsert_sentinel_block",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user