---
name: vault_layout_ensure
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func VaultLayoutEnsure(vaultPath string, dryRun bool) (LayoutReport, error)"
description: "Normaliza el layout de un vault al esquema hibrido canónico data/{raw,processed,exports} + knowledge/{decisions,domains,models,benchmarks,test_documents}. Migra directorios legacy en la raíz del vault a su ubicación correcta; idempotente."
tags: [vault, layout, migration, infra, filesystem, idempotent]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
  - "fmt"
  - "os"
  - "path/filepath"
  - "strings"
params:
  - name: vault_path
    desc: "Ruta al directorio raíz del vault. Puede ser absoluta, relativa o un symlink — se resuelve con filepath.Abs + filepath.EvalSymlinks. Trailing slashes se ignoran."
  - name: dry_run
    desc: "Si true, calcula el reporte completo (qué se crearía, migraría, etc.) pero no modifica el disco. Util para previsualizar antes de ejecutar."
output: "LayoutReport con: VaultPath (ruta resuelta), Created (dirs creados), Migrated (renombres ejecutados, formato 'src -> dst'), AlreadyOK (destinos que ya existían), Skipped (entradas en raíz no reconocidas, no tocadas), DryRun (flag). Error si el path no existe, no es directorio, o hay conflicto de merge (mismo nombre de archivo en src y dst)."
tested: true
tests:
  - "TestVaultLayoutEnsure_DryRun_NoChange"
  - "TestVaultLayoutEnsure_FreshDir_CreatesLayout"
  - "TestVaultLayoutEnsure_LegacyDataLayout_Migrates"
  - "TestVaultLayoutEnsure_LegacyKnowledgeLayout_Migrates"
  - "TestVaultLayoutEnsure_AlreadyMigrated_Idempotent"
  - "TestVaultLayoutEnsure_Mixed_PartialMigration"
  - "TestVaultLayoutEnsure_MergeConflict_Errors"
  - "TestVaultLayoutEnsure_UnknownFiles_Skipped"
  - "TestVaultLayoutEnsure_NotADir_Errors"
test_file_path: "functions/infra/vault_layout_ensure_test.go"
file_path: "functions/infra/vault_layout_ensure.go"
---

## Ejemplo

```go
// Previsualizar sin tocar disco:
report, err := VaultLayoutEnsure("/home/lucas/vaults/turismo_spain", true)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Would migrate: %v\n", report.Migrated)
fmt.Printf("Would create:  %v\n", report.Created)

// Ejecutar la migración:
report, err = VaultLayoutEnsure("/home/lucas/vaults/turismo_spain", false)
if err != nil {
    log.Fatalf("migration failed: %v", err)
}
fmt.Printf("Migrated: %v\n", report.Migrated)
fmt.Printf("Created:  %v\n", report.Created)
fmt.Printf("Skipped:  %v\n", report.Skipped)
```

## Comportamiento detallado

**Directorios gestionados:**

| Raíz (legacy) | Destino canónico |
|---|---|
| `raw/` | `data/raw/` |
| `processed/` | `data/processed/` |
| `exports/` | `data/exports/` |
| `decisions/` | `knowledge/decisions/` |
| `domains/` | `knowledge/domains/` |
| `models/` | `knowledge/models/` |
| `benchmarks/` | `knowledge/benchmarks/` |
| `test_documents/` | `knowledge/test_documents/` |
| `README.md` / `README.txt` | `knowledge/README.md` |

**Lógica de migración (por cada entrada conocida):**

- Solo `src` existe → rename atómico `src` → `dst`, registrado en `Migrated`.
- Solo `dst` existe → ya migrado, registrado en `AlreadyOK`.
- Ambos existen (dir) → merge: mueve cada hijo de `src/` a `dst/`; error si mismo nombre. Registrado en `Migrated` por hijo.
- Ambos existen (archivo README) → error inmediato con paths concretos.
- Ninguno existe → crea `dst` vacío, registrado en `Created`.

**Archivos/dirs no reconocidos** en la raíz (`.git`, `vault_index.db`, archivos custom) se registran en `Skipped` y no se tocan.

**Idempotencia:** segunda ejecución sobre un vault ya migrado reporta todo en `AlreadyOK` y no toca disco.

## Notas

`LayoutReport` es un tipo local de esta función (no un tipo del registry). El struct exportado vive en `functions/infra/vault_layout_ensure.go` junto con la función.

Para aplicar la migración a múltiples vaults en batch, invocar desde un pipeline que lea los paths de `vault.yaml` (ver `vault_manifest_read_go_infra`) y llame a `VaultLayoutEnsure` en cada uno.
