Saltar a contenido

Vault y archivos

El autoguardado serializa las escrituras de página dentro del cliente web. Una versión obsoleta devuelve un conflicto (409), no un error de página inexistente (404), aunque la lectura condicional omita el cuerpo. Las páginas inexistentes siguen devolviendo 404; los conflictos no sobrescriben silenciosamente contenido más reciente.

Se puede retirar del registro un alias inactivo si otra entrada del mismo espacio de trabajo conserva la carpeta. La eliminación utiliza la misma identidad seleccionada que el catálogo y sigue protegiendo la entrada activa y la última entrada del vault principal. Retirar una entrada compartida conserva la carpeta, las bases de datos locales, las cachés y el índice de archivos; no se permite borrar los archivos mientras otro vault registrado utilice la misma ruta, aunque pertenezca a otro espacio de trabajo.

Contratos de comentarios y traducciones

La persistencia de comentarios valida solo el diccionario o lista raíz almacenado; conserva las claves desconocidas, los valores anidados y la identidad de los registros. Los modelos HTTP siguen validando las respuestas. comments/composition.py construye dependencias resueltas en el momento de uso en el punto de registro original y permite importar primero la fachada o los comentarios. La importación Markdown conserva el corte nativo del ID en colisiones de nombres. Las suscripciones SSE siguen limitadas al vault y retiran su propia cola al cancelarse.

Los helpers de traducción, la búsqueda, los efectos de metadatos y los servicios de filas y páginas usan metadatos abiertos sin suponer claves YAML solo textuales. El ciclo de vida comprueba los responsables reales de los callbacks y un protocolo de conector cargado cuando se necesita; conserva las dependencias capturadas y resuelve tarde los miembros sustituibles. Los resultados de creación de páginas usan un contrato de lectura de campos, distinto de los metadatos mutables. Los handlers de escritura registrados mantienen el retorno asíncrono y el contexto inyectado opcional. Los resultados de Web Clipper pasan por el modelo de respuesta existente sin coerción de campos.

Estos cambios conservan los límites de captura de errores de los proveedores, la recuperación ordenada del disco, los idiomas de destino duplicados y el orden de escritura de estados. Las pruebas sintéticas usan dobles de proveedor, no cuentas reales de la nube. La composición Drupal y el HTTP de traducción comprueban ahora los responsables reales de las dependencias; esto no certifica todo el backend, un servidor Drupal real ni una migración de proveedor de nube.

Contratos del transporte Drupal y del HTTP

El mapeo de campos Drupal, la preparación multimedia, el descubrimiento de idiomas, la coincidencia de títulos y la sincronización de filas conservan metadatos abiertos e identificadores opacos del conector. Las respuestas construidas tienen tipos explícitos; los valores decodificados del transporte no presuponen un esquema de la aplicación. Se conservan el orden de campos, los errores parciales, la identidad de identificadores en caché y los errores nativos de entrada. Las factorías capturan el conector y las clases de error, pero resuelven los miembros sustituibles al llamarlos. Pillow sigue siendo opcional y se carga cuando hace falta.

Las rutas de traducción conservan los esquemas existentes de petición y respuesta. La publicación masiva Drupal convierte cada identificador a texto y conserva duplicados; la traducción masiva filtra identificadores no textuales y elimina duplicados tras recortar espacios. Publicar una fila sigue enviando multimedia por defecto. Las entradas textuales mantienen los valores alternativos para entradas vacías y los errores nativos ante JSON malformado. Los llamadores Python no JSON cuya operación strip personalizada devuelve un valor no textual reciben un error de tipo explícito; esta comprobación solo HTTP no normaliza YAML ni metadatos de plugins. El JSON generado válido con una forma de respuesta incorrecta llega a la validación HTTP, no al resultado alternativo por error de proveedor. Transportes sintéticos y archivos temporales verifican estos contratos sin leer credenciales, publicar contenido ni llamar a proveedores externos.

Responsabilidad

El dominio Vault relaciona el Markdown portátil y los recursos con páginas, carpetas, adjuntos, búsquedas, esquemas, historiales, papelera, exportaciones, citas y selección entre varios Vaults. Es el dominio más grande y el principal responsable de la soberanía de los datos.

El reconocimiento local de escritura manuscrita es un adaptador opcional de ingestión en la capa del Vault. Los objetos de modelo y procesador permanecen aislados como valores de terceros en tiempo de ejecución; el servicio expone un resultado tipado que contiene el texto, el reconocimiento sin procesar, los valores por línea, la identidad del modelo y el estado de corrección, sin cambiar el contrato público de subida. Los diccionarios de estado, preparación y reconocimiento se validan mediante modelos de respuesta Pydantic específicos, conservando después su estructura histórica de diccionario para las llamadas directas y la interfaz OpenAPI estable byte a byte.

El catálogo de Vaults marca una sola identidad del registro como activa, aunque varios alias antiguos compartan carpeta. La selección sigue la cabecera de la petición, el parámetro de consulta y después la cookie, limitada a las entradas que coinciden con la carpeta activa resuelta. La selección actual del navegador tiene prioridad sobre los indicadores guardados en caché, manteniendo sincronizados el menú, el selector de configuración y el nombre del Vault mostrado. Cambiar de Vault no reescribe los alias del registro ni los metadatos de favoritos.

Ciclo de vida de las páginas

sequenceDiagram
    participant UI as Interfaz o editor del vault
    participant R as API del dominio Vault
    participant C as Contexto del vault
    participant F as Proveedor de archivos
    participant I as Índices de páginas y enlaces
    UI->>R: Leer página por identificador estable
    R->>C: Resolver vault activo autorizado
    C->>I: Resolver identificador a ruta actual
    I->>F: Leer Markdown si la caché no basta
    F-->>R: Contenido, metadatos y ETag
    R-->>UI: Representación editable
    UI->>R: Guardar con el ETag esperado
    R->>F: Escritura atómica si el ETag sigue coincidiendo
    R->>I: Actualizar entradas de páginas y relaciones

La identidad de una página es independiente de su título y su ruta. El frontmatter se normaliza en los puntos de escritura, conservando las claves creadas por el usuario. El estado exclusivamente interno debe almacenarse en archivos auxiliares de .gnosi cuando exponerlo en el frontmatter contaminaría o desestabilizaría el contenido portátil.

