Relationships
#3259 repo init: create a default local_encryption vault, and stop persisting absolute base_dir in vault configs
Opened by stack72 · 10/11/2026· Shipped 10/11/2026
Problem
Every new swamp repo gets a working datastore without any setup, but no vault. A vault is opt-in: you only have one after running swamp vault create. As a result, the first model with a sensitive output fails on a fresh repo with "no vault is configured. Create a vault using: swamp vault create ". Storing a first secret also means picking a vault type before doing anything else. Secret storage should work out of the box, just as data storage does.
Proposed solution
From now on, swamp repo init creates a local_encryption vault named default and makes it the repo's default vault.
- What init writes:
vaults/local_encryption/<uuid>.yamlwithname: default,type: local_encryption,config: { auto_generate: true }.base_diris left out on purpose, becauseVaultService.fromRepositoryalready fills it in from the repo directory when vaults load. That keeps the committed file portable across clones and machines. .swamp.yaml: the same marker write that init already does setsdefaultVault: default. Sensitive outputs and serve vault operations then resolve to it through the existing resolution order.- No key material at init: with
auto_generate, the key is created on the first secret write, under.swamp/secrets/local_encryption/default/.key.secretsis inALWAYS_LOCAL_SUBDIRSand.swamp/is gitignored, so neither the key nor the secrets reach git or a remote datastore. - Opt-out:
swamp repo init --no-vaultskips creating the vault. --forcere-init: if a vault nameddefaultalready exists (any type), it is left untouched, and adefaultVaultthat is already set is never changed.--serverinit: the vault is still created. It is harmless and costs only a YAML file.- Output:
RepoInitDatagainsvault: { name, type } | nullfor--json, and log mode prints a line saying the vault was created. - Remote-datastore warning:
swamp datastore setupwarns when the repo'sdefaultVaultis alocal_encryptionvault and the datastore being configured is remote. Secrets in that vault are per machine, so teammates sharing the datastore cannot read values it holds. - Scope: new repos only.
swamp repo upgradeand existing repos are unchanged, so no existing repo's behaviour changes. For example, a sensitive output in an existing repo with no vault still fails loudly.
Tests
- Unit tests for the init paths: default creation,
--no-vault,--forcewith an existingdefaultvault, and an existingdefaultVault. - Update in-repo fixtures that call
repoInitand assume a fresh repo has no vaults. - List the swamp-uat expectations that need updating (empty
vault listafter init, and the "no vault is configured" error on a fresh repo).
Docs
design/surfaces/repo.md, design/primitives/vaults.md (default vault / resolution order), the swamp skill's vault guide and troubleshooting, swamp-getting-started, and the share joiner instructions. The joiner instructions should say that the default vault is per machine and starts empty on a fresh clone.
Also in scope: stop persisting an absolute base_dir for local_encryption vaults
Today swamp vault create local_encryption <name> writes base_dir: <absolute repo path on this host> into the committed vault YAML (resolveBuiltInProviderConfig in src/libswamp/vaults/create.ts, unchanged since 2026-01-31). On a teammate's clone that path points at someone else's home directory. Under managedConfig the config is synced to every instance, so every instance gets the original host's path.
The same absolute path is written by:
vault migratetolocal_encryption(resolveBuiltInProviderConfiginsrc/libswamp/vaults/migrate.ts)- the serve-side key-source defaulting
withServerDefaultKeySource/serverDefaultKeySource(src/domain/vaults/local_encryption_key_source.ts), whichvault create,vault migrateandvault editrepair use over--server
Loading already handles an unset base_dir: VaultService.fromRepository and src/cli/datastore_expression_resolver.ts fill it in from the repo directory. Fix:
- Stop writing it. None of these paths writes
base_dirany more. An unsetbase_dirmeans "this repo's directory". - One shared runtime fill-in. A single domain helper resolves the runtime config (adds the repo dir when
base_diris unset).VaultService, the datastore expression resolver andvault migrateall use it. Todayvault migratebuilds the target provider directly, and would fall back toDeno.cwd()instead of the repo dir under--repo-dir. - Serve keeps its swamp-club#2690 guarantee. A remote caller still cannot choose the key source. A
base_direxactly equal to the server's repo dir is still accepted, and is stored as unset. - Explicit paths are kept. A
base_dira local user deliberately sets to a different path via--configis still persisted as given. - Existing vault files are never rewritten. Neither
swamp repo upgradenor any other command changes a storedbase_dir. Existing vault YAML keeps whatever it has today, and only newly written configs drop the absolute path. Users can remove it themselves withswamp vault edit.
Alternatives considered
Implicit vault, like the implicit filesystem datastore.
VaultService.ensureDefaultVaults(), now a no-op, would register a virtualdefaultvault whenever none is configured. Rejected for three reasons:- It changes behaviour in every existing repo: sensitive outputs that fail loudly today would start succeeding silently.
vault list/edit/delete/migrate, the serve vault APIs and managedConfig would all need special cases.- The vault could never be deleted.
A vault is user-managed configuration referenced by name in expressions, so it should be a real config file.
Leave
defaultVaultunset and rely on the "first user vault" fallback. Rejected because adding a second vault (e.g.@swamp/aws-sm) would make where sensitive outputs go depend on file ordering. An explicitdefaultVault: defaultis predictable, and users change it when they want another vault to be the default.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.