Skip to content

Honor environment and HDB_CONFIG storage engine settings during first-time install - #3233

Draft
kriszyp wants to merge 17 commits into
mainfrom
fix/install-env-storage-engine
Draft

kriszyp wants to merge 17 commits into
mainfrom
fix/install-env-storage-engine

Conversation

@kriszyp

@kriszyp kriszyp commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

⊙ Problem

During first-time install, the installer created the system database before composing harper-config.yaml. It resolved only the CLI and legacy STORAGE_ENGINE override in memory, while workers later read the config file from disk. Setting the storage engine or RocksDB compression via HARPER_DEFAULT_CONFIG, HARPER_SET_CONFIG, STORAGE_* environment variables, or an HDB_CONFIG template could leave system on RocksDB while workers opened LMDB. HDB_CONFIG installs also skipped applying CLI and environment overrides to the file.

🤔 Should HDB_CONFIG templates also receive environment, CLI, and default backfills during install?

Yes. Operators pass partial HDB_CONFIG templates alongside CLI flags or HARPER_SET_CONFIG overrides, and expect the installer and workers to share one complete config. Backfilling missing defaults and applying layered overrides keeps HDB_CONFIG consistent with standard installs. Restoring the pre-install file on rollback makes it reversible.

💡 Solution

  • Compose the full install configuration (HARPER_DEFAULT_CONFIG, HDB_CONFIG template, CLI flags, STORAGE_* env vars, and HARPER_SET_CONFIG) in memory before mountHdb(), and validate the staged configuration before creating system tables.
  • Stage the composed config onto the active in-memory config without recording sticky worker overrides, then write that exact document to disk after certificate generation.
  • Roll back only files and directories created during the failed install attempt, restoring any pre-existing HDB_CONFIG file on failure.

⚖️ Alternatives

  • Only forward createConfigFile() storage settings into envManager before createTables() — leaves HDB_CONFIG template installs broken because main skipped createConfigFile() when HDB_CONFIG existed, so CLI and env overrides never reached disk.
  • Write harper-config.yaml beside an existing HDB_CONFIG instead of updating HDB_CONFIG in place — diverges from keys.updateConfigCert() on main, which already rewrites HDB_CONFIG in place, and changes which file harper-boot.json points to.

🤔 Should install update a pre-existing HDB_CONFIG file in place or write a separate harper-config.yaml?

We update HDB_CONFIG in place and restore its original bytes on rollback to match keys.updateConfigCert() on main and keep harper-boot.json pointed at the operator's path. Writing a sibling harper-config.yaml instead is a local change in installer.ts if we prefer treating HDB_CONFIG as read-only.

🔧 Changes

🤔 How should empty maps in an HDB_CONFIG template interact with HARPER_DEFAULT_CONFIG?

An empty map where defaultConfig.yaml defines a non-empty section (such as http: {}) is backfilled from defaults. It stays overridable by HARPER_DEFAULT_CONFIG. Any other empty map (such as myComponent: {} or operationsApi.tls: {}) is recorded as 'user'-authored and preserved. Allowing HARPER_DEFAULT_CONFIG to populate all empty maps is a two-line change in collectLeafDotPaths().

✅ Verification

  • Ran npx mocha unitTests/install/installer.test.js unitTests/config/configUtils.test.js unitTests/config/harperConfigEnvVars-config.test.js unitTests/config/harperConfigEnvVars-filter.test.js unitTests/utility/environment/environmentManager.test.js (180 passing), verifying real install() runs across RocksDB and LMDB, HDB_CONFIG templates, HARPER_DEFAULT_CONFIG / HARPER_SET_CONFIG / STORAGE_* layers, pre-table validation failures, and snapshot-scoped rollback.
  • Ran npm run lint and npm run check:design-docs with zero warnings or errors.
  • PR QA: Does a first-time server boot with HDB_CONFIG and HARPER_SET_CONFIG storage overrides start all worker threads on the same storage engine? — queued @ eb2bcdc, qa-pr-harper-3233-eb2bcdc
Added unit test coverage by file

🤖 Generated by Google Gemini; posted via @kriszyp.

Release target: main — 134 changed lines predate this shallow clone's history, so who introduced them is unknown
Related PRs: #3078 overlaps (both touch install() and rollbackInstall() in utility/install/installer.ts)
Complexity: complicated

Deferred findings

Review-Coverage: authored=gemini; ran=muse,claude,cursor-composer,cursor-muse; adjudicated=domain; declined=cursor-grok,cursor-kimi; rounds=16; full=4 @ eb2bcdc

Review-Attention: study ~14m (sensitive: keys.ts; decisions: do-less-alternative, hdb-config-in-place-rewrite, snapshot-scoped-rollback, empty-template-section-semantics, setproperty-undefined-clears) @ eb2bcdc

