hosystem Engagements

ho-04.14 — fresh-install vault scaffold

created 2026-07-08
status complete
type ho-document
project sharibako
ho 04.14
kamae 5
shape ha

Responds to a finding surfaced during ho-04.13 execution (; ho-04.13 is closed). The -4 overview anticipated this as a "ho-04.14-shaped insert" gating Phase 4: "if a clean production init doesn't build the vault, fix it first." It doesn't. This fixes it.

The finding — a fresh-install catch-22. On a machine with no vault directory:

  1. sharibako key generate writes the age key but never creates the vault directory (scopes/ + shared/).
  2. sharibako init <proj> then fails at VaultLocator.resolve with Vault not found at …/vault. Hint: Run 'sharibako key generate' to create one.
  3. But key generate is the command that just didn't create it. The hint points the user back at the command that can't do what the hint promises. A brand-new user is stuck in a loop.

Reproduced live this session with the installed ho-04.13 binary against a fresh vault path. Independently flagged by the 2026-07-03 signed-install smoke as "createVaultLayout never called in production."

Root cause — a documented contract with no public API. VaultCore.init(vaultURL:)'s own doc comment instructs:

Does not create the vault; call VaultLayout.createVaultLayout(at:) first when initializing a fresh vault.

But VaultLayout is internal enum and createVaultLayout is internal static. The SharibakoCLI module cannot see it — only @testable import tests can. So the tests scaffold vaults and pass, while every production command that resolves a vault requires one to already exist, and no command ever creates it. The layout code works; it was never reachable from production.


Phase 1 — · RATIFIED 2026-07-08

Decision 1 — key generate is the vault-creation command; it scaffolds the layout

key generate already is the command the "vault not found" hint names as the way to create a vault, and a vault with no age key is useless — so coupling "create the vault directory" to "generate the key" matches both the existing hint and the (one bootstrap step gives you a usable vault). GenerateCommand will resolve the intended vault path and scaffold the layout (idempotently) before generating the key. This makes the existing hint true and breaks the catch-22: key generateinit now works, on both the Keychain (default) and --age-key (file) paths.

Decision 2 — expose one public creation API: VaultCore.createVault(at:)

The bug was possible because the only scaffold function is internal. Rather than make the whole VaultLayout URL-helper enum public (it deliberately hides the path grammar in one place), add a single focused public entry point on the public type — public static func VaultCore.createVault(at:) — that delegates to the internal VaultLayout.createVaultLayout. Idempotent, wraps FS failures as VaultError.fileSystemError. VaultCore.init's doc comment is updated to name this public API instead of the unreachable internal one.

Decision 3 — split path resolution from existence checking in VaultLocator

VaultLocator.resolve bundles "where is the vault" with "does it exist," and the existence check is a deliberate safety net (a typo'd --vault /wrong must error, not silently create a vault at the wrong place). Creation needs the path without the check. Add VaultLocator.intendedVaultURL(...) — flag → env → default, no existence check — and refactor resolve to call it and then assert existence. key generate uses intendedVaultURL; every read/write path keeps resolve's safety check unchanged.

Decision 4 — scope boundary: do not change init; leave non-atomic ingest to its own ho

init still resolves an existing vault up front and throws vaultNotFound if the vault is missing — the two-step bootstrap (key generate then init) resolves the catch-22, and the hint it prints is now true. Making init itself scaffold


Phase 2 — Execute

Branch ho-04.14 off main.

Code changes

Sources/SharibakoCore/VaultCore.swift — add:

public static func createVault(at vaultURL: URL) throws {
    try VaultLayout.createVaultLayout(at: vaultURL)
}

and update init(vaultURL:)'s doc comment to name VaultCore.createVault(at:).

Sources/SharibakoCLI/Support/VaultLocator.swift — add intendedVaultURL(...) (path resolution, no existence check); refactor resolve(...) to delegate to it then assert existence. Behavior of resolve is unchanged.

Sources/SharibakoCLI/Commands/KeyCommand.swift — in GenerateCommand._run, scaffold the vault first:

let vaultURL = VaultLocator.intendedVaultURL(globalFlag: global.vaultURL)
try VaultCore.createVault(at: vaultURL)

Tests

Verification and the dogfood gate

  1. swift build (warnings-as-errors) → swift-format lintswiftlint --strictswift test → coverage ≥90%.
  2. Dogfood gate — the fresh-install path end to end on the installed binary: from a directory with no vault, run key generate --vault <fresh> --age-key <fresh>, confirm scopes/+shared/ appear, then init a project and confirm it succeeds (no vaultNotFound). Not done until a clean bootstrap works.

Phase 3 — · CLOSED 2026-07-08


Authored and executed 2026-07-08 as the forward-only response to the fresh-install catch-22 surfaced during ho-04.13 dogfooding; the Phase-4 gate the Kamae-4 overview named.

Rendered from the corpus, verbatim · source on GitHub →

ingested: sharibako @ 75a79d08a2b3 · ho-system @ 0f93b7fa32f7 · the glossary · the colophon