Configuration storage
New-account preset
accounts.newAccountPresetId is a nullable preset ID, defaulting to null. Human-account creation through
POST /users and MicrosoftAccountProvisioner assigns that preset through AccountPresetService.
With no selection, a new human account starts closed. The first administrator keeps the
fullAdministratorGrant() bootstrap path, and chatbot accounts keep their fixed grant.
The defaults API selects a preset with newAccountPresetId, validating that it exists. A human
creation request may select another preset or explicitly pass null. Preset deletion clears a matching
configured pointer. Grant validation reuses the account permission schema, configured model catalog
and grantable plugin manifests; actual authorization continues to read the materialized account columns.
The migration removes accounts.newAccountGrants and converts nonempty legacy arrays to the preset
Nový účet. There is no runtime compatibility accessor. Preset edits propagate to all attached
accounts, while changing the configured pointer affects subsequent account creation only.
See account preset persistence.
Operator harness settings
harness.json lives in the explicit root of instancePaths(instanceRoot)
(src/shared/instancePaths.ts), selected by ELOWEN_INSTANCE_ROOT or the HOME-based default in
src/shared/paths.ts. The PostgreSQL connection
URL in ELOWEN_DATABASE_URL is independent of that directory. The service user owns the file and the
host operator edits it by hand.
It contains no secrets. A missing file uses the typed defaults; a present malformed, unknown-key,
wrong-type, fractional or out-of-range value fails startup with the file path and validation error.
src/shared/harnessConfig.ts is the sole schema/default owner.
startDaemon reads it once before boot and passes the frozen snapshot through buildApp,
buildBrainCore and the existing runner boot message. Runners never read the file. A daemon restart
applies edits to all new runners and plugin registrations. There is no hot reload, environment
override, database precedence, settings route or UI for the file.
Bundled Terminal reads only its two foreground deadlines through ctx.host.terminalTimeouts().
Other plugins cannot use that accessor. Embedded/test cores use the same schema defaults or an
explicitly injected snapshot. Root worker caps and guest framing remain independent fixed contracts.
Admin RuntimeLimits remains in ConfigStore and its existing settings API/UI.
runtime.subagentRunnerPoolMax remains the sole pool-cap owner, although it has no Runtime-dialog
field; null is automatic sizing and zero disables the pool. Delegation's stallMinutes remains
the sole stall-watchdog owner in its plugin settings. Neither gets a duplicate harness key.
Retired environment overrides are ignored. Tests inject short budgets directly.
The config directory must be outside ordinary host Project roots. Host path guards resolve symlinks against those authorized roots; non-admin shell execution binds only approved roots and a scoped home. Managed Projects receive their own workspace/home/data mounts, never the host instance root, and host file APIs refuse managed turns. Session plan/result allowances name only their specific files or result subtrees. Host administrators with shell or Editor access can read and edit the operator file by design. Do not assign an ordinary host Project containing the instance directory.
See the single harness limit catalog for keys, defaults, ranges, fixed protocol/root caps and pointers to existing administrator settings.
Configuration storage boundary
Public configuration contracts are exported by elowen-db. Defaults and document normalization live in
packages/db/src/config/document.ts; packages/db/src/config/configStore.ts implements the asynchronous
configuration repository.
The provider codec in packages/db/src/config/codecs/store/config/brainProviders.ts owns
DEFAULT_OPENAI_COMPATIBILITY; callers use sanitizeBrainProviders(input)
without supplying defaults. ConfigStore reads and brainProviders(input) use
this same normalizer, while src/brain/providers.ts imports the baseline directly
for openAiCompatibilityFor(entry) during Chat Completions registration.
Missing or malformed compatibility fields resolve independently to the unchanged
conservative baseline; non-OpenAI entries retain no compatibility block.
A connection that omits its API key retains the current one on provider edits, and stored keys never
enter the public provider view: get() maps each connection to
apiKeySet and drops the secret (get() in packages/db/src/config/configStore.ts, see
Provider accounts and usage). The codec performs no database or network
operations and adds only per-entry validation; its stored type comes from its
existing membership list without importing ConfigStore.
The web compatibility parity test reads the codec owner to pin the browser defaults.
packages/db/src/config/codecs/store/config/limits.ts owns the private brain/runtime bound
tables and runtime defaults. The brain table is initialized once from the store's canonical public
defaults, avoiding a runtime import cycle or a second copy (BRAIN_LIMIT_BOUNDS in packages/db/src/config/document.ts). Web parity tests
keep the existing browser tables pinned to the same bounds, including the stream heartbeat floor.
They compare source text, because the web may not import the daemon:
web/tests/modules/settings/brainLimitsParity.test.ts reads both packages/db/src/config/document.ts and
packages/db/src/config/codecs/store/config/limits.ts, while runtimeLimitsParity.test.ts reads
packages/db/src/config/codecs/store/config/limits.ts and the browser mirror web/lib/runtimeLimits.ts. The
stream heartbeat floor is exported as MIN_SILENCE_LIMIT_MS in web/lib/streamWatchdog.ts.
ConfigRepository.update checks problem-reporting consent and the expected revision under the settings
transaction lock before calling private validatePatch and prepareStoredPatch in
packages/db/src/config/configStore.ts. Preparation keeps the existing field merge and validation order.
A standalone update uses a PostgreSQL SERIALIZABLE transaction with at most three attempts; a repository
already bound to a transaction joins that transaction. A provider patch cascades account/settings references
before writing the settings row. The settings write and revision, consent event through the bound events
repository, and outbox deletion when reporting is disabled commit together.
The problem-reporting callback and provider-change listeners run through sql.afterCommit, after the
outermost transaction commits and releases its connection. The host's registered listeners invalidate
provider runtimes in src/daemon/brainCore.ts.
Catalog pruning through retainProviderModels in src/brain/providerModelCatalog.ts and refresh in
src/brain/modelCatalog.ts remain host work outside the retried SQL callback.
An omitted provider patch performs no cascade/catalog effect; an unchanged reporting flag emits no consent
event or callback. The settings API remains the real consumer of this boundary.
Strict retry policy source and authenticated distribution
readRetryPolicy(config) in src/shared/retryPolicyProjection.ts awaits the
strict config.snapshot(), projects only revision and the existing
brain.limits.retryMaxRetries/retryBaseDelayMs, adds the shared fixed
60000 ms cap/no-jitter contract and validates/freezes the canonical RetryPolicy.
It never uses get() or committed() closed defaults as a readability proof.
createRetryPolicySource(config) synchronously subscribes to
onCommittedSnapshot and returns retryPolicy, onRetryPolicyChanged and
dispose. Construction performs no initial read and does not prove availability.
Each read still awaits a strict snapshot and rejects unreadable settings, even if
a prior valid policy exists. A later valid read cannot rewind a newer observed
revision. Changes-only listeners receive frozen increasing revisions; one listener
failure is logged without its diagnostic text and does not withhold other listeners.
Subscribe before reading, fence same/older revisions and dispose through the
owning lifetime. Source disposal unregisters the DB observer, clears listeners
and rejects later use. Policy updates do not restart or mutate active episodes.
GET /connection-policy returns that canonical policy directly with no-store
for resolved authenticated users, including paired-device personal API tokens.
It has no anonymous/setup exemption or new config-read permission. Unreadable
settings retain the typed 503 settings_unreadable refusal; GET /config access
is unchanged. The route's declareRoute remains the endpoint catalog authority.
PluginContext delegates its policy read/subscription to this process source and
fails explicitly when the process has no source; adapter adoption is separate.
The existing authenticated /events stream subscribes before the strict initial
read and serializes {type:'retry-policy', policy} with increasing revisions.
An unreadable initial read fails before SSE headers; abort/read failure/disconnect
removes the subscription, including while the read is pending. This non-secret
instance policy does not require a Project or administrator role. The feed
subscribes directly to the source, not a second global policy publisher.
Computer transitions use the distinct
{type:'computer-connection', ...ComputerConnectionObservation} event.
One daemon-owned registry subscription publishes it; eventProjectId supplies
its Project and every delivery rechecks canAccessProject, including for
administrators and after membership revocation. No credential, path or invented
remote retry counter enters this projection. Plugins cannot forge either
host-authoritative event through publishEvent.
retry-policy and computer-connection are live host observations, not activity
history. installEventRecording rejects those exact discriminants before allocating
its work promise or calling events.record; other bus subscribers still receive
them. Auth/plugin/activity persistence, deferred transaction acquisition, error
logging and shutdown drain retain their existing owners. No DB input union,
schema, history copy or second policy publisher is added; their recorder cost is
one discriminant check and no database work.
Daemon BrainCore constructs one source before plugin loading and forwards its
same strict retryPolicy getter to BrainDeps, image, embedding and sessionless
inference factories; the same source reaches plugin host and bootstrap/API wiring.
Runner construction instead requires the daemon-fed BrainCoreOpts.retryPolicySource;
there is no independent runner DB/default-policy fallback. After construction
failure the observer is disposed before DB close. Real BrainCore/ephemeral-DB
component tests exercise shared getter identity, missing-runner-source refusal
and failure cleanup, not the full runner boot/control IPC.
Normal shutdown retains policy availability through checkpoint and plugin stop;
beforeExit first awaits executor stop, then disposes the registry subscription
and policy source before draining runner/event work and closing DB. It does not
dispose policy in beforePause. The runner IPC component
now defines its strict boot/update mirror. Assembled bootstrap/origin recovery,
full adapter adoption, physical-device behavior and any client admission-contract
cut remain separate verification. No settings keys, defaults, ranges or database
schema are migrated by this source.
Raw configuration migrations
The existing retired-config, live-call and key-account raw cleanups share private migrateRaw
(packages/db/src/config/configStore.ts).
Each mutator explicitly returns whether it changed the document. Missing, malformed, non-object
and unchanged rows receive no write and keep their original bytes/revision; changed rows retain
unrelated keys and advance the revision once. Historical matching conditions are unchanged. This
helper is local to ConfigStore and adds no migration registry, boot hook or database access cost.