Las lecturas y escrituras de archivos auxiliares utilizan un único contrato explícito de correspondencia de metadatos, incluidos los resultados de separación, fusión y persistencia portátil. El mecanismo compartido y tolerante de recuperación del frontmatter devuelve los valores escalares de primer nivel como objetos tipados cuando es necesario recuperar YAML; el contenido anidado mal formado se sigue ignorando deliberadamente. Estos contratos no fuerzan la conversión de los valores del usuario ni cambian las protecciones existentes para archivos en la nube.

La lectura de archivos auxiliares admite plataformas como Windows, donde las estadísticas de archivos no incluyen st_blocks. Si el número de bloques no está disponible, se lee el JSON local normalmente; un cero explícito sigue evitando la lectura de un archivo solo en la nube para no bloquearse durante su descarga. Las páginas guardadas y sus indicadores internos deben seguir siendo legibles después de reiniciar la aplicación.

pages/markdown_writer.py es la capa canónica de serialización: recupera o crea un ID estable cuando falta, asigna las claves del esquema a los nombres de almacenamiento, elimina los campos virtuales, escribe el estado interno en el archivo auxiliar, decora las relaciones portátiles y materializa las instantáneas de vistas antes de escribir el archivo de forma atómica. services/field_resolver.py es responsable de ese contrato de correspondencia de claves del esquema. Acepta ID de campo inmutables, nombres actuales y alias históricos, resuelve los conflictos de forma determinista y emite únicamente los nombres actuales legibles para las personas en las capas de almacenamiento y respuesta, conservando los metadatos locales ajenos a esa correspondencia.

pages/save_helpers.py se encarga de preparar los metadatos de guardado completo, seleccionar el destino, reutilizar los ID existentes y crear una versión antes de escribir. pages/patch_helpers.py se encarga de las lecturas que tienen en cuenta el ETag, la preparación de metadatos PATCH, la reubicación de archivos y las actualizaciones coordinadas de las cachés de páginas, cuerpos, citas y documentos analizados. Los ocho nombres históricos de funciones auxiliares privadas siguen siendo fachadas ligeras de compatibilidad, y cada colaborador sustituible o caché mutable se resuelve mediante un puerto tipado en el momento de uso.

Metadatos abiertos y HTTP validado

Los documentos internos del registro y de las páginas usan dict[object, object]: el YAML histórico puede contener claves no textuales y valores de extensiones sin un esquema declarado. Las comprobaciones de diccionario preservan la identidad; las fusiones de archivos auxiliares y el mapeo de nombres conservan los campos desconocidos. Esto es distinto de la validación HTTP pública. PageInfo mantiene su validación original de claves textuales y el esquema OpenAPI, mientras la construcción y asignación del índice preservan el documento abierto. Las cachés de respuesta guardan los objetos de página existentes sin clonar sus metadatos ni bloqueos.

backend/utils/open_values.py aísla las operaciones nativas sobre entradas opacas. Sus excepciones de tipado, solo en la entrada, preservan los protocolos de iteración, conversión numérica, longitud y mapeo de Python y sus errores; nunca afirman la forma de un registro devuelto. Las pruebas comparan el momento de las llamadas, las secuencias antiguas, los valores mal formados y la identidad de objetos compartidos. Esto elimina espacios de nombres dinámicos amplios de la composición del registro y de la base de páginas, no todos los tipos heredados del backend.

Contratos de vista previa y escritura

pages/preview_routes.py comprueba las dependencias de la fachada con sus responsables reales. Las respuestas corta y completa comparten una estructura de caché tipada y preservan los valores opacos de título, icono y portada. La materialización precede a la lectura; solo errno 35 usa la secuencia de reintentos existente. Las peticiones simultáneas comparten el mismo resultado, y la caché se actualiza antes de avisar a las peticiones que esperan.

Los guardados completos y parciales mantienen metadatos abiertos, ETags anulables, el momento de las copias superficiales y la resolución del callback antes de evaluar sus argumentos. Los auxiliares de citas preservan la identidad del diccionario con claves textuales o no textuales. El inventario de documentos y el resolutor de rutas conservan las mutaciones y el comportamiento con entradas malformadas.

Dos casos históricos quedan caracterizados, sin cambiarlos en esta refactorización: un conflicto de ETag en el auxiliar PATCH llega al 404 existente antes de la rama 409 del servicio; cancelar al propietario de una vista previa retira su entrada en curso sin completar su futuro compartido. Son limitaciones de compatibilidad, no una validación satisfactoria de concurrencia. Los esquemas públicos y el comportamiento ordinario de escritura no cambian.

Límites del backend

Las lecturas y escrituras de páginas, las vistas previas, la duplicación, el historial y la papelera se implementan en backend/domains/vault/pages, mientras que las subidas de recursos, los iconos y el servicio de imágenes están en backend/domains/vault/assets. El servicio de archivos dentro de las rutas permitidas, las rutas Library/raw/thumbnail, los tokens de archivos locales, las subidas de propiedades, los enlaces portátiles y la eliminación física están en backend/domains/vault/files. Estos paquetes separan los esquemas de solicitud estrictos, los adaptadores de rutas, los servicios de aplicación, los repositorios y los responsables únicos de los bloqueos mutables, las cachés y los almacenes de tokens. El comportamiento nuevo del Vault debe incorporarse a la capa del dominio que corresponda.

La capa transitoria pages/runtime.py conserva el estado dinámico del módulo histórico de rutas, pero exige un Vault activo antes de construir rutas del sistema de archivos o motores de reglas. Sus modelos de solicitud ahora se vinculan directamente a Pydantic, evitando clases base dependientes del tiempo de ejecución sin cambiar la identidad pública de su módulo ni el contrato HTTP generado.

backend/domains/media se encarga de resolver las raíces multimedia, del recorrido recursivo adaptado al proveedor y su caché derivada persistente, de los archivos auxiliares sincronizados de metadatos y vistas guardadas, los filtros, la paginación, el árbol de carpetas de carga diferida, las subidas dentro de las rutas permitidas, la extracción EXIF y la serialización estable de archivos. backend/services/media_service.py sigue siendo la fachada Python compatible: conserva la clase histórica, el singleton, la forma de invocación, los descriptores, el estado y los errores, resolviendo el estado mutable y los colaboradores sustituibles en el momento de uso. Su constructor interno ahora tiene una anotación explícita de retorno None, lo que elimina la antigua excepción de tipado de ese constructor sin cambiar el comportamiento de construcción. La fachada valida que exista un Vault activo antes de acceder al sistema de archivos y utiliza los contratos multimedia tipados para raíces, recorridos, consultas, subidas, datos EXIF e información serializada de archivos. Los módulos de dominio nunca importan el router HTTP ni la fachada de compatibilidad.