kriszyp and others added 17 commits October 15, 2026 10:37
…errides

- Stage storage.engine in installer.ts before mountHdb() using the same
  precedence createConfigFile() persists (HARPER_SET_CONFIG > HARPER_CONFIG
  > CLI/prompt/HDB_CONFIG > HARPER_DEFAULT_CONFIG), then force-reload the
  persisted config in createConfigFile() so the temporary install-time
  override is retired once harper-config.yaml owns storage.engine.
- Guard setProperty(propName, undefined) in environmentManager.ts so it
  does not record an undefined override in appliedOverrides (and clears any
  prior override for that key), preventing undefined from overriding the
  persisted config after initSync() or across worker threads.
- Skip updateConfigValue() in bin/run.ts when initialize() itself just ran
  install() on first boot so lower-precedence CLI/env args do not overwrite
  the newly created harper-config.yaml.

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…tion

- Re-apply environment and HARPER_SET_CONFIG overrides during install before reading storage_engine
- Ensure resolveInstallStorageEngine initializes default config when uninitialized
- Preserve existing YAML content and unmapped component blocks when HDB_CONFIG is used during install
- Restore temporary install storage_engine override after createConfigFile instead of clearing pre-existing overrides
- Make unitTests/install/installer.test.js self-contained by restoring storage_engine and databases in afterEach

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
- Avoid recording setProperty override in stageStorageEngine before createConfigFile
- Delete flatConfigObj entry when updateConfigObject is called with undefined
- Strip runtime env-var keys case-insensitively and for legacy aliases in filterArgsAgainstRuntimeConfig
- Preserve undefined values in HARPER_DEFAULT_CONFIG when reading resolveStorageEngineFromEnvAndFile
- Keep bin/run.ts focused on install storage.engine resolution
- Add environmentManager, configUtils, harperConfigEnvVars, and installer unit tests

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…rArgsAgainstRuntimeConfig

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…ARPER_CONFIG

Avoid spreading lossy flattenConfig output from HDB_CONFIG into createConfigFile
args so operationsApi.network, node.hostname, and comments in HDB_CONFIG are
preserved intact. Track CLI/individual env var storage args distinctly from
HDB_CONFIG user paths at install time so CLI overrides beat HARPER_CONFIG while
HARPER_CONFIG still overrides HDB_CONFIG.

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…ommit

Dispatch-Task: harper-install-env-storage-engine

Co-Authored-By: Gemini <noreply@google.com>
…i source decay, and design docs

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…precedence

- Extract composeInstallConfig and stageInstallConfig in configUtils.ts so
  install-time config is composed once before mountHdb() and passed directly
  to createConfigFile(), ensuring storage.engine, compression, path, and
  caching settings never drift between mountHdb() and harper-config.yaml
- Align install-time and runtime config precedence on HARPER_DEFAULT_CONFIG <
  HDB_CONFIG / CLI / user < HARPER_CONFIG < HARPER_SET_CONFIG so system DB
  engine and subsequent runtime boots cannot disagree
- Guard post-install updateConfigValue loop in bin/run.ts when auto-install
  already composed CLI args during initialize()
- Validate configDoc before touching target file in createConfigFile() and
  add writeFileSync fallback when restoring pre-seeded root HDB_CONFIG on
  rollback
- Add unit tests for RocksDB zstd compression, default kNoCompression,
  STORAGE_COMPRESSION=false coercion during mountHdb, and ENOSPC rollback

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…sion before mountHdb

- Validate staged install config after mountHdb creates the default storage
  directory and before createTables() opens databases, refreshing flatConfigObj
  and initPaths so custom storage.path and nonexistent storage.path fail cleanly.
- Call resetRocksCompression() and initPaths.resetPaths() when staging install
  config and in test setup/teardown, and assert compression= in RocksDB OPTIONS-*.
- Track un-authored DEV_MODE_CONFIG keys via defaultedArgKeys so HARPER_DEFAULT_CONFIG
  overrides un-authored dev mode defaults while yielding to explicit CLI/env args.
- Preserve pre-seeded root HDB_CONFIG via sibling backup-aside on rollback.

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…n rollback

Dispatch-Task: harper-install-env-storage-engine

Co-Authored-By: Gemini <noreply@google.com>
…taller tests

