Feature config#113
Open
ascottDI wants to merge 12 commits into
Open
Conversation
…on tests, removing hardcoded ports/paths
…og fix, 116 tests
…sed di.logging and di.torq required for full deployment
DIReview Summary2 critical | 4 warning(s) | 0 suggestion(s)
|
DIReview Summary1 critical | 3 warning(s) | 0 suggestion(s)
|
JamesMassey97
requested changes
Jul 14, 2026
DIReview Summary0 critical | 1 warning(s) | 0 suggestion(s)
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
di.config — TorQ Modularisation PR
Summary
Extracts TorQ's in-process configuration handling (
torq.q:loadf,loadconfig,loadaddconfig,overrideconfig) into a standalone kdb-x module:di.config. The moduleloads a process's q settings files from disk, resolves the override cascade
(
default → proctype → procname, framework → app), and exposes the resolved config forquerying.
di.torqwill use it to partition config per module and inject each slice intothat module's
init. It satisfies the di.* contract: a required injected logger, a minimalexported API, no hard module dependencies, and a full k4unit suite.
Background
In TorQ,
torq.qreads a cascade of settings files at start-up and every module pulls itsown values out of global namespaces via
@[value;\var;default]`. Config resolution wasentangled with the monolithic bootstrap.
Per the modularisation plan, config is now a hybrid:
di.confighandles the mechanics(load files, resolve the cascade, expose a queryable store);
di.torqhandles distribution(partition by module, inject via each module's
init). Modules receive a config dictionaryand don't know or care where it came from.
Changes
New files
di/config/config.qinit,loadcascade,overrideconfig,getmodule, and internal helpersloadconfig,applyoverride,raiseerrordi/config/init.qconfig.q, declares the exportdi/config/test.csvdi/config/config.mdDifferences from the TorQ original
di.config.lg.o/.lg.elogdep, required atinit, no fallback and no adaptation@[value;\var;default]` per modulegetmodule; distributed bydi.torqtorq.qcode)getenv+process.csvinternallyloaddir(loads code dirs viaorder.txt)di.torq's concern, never used by the config cascade.proc.override(reads.z.x)overrideconfig[params]— caller passes the params; per-variable log-and-skipusesingleton,init[deps],export:listLogging contract (injected dependency)
Logging is an injected dependency, wired via
init— there is no default logger and themodule does no adaptation of the logger.
initmust be called before any otherfunction.
init[deps]takes a dict carrying the requiredlogdependency. It errors immediately(plain signal,
di.config:prefix) ifdepsis not a dict, is missing`log,logisnot a dict, or
loglacks any of`info`warn`error.logvalue must already be a binary`info`warn`error!{[c;m]}dict — eachfunction takes a context symbol
cand a message stringm. Build it fromdi.log(thestandard logger, which exports binary
info/warn/errordirectly) or hand-roll one:{[c;m]}(e.g. a raw monadickx.loginstance) must bewrapped by the caller first — di.config does not wrap it. (This is the settled no-
normlogdirection: adaptation belongs in
di.log, not in every consumer.)initfans the injectedlogdict out into three module-local functions —.z.m.loginfo/.z.m.logwarn/.z.m.logerr— and every call site uses.z.m.loginfo[`ctx;"msg"](the three-flat-var convention fromconsistency.mdanddi.compression; maps 1:1 with TorQ's.lg.o[`ctx;"msg"]).raiseerror[ctx;msg]that logs via.z.m.logerrthen signals
'"di.config: ",ctx,": ",msg— so every failure is observable in the log aswell as thrown.
Exported API
dirs— a settings-directory path or list of paths (low→high priority).names— a config name or list (least→most specific), e.g.`default`proctype`procname.namespace— a bare symbol, no leading dot (getmodule[\rdb]`); index the returned dict for one value.Typical caller (di.torq):
config.loadcascade[(kdbconfig;appconfig);\default,proctype,procname]→config.overrideconfig[cliparams]→config.getmodule[`rdb]→rdb.init[…]`.Key design decisions
dir/{name}.qfrom eachdirectory in turn, so a more specific name wins over the directory layer, and within a name the
higher-priority directory overrides. This matches the plan's documented file order and is a
deliberate departure from the actual
torq.qcode (which is dir-major).those namespaces are the store;
getmodulereads them (and reflectsoverrideconfigchanges). An explicit store with provenance / cross-source precedence (1b) is deferred until a
second config source (env vars, k8s) exists — the
getmodulecontract isstorage-independent, so that swap won't touch consumers. Flagged as an
EXTENSION POINTin code.di.torqresolves
KDBCONFIG/KDBAPPCONFIG→ dirs andproctype/procname→ names and passes themexplicitly. Deliberately no zero-arg
load[](config loads once; a blank call would only addhidden state and temporal coupling).
overrideconfigis the command-line layer, not a manual step. It ports TorQ'soverrideconfig/override[](torq.q), which TorQ applies at startup off.Q.opt .z.xsolaunch-time flags (
-loglevel,-tablelist, …) beat the settings files. di.torq owns thecommand-line parse and calls this after
loadcascade; di.config just does the type-aware apply(staying env-free). It improves on TorQ on error handling: per-variable problems (undefined
name, non-basic type, value that won't parse) are logged and skipped — a single bad override
never aborts the batch and a null is never written into config.
loaddirwas dropped (code-directory loading, not config).loadfilewas mergedinto the internal
loadconfig(its string-check was dead and its existence check duplicated oncethe loader was internal-only). A single-key
get[namespace;key;default]getter was also dropped —getmodulereturns the slice and callers index it inline, and every config boots with a default soa fallback arg was dead weight (a missing key is now a caller bug, surfaced as a q null). Public
surface is 4 functions;
loadconfig/applyoverride/raiseerrorstay internal.'assigntrap not listed in.Q.res): the cascade driveris
loadcascade(notload, which is reserved and throws'assignat parse time).Test coverage —
test.csv(k4unit), 45 assertions, all greeninit— dependency validationdeps, missinglog, non-dictlog,logdict missing a keyloadcascadeoverrideconfiggetmoduleoverrideconfigchanges; non-symbol-namespace rejectiondi.config'sinit/cascade/load messages via a file sink — proves the injected-logger path end to endRunning the tests
export QPATH=/path/to/kx/mod:/path/to/kdbx-modulesCaveats — not deployable in a live process until two things land
di.config is complete and tested as a standalone unit, and its logging path was verified
end-to-end against the real
di.logwith no code change. It cannot yet run inside a liveprocess because:
di.log(the standard logger) is not merged and not final. di.config requires a conformingbinary
`info`warn`errorlogger injected atinit;di.logprovides exactly that (verified),but it currently lives on the
feature-loggingbranch and its contract isn't approved. If thatcontract changes, re-verify — no di.config code change is expected (di.config only needs the
three binary functions). Interim callers can hand-roll a logger.
di.torqdoes not exist yet. The orchestrator that reads process identity, resolves theconfig dirs, calls
loadcascade, appliesoverrideconfig, and distributes config viagetmodulehasn't been built.
getmoduleis unit-tested for that role, but the real partition-and-inject flowis unproven until
di.torqlands.Deferred follow-ups (documented in
config.md)di.logintegration test oncedi.logmerges (verified manually for now; ause`di.logtest can't be committed on this branch yet).versionexport +deps.qwhendi.depcheckships (repo-wide versioning rollout — nomodule carries these yet).
config source (env vars, k8s) is added — behind the unchanged
getmodulecontract.Test results screenshot