Las rutas HTTP multimedia importan directamente el router compartido y los servicios estables. media/composition.py conserva la resolución tardía del servicio y los callbacks de duplicación mediante interfaces con nombre; los tokens de archivo y bloqueos conservan sus propietarios canónicos. El servicio concreto se comprueba contra el contrato de las rutas sin conversiones de resultados. Los valores JSON del proveedor no cambian para los clientes Python; los modelos HTTP existentes validan la respuesta pública. La conversión única de fachada y las anotaciones heredadas de metadatos siguen siendo deuda explícita.

Las rutas de dibujos importan directamente el router compartido y los servicios tipados de dibujos e historial. drawings/composition.py limita los colaboradores con resolución tardía a DrawingVaultPort: rutas, papelera, serialización y callbacks del historial. El puerto no tiene miembros Any; su única conversión de compatibilidad es transitoria hasta separar la composición de los proveedores heredados. No acredita el tipado completo de la fachada ni del modelo de petición histórico compartido. No se normalizan resultados solo para tiparlos: las llamadas directas conservan los datos originales y los modelos HTTP imponen el contrato existente. Se preservan copias, recuperación, permisos, momento de resolución de callbacks, valores de metadatos y orden de rutas.

La composición de la vista previa y el guardado de páginas también comparte un único router de tipo acotado para la resolución de títulos y el registro delegado de vistas previas y escrituras. La identidad de las cachés, la correspondencia de alias, las comprobaciones del Vault activo y los esquemas de rutas generados permanecen sin cambios.

Las rutas de traducción y sincronización con Drupal también acotan en la capa del módulo el tipo de su router resuelto en el momento de uso. Las operaciones de traducción de una fila, masivas, por correspondencia, de botones generados y de páginas conservan las comprobaciones de roles, el trabajo en segundo plano y la correspondencia de errores externos, y siguen siendo visibles para el tipado estricto.

El almacenamiento asociado a tablas tiene responsables explícitos. assets/table_paths.py se encarga de las rutas de recursos confinadas, los directorios por propiedad, las revisiones y las funciones auxiliares de cambio de nombre que evitan colisiones; assets/persistence.py, de la ingestión recursiva de metadatos y la eliminación de recursos de registros dentro de las rutas permitidas; assets/quarantine.py, de la eliminación de tablas resistente a fallos y la recuperación al iniciar. tables/folders.py se encarga de crear y migrar el directorio físico BD/<database>/<table> de la tabla. Estos módulos reciben puertos acotados del sistema de archivos y del registro de configuración desde la fachada de compatibilidad y nunca importan el router HTTP.

tables/routes.py ahora es responsable de las 23 operaciones históricas de bases de datos, tablas, catálogos de opciones, vistas guardadas y esquemas de carpetas, en su orden original. Sus manejadores estrictos delegan en los servicios existentes de filas, ciclo de vida, propiedades, opciones y vistas; tables/composition.py es el conjunto inmutable de dependencias para esas rutas y para las consultas de filas y el enriquecimiento de metadatos. tables/security.py expone únicamente las dos fábricas tipadas de autorización del espacio de trabajo, evitando una dependencia estática del dominio de tablas respecto a la amplia composición heredada de autenticación. El router heredado registra las rutas del dominio en una estructura plana por compatibilidad con los consumidores del inventario de rutas y reexporta los objetos invocables de Python admitidos.

backend/api/vault_routes.py es ahora un módulo de inicialización de compatibilidad, sin asumir la implementación del dominio. Los módulos tipados de backend/domains/vault se encargan del comportamiento restante de API, anotaciones, citas, dibujos, Drupal, archivos, conocimiento, enlaces, multimedia, páginas, registro de configuración, tablas y traducción. El módulo de inicialización carga y registra esos responsables en el orden histórico del código fuente, mientras que facade_bridge.py conserva las importaciones admitidas, las variables globales mutables y los puntos de sustitución dinámica mediante monkeypatch resueltos en el momento de uso. El router padre sigue exponiendo el mismo inventario plano de APIRoute y un OpenAPI determinista idéntico byte a byte. Por tanto, la fachada no necesita ninguna excepción en los controles del código fuente.

pages/foundation.py declara sus funciones antes de cargar la fachada. El punto de entrada initialize_foundation vincula los proveedores existentes una sola vez en la posición original del arranque, incluso cuando se importa primero el módulo de páginas. Las llamadas repetidas conservan los mismos objetos de dependencias, callbacks capturados y orden de rutas; se rechaza vincular otra fachada. Las pruebas aisladas comparan ambos órdenes de importación, las anotaciones resueltas y el OpenAPI completo del Vault, y comprueban claves YAML antiguas, sidecars y traslados de archivos. Esto elimina un ciclo de inicialización; no acredita el tipado completo de los contratos de metadatos heredados que siguen pendientes.

El comportamiento del ciclo de vida de las traducciones es responsabilidad de backend/domains/vault/translation: la carga opcional de proveedores, la recuperación de archivos en la nube, la traducción de filas y páginas completas, los efectos mínimos sobre los metadatos y la propagación de obsolescencia a los hijos son servicios tipados separados. La capa compartida de funciones auxiliares puras normaliza las identidades de origen a su forma canónica, detecta cambios traducibles y campos de idioma, reutiliza etiquetas de opciones existentes y traduce únicamente los subcampos textuales de las imágenes, conservando el recurso de origen. La publicación de filas en Drupal es responsabilidad de backend/domains/vault/drupal, que separa la correspondencia de campos e identidades, la preparación de recursos multimedia locales, la conversión de Markdown y wikilinks, las cachés de idiomas, la correspondencia de títulos y la sincronización idempotente de nodos. El router de compatibilidad conserva los decoradores FastAPI originales, los docstrings de las rutas y los puntos de sustitución Python resueltos en el momento de uso, mientras que el conector de Drupal sigue siendo la capa de transporte externo. Estos cambios de ubicación no alteran rutas, datos intercambiados, códigos de estado, tareas en segundo plano ni el orden de las rutas.

Los proveedores de traducción se cargan dentro de los bloques existentes de gestión de errores durante la petición, con imports de sus propietarios tipados reales y sin afirmaciones de tipos sobre módulos. Las funciones devueltas conservan su identidad, argumentos por nombre originales y sustitución tardía. Los protocolos de página describen el contrato posicional compartido de contenido e idiomas y el argumento de credencial solo por nombre. Los módulos o miembros ausentes conservan los mismos errores HTTP; las credenciales no disponibles conservan el retorno de clave vacía. Cargar estos adaptadores no carga modelos de traducción ni lee credenciales durante el arranque de la aplicación.

La autorización de tablas utiliza los tipos reales de workspace_service. La dependencia get_workspace_context capturada inicialmente y las consultas posteriores de require_role conservan su identidad y momento de resolución; las comprobaciones devuelven la misma instancia de WorkspaceContext o el mismo error de permisos. No cambian los umbrales de los roles, las reglas de autenticación ni la selección del workspace.

Índices y cachés

El índice de páginas acelera los listados, la resolución de identificadores, el acceso al frontmatter y las búsquedas. El índice de wikilinks resuelve los enlaces entrantes para poder actualizar las referencias al renombrar páginas. Las cachés de cuerpos y documentos analizados evitan lecturas repetidas. Todas las cachés son derivadas y deben admitir una reconstrucción desde cero.

links/document_inventory.py se encarga del inventario con TTL por Vault que utilizan los enlaces globales. Excluye el historial y la papelera, aísla los archivos que no se pueden leer, incluye los paneles JSON y recurre a un recorrido del disco mientras el índice del proveedor no está disponible. links/document_cache.py se encarga de las cachés persistentes del cuerpo de Markdown y del frontmatter analizado, cuya clave se basa en mtime. El router solo proporciona las rutas de caché activas, el analizador y el escritor JSON seguro, por lo que el comportamiento de las cachés es independiente del proveedor de archivos. links/relation_sync.py se encarga de las actualizaciones idempotentes del sistema de archivos y las cachés cuando cambian las relaciones directas y sus inversas. La correspondencia pura de esquemas permanece en un puerto de reglas tipado separado: resuelve los campos de relación mediante nombres actuales y alias normalizados, exige un único campo inverso sin ambigüedades y emite únicamente operaciones de adición y eliminación sobre ID canónicos de relaciones. El router de compatibilidad proporciona la entrada y salida de páginas resuelta en el momento de uso.

El inicio carga primero las instantáneas válidas del disco y después pone en marcha la actualización. Un recorrido parcial del proveedor de archivos se marca como parcial y no puede reemplazar una caché que se sabe completa. Los fallos de cada archivo se aíslan para que un marcador de posición disponible solo en línea o huérfano no elimine el resto del Vault de una respuesta.

pages/index_entries.py se encarga de las lecturas acotadas del frontmatter, los reintentos ante bloqueos de la nube y la normalización de las entradas de caché. pages/index_service.py se encarga del descubrimiento, la actualización, los mapas inversos de ID y las instantáneas deduplicadas. pages/resolver.py se encarga de la resolución por ID estable, UUID canónico, título indexado y recorridos acotados sin caché. pages/tags.py se encarga de agregar las etiquetas del frontmatter y las etiquetas semánticas de tablas de forma independiente del proveedor, incluida la deduplicación por página. El router de compatibilidad inyecta los puertos del Vault activo, el registro de configuración, el calendario y las cachés, de modo que ninguno de estos servicios importa la fachada HTTP.

El entorno de ejecución del registro referencia los propietarios tipados reales de los callbacks y del estado, utiliza el decorador estándar de gestores de contexto para los ciclos de modificación y trata la falta de un Vault activo como ausencia de una raíz de adjuntos en la nube. El orden de las rutas de registro y tablas, los bloqueos, las cachés y los candidatos a adjuntos específicos de cada proveedor permanecen sin cambios.

La API principal del Vault importa directamente el router y los servicios y limita sus colaboradores de resolución tardía a CoreVaultPort. La creación de páginas admite metadatos abiertos sin coerciones; la inserción en el índice actualiza el propietario existente de la caché. Los nombres de usuario conservan las alternativas de nombre, correo e identificador. La creación de notas diarias pasa el usuario del espacio de trabajo ya autorizado al servicio canónico, en vez de llamar a un manejador HTTP con una dependencia pendiente de resolver. Los callbacks explícitos de plugins conservan sus dos argumentos históricos. Los permisos, controles de plugins, recuperación de notas existentes, bloqueo de creación y esquemas HTTP públicos no cambian.

La composición de formato, búsqueda, catálogo y exportación de citas utiliza contratos explícitos de registros y callbacks sin conversiones de resultados. Las propiedades del registro conservan su identidad; los consumidores de lectura admiten interfaces de mapeo y secuencia. Las referencias importadas reciben el contexto del usuario autorizado cuando usan el manejador canónico de páginas; los callbacks tardíos de dos argumentos siguen admitidos. La deduplicación, formatos, descargas y errores de Pandoc no cambian. Todos los entornos guardan la designación bibliográfica en GNOSI_DATA_DIR/config/references.json. La configuración antigua requiere scripts/migrate-reference-config.py: su migración explícita sin sobrescritura conserva los bytes, campos desconocidos y el original, con diario privado y reversión recuperable. El arranque comprueba este requisito antes de migrar bases de datos o iniciar tareas. La validación temporal nunca consulta archivos antiguos.

La consulta de metadatos, el reconocimiento de PDF, la traducción de URL, la promoción de Zotero, las actualizaciones masivas y el registro del catálogo y la búsqueda de citas comparten esa misma capa HTTP de tipo acotado. Los mecanismos alternativos de proveedores, los permisos del editor y la unicidad de las claves de cita siguen resolviéndose en el momento de uso y conservan su comportamiento.

La consulta bibliográfica importa los servicios directamente y declara bajo TYPE_CHECKING alias comprobados de los propietarios reales de los callbacks, sin conversiones de módulos ni resultados. La sustitución tardía sigue vigente. Las pruebas cubren ambos órdenes de importación, los esquemas HTTP exactos, la sustitución de callbacks durante una consulta y la conservación de los metadatos desconocidos. citations/title_regex.py conserva los errores nativos de Python: la única excepción documentada del verificador valida entradas incorrectas y nunca afecta a los datos devueltos. Los tipos heredados de proveedores de registro y páginas siguen siendo deuda separada.