- Restore pre-seeded HDB_CONFIG inside rootPath on rollback via .install-backup rename or writeFileSync fallback, retaining backup if restore fails
- Gate deleteBootPropertiesFile on shouldWriteBootProps so rollback does not delete a pre-existing ~/.harperdb boot properties directory
- Resolve CLI/env keys and legacy CONFIG_PARAM_MAP aliases in filterArgsAgainstRuntimeConfig before matching HARPER_CONFIG/HARPER_SET_CONFIG paths
- Skip isObject entries in assignCmdEnvVariables instead of coercing object config entries to '[object Object]'
- Wrap backfilled YAML defaults and synced env objects in YAMLMap nodes while preserving inline comments on overridden scalars
- Refresh rootPath from configUtils.getConfigValue(ROOTPATH) in createCertificates before joining HDB_KEYS_DIR_NAME
- Isolate installer.test.js system database and analytics from sibling suites

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
… install config staging

- Snapshot pre-existing top-level entries in hdbRoot before install so rollback only removes entries created by the failed install even when HDB_CONFIG points outside hdbRoot
- Remove test-only resetRocksCompression() and setProperty(..., undefined) calls from production stageInstallConfig()
- Expand numeric threads shorthand and clear scalar ancestor nodes before setIn in composeInstallConfig
- Reject dead args/baseConfigPath/defaultedArgKeys arguments when staged is passed to createConfigFile and update JSDoc
- Export initialize() from bin/run.ts and test subsequent-boot HARPER_CONFIG filtering and auto-install installedDuringInit skip

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>
…and clean pre-existing storage dirs on rollback

- Share clearScalarAncestors(configDoc, splitParam) across composeInstallConfig and updateConfigValue (guarded by typeof configDoc.deleteIn === 'function' for stub docs) so setting nested keys under boolean/scalar parents (e.g. http_mtls_required under http.mtls: false) expands the ancestor map instead of failing in setIn
- Snapshot pre-existing entries in existing storage directories and candidate system DB paths before mountHdb so rollback removes install-created system DB artifacts inside pre-existing empty database mounts while preserving pre-existing directories
- Keep addUnhandleRejectionListener() gated only by !skipListeners in bin/run.ts initialize()
- Remove duplicate unreachable threads_ block in composeInstallConfig and test both THREADS_COUNT and THREADS_DEBUG in configUtils.test.js

Dispatch-Task: harper-install-env-storage-engine
Co-Authored-By: Gemini <noreply@google.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a staged configuration flow during installation, allowing the full install configuration to be composed, validated, and staged in-memory before the database is mounted and written to disk. It adds support for backfilling missing defaults from defaultConfig.yaml when a user-supplied HDB_CONFIG template is provided, while tracking user-authored paths to preserve the precedence ladder (HARPER_DEFAULT_CONFIG < user/HDB_CONFIG/CLI < HARPER_CONFIG < HARPER_SET_CONFIG). Additionally, it enhances argument filtering to include HARPER_CONFIG alongside HARPER_SET_CONFIG, improves the robustness of the installation rollback process to preserve pre-existing operator files and restore pre-seeded configurations, and updates setProperty to clear overrides when passed undefined. As there are no review comments, I have no feedback to provide.

@kriszyp

kriszyp commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Thanks for taking this on. The staged install config looks like the right shape. One acceptance case worth adding explicitly, since the PR text frames the bug as engine and compression and does not mention storage.path.

Case: first-boot storage.path outside ROOTPATH. On main, mountHdb runs before createConfigFile (utility/install/installer.ts), and both getBaseSchemaPath (initializePaths.js:22-32) and resolveDatabaseStorageRoot (resources/databases.ts:2647-2648) read getConfigPath(STORAGE_PATH), which is still undefined then. The install-time system tables land in <rootDir>/database/system while later config points the rest of Harper at the external path, so main/0 and the http workers disagree about where system lives.

Test shape, for both RocksDB and LMDB, with HARPER_SET_CONFIG='{"storage":{"path":"<extDir>"}}' (no STORAGE_PATH env var or flag) on a first boot:

  • REST request with Basic auth as the install admin succeeds on an http worker (on main it returns 401 Table hdb_user not found).
  • Operations describe_all on an http worker lists system, and <extDir> holds the system database, not only the component databases.
  • Two-process variant (harper install, then harper run with the same config) starts without database 'system' does not exist.

By my read of stageInstallConfig() this is already covered, since getConfigPath(STORAGE_PATH) would see <extDir> before mountHdb. I have not run it against the PR head (eb2bcdc84), so a test would settle it.

Possible gap, for after merge. A pre-seeded <rootDir>/harper-config.yaml (documented as HDB_CONFIG=/existing/rootpath/harper-config.yaml harper) looks uncovered. It fails earlier, in the module-load envMgr.initSync() (bin/harper.ts into bin/cliOperations.ts:6): noBootFile() and isHdbInstalled() treat a root config as already installed and skip install, and the PR changes bin/run.ts only. I only confirmed those guards are unchanged on the PR; I have not reproduced it there. I will file it separately after this merges rather than widen the PR.

— Claude Sonnet 5.5

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant