--- name: vault_index_open kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func VaultIndexOpen(vaultPath string) (*sql.DB, error)" description: "Abre (o crea) vault_index.db dentro de vaultPath con WAL + FK y aplica las migraciones embebidas idempotentemente. El caller cierra la conexion." tags: [vault, sqlite, index, migration, infra] uses_functions: ["sqlite_open_go_infra", "sqlite_apply_migrations_go_infra"] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [database/sql, embed, fmt, path/filepath] params: - name: vaultPath desc: "ruta absoluta o relativa al directorio raiz del vault" output: "*sql.DB apuntando a /vault_index.db con schema completo aplicado; el caller es responsable de cerrar" tested: true tests: - "crea vault_index.db en tmpdir vacio" - "segunda apertura no falla (idempotente)" - "todas las tablas esperadas existen en sqlite_master" - "fts5 INSERT y MATCH funcionan" test_file_path: "functions/infra/vault_index_open_test.go" file_path: "functions/infra/vault_index_open.go" --- ## Ejemplo ```go db, err := VaultIndexOpen("/data/vaults/turismo_spain") if err != nil { log.Fatal(err) } defer db.Close() ``` ## Notas El archivo de base de datos se crea en `/vault_index.db`. Las migraciones viven en `vault_index_migrations/*.sql` embebidas via `//go:embed` en el mismo paquete. Schema creado por `001_init.sql`: - `files` — inventario de archivos (PK: rel_path) - `files_fts` — tabla FTS5 virtual para busqueda de texto (content_text lo llenan profilers posteriores) - `csv_profiles` — perfil de columnas/filas para .csv (FK → files) - `pdf_extracts` — metadatos de extraccion de texto para .pdf (FK → files) - `knowledge_docs` — headings/frontmatter para .md del bucket knowledge (FK → files) `SQLiteOpen` abre con WAL mode + foreign keys. `ApplyMigrations` es idempotente: los errores de "already exists" y "duplicate column" se ignoran silenciosamente.