La importación de Markdown, los comentarios en línea, los bloques sincronizados, la navegación por enlaces y las menciones sin enlazar comparten un router tipado de sincronización de páginas. Los modelos de solicitud utilizan Pydantic directamente y mantienen la identidad histórica de su módulo, conservando los nombres de los esquemas, el comportamiento SSE y la salida OpenAPI.

El CRUD de anotaciones PDF importa directamente el router compartido y las dependencias de autorización y persistencia. Los payloads TypedDict con nombre describen los diccionarios devueltos a los consumidores Python sin conversiones de tipo ni Any. Los rectángulos guardados conservan la decodificación JSON original; los modelos HTTP siguen validando su forma. No cambian las identidades de los esquemas, el filtrado por URI, el orden por página y fecha de creación, los permisos, las actualizaciones con campos nulos u omitidos ni el esquema SQLite. Las pruebas aisladas SQLite y HTTP cubren ambos órdenes de importación: primero la fachada o primero el dominio.

La administración del Vault ahora falla explícitamente con una respuesta de servicio no disponible cuando falta la ruta principal del Vault, en lugar de construir una ruta a partir de None. Las anotaciones heredadas de respuesta permanecen congeladas, y el cambio de nombre lógico cruza la antigua capa de descriptores del ORM sin cambiar carpetas en disco, slugs, reglas de purga ni comprobaciones de confinamiento de rutas.

El catálogo de plantillas del Vault, la instalación, la exportación y el envío moderado exponen contratos tipados de solicitud y respuesta. Los manejadores validan cada diccionario antes de devolverlo y deshabilitan la publicación del modelo de respuesta en las rutas de compatibilidad, para que no varíen los esquemas FastAPI congelados ni el contrato de diccionarios de las llamadas directas. Las comprobaciones de firmas, los hallazgos de privacidad, los paquetes deterministas y la reversión ante fallos de registro no cambian.

Proveedores de archivos

Al iniciar o cambiar de Vault se solicita la descarga de todos los metadatos de la nube de .gnosi, incluidos habilidades, plugins, sidecars, estado de Wiki, iconos y copias de seguridad. El lector también solicita archivos individuales cuando se necesitan. Dos procesos auxiliares y plazos de diez segundos acotan el trabajo del proveedor; las lecturas concurrentes comparten una petición y los fallos requieren esperar treinta segundos antes de reintentar. Las lecturas asíncronas devuelven un 503 recuperable sin bloquear el bucle de eventos. Los metadatos remotos no se sustituyen por valores predeterminados: los lectores propagan la indisponibilidad y las escrituras atómicas requieren que el archivo original esté disponible localmente. Los archivos inexistentes conservan su creación normal. La descarga en segundo plano excluye árboles con enlaces simbólicos. Si el proveedor falla, no se garantiza la disponibilidad; la configuración crítica permanece cerrada por seguridad.

La abstracción de proveedores selecciona el comportamiento local, genérico de macOS File Provider, o adaptado a OneDrive, iCloud Drive, Google Drive, Nextcloud o Dropbox. El código habitual del dominio sigue trabajando con Path; el adaptador añade detección de marcadores de posición, hidratación, disponibilidad y correspondencia de rutas. Configurar GNOSI_FILES_PROVIDER explícitamente cuando la detección automática de rutas sea ambigua.

El entorno de archivos bajo demanda es independiente del proveedor. En macOS se utiliza por defecto un auxiliar de solo lectura basado en NSFileCoordinator, compilado y firmado en Contents/Resources/native/gnosi-file-access. Cada llamada solicita un archivo, lee como máximo 64 KiB y no devuelve contenido del documento. El límite de dos descargas simultáneas y la unificación de peticiones por ruta acotan la carga; el tiempo límite o la cancelación terminan el proceso auxiliar. Este modo no abre aplicaciones gráficas, fija archivos al disco, descarga recursivamente ni reinicia clientes de nube. Docker conserva el auxiliar configurado en el host. Los modos antiguos open y direct requieren una selección explícita; solo el adaptador antiguo de OneDrive puede activar una recuperación específica del proveedor.

La detección del índice utiliza indicadores de archivos sin datos para conservar marcadores accesibles sin abrir todos los documentos de la nube. Las entradas existentes más completas se conservan si un archivo no se puede leer; la descarga no necesita cambiar el mtime para actualizar un marcador. Una exploración inicial fallida no se marca como inicializada. La navegación lateral y la tabla solicitada obtienen los metadatos necesarios, mientras que las lecturas de páginas y adjuntos materializan el archivo seleccionado. Los archivos no disponibles devuelven 503 con Retry-After en lugar de una lectura vacía correcta. El precalentamiento masivo del árbol crítico está desactivado por defecto con el acceso coordinado nativo.

La validación del código del 14 de septiembre incluye pruebas sintéticas de fallo, cancelación, deduplicación y ausencia de lecturas masivas, además de una lectura real de un marcador de OneDrive. Esa descarga tardó 2,2 segundos; las lecturas directas posteriores del mismo archivo de 3,9 KiB tardaron 0,24–1,03 ms, no el tiempo total de renderización de la página.

El cambio está empaquetado en el DMG local con firma ad hoc Gnosi-3.0.2-on-demand-arm64.dmg, e instalado desde esa imagen. El backend empaquetado ha superado una prueba aislada de vaults, páginas y favoritos. Un PDF real de OneDrive de 428.327 bytes ha pasado de no tener datos ni bloques locales a estar disponible mediante el endpoint Library de la aplicación instalada. Una petición HTTP Range de 16 bytes tardó 3,5959 segundos la primera vez y 14,8 ms al repetirla; ambas devolvieron 206 y una cabecera PDF. No son medidas de renderización del documento completo. La aplicación instalada enumera los tres vaults existentes; su barra lateral devuelve 2.672 páginas y 19 favoritos, y la interfaz abre un favorito con el contenido de la página. También se ha probado correctamente la opción de expulsar solo la imagen. No es una release publicada ni notarizada por Apple; otros proveedores de nube y Windows no se han probado en vivo. Mostrar todos los favoritos inmediatamente en un dispositivo nuevo aún requiere metadatos portables de navegación: los nombres de archivo no permiten deducir favoritos que solo constan dentro de un documento no descargado.

Las rutas de Dropbox File Provider se detectan explícitamente. Un servicio desconocido en ~/Library/CloudStorage de macOS utiliza el adaptador fileprovider, que no tiene efectos secundarios; cualquier carpeta totalmente sincronizada o montada de forma ordinaria utiliza local. Solo hace falta un nuevo adaptador con nombre propio para una señal de marcador de posición distinta o un mecanismo de hidratación específico del proveedor. GNOSI_DATA_DIR sigue siendo local independientemente del proveedor del Vault.

Solo el Markdown portátil del Vault y los adjuntos pueden residir en un árbol sincronizado. Las bases de datos SQLite, los bloqueos, las cachés derivadas, los secretos y GNOSI_DATA_DIR permanecen en el almacenamiento local de la aplicación. Una carpeta de Nextcloud totalmente sincronizada se comporta como local; los despliegues con archivos virtuales utilizan el proveedor correspondiente o el adaptador genérico fileprovider. WebDAV y las API directas de la nube son transportes de transferencia o copia de seguridad, no almacenamiento activo para SQLite. El destino de las copias de seguridad y el proveedor del Vault se configuran de forma independiente.

Adjuntos y propiedades de tipo archivo

Las escrituras eligen un destino permitido dentro del Vault activo, normalizan los nombres, evitan colisiones y devuelven metadatos portátiles. La raíz de los enlaces a archivos se adapta al host actual en el momento de la lectura. Las operaciones de subida y eliminación validan el confinamiento de las rutas; una ruta proporcionada por el cliente nunca constituye autorización suficiente.

Los manejadores de rutas de recursos y archivos son exportaciones canónicas del dominio. El router heredado del Vault los registra en sus posiciones históricas e inyecta puertos acotados para consultas al registro de configuración, resolución de rutas y selección de proveedores. No debe mantener un segundo mapa de tokens locales, bloqueo de iconos personalizados o semáforo de flujos de archivos. Los decoradores repetidos de /local-file/{token} conservan su orden original de rutas de abajo arriba, y cada cambio estructural debe preservar las cabeceras de transmisión y el documento OpenAPI exacto.

Los metadatos de tipo archivo se normalizan recursivamente sin cambiar su estructura de lista u objeto. Las rutas Assets/ existentes y las URL HTTP remotas siguen siendo referencias; las URL de datos y los archivos locales aprobados se copian de forma atómica al directorio de recursos de la propiedad. La limpieza física resuelve cada candidato dentro de la raíz Assets del Vault activo antes de desvincularlo, por lo que una cadena de recorrido de directorios en el frontmatter no puede salir del Vault.

Papelera y operaciones destructivas

drawings/service.py se encarga del descubrimiento de dibujos Tldraw y Excalidraw heredados, las lecturas, las instantáneas del historial con un intervalo mínimo entre ellas, las escrituras atómicas y la eliminación recuperable. El trabajo sobre el sistema de archivos se ejecuta fuera del bucle de eventos, y la eliminación reutiliza el mismo contrato de archivos auxiliares de la papelera del Vault que las páginas.

La eliminación ordinaria es recuperable: las páginas y los recursos relacionados pasan por el modelo de papelera del Vault. La purga es una operación distinta y elimina el contenido junto con los metadatos derivados y las relaciones inversas. trash/purge.py se encarga de la fase irreversible sobre el sistema de archivos y de la limpieza del historial, los archivos auxiliares de metadatos y los comentarios, mediante puertos de la fachada resueltos en el momento de uso. La eliminación de un Vault del registro borra por defecto la fila lógica del registro; eliminar físicamente la carpeta requiere una señal explícita separada y comprobaciones de confinamiento más estrictas.

Al eliminar una tabla, primero se mueve de forma atómica cada árbol de recursos perteneciente a la tabla a .gnosi/pending-cleanup/table-assets/in-progress-* y se escribe un manifiesto dentro de los límites permitidos. Después, la confirmación de la transacción del registro renombra ese directorio a ready-* antes de una purga en segundo plano. La recuperación al iniciar restaura una cuarentena en curso si la tabla todavía existe, la purga cuando el registro persistente demuestra la eliminación y deja intactas las entradas que no se pueden leer o son desconocidas. Las revisiones de recursos incluyen los enlaces simbólicos sin seguir sus destinos e impiden la eliminación tras una vista previa desactualizada.

Plantillas de Vault

El repositorio de plantillas es un catálogo firmado que se utiliza en tiempo de ejecución; los recursos de los paquetes no se versionan en el repositorio Git de la aplicación. La creación a partir de una plantilla verifica la firma separada del índice, el SHA-256 del paquete, la firma del publicador, el manifiesto, el inventario de archivos, los límites del archivo comprimido, las rutas, los tipos de archivo y los enlaces antes de escribir. La extracción se realiza en un directorio de preparación hermano dentro de la raíz de Vaults. El directorio completado se mueve a su ubicación definitiva de forma atómica y solo entonces se registra en la base de datos de gestión, de modo que un fallo no pueda exponer un Vault parcial.

La validación del archivo comprimido se divide en validación acotada de entradas, decodificación del manifiesto, comparación del inventario y comprobaciones de integridad del contenido. Estos pasos puros y tipados conservan el mismo contrato de paquetes que rechaza la operación ante fallos y mantienen cada función auxiliar por debajo del límite de complejidad del backend.

La exportación se basa en una lista de elementos permitidos y es determinista. Excluye .gnosi, plugins, almacenes de confianza, correo, papelera, historial, contenido ejecutable, archivos de entorno, enlaces, archivos que no se pueden leer y contenido de tamaño excesivo. Una vista previa enumera todos los archivos incluidos y excluidos y analiza archivos de texto con límites definidos para buscar valores que parezcan credenciales. Los hallazgos requieren una aceptación explícita. Los plugins recomendados son identificadores en el manifiesto; el código ejecutable de plugins nunca se incluye en una plantilla de Vault.

El envío público es independiente de la exportación y requiere acceso de administrador. Utiliza un intermediario opcional de moderación en lugar de una credencial de GitHub incorporada en Gnosi. Los campos adicionales del acuse de recibo específicos del intermediario se conservan sin pérdida mediante un modelo de respuesta que permite campos adicionales; los datos de error del catálogo mantienen su estructura heredada para la recuperación sin conexión y ante errores de firma.

Invariantes de concurrencia

daily/service.py se encarga, de forma independiente del proveedor, del descubrimiento de carpetas y tablas, la normalización de fechas, la creación inicial a partir de plantillas, los listados y el flujo atómico de obtención o creación de notas diarias. El router de compatibilidad conserva los decoradores públicos de FastAPI e inyecta comandos de página resueltos en el momento de uso para que los plugins y las pruebas existentes mantengan sus puntos de sustitución.

  • Los ETags desactualizados impiden las sobrescrituras.
  • La creación de registros de configuración y notas diarias utiliza nuevas comprobaciones seguras frente a condiciones de carrera.
  • Las actualizaciones de páginas, registros de configuración, índices de enlaces y archivos auxiliares mantienen la coherencia tras un cambio de nombre o una eliminación.
  • Las rutas absolutas recibidas de un cliente se resuelven dentro de raíces aprobadas.
  • Los enlaces simbólicos y el recorrido de directorios no pueden salir de los límites del Vault seleccionado.
  • La extracción de plantillas no puede publicar un directorio parcial ni registrarlo antes de tiempo.
  • Las exportaciones de plantillas no pueden incluir estado de ejecución ni contenido ejecutable de plugins.
  • Las conversiones de ida y vuelta de Markdown conservan el contenido sensible al uso de caracteres de escape y la sintaxis de wikilinks.

Frontend

VaultDashboard se encarga del historial de navegación y selecciona las interfaces de página, tabla, dibujo, galería, tablero, calendario, línea temporal, feed o lector. VaultShell proporciona la estructura; los componentes especializados implementan los editores y las vistas. El frontend almacena en caché el estado de interacción, pero considera autoritativos el contenido de las páginas y los ETags del backend.

El traslado revisado sitúa VaultDashboard.tsx en la raíz de la feature y su orquestación en features/vault/dashboard/. La composición de tablas y la edición de celdas están en features/vault/views/vault-table/; los campos y las opciones del esquema, en features/vault/schema/schema-config/; y las propiedades de página, los documentos enriquecidos, los efectos y la persistencia, en features/vault/editor/block-editor/. El renderizado, los hooks de registros y los controles de vista reutilizables pertenecen a shared/editor/, shared/records/ y shared/record-views/; nunca importan UI del Vault. Un módulo solo es público mediante la raíz de la feature o su entrada exacta revisada en el manifiesto; ser un archivo de composición no lo hace público. Los cambios de propiedad conservan rutas API y formatos de almacenamiento; la verificación de integración sigue siendo un paso separado.

Las transiciones de Markdown al modo visual publican los borradores pendientes antes de montar el editor enriquecido, evitando que el contenido desactualizado del componente padre reemplace una edición sin guardar. Los guardados de solo metadatos omiten el cuerpo; las fórmulas de valores predeterminados conservan los valores anidados de relaciones y plugins. Las pruebas de regresión cubren estos traspasos, además de los identificadores de opciones del esquema, la identidad de las filas de las tablas y las extensiones de metadatos desconocidas.

Continuidad de las pestañas de documentos

Las pestañas abiertas de página, tabla y PDF conservan sus vistas montadas tras la primera visita, también cuando un documento pasa entre el panel principal y los paneles divididos. Cerrar una pestaña libera su vista. Volver del PDF de una cita restaura el foco en la tarjeta de origen de la galería y las posiciones de desplazamiento anidadas sin desplazar al dar el foco; la navegación con flechas continúa desde esa tarjeta. Los desplazamientos pendientes del layout no pueden sustituir la posición guardada, y una entrada de teclado o puntero cancela cualquier frame de restauración pendiente.

Los paneles ocultos son inertes. Sus atajos globales de selección, edición y navegación ceden a los paneles visibles, y sus menús renderizados en portales se ocultan con la pestaña propietaria. La expansión de los grupos de galería se guarda por sesión, página y vista de anclaje guardada, en lugar de los identificadores de bloque regenerados del editor. Este estado de interacción no modifica el contenido de las páginas, las definiciones de vista guardadas ni los ETags.

Aspectos que verificar

Ejecutar pruebas de concurrencia con ETag, confinamiento de rutas, entrada y salida seguras, condiciones de carrera del registro, cambios de nombre, papelera y purga, numeración de adjuntos, relaciones, actualización de índices y flujos representativos del Vault con Playwright. Los incidentes de proveedores en la nube también requieren leer un marcador de posición real, porque las pruebas con datos locales de prueba no pueden reproducir el comportamiento de File Provider.

Recuperación de metadatos de la barra lateral

El resumen y el árbol compacto de la barra lateral recuperan las entradas incompletas de la raíz, Wiki y los tableros mediante el proveedor de archivos activo antes de releer sus metadatos. Esto también se aplica a las instantáneas en caché. La recuperación restaura los identificadores canónicos y de los padres, las marcas de base de datos, los favoritos y los iconos, actualiza la búsqueda por identificador y reconstruye la instantánea versionada para eliminar duplicados. Las entradas completas y los catálogos de tablas no activan esta recuperación. Las lecturas parciales de la cabecera reintentan ambos códigos de error transitorio de la nube (11 y 35), empezando con el búfer y los delimitadores vacíos cada vez que se reabre el archivo. Las lecturas fallidas conservan los metadatos existentes en caché.

Cambiar de biblioteca con una nota abierta cierra su editor. BlockNote puede destruir la vista ProseMirror antes de que React ejecute la limpieza de navegación de los elementos incrustados. Esta limpieza restaura los controladores de teclado anteriores solo si la vista sigue viva y conserva los controladores instalados; nunca llama a setProps sobre una vista destruida. Las pruebas de regresión utilizan una vista ProseMirror real y cubren ambos órdenes de cierre para evitar que una excepción elimine toda la interfaz.

Recuperación de creaciones por ámbito y descargas de manuscritos

Las peticiones de creación persisten un recibo privado y checkpoints antes de los efectos recuperables. La recuperación conserva el ID original de la página y las entradas tipadas congeladas, verifica las revisiones del documento y la configuración y comprueba el acceso antes de cada efecto. Solo se repite trabajo no iniciado. Un paso de índice iniciado solo se puede reconciliar tras verificar las entradas registradas y la postcondición actual; los demás efectos inciertos siguen bloqueados. La recuperación no selecciona código ejecutable a partir de datos almacenados. El navegador consulta la petición guardada y abre la página existente, sin crear un duplicado cuando se pierde la respuesta.

El vault efectivo seleccionado por cabecera, query o cookie es también la identidad usada para la autorización. Un vault explícitamente inexistente, sin acceso o sin montar falla sin recurrir a otro vault.

Abrir el canvas de dibujo no descarga ni carga automáticamente un modelo OCR. El estado distingue pesos ausentes, descarga, descargado, carga, cancelación y error. La cancelación queda limitada al propietario y afecta a la transferencia, no a una inferencia activa. Los archivos solo se hacen visibles en la caché tras verificar su tamaño y checksum; los archivos parciales se eliminan. La interfaz muestra el tamaño transferido y el estado traducido. Las pruebas sintéticas de transferencia y canvas verifican estos contratos, pero no la precisión con manuscritos reales ni archivos de nube sin materializar.

Notas generadas, referencias legibles y revisión del Conocimiento

El tipo de idea es una asignación por IA predeterminada mediante el ID de propiedad, independiente de los campos de índice. El contrato existente de dimensiones transmite las etiquetas traducidas permitidas y una justificación breve classification_reason; los valores vacíos representan una abstención explicada. Las asignaciones explícitas copiadas, fijas y vacías tienen prioridad. La interpretación, la revisión y el guardado conservan el valor, y llm_wiki_idea_classification registra su versión, método y procedencia. El reprocesamiento preserva los valores modificados manualmente. La clasificación antigua ausente debe completarse, sin aplicar Concepto implícitamente.

El Cerebro ya no crea, vincula ni rellena el estado de verificación o la fecha de última revisión. El estado de trabajo y las fechas del sistema pertenecen a la aplicación; las asignaciones del recurso no pueden sobrescribirlos. Las nuevas notas de lectura son borradores. El estado existente y la fecha de creación se conservan al reprocesar. Se retira la categoría de notas antiguas basada en fechas; la API conserva stale como lista vacía y el recuento a cero por compatibilidad. Las notas generadas sustituidas siguen siendo una incidencia diferente.

La orden explícita de migración es python -m backend.domains.llm_wiki.note_migration --vault /path/vault --output /private/report --action preview. Las acciones classify, apply y rollback separan el trabajo gobernado del modelo de la modificación de datos. La simulación solo lee; la clasificación reutiliza los resultados de notas sin cambios y los presupuestos de lectura existentes sin reiniciar el gasto. La aplicación retira los campos y referencias en vistas y configuración por ID y rol, conserva el cuerpo y las demás propiedades, verifica una copia comprimida, compara cada original antes de escribir y registra el progreso para recuperarse. Las copias e informes deben quedar fuera del vault sincronizado y de Git. La migración de esquema registra note_fields_revision y repetirla es idempotente. La acción opcional retire aplica solo la retirada de campos y la limpieza del estado de trabajo, informa de la clasificación pendiente y no llama a ningún modelo; se debe usar una carpeta de salida diferente para cada fase aplicada.

El procesamiento de recursos crea notas de lectura con el estado Esborrany y las fechas canónicas de creación y modificación de la tabla. El reprocesamiento conserva la fecha de creación y el estado de trabajo existente, actualiza la modificación y mantiene los identificadores estables y el texto del usuario.

Los enlaces internos muestran los títulos actuales en editores, relaciones, tablas, galerías, kanban, búsqueda, grafos, menciones y predecesores de planificación. El resolutor compartido admite identificadores, rutas de página, ambos órdenes de wikilink y citas de recursos gnosi-cite:. Las referencias desconocidas muestran un texto provisional localizado mientras se carga su título. Los índices de relaciones conservan sus claves gestionadas e identificadores guardados con etiquetas legibles.

Las previsualizaciones de lectura ocultan los comentarios HTML de procesamiento, incluidos los límites <!-- ... -->. Los comentarios literales de bloques de código y código en línea siguen visibles; el Markdown guardado se conserva.

Las conexiones del Conocimiento solicitan sus textos en el idioma activo mediante el parámetro de consulta opcional locale. El agente del Conocimiento traduce solo títulos y explicaciones y guarda cada idioma por propuesta. Se conservan las evidencias y los identificadores de las notas. Las lecturas de caché evitan otra llamada al modelo y una propuesta descartada durante la traducción no se restaura.

El diálogo de revisión agrupa notas y recursos afectados en categorías desplegables con enlaces por título y lotes de veinte entradas. Abrir un resultado cierra el diálogo y abre su registro. La comprobación de notas huérfanas reconoce tanto [[id|title]] como [[title|id]]. La revisión no reprocesa recursos automáticamente ni crea notas permanentes.

En el escritorio, la selección de archivos y carpetas utiliza la capacidad IPC fiable y validada pick-filesystem y el diálogo nativo del sistema operativo. Cancelar cierra el selector sin aplicar una selección; las selecciones múltiples conservan el tipo de archivo o carpeta de cada ruta. Windows y Linux muestran primero una elección nativa entre archivos y carpetas para la selección combinada. El navegador interno sigue disponible para clientes web sin el puente de escritorio.

La cabecera de un solo documento muestra Cerrar pestaña únicamente cuando el identificador activo corresponde a una pestaña abierta. Conservar una pestaña inactiva no debe mostrar la acción de cerrar en la pantalla de bienvenida. Los controles de apertura y búsqueda siguen disponibles y cerrar la última pestaña activa elimina su botón de cierre.

Añada --resource-id <id> a preview para clasificar solo un recurso; las acciones posteriores reutilizan esa selección guardada. La clasificación vincula al propietario personal verificado, excluye contextos adjuntos y memorias revisadas, desactiva las delegaciones y reserva como máximo 4096 tokens de salida por lote sin cambiar los límites de gasto.

Los encabezados plegables admiten Alt/Opción+Intro con el cursor dentro del título. El botón nativo se puede enfocar con Tab y activar con Intro o Espacio, con foco visible, etiqueta traducida y estado de despliegue. La activación conserva el cursor o el foco del botón y el estado de despliegue guardado. Intro sin modificadores conserva su comportamiento de edición; los párrafos hijos, los controles incrustados y la composición de texto no pliegan una sección